From ee0b045db19668e1d06a73386df2cf04a129d9a0 Mon Sep 17 00:00:00 2001
From: Jonas Smedegaard <dr@jones.dk>
Date: Tue, 25 Feb 2025 21:33:24 +0100
Subject: add code as appendices

---
 .../include-code-files/include-code-files.lua      | 130 +++++++++++++++++++++
 1 file changed, 130 insertions(+)
 create mode 100644 _extensions/quarto-ext/include-code-files/include-code-files.lua

(limited to '_extensions/quarto-ext/include-code-files/include-code-files.lua')

diff --git a/_extensions/quarto-ext/include-code-files/include-code-files.lua b/_extensions/quarto-ext/include-code-files/include-code-files.lua
new file mode 100644
index 0000000..c74aa53
--- /dev/null
+++ b/_extensions/quarto-ext/include-code-files/include-code-files.lua
@@ -0,0 +1,130 @@
+--- include-code-files.lua – filter to include code from source files
+---
+--- Copyright: © 2020 Bruno BEAUFILS
+--- License:   MIT – see LICENSE file for details
+
+--- Dedent a line
+local function dedent(line, n)
+  return line:sub(1, n):gsub(" ", "") .. line:sub(n + 1)
+end
+
+--- Find snippet start and end.
+--
+--  Use this to populate startline and endline.
+--  This should work like pandocs snippet functionality: https://github.com/owickstrom/pandoc-include-code/tree/master
+local function snippet(cb, fh)
+  if not cb.attributes.snippet then
+    return
+  end
+
+  -- Cannot capture enum: http://lua-users.org/wiki/PatternsTutorial
+  local comment
+  local comment_stop = ""
+  if
+    string.match(cb.attributes.include, ".py$")
+    or string.match(cb.attributes.include, ".jl$")
+    or string.match(cb.attributes.include, ".r$")
+  then
+    comment = "#"
+  elseif string.match(cb.attributes.include, ".o?js$") or string.match(cb.attributes.include, ".css$") then
+    comment = "//"
+  elseif string.match(cb.attributes.include, ".lua$") then
+    comment = "--"
+  elseif string.match(cb.attributes.include, ".html$") then
+    comment = "<!%-%-"
+    comment_stop = " *%-%->"
+  else
+    -- If not known assume that it is something one or two long and not alphanumeric.
+    comment = "%W%W?"
+  end
+
+  local p_start = string.format("^ *%s start snippet %s%s", comment, cb.attributes.snippet, comment_stop)
+  local p_stop = string.format("^ *%s end snippet %s%s", comment, cb.attributes.snippet, comment_stop)
+  local start, stop = nil, nil
+
+  -- Cannot use pairs.
+  local line_no = 1
+  for line in fh:lines() do
+    if start == nil then
+      if string.match(line, p_start) then
+        start = line_no + 1
+      end
+    elseif stop == nil then
+      if string.match(line, p_stop) then
+        stop = line_no - 1
+      end
+    else
+      break
+    end
+    line_no = line_no + 1
+  end
+
+  -- Reset so nothing is broken later on.
+  fh:seek("set")
+
+  -- If start and stop not found, just continue
+  if start == nil or stop == nil then
+    return nil
+  end
+
+  cb.attributes.startLine = tostring(start)
+  cb.attributes.endLine = tostring(stop)
+end
+
+--- Filter function for code blocks
+local function transclude(cb)
+  if cb.attributes.include then
+    local content = ""
+    local fh = io.open(cb.attributes.include)
+    if not fh then
+      io.stderr:write("Cannot open file " .. cb.attributes.include .. " | Skipping includes\n")
+    else
+      local number = 1
+      local start = 1
+
+      -- change hyphenated attributes to PascalCase
+      for i, pascal in pairs({ "startLine", "endLine" }) do
+        local hyphen = pascal:gsub("%u", "-%0"):lower()
+        if cb.attributes[hyphen] then
+          cb.attributes[pascal] = cb.attributes[hyphen]
+          cb.attributes[hyphen] = nil
+        end
+      end
+
+      -- Overwrite startLine and stopLine with the snippet if any.
+      snippet(cb, fh)
+
+      if cb.attributes.startLine then
+        cb.attributes.startFrom = cb.attributes.startLine
+        start = tonumber(cb.attributes.startLine)
+      end
+
+      for line in fh:lines("L") do
+        if cb.attributes.dedent then
+          line = dedent(line, cb.attributes.dedent)
+        end
+        if number >= start then
+          if not cb.attributes.endLine or number <= tonumber(cb.attributes.endLine) then
+            content = content .. line
+          end
+        end
+        number = number + 1
+      end
+
+      fh:close()
+    end
+
+    -- remove key-value pair for used keys
+    cb.attributes.include = nil
+    cb.attributes.startLine = nil
+    cb.attributes.endLine = nil
+    cb.attributes.dedent = nil
+
+    -- return final code block
+    return pandoc.CodeBlock(content, cb.attr)
+  end
+end
+
+return {
+  { CodeBlock = transclude },
+}
-- 
cgit v1.2.3