include-code-files.lua
1 --- include-code-files.lua – filter to include code from source files 2 --- 3 --- Copyright: © 2020 Bruno BEAUFILS 4 --- License: MIT – see LICENSE file for details 5 6 --- Dedent a line 7 local function dedent(line, n) 8 return line:sub(1, n):gsub(" ", "") .. line:sub(n + 1) 9 end 10 11 --- Find snippet start and end. 12 -- 13 -- Use this to populate startline and endline. 14 -- This should work like pandocs snippet functionality: https://github.com/owickstrom/pandoc-include-code/tree/master 15 local function snippet(cb, fh) 16 if not cb.attributes.snippet then 17 return 18 end 19 20 -- Cannot capture enum: http://lua-users.org/wiki/PatternsTutorial 21 local comment 22 local comment_stop = "" 23 if 24 string.match(cb.attributes.include, ".py$") 25 or string.match(cb.attributes.include, ".jl$") 26 or string.match(cb.attributes.include, ".r$") 27 then 28 comment = "#" 29 elseif string.match(cb.attributes.include, ".o?js$") or string.match(cb.attributes.include, ".css$") then 30 comment = "//" 31 elseif string.match(cb.attributes.include, ".lua$") then 32 comment = "--" 33 elseif string.match(cb.attributes.include, ".html$") then 34 comment = "<!%-%-" 35 comment_stop = " *%-%->" 36 else 37 -- If not known assume that it is something one or two long and not alphanumeric. 38 comment = "%W%W?" 39 end 40 41 local p_start = string.format("^ *%s start snippet %s%s", comment, cb.attributes.snippet, comment_stop) 42 local p_stop = string.format("^ *%s end snippet %s%s", comment, cb.attributes.snippet, comment_stop) 43 local start, stop = nil, nil 44 45 -- Cannot use pairs. 46 local line_no = 1 47 for line in fh:lines() do 48 if start == nil then 49 if string.match(line, p_start) then 50 start = line_no + 1 51 end 52 elseif stop == nil then 53 if string.match(line, p_stop) then 54 stop = line_no - 1 55 end 56 else 57 break 58 end 59 line_no = line_no + 1 60 end 61 62 -- Reset so nothing is broken later on. 63 fh:seek("set") 64 65 -- If start and stop not found, just continue 66 if start == nil or stop == nil then 67 return nil 68 end 69 70 cb.attributes.startLine = tostring(start) 71 cb.attributes.endLine = tostring(stop) 72 end 73 74 --- Filter function for code blocks 75 local function transclude(cb) 76 if cb.attributes.include then 77 local content = "" 78 local fh = io.open(cb.attributes.include) 79 if not fh then 80 io.stderr:write("Cannot open file " .. cb.attributes.include .. " | Skipping includes\n") 81 else 82 local number = 1 83 local start = 1 84 85 -- change hyphenated attributes to PascalCase 86 for i, pascal in pairs({ "startLine", "endLine" }) do 87 local hyphen = pascal:gsub("%u", "-%0"):lower() 88 if cb.attributes[hyphen] then 89 cb.attributes[pascal] = cb.attributes[hyphen] 90 cb.attributes[hyphen] = nil 91 end 92 end 93 94 -- Overwrite startLine and stopLine with the snippet if any. 95 snippet(cb, fh) 96 97 if cb.attributes.startLine then 98 cb.attributes.startFrom = cb.attributes.startLine 99 start = tonumber(cb.attributes.startLine) 100 end 101 102 for line in fh:lines("L") do 103 if cb.attributes.dedent then 104 line = dedent(line, cb.attributes.dedent) 105 end 106 if number >= start then 107 if not cb.attributes.endLine or number <= tonumber(cb.attributes.endLine) then 108 content = content .. line 109 end 110 end 111 number = number + 1 112 end 113 114 fh:close() 115 end 116 117 -- remove key-value pair for used keys 118 cb.attributes.include = nil 119 cb.attributes.startLine = nil 120 cb.attributes.endLine = nil 121 cb.attributes.dedent = nil 122 123 -- return final code block 124 return pandoc.CodeBlock(content, cb.attr) 125 end 126 end 127 128 return { 129 { CodeBlock = transclude }, 130 }