summaryrefslogtreecommitdiff
path: root/Master/texmf-dist/doc/generic/pgf/extract.lua
blob: 89141b6da623f9ace32f360bd5291fa970f8f7d5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
-- TODO: this has to go
local preamble = [[
\usetikzlibrary{3d,arrows,arrows.spaced,arrows.meta,bending,babel,calc,
  fit,patterns,plotmarks,shapes.geometric,shapes.misc,shapes.symbols,
  shapes.arrows,shapes.callouts,shapes.multipart,shapes.gates.logic.US,
  shapes.gates.logic.IEC,circuits.logic.US,circuits.logic.IEC,
  circuits.logic.CDH,circuits.ee.IEC,datavisualization,
  datavisualization.polar,datavisualization.formats.functions,er,automata,
  backgrounds,chains,topaths,trees,petri,mindmap,matrix,calendar,folding,
  fadings,shadings,spy,through,turtle,positioning,scopes,
  decorations.fractals,decorations.shapes,decorations.text,
  decorations.pathmorphing,decorations.pathreplacing,decorations.footprints,
  decorations.markings,shadows,lindenmayersystems,intersections,
  fixedpointarithmetic,fpu,svg.path,external,graphs,graphs.standard,quotes,
  math,angles,views,animations,rdf,perspective}
\usetikzlibrary{graphdrawing}
\usegdlibrary{trees,circular,layered,examples,force,phylogenetics,routing}
]]

local lfs = require("lfs")
local lpeg = require("lpeg")
local C, Cf, Cg, Ct, P, S, V = lpeg.C, lpeg.Cf, lpeg.Cg, lpeg.Ct, lpeg.P, lpeg.S, lpeg.V

-- strip leading and trailing whitespace
local function strip(str)
    return str:match"^%s*(.-)%s*$"
end
-- strip braces
local function strip_braces(str)
    return str:match"^{?(.-)}?$"
end

-- optional whitespace
local ws = S" \t\n\r"^0

-- match string literal
local function lit(str)
    return ws * P(str) * ws
end

-- setter for options table
local invalid = string.char(0x8)
local function set(t,k,v)
    -- strip whitespace from keys
    k = strip(k)
    -- if the value is empty, set it to invalid character
    v = v and strip_braces(v) or invalid
    return rawset(t,k,v)
end

-- Grammar to extract code examples
local extractor = lpeg.P{"document",
    name =
        C((1 - S",]=")^1),

    pair =
        Cg(V"name" * (lit"=" * (V"braces" + V"name"))^0) * lit","^-1,

    list =
        Cf(Ct"" * V"pair"^0, set),

    balanced =
        "{" * ((1 - S"{}") + V"balanced")^0 * "}",

    braces =
        C(V"balanced"),

    optarg =
        lit"[" * V"list" * lit"]",

    begincodeexample =
        P"\\begin{codeexample}" * V"optarg",

    endcodeexample =
        P"\\end{codeexample}",

    content =
        C((1 - V"endcodeexample")^0),

    codeexample =
        Ct(V"begincodeexample" * V"content" * V"endcodeexample"),

    anything =
        (1 - V"codeexample")^0,

    document =
        V"anything" * Ct(V"codeexample" * (V"anything" * V"codeexample")^0) * V"anything"
}

-- get the basename and extension of a file
local function basename(file)
    local basename, ext = string.match(file, "^(.+)%.([^.]+)$")
    return basename or "",  ext or file
end

local pathsep = package.config:sub(1,1)

-- Walk the file tree
local function walk(sourcedir, targetdir)
    -- Make sure the arguments are directories
    assert(lfs.attributes(sourcedir, "mode") == "directory", sourcedir .. " is not a directory")
    assert(lfs.attributes(targetdir, "mode") == "directory", targetdir .. " is not a directory")

    -- Append the path separator if necessary
    if sourcedir:sub(-1, -1) ~= pathsep then
        sourcedir = sourcedir .. pathsep
    end
    if targetdir:sub(-1, -1) ~= pathsep then
        targetdir = targetdir .. pathsep
    end

    -- Process all items in the directory
    for file in lfs.dir(sourcedir) do
        if file == "." or file == ".." then
            -- Ignore these two special ones
        elseif lfs.attributes(sourcedir .. file, "mode") == "directory" then
            -- Recurse into subdirectories
            lfs.mkdir(targetdir .. file)
            walk(sourcedir .. file .. pathsep, targetdir .. file .. pathsep)
        elseif lfs.attributes(sourcedir .. file, "mode") == "file" then
            print("Processing " .. sourcedir .. file)

            -- Read file into memory
            local f = io.open(sourcedir .. file)
            local text = f:read("*all")
            f:close()
            local name, ext = basename(file)

            -- preprocess, strip all commented lines
            text = text:gsub("\n%%[^\n]*","")

            -- extract all code examples
            local matches = extractor:match(text) or {}

            -- write code examples to separate files
            local setup_code = ""
            for n, e in ipairs(matches) do
                local options = e[1]
                local content = e[2]

                if content:match("remember picture") then
                    goto continue
                end

                -- If the snippet is marked as setup code, we have to put it before
                -- every other snippet in the same file
                if options["setup code"] then
                    setup_code = setup_code .. strip(content) .. "\n"
                    goto continue
                end

                -- Skip those that say "code only"
                if not options["code only"] then
                    local newname = name .. "-" .. n .. ".tex"
                    local examplefile = io.open(targetdir .. newname, "w")

                    examplefile:write"\\documentclass{article}\n"
                    examplefile:write"\\usepackage{fp,pgf,tikz,xcolor}\n"
                    examplefile:write(preamble) -- TODO: this has to go
                    examplefile:write(options["preamble"] and options["preamble"] .. "\n" or "")
                    examplefile:write"\\begin{document}\n"
                    examplefile:write"\\makeatletter\n" -- TODO: this has to go
                    examplefile:write(setup_code)
                    local pre = options["pre"] or ""
                    pre = pre:gsub("##", "#")
                    examplefile:write(pre .. "\n")
                    if options["render instead"] then
                        examplefile:write(options["render instead"] .. "\n")
                    else
                        examplefile:write(strip(content) .. "\n")
                    end
                    examplefile:write(options["post"] and options["post"] .. "\n" or "")
                    examplefile:write"\\end{document}\n"

                    examplefile:close()
                end

                ::continue::
            end
        end
    end
end

-- Main loop
if #arg < 2 then
    print("Usage: " .. arg[-1] .. " " .. arg[0] .. " <source-dirs...> <target-dir>")
    os.exit(1)
end
for n = 1, #arg - 1 do
    walk(arg[n], arg[#arg])
end