m-dirtree.mkxl /size: 11 Kb    last modification: 2025-02-21 11:03
1%D \module
2%D   [       file=m-dirtree,
3%D        version=2024.08.31,
4%D          title=\CONTEXT\ Modules,
5%D       subtitle=Rendering Directory Trees,
6%D         author=Hans Hagen,
7%D           date=\currentdate,
8%D      copyright={PRAGMA ADE \& \CONTEXT\ Development Team}]
9
10%C This module is part of the \CONTEXT\ macro||package and is
11%C therefore copyrighted by \PRAGMA. See mreadme.pdf for
12%C details.
13
14%D This module is triggered by a question that Hraban posted to the mailing list. I
15%D played abit with \LUA, then foudn that the fonts were not up to it which is
16%D mostly due to the fact that it needs to fit into a baseline (lineheight) model so
17%D then I made a \METAPOST\ font. Eventually a bit of interfacing was added.
18
19%D Tha basic \LUA\ handler might move to a utility module (util-dtr.lua) once I feel
20%D the need for that.
21
22%  U+2500  right      ─
23%  U+2502  down       │
24%  U+2514  corner     └
25%  U+251C  downright  ├
26%  U+252C  rightdown  ┬
27
28\startluacode
29    local newprivateslot = fonts.helpers.newprivateslot
30
31    newprivateslot("dirtree one yes")    -- 1 "└─┬──"
32    newprivateslot("dirtree one nop")    -- 2         "└────"
33    newprivateslot("dirtree first yes")  -- 3 "├─┬──"
34    newprivateslot("dirtree first nop")  -- 4         "├────"
35    newprivateslot("dirtree last yes")   -- 2 "└────"
36    newprivateslot("dirtree last nop")   -- 2         "└────"
37    newprivateslot("dirtree middle yes") -- 3 "├─┬──"
38    newprivateslot("dirtree middle nop") -- 5         "├────"
39    newprivateslot("dirtree down yes")   -- 6 "│ "
40    newprivateslot("dirtree down nop")   --           "  "
41    newprivateslot("dirtree space yes")  --   " "
42    newprivateslot("dirtree space nop")  --           " "
43\stopluacode
44
45\startMPcalculation{simplefun}
46
47    path dirtree_glyphs[] ;
48
49    def InitializeDirTree =
50        dirtree_glyphs[1] := (1,2) -- (1,1) -- (5,1) && (3,1) -- (3,0)          ; % "└─┬──"
51        dirtree_glyphs[2] := (1,2) -- (1,1) -- (5,1)                            ; %         "└────"
52        dirtree_glyphs[3] := (1,0) -- (1,2) && (1,1) -- (5,1) && (3,1) -- (3,0) ; % "├─┬──"
53        dirtree_glyphs[4] := (1,0) -- (1,2) && (1,1) -- (5,1)                   ; %         "├────"
54        dirtree_glyphs[5] := (1,2) -- (1,0) && (1,1) -- (5,1)                   ; % "├────"
55        dirtree_glyphs[6] := (1,2) -- (1,0)                                     ; %         "│ "
56    enddef ;
57
58
59    vardef DirTree(expr i, w, h) =
60        numeric u ; u := 1 ;
61        picture p ; p := image (
62            if i > 0 :
63                draw dirtree_glyphs[i]
64                    xyscaled(u,3u/2)
65                    shifted (-u/2,-u)
66                    withpen pencircle scaled (u/10)
67                ;
68            fi
69        ) ;
70        setbounds p to unitsquare xyscaled(w*u,h*u) ;
71        draw p ;
72    enddef ;
73
74    lmt_registerglyphs [
75        name     = "dirtree",
76        units    = 2,
77        usecolor = true,
78        width    = 1,
79        height   = 2,
80        depth    = 1,
81        preamble = "InitializeDirTree"
82    ] ;
83
84    lmt_registerglyph [ category  = "dirtree", private = "dirtree one yes"   , code = "DirTree(1,5,3)", width = 5 ] ;
85    lmt_registerglyph [ category  = "dirtree", private = "dirtree one nop"   , code = "DirTree(2,5,3)", width = 5 ] ;
86    lmt_registerglyph [ category  = "dirtree", private = "dirtree first yes" , code = "DirTree(3,5,3)", width = 5 ] ;
87    lmt_registerglyph [ category  = "dirtree", private = "dirtree first nop" , code = "DirTree(4,5,3)", width = 5 ] ;
88    lmt_registerglyph [ category  = "dirtree", private = "dirtree last yes"  , code = "DirTree(1,5,3)", width = 5 ] ;
89    lmt_registerglyph [ category  = "dirtree", private = "dirtree last nop"  , code = "DirTree(2,5,3)", width = 5 ] ;
90    lmt_registerglyph [ category  = "dirtree", private = "dirtree middle yes", code = "DirTree(3,5,3)", width = 5 ] ;
91    lmt_registerglyph [ category  = "dirtree", private = "dirtree middle nop", code = "DirTree(5,5,3)", width = 5 ] ;
92    lmt_registerglyph [ category  = "dirtree", private = "dirtree down yes"  , code = "DirTree(6,2,3)", width = 2 ] ;
93    lmt_registerglyph [ category  = "dirtree", private = "dirtree down nop"  , code = "DirTree(0,2,3)", width = 2 ] ;
94    lmt_registerglyph [ category  = "dirtree", private = "dirtree space yes" , code = "DirTree(0,1,3)", width = 1 ] ;
95    lmt_registerglyph [ category  = "dirtree", private = "dirtree space nop" , code = "DirTree(0,1,3)", width = 1 ] ;
96
97\stopMPcalculation
98
99\definefontfeature
100  [default]
101  [default]
102  [metapost={category=dirtree,weight=2.0}]
103
104\definefontfeature
105  [none]
106  [none]
107  [metapost={category=dirtree,weight=2.0}]
108
109\startluacode
110    local gmatch     = string.gmatch
111    local concat     = table.concat
112    local insert     = table.insert
113    local remove     = table.remove
114    local sortedkeys = table.sortedkeys
115
116    local dirtree     = utilities.dirtree or { }
117    utilities.dirtree = dirtree
118
119    local default = {
120        one    = { "     ", "     " },
121        first  = { "     ", "     " },
122        last   = { "     ", "     " },
123        middle = { "     ", "     " },
124        down   = { "  ",    "  "    },
125        space  = { " ",     " "     },
126    }
127
128    local unicode = {
129        one    = { "└─┬──", "└────" },
130        first  = { "├─┬──", "├────" },
131        last   = { "└─┬──", "└────" },
132        middle = { "├────", "├────" },
133        down   = { ""   , "  "    },
134        space  = { " "    , " "     },
135    }
136
137    local simple = {
138        one    = { "    +", "    +" },
139        first  = { "    +", "     " },
140        last   = { "    +", "     " },
141        middle = { "    +", "     " },
142        down   = { "  "   , "  "    },
143        space  = { " "    , " "     },
144    }
145
146    local private ; if buffers then
147
148        local ps = fonts.helpers.privateslot
149        local uc = utf.char
150
151        private = {
152            one    = { uc(ps("dirtree one yes"   )), uc(ps("dirtree one nop"   )) },
153            first  = { uc(ps("dirtree first yes" )), uc(ps("dirtree first nop" )) },
154            last   = { uc(ps("dirtree last yes"  )), uc(ps("dirtree last nop"  )) },
155            middle = { uc(ps("dirtree middle yes")), uc(ps("dirtree middle nop")) },
156            down   = { uc(ps("dirtree down yes"  )), uc(ps("dirtree down nop"  )) },
157            space  = { uc(ps("dirtree space yes" )), uc(ps("dirtree space nop" )) },
158        }
159
160    end
161
162    dirtree.symbols = {
163        default  = default,
164        unicode  = unicode,
165        simple   = simple,
166        private  = private,
167    }
168
169    -- Performance is okay, otherwise we would reuse the con table and do a selective
170    -- concat here.
171
172    local nameonly, suffix = file.nameonly, file.suffix
173
174    dirtree.entries = {
175        default = function(str)
176            return str
177        end,
178        split = function(str)
179            return "\\dirtreeentry{" .. nameonly(str) .. "}{" .. suffix(str) .. "}"
180        end,
181    }
182
183    function dirtree.show(tree,symbolset,direntry)
184
185        if type(tree) == "string" then
186            tree = dir.glob(tree)
187        end
188
189        if type(tree) ~= "table" then
190            return
191        end
192
193        if not direntry then
194            direntry = "default"
195        end
196        direntry = dirtree.entries[direntry] or dirtree.entries.default
197
198        local result = { }
199        local output = { }
200
201        if not symbolset then
202            symbolset = "default"
203        end
204
205        local used = dirtree.symbols[symbolset] or dirtree.symbols.default
206
207        table.setmetatableindex(used,dirtree.symbols.default)
208
209        local yes_one   = used.one   [1] local nop_one   = used.one   [2]
210        local yes_first = used.first [1] local nop_first = used.first [2]
211        local yes_last  = used.last  [1] local nop_last  = used.last  [2]
212        local yes_else  = used.middle[1] local nop_else  = used.middle[2]
213        local yes_down  = used.down  [1] local nop_down  = used.down  [2]
214        local yes_space = used.space [1] local nop_space = used.space [2]
215
216        if #tree > 0 then
217            for i=1,#tree do
218                local d = false
219                local r = result
220                for s in gmatch(tree[i],"[^/]+") do
221                    if d then
222                        local rs = r[s]
223                        if not rs then
224                            rs = { }
225                            r[s] = { }
226                        end
227                        r = rs
228                    else
229                        d = true
230                    end
231                end
232            end
233        else
234            -- assume a hash
235        end
236
237        local level  = 0
238        local con    = { }
239
240        local function show(result,level)
241            local list = sortedkeys(result)
242            local size = #list
243            for i=1,size do
244                local l = list[i]
245                local r = result[l]
246                local sub = next(r)
247                if level == 1 then
248                    -- nothing special to do
249                elseif size == 1 then
250                    insert(con,sub and yes_one or nop_one)
251                elseif i == 1 then
252                    insert(con,sub and yes_first or nop_first)
253                elseif i == size then
254                    insert(con,sub and yes_last or nop_last)
255                else
256                    insert(con,sub and yes_else or nop_else)
257                end
258                if level == 1 then
259                    output[#output+1] = direntry(l)
260                else
261                    output[#output+1] = concat(con) .. (sub and yes_space or nop_space) .. direntry(l)
262                end
263                if sub then
264                    if size == 1 then
265                        con[#con] = nop_down
266                    elseif i == size then
267                        con[#con] = nop_down
268                    else
269                        con[#con] = yes_down
270                    end
271                    show(r,level+1)
272                end
273                if level > 1 then
274                    remove(con)
275                end
276            end
277        end
278
279        show(result,1)
280
281        output = concat(output,"\r")
282
283        if buffers then
284            buffers.assign("dirtree",output)
285        end
286
287        return output
288
289    end
290
291\stopluacode
292
293% \protected\def\ShowTree[#1]%
294%   {\begingroup
295%      \ctxlua{document.showtree(dir.glob("#1"))}
296%      \setbox\scratchbox\hbox{\tttf │}
297%      \normalexpanded{
298%           \setupinterlinespace
299%            [line=\the\dimexpr\htdp\scratchbox\relax]
300%      }
301%      \lineskip-1pt
302%      \typebuffer[dirtree]
303%    \endgroup}
304
305\let\dirtreeentry\firstofoneargument
306
307\permanent\protected\def\showdirtree[#1]%
308  {\begingroup
309     \ctxlua{utilities.dirtree.show("#1","private","split")}
310     \startlines
311     \getbuffer[dirtree]
312     \stoplines
313   \endgroup}
314
315\continueifinputfile{m-dirtree.mkxl}
316
317\usemodule[article-basic]
318
319\setuplayout[tight]
320
321\starttext
322
323    \definecolor[dirtree:lua] [darkred]
324    \definecolor[dirtree:lmt] [darkred]
325    \definecolor[dirtree:lfg] [darkblue]
326    \definecolor[dirtree:llg] [darkblue]
327
328  % \protected\def\dirtreeentry#1#2%
329  %   {\doifelsesomething{#2}{\color[dirtree:#2]{#1.#2}}{#1}}
330
331    \protected\def\dirtreeentry#1#2%
332      {\ifparameter#2\or
333         \color[dirtree:#2]{#1.#2}%
334       \else
335         #1%
336       \fi}
337
338    \startcolumns
339       % \showdirtree[t:/texmf/tex/context/base/**]
340         \showdirtree[./**]
341    \stopcolumns
342
343    \startluacode
344     -- local list   = dir.glob("t:/texmf/tex/context/base/**")
345        local list   = dir.glob("./**")
346        local output = utilities.dirtree.show(list,"unicode","default")
347
348        inspect(output)
349    \stopluacode
350
351\stoptext
352