Warning: this is an htmlized version!
The original is here, and
the conversion rules are here.
-- This file:
--   http://anggtwu.net/blogme3/sandwiches.lua.html
--   http://anggtwu.net/blogme3/sandwiches.lua
--           (find-angg "blogme3/sandwiches.lua")
-- Author: Eduardo Ochs <eduardoochs@gmail.com>
-- Version: 2023oct23

-- This file is part of blogme3.
-- See: http://anggtwu.net/blogme3-sandwiches.html
--               (find-TH "blogme3-sandwiches")
-- Typical usage:
--
--   require "sandwiches-defs"
--   use_sand_htmlizeline()
--
-- See: (find-blogme3 "sandwiches-defs.lua")
--      (find-blogme3 "sandwiches-defs.lua" "use_sand_htmlizeline")
--
--
-- This file defines classes for transforming sexps into "sandwiches"
-- that alternate between parts that are plain text (slices of bread)
-- and parts that are hrefs with both a target URL and text
-- (fillings). For example, this sexp
--
--   (find-eev-intro "1." (+ -2 -3) +4 "foo" "bar")
--
-- can have these three intervals marked as "fillings":
--
--   (find-eev-intro "1." (+ -2 -3) +4 "foo" "bar")
--    --------------                               :1: head
--                    --                           :2m: sec
--                                               --:e: end
--
-- its intervals are:
--
--   (find-eev-intro "1." (+ -2 -3) +4 "foo" "bar")
--   -                                             :bread
--    --------------                               :filling:1:  head
--                  --                             :bread
--                    --                           :filling:2m: sec
--                      -------------------------  :bread
--                                               --:filling:e:  end
--                                                 :bread
--
-- Each interval is represented by a "be" structure, that in the case
-- of bread is just a pair {b=<pos>, e=<pos>}, and in the case of
-- filling is a table {b=<pos>, e=<pos>, text=<string>, st=<st>},
-- where <st> is either a SexpTarget structure or nil. The
-- representation above displays for each filling the "name" of its
-- interval:
--
--   "1"  means "first item of the sexp",
--   "2m" means "middle of the second item of the sexp",
--   "e"  means "end of the sexp" (the '")"').
--
-- An object si of the class SexpIntervals has these fields:
--
--    si.line: the line that we are trying to htmlize,
--
--    si.left,si.sexp,si.head,si.skel,si.right: the results of running
--      getsexpr(si.line),
--
--    si.item_be_s: an array of "be"s with just the fields b and e,
--      one for each item, 
--
--    si.filling_be_s: a table of "be"s with the fields b, e, and
--      text, and maybe st, indexed by `b's,
--
--    si.b_to_name: a table that lets us converts the `b's of
--      intervals that are fillings to their names, and back; the `b's
--      are numbers, and the names are strings.
--
-- The documentation at this moment consists mostly of test blocks.
-- See: (find-eepitch-intro "3. Test blocks")



-- «.getsexpr»			(to "getsexpr")
-- «.SexpIntervals»		(to "SexpIntervals")
-- «.SexpIntervals-tests»	(to "SexpIntervals-tests")
-- «.SexpTarget»		(to "SexpTarget")
-- «.SexpTarget-tests»		(to "SexpTarget-tests")
-- «.SexpHead»			(to "SexpHead")
-- «.SexpHead-tests»		(to "SexpHead-tests")
-- «.code_helponly»		(to "code_helponly")
-- «.code_helponly-tests»	(to "code_helponly-tests")
-- «.HtmlizeLine»		(to "HtmlizeLine")
-- «.HtmlizeLine-tests»		(to "HtmlizeLine-tests")
-- «.code_c_d_angg»		(to "code_c_d_angg")
-- «.code_c_d_angg-tests»	(to "code_c_d_angg-tests")
-- «.code_c_d_remote»		(to "code_c_d_remote")
-- «.code_c_d_remote-tests»	(to "code_c_d_remote-tests")
-- «.code_intro»		(to "code_intro")
-- «.code_intro-tests»		(to "code_intro-tests")
-- «.code_c_m_b»		(to "code_c_m_b")
-- «.code_c_m_b-tests»		(to "code_c_m_b-tests")
-- «.code_youtube»		(to "code_youtube")
-- «.code_youtube-tests»	(to "code_youtube-tests")


-- «getsexpr»  (to ".getsexpr")
-- Based on:    (find-angg "LUA/lua50init.lua" "getsexp")
-- New version: (find-angg "LUA/SexpAtEol1.lua" "SexpAtEol-tests")
--
getsexpr = function (linestr)
    local right = linestr:reverse():match("%s*"):reverse()
    local linestr0 = linestr:sub(1, -#right-1)
    local sexp, head, skel, left = getsexp(linestr0)
    return sexp, head, skel, left, right
  end




--  ____                 ___       _                       _     
-- / ___|  _____  ___ __|_ _|_ __ | |_ ___ _ ____   ____ _| |___ 
-- \___ \ / _ \ \/ / '_ \| || '_ \| __/ _ \ '__\ \ / / _` | / __|
--  ___) |  __/>  <| |_) | || | | | ||  __/ |   \ V / (_| | \__ \
-- |____/ \___/_/\_\ .__/___|_| |_|\__\___|_|    \_/ \__,_|_|___/
--                 |_|                                           
--
-- «SexpIntervals»  (to ".SexpIntervals")
--
SexpIntervals = Class {
  type = "SexpIntervals",
  from = function (linestr)
      local sexp, head, skel, left, right = getsexpr(linestr)
      if not sexp then return end
      local si = SexpIntervals {
	line=str,		-- Like '# (find-eev-intro "1.")'
	sexp=sexp,              -- like   '(find-eev-intro "1.")'
	head=head,              -- like    'find-eev-intro'
	skel=skel,              -- like   '(find-eev-intro "__")'
	left=left,		-- like '# '
        right=right,            -- like                         ''
      }
      local skelbody = " "..si.skel:sub(2,-2).." "
      local item_be_s = {}
      for b,e in skelbody:gmatch("()[^ \t]+()") do
	table.insert(item_be_s, {b=b, e=e})
      end
      si.item_be_s = item_be_s
      si.filling_be_s = VTable {}  -- filling intervals, indexed by `b's
      si.b_to_name    = HTable {}  -- `b's <-> `name's; for example 2 <-> "1"
      return si
    end,
  __tostring = function (si) return si:tostring() end,
  __index = {
    --
    -- When si.sexp = '(find-foopdf (+ -2 42) "Text")'
    -- we have si:sexp_rawarg(1)     = 'find-foopdf',
    --         si:sexp_numericarg(2) = 40,
    --         si:sexp_strarg(3)     = 'Text'.
    sexp_sub = function (si, b, e)
        return si.sexp:sub(b, e-1)
      end,
    sexp_rawarg = function (si, n)
        local be = si.item_be_s[n]
	if not be then return end
	return si:sexp_sub(be.b, be.e)
      end,
    sexp_strarg = function (si, n)
	local rawarg = si:sexp_rawarg(n)
        if not rawarg then return end
	if not rawarg:match('^".*"$') then return end
	return rawarg:sub(2, -2)
      end,
    sexp_numericarg = function (si, n)
	local rawarg = si:sexp_rawarg(n)
        if not rawarg then return end
	if rawarg:match('^[-+]?%d+$') then return tonumber(rawarg) end
	local body = rawarg:match('^%(%+ (.*)%)')
	if body then
	  local total = 0
	  for _,k in ipairs(map(tonumber, split(body))) do
	    total = total + k
	  end
          return total
	end
      end,
    --
    n_shrink_to_b_e_name = function (si, n, shrink)
	if n == "e" then 
	  local e = #si.sexp+1
	  local b = (si.sexp:sub(-2, -2) == '"') and e-2 or e-1
	  return b,e,"e"
	end
	local be = si.item_be_s[n]
	if shrink == nil or shrink == 0 then return be.b, be.e, n.."" end
	if shrink == 1 then return be.b+1, be.e-1, n.."m" end
	error()
      end,
    add_filling0 = function (si, be)
        local b,name = be.b, be.name
	si.filling_be_s[b] = be
	si.b_to_name[name] = b
	si.b_to_name[b] = name
      end,
    add_filling = function (si, n, shrink, text, st)
        local b,e,name = si:n_shrink_to_b_e_name(n, shrink)
	local be = VTable {b=b, e=e, name=name, text=text}
	be.st = (st and SexpTarget.from(st)) or SexpTarget.from(text)
	-- if not be.st then be.st = SexpTarget {text=text} end
	si:add_filling0(be)
	return si
      end,
    --
    dash = function (si, b, e)
        return (" "):rep(b-1) .. ("-"):rep(e-b) .. (" "):rep(#si.sexp-e+1)
      end,
    be_name = function (si, be) return si.b_to_name[be.b] end,
    filling_to_string = function (si, be)
        return format("%s:filling:%s: %s",
		      si:dash(be.b, be.e),
		      si:be_name(be),
		      be.text)
      end,
    fillings_sorted = function (si)
        local bes = {}
	for _,b in ipairs(sorted(keys(si.filling_be_s))) do
          table.insert(bes, si.filling_be_s[b])
	end
	return bes
      end,
    tostring = function (si)
        local bigstr = si.sexp
	for _,be in ipairs(si:fillings_sorted()) do
	  bigstr = bigstr.."\n"..si:filling_to_string(be)
	end
	return bigstr
      end,
    --
    tosandwich = function (si)
        local fills = si:fillings_sorted()
	local bes, lastpos = VerticalTable {}, 1
	local add = function (b, e, st)
	    table.insert(bes, {b=b, e=e, origtext=si:sexp_sub(b, e), st=st})
	  end
	for _,be in ipairs(fills) do
	  add(lastpos, be.b) 
	  add(be.b, be.e, be.st)
	  lastpos = be.e
        end
	add(lastpos, #si.sexp+1)
	return bes
      end,
    --
    name_to_st = function (si, name)
        return si.filling_be_s[si.b_to_name[name]].st
      end,
    target_st = function (si)
        local o = si.b_to_name["e"]
               or si.b_to_name["2m"]
               or si.b_to_name["2"]
               or si.b_to_name["1"]
        return si.filling_be_s[o].st
      end,
    --
    -- :head_apply() uses the table of SexpHeads defined below.
    head_apply = function (si)
        local sh = _SH[si.head]
	if sh then sh:f(si); return true end
      end,
  },
}

-- «SexpIntervals-tests»  (to ".SexpIntervals-tests")
--[==[
* (eepitch-lua51)
* (eepitch-kill)
* (eepitch-lua51)
dofile "sandwiches.lua"

si = SexpIntervals.from [[ (find-foopdf (+ -2 42) "Text") ]]
PPV(si)
= si:sexp_rawarg(1)     --> "find-foopdf"
= si:sexp_numericarg(2) --> 40
= si:sexp_strarg(3)     --> "Text"

= si
= si:add_filling(1,   nil, "head")
= si:add_filling("e", nil, "end")
= si:add_filling(3,   1,   "str")

= si:name_to_st("3m")
= si:target_st("e")

PPV(si:fillings_sorted())
= si:tosandwich()

--]==]





--  ____                _____                    _   
-- / ___|  _____  ___ _|_   _|_ _ _ __ __ _  ___| |_ 
-- \___ \ / _ \ \/ / '_ \| |/ _` | '__/ _` |/ _ \ __|
--  ___) |  __/>  <| |_) | | (_| | | | (_| |  __/ |_ 
-- |____/ \___/_/\_\ .__/|_|\__,_|_|  \__, |\___|\__|
--                 |_|                |___/          
--
-- «SexpTarget»  (to ".SexpTarget")
--
infomanual_basedir = infomanual_basedir or VerticalTable {}

SexpTarget = Class {
  type = "SexpTarget",
  from = function (o)
      if type(o) == "string" then return SexpTarget {text=o} end
      if otype(o) == "SexpTarget" then return o end
      error()
    end,
  __tostring = mytabletostring,
  __index = {
    url = function (st)
        if st.f then return st[st.f](st) end
        return st.text
      end,
    ru = function (st, relativeurl)
        return "http://anggtwu.net/"..relativeurl -- change this
      end,
    --
    find_angg = function (st)
        local fname,anchor = st.fname, st.anchor
        return st:ru(fname..(st.ext or ".html")..
		     (anchor and ("#"..anchor) or ""))
      end,
    find_intro = function (st)
        local stem,anchor = st.stem, st.anchor
        local anggurl = "eev-intros/find-"..stem.."-intro.html"
        if anchor then anggurl = anggurl.."#"..anchor end
        return st:ru(anggurl)
      end,
    find_node = function (st)
        local manual,node = st.manual, st.node
	local baseurl = infomanual_basedir[manual]
	if not baseurl then return nil end
	local shre    = "([-'/ &])"
	local shtable = {["-"] = "_002d", ["'"] = "_0027", ["/"] = "_002f",
	                 [" "] = "-",     ["&"] = "-"}
	local shnode  = node and node:gsub("%s+", " "):gsub(shre, shtable)
	return baseurl..(shnode or "")
      end,
    find_youtube = function (st)
        local hash,time = st.hash, st.time
        return youtube_make_url(hash, time)
      end,
    find_pdf = function (st)
        local pdfurl,page = st.pdfurl, st.page
	return pdfurl..(page and "#page="..page or "")
      end,
    find_remote = function (st)
        local baseurl,rest = st.baseurl, st.rest
	return baseurl..rest
      end,
  },
}

-- «SexpTarget-tests»  (to ".SexpTarget-tests")
--[==[
* (eepitch-lua51)
* (eepitch-kill)
* (eepitch-lua51)
dofile "sandwiches.lua"

st = SexpTarget.from("foo")
= st
= st:url()

SexpTarget.__index.foo = function (st) return "FOO" end
= SexpTarget { f="foo" } :url()

SexpTarget.__index.foo = function (st) return "FOO"..st.a end
= SexpTarget { f="foo", a="AAA" } :url()

= SexpTarget { f="find_angg", fname="abc/def"               } :url()
= SexpTarget { f="find_angg", fname="abc/def", anchor="ghi" } :url()
= SexpTarget { f="find_intro", stem="eev" }                   :url()
= SexpTarget { f="find_intro", stem="eev", anchor="1" }       :url()
= SexpTarget { f="find_youtube", hash="0123456789abc" }              :url()
= SexpTarget { f="find_youtube", hash="0123456789abc", time="1:23" } :url()

infomanual_basedir["emacs"] = "http://gnu/manual/emacs/"
= SexpTarget { f="find_node", manual="emacs" }                    :url()
= SexpTarget { f="find_node", manual="emacs", node="Sec  1-2/3" } :url()
= SexpTarget { f="find_node", manual="emAcs", node="Sec  1-2/3" } :url()

--]==]





--  ____                  _   _                _ 
-- / ___|  _____  ___ __ | | | | ___  __ _  __| |
-- \___ \ / _ \ \/ / '_ \| |_| |/ _ \/ _` |/ _` |
--  ___) |  __/>  <| |_) |  _  |  __/ (_| | (_| |
-- |____/ \___/_/\_\ .__/|_| |_|\___|\__,_|\__,_|
--                 |_|                           
--
-- «SexpHead»  (to ".SexpHead")
--
SexpHead = Class {
  type    = "SexpHead",
  __tostring = mytabletostring,
  __index = {
  },
}

_SH = VerticalTable {}

-- «SexpHead-tests»  (to ".SexpHead-tests")
--[==[
* (eepitch-lua51)
* (eepitch-kill)
* (eepitch-lua51)
dofile "sandwiches.lua"
dofile "cruft-jan2024.lua"
_SH["foo"] = SexpHead {
  e = "abc",
  f = function (sh, si)
      si:add_filling(1, nil,   "FOO")
      si:add_filling(2, 1,     si:sexp_strarg(3))
      si:add_filling("e", nil, sh.e)
    end,
}

si = SexpIntervals.from [[ (foo "def" "ghi") ]]
si:head_apply()
= si

= _SH["foo"]
= _SH

--]==]




--                _          _          _                   _       
--   ___ ___   __| | ___    | |__   ___| |_ __   ___  _ __ | |_   _ 
--  / __/ _ \ / _` |/ _ \   | '_ \ / _ \ | '_ \ / _ \| '_ \| | | | |
-- | (_| (_) | (_| |  __/   | | | |  __/ | |_) | (_) | | | | | |_| |
--  \___\___/ \__,_|\___|___|_| |_|\___|_| .__/ \___/|_| |_|_|\__, |
--                     |_____|           |_|                  |___/ 
--
-- «code_helponly»  (to ".code_helponly")
-- (find-blogme3 "sandwiches-defs.lua" "code_helponly2")
--
code_helponly = function (head, stem, anchor)
    _SH[head] = SexpHead {
      head = head,
      help = SexpTarget {f="find_intro", stem=stem, anchor=anchor},
      f = function (sh, si)
          si:add_filling(1, nil, "help", sh.help)
        end,
    }
  end

code_helponly_line = function (linestr)
    local si = SexpIntervals.from(linestr)
    if not si then return end
    local head = bitrim(si.left)
    local stem = si.head:match "find%-(.*)%-intro(.*)"
    local posspec = si:sexp_strarg(2)
    local anchor = posspec and posspec:match "^(%d[.%d]*)%."
    code_helponly(head, stem, anchor)
  end

code_helponly_lines = function (bigstr)
    for _,linestr in ipairs(splitlines(bigstr)) do
      code_helponly_line(linestr)
    end
  end

-- «code_helponly-tests»  (to ".code_helponly-tests")
--
--[==[
* (eepitch-lua51)
* (eepitch-kill)
* (eepitch-lua51)
dofile "sandwiches.lua"

code_helponly_lines [[
  eelatex-bounded (find-bounded-intro)
  eev-bounded     (find-bounded-intro)
  eek             (find-eev-quick-intro "3. Elisp hyperlinks")
  find-efunction  (find-eev-quick-intro "3. Elisp hyperlinks")
]]

= _SH

si = SexpIntervals.from [[ (eek "H e l l o") ]]
si:head_apply()
= si
= si:tosandwich()

--]==]




--  _   _ _             _ _         _     _            
-- | | | | |_ _ __ ___ | (_)_______| |   (_)_ __   ___ 
-- | |_| | __| '_ ` _ \| | |_  / _ \ |   | | '_ \ / _ \
-- |  _  | |_| | | | | | | |/ /  __/ |___| | | | |  __/
-- |_| |_|\__|_| |_| |_|_|_/___\___|_____|_|_| |_|\___|
--                                                     
-- «HtmlizeLine»  (to ".HtmlizeLine")
--
HtmlizeLine = Class {
  type    = "HtmlizeLine",
  __index = {
    left  = function (hl, str) return str end,
    plain = function (hl, str) return str end,
    href  = function (hl, url, text)
        return format('<a href="%s">%s</a>', url, text)
      end,
    be = function (hl, be)
        local texthtml = hl:plain(be.origtext)
	local url = be.st and be.st:url()
        if url then return hl:href(url, texthtml) end
	return texthtml
      end,    
    sandwich = function (hl, sand)
        local bigstr = ""
	for _,be in ipairs(sand) do bigstr = bigstr..hl:be(be) end
        return bigstr
      end,
    line = function (hl, linestr)
        local si = SexpIntervals.from(linestr)
        if not si then return hl:left(linestr) end
	if not si:head_apply() then return hl:left(linestr) end
        DBG("s530")
        local left,right = si.left, si.right
	local sandwich = si:tosandwich()
        DBG("s533")
	local html = hl:left(left) .. hl:sandwich(sandwich) .. right
        DBG("s535")
	return html,si
      end,
  },
}

-- «HtmlizeLine-tests»  (to ".HtmlizeLine-tests")
--[==[
* (eepitch-lua51)
* (eepitch-kill)
* (eepitch-lua51)
dofile "sandwiches.lua"
code_helponly_lines [[
  eev-bounded     (find-bounded-intro)
  eek             (find-eev-quick-intro "3. Elisp hyperlinks")
]]

hl = HtmlizeLine {}
linestr = [[ (eek "H e l l o") ]]
html,si = hl:line(linestr)

= html
= si
= si:tosandwich()

linestr = [[ (foo) ]]
= hl:line(linestr)

--]==]





--                _                     _                            
--   ___ ___   __| | ___     ___     __| |    __ _ _ __   __ _  __ _ 
--  / __/ _ \ / _` |/ _ \   / __|   / _` |   / _` | '_ \ / _` |/ _` |
-- | (_| (_) | (_| |  __/  | (__   | (_| |  | (_| | | | | (_| | (_| |
--  \___\___/ \__,_|\___|___\___|___\__,_|___\__,_|_| |_|\__, |\__, |
--                     |_____| |_____|  |_____|          |___/ |___/ 
--
-- «code_c_d_angg»  (to ".code_c_d_angg")
--
code_c_d_angg = function (c, d, ext)
    local find_c = "find-"..c
    _SH[find_c] = SexpHead {
      head = find_c,
      -- (find-eev-quick-intro "9. Shorter hyperlinks")
      help = SexpTarget {f="find_intro", stem="eev-quick", anchor="9"},
      f = function (sh, si)
	  si:add_filling(1, nil, "help", sh.help)
	  local a, b = si:sexp_strarg(2), si:sexp_strarg(3)
	  if a then
	    local target = SexpTarget {f="find_angg", fname=d..a, anchor=b, ext=ext}
	    si:add_filling("e", nil, "target", target)
	  end
      end,
    }
  end

-- «code_c_d_angg-tests»  (to ".code_c_d_angg-tests")
--
--[==[
* (eepitch-lua51)
* (eepitch-kill)
* (eepitch-lua51)
dofile "sandwiches.lua"
code_c_d_angg("LATEX", "LATEX/")
linestr = [[ (find-LATEX "2021haskell.tex" "title") ]]
si = SexpIntervals.from(linestr)
si:head_apply()
= si
= si:tosandwich()

--]==]




--                _                     _                              _       
--   ___ ___   __| | ___     ___     __| |    _ __ ___ _ __ ___   ___ | |_ ___ 
--  / __/ _ \ / _` |/ _ \   / __|   / _` |   | '__/ _ \ '_ ` _ \ / _ \| __/ _ \
-- | (_| (_) | (_| |  __/  | (__   | (_| |   | | |  __/ | | | | | (_) | ||  __/
--  \___\___/ \__,_|\___|___\___|___\__,_|___|_|  \___|_| |_| |_|\___/ \__\___|
--                     |_____| |_____|  |_____|                                
--
-- «code_c_d_remote»  (to ".code_c_d_remote")
--
code_c_d_remote = function (c, baseurl)
    local find_c = "find-"..c
    _SH[find_c] = SexpHead {
      head = find_c,
      -- (find-eev-quick-intro "9. Shorter hyperlinks")
      help = SexpTarget {f="find_intro", stem="eev-quick", anchor="3"},
      f = function (sh, si)
	  si:add_filling(1, nil, "help", sh.help)
	  local a = si:sexp_strarg(2)
	  if a then
	    local target = SexpTarget {f="find_remote", baseurl=baseurl, rest=a}
	    local shrink = (a == "") and 0 or 1
	    si:add_filling(2, shrink, "target", target)
	  end
      end,
    }
  end

-- «code_c_d_remote-tests»  (to ".code_c_d_remote-tests")
--
--[==[
* (eepitch-lua51)
* (eepitch-kill)
* (eepitch-lua51)
dofile "sandwiches.lua"
code_c_d_remote("lua51manual", "http://www.lua.org/manual/5.1/manual.html")

linestr = [[ (find-lua51manual "" "title") ]]
linestr = [[ (find-lua51manual "#2.1" "title") ]]
si = SexpIntervals.from(linestr)
si:head_apply()
= si
= si:tosandwich()

--]==]




--                _          _       _             
--   ___ ___   __| | ___    (_)_ __ | |_ _ __ ___  
--  / __/ _ \ / _` |/ _ \   | | '_ \| __| '__/ _ \ 
-- | (_| (_) | (_| |  __/   | | | | | |_| | | (_) |
--  \___\___/ \__,_|\___|___|_|_| |_|\__|_|  \___/ 
--                     |_____|                     
--
-- «code_intro»  (to ".code_intro")
--
code_intro = function (stem)
    local find_stem_intro = "find-"..stem.."-intro"
    _SH[find_stem_intro] = SexpHead {
      head = find_stem_intro,
      help = SexpTarget {f="find_intro", stem=stem, anchor=nil},
      f = function (sh, si)
          si:add_filling(1, nil, "help", sh.help)
          local a = si:sexp_strarg(2)
	  local anchor = a and a:match("^(%d[.%d]*)%.")
          local target = anchor and SexpTarget {f="find_intro", stem=stem, anchor=anchor}
          si:add_filling("e", nil, "target", target or sh.help)
        end,
    }
  end

-- «code_intro-tests»  (to ".code_intro-tests")
--
--[==[
* (eepitch-lua51)
* (eepitch-kill)
* (eepitch-lua51)
dofile "sandwiches.lua"

code_intro("foobar")
linestr = [[ (find-foobar-intro "42.3. plic") ]]
si = SexpIntervals.from(linestr)
si:head_apply()
= si
= si:tosandwich()

--]==]




--                _                                _     
--   ___ ___   __| | ___     ___     _ __ ___     | |__  
--  / __/ _ \ / _` |/ _ \   / __|   | '_ ` _ \    | '_ \ 
-- | (_| (_) | (_| |  __/  | (__    | | | | | |   | |_) |
--  \___\___/ \__,_|\___|___\___|___|_| |_| |_|___|_.__/ 
--                     |_____| |_____|       |_____|     
--
-- «code_c_m_b»  (to ".code_c_m_b")
-- Skel: (find-sandwiches-def-links "find-enode")
--
code_c_m_b_f = function (sh, si)
    si:add_filling(1, nil, "help", sh.help)
    local a = si:sexp_strarg(2)
    if not a then return end
    local target = SexpTarget {f="find_node", manual=manual, node=a}
    if target then si:add_filling("e", nil, "target", target) end
  end

code_c_m_b = function (c, manual, basedir)
    infomanual_basedir[manual] = basedir
    local find_cnode = "find-"..c.."node"
    _SH[find_cnode] = SexpHead {
      head = find_cnode,
      -- (find-eev-quick-intro "9.2. Extra arguments to `code-c-d'")
      help = SexpTarget {f="find_intro", stem="eev-quick", anchor="9.2"},
      f = code_c_m_b_f,
      f = function (sh, si)
          si:add_filling(1, nil, "help", sh.help)
          local a = si:sexp_strarg(2)
          if not a then return end
          local target = SexpTarget {f="find_node", manual=manual, node=a}
          if target then si:add_filling("e", nil, "target", target) end
        end,
    }
  end

-- «code_c_m_b-tests»  (to ".code_c_m_b-tests")
--
--[==[
* (eepitch-lua51)
* (eepitch-kill)
* (eepitch-lua51)
dofile "sandwiches.lua"
dofile "cruft-jan2024.lua" -- (find-blogme3file "cruft-jan2024.lua")

code_c_m_b("e", "emacs", "http://www.gnu.org/software/emacs/manual/html_node/emacs/")

linestr = [[ (find-enode "Keys") ]]
si = SexpIntervals.from(linestr)
si:head_apply()
= si
= si:tosandwich()

-- Not working yet:
= sexp_to_target_si  [[ (find-enode "37675653 202207 1" "structural") ]]
= sexp_to_target_st  [[ (find-enode "37675653 202207 1" "structural") ]]

--]==]




--                _                            _         _          
--   ___ ___   __| | ___     _   _  ___  _   _| |_ _   _| |__   ___ 
--  / __/ _ \ / _` |/ _ \   | | | |/ _ \| | | | __| | | | '_ \ / _ \
-- | (_| (_) | (_| |  __/   | |_| | (_) | |_| | |_| |_| | |_) |  __/
--  \___\___/ \__,_|\___|____\__, |\___/ \__,_|\__|\__,_|_.__/ \___|
--                     |_____|___/                                  
--
-- «code_youtube»  (to ".code_youtube")
--
code_youtube = function (c, hash)
    local find_c = "find-"..c
    _SH[find_c] = SexpHead {
      head = find_c,
      -- (find-audiovideo-intro "4. Short hyperlinks to audio and video files")
      help = SexpTarget {f="find_intro", stem="audiovideo", anchor="4"},
      f = function (sh, si)
          si:add_filling(1, nil, "help", sh.help)
          local time = si:sexp_strarg(2)
	  if time then
            local target = SexpTarget {f="find_youtube", hash=hash, time=time}
            si:add_filling(2, 1, "target", target)
          end
        end,
    }
  end

-- «code_youtube-tests»  (to ".code_youtube-tests")
--
--[==[
* (eepitch-lua51)
* (eepitch-kill)
* (eepitch-lua51)
dofile "sandwiches.lua"

code_youtube("eev2019video", "86yiRG8YJD0")
linestr = [[ (find-eev2019video "15:56") ]]
si = SexpIntervals.from(linestr)
si:head_apply()
= si
= si:tosandwich()

--]==]