%D \module
%D   [       file=m-dirtree,
%D        version=2024.08.31,
%D          title=\CONTEXT\ Modules,
%D       subtitle=Rendering Directory Trees,
%D         author=Hans Hagen,
%D           date=\currentdate,
%D      copyright={PRAGMA ADE \& \CONTEXT\ Development Team}]

%C This module is part of the \CONTEXT\ macro||package and is
%C therefore copyrighted by \PRAGMA. See mreadme.pdf for
%C details.

%D This module is triggered by a question that Hraban posted to the mailing list. I
%D played abit with \LUA, then found that the fonts were not up to it which is
%D mostly due to the fact that it needs to fit into a baseline (lineheight) model so
%D then I made a \METAPOST\ font. Eventually a bit of interfacing was added.

%D Tha basic \LUA\ handler might move to a utility module (util-dtr.lua) once I feel
%D the need for that.

%  U+2500  right      ─
%  U+2502  down       │
%  U+2514  corner     └
%  U+251C  downright  ├
%  U+252C  rightdown  ┬

\startluacode
    local newprivateslot = fonts.helpers.newprivateslot

    newprivateslot("dirtree one yes")    -- 1 "└─┬──"
    newprivateslot("dirtree one nop")    -- 2         "└────"
    newprivateslot("dirtree first yes")  -- 3 "├─┬──"
    newprivateslot("dirtree first nop")  -- 4         "├────"
    newprivateslot("dirtree last yes")   -- 2 "└────"
    newprivateslot("dirtree last nop")   -- 2         "└────"
    newprivateslot("dirtree middle yes") -- 3 "├─┬──"
    newprivateslot("dirtree middle nop") -- 5         "├────"
    newprivateslot("dirtree down yes")   -- 6 "│ "
    newprivateslot("dirtree down nop")   --           "  "
    newprivateslot("dirtree space yes")  --   " "
    newprivateslot("dirtree space nop")  --           " "
\stopluacode

\startMPcalculation{simplefun}

    path dirtree_glyphs[] ;

    def InitializeDirTree =
        dirtree_glyphs[1] := (1,2) -- (1,1) -- (5,1) && (3,1) -- (3,0)          ; % "└─┬──"
        dirtree_glyphs[2] := (1,2) -- (1,1) -- (5,1)                            ; %         "└────"
        dirtree_glyphs[3] := (1,0) -- (1,2) && (1,1) -- (5,1) && (3,1) -- (3,0) ; % "├─┬──"
        dirtree_glyphs[4] := (1,0) -- (1,2) && (1,1) -- (5,1)                   ; %         "├────"
        dirtree_glyphs[5] := (1,2) -- (1,0) && (1,1) -- (5,1)                   ; % "├────"
        dirtree_glyphs[6] := (1,2) -- (1,0)                                     ; %         "│ "
    enddef ;


    vardef DirTree(expr i, w, h) =
        numeric u ; u := 1 ;
        picture p ; p := image (
            if i > 0 :
                draw dirtree_glyphs[i]
                    xyscaled(u,3u/2)
                    shifted (-u/2,-u)
                    withpen pencircle scaled (u/10)
                ;
            fi
        ) ;
        setbounds p to unitsquare xyscaled(w*u,h*u) ;
        draw p ;
    enddef ;

    lmt_registerglyphs [
        name     = "dirtree",
        units    = 2,
        usecolor = true,
        width    = 1,
        height   = 2,
        depth    = 1,
        preamble = "InitializeDirTree"
    ] ;

    lmt_registerglyph [ category  = "dirtree", private = "dirtree one yes"   , code = "DirTree(1,5,3)", width = 5 ] ;
    lmt_registerglyph [ category  = "dirtree", private = "dirtree one nop"   , code = "DirTree(2,5,3)", width = 5 ] ;
    lmt_registerglyph [ category  = "dirtree", private = "dirtree first yes" , code = "DirTree(3,5,3)", width = 5 ] ;
    lmt_registerglyph [ category  = "dirtree", private = "dirtree first nop" , code = "DirTree(4,5,3)", width = 5 ] ;
    lmt_registerglyph [ category  = "dirtree", private = "dirtree last yes"  , code = "DirTree(1,5,3)", width = 5 ] ;
    lmt_registerglyph [ category  = "dirtree", private = "dirtree last nop"  , code = "DirTree(2,5,3)", width = 5 ] ;
    lmt_registerglyph [ category  = "dirtree", private = "dirtree middle yes", code = "DirTree(3,5,3)", width = 5 ] ;
    lmt_registerglyph [ category  = "dirtree", private = "dirtree middle nop", code = "DirTree(5,5,3)", width = 5 ] ;
    lmt_registerglyph [ category  = "dirtree", private = "dirtree down yes"  , code = "DirTree(6,2,3)", width = 2 ] ;
    lmt_registerglyph [ category  = "dirtree", private = "dirtree down nop"  , code = "DirTree(0,2,3)", width = 2 ] ;
    lmt_registerglyph [ category  = "dirtree", private = "dirtree space yes" , code = "DirTree(0,1,3)", width = 1 ] ;
    lmt_registerglyph [ category  = "dirtree", private = "dirtree space nop" , code = "DirTree(0,1,3)", width = 1 ] ;

\stopMPcalculation

\definefontfeature
  [default]
  [default]
  [metapost={category=dirtree,weight=2.0}]

\definefontfeature
  [none]
  [none]
  [metapost={category=dirtree,weight=2.0}]

\startluacode
    local gmatch     = string.gmatch
    local concat     = table.concat
    local insert     = table.insert
    local remove     = table.remove
    local sortedkeys = table.sortedkeys

    local dirtree     = utilities.dirtree or { }
    utilities.dirtree = dirtree

    local default = {
        one    = { "     ", "     " },
        first  = { "     ", "     " },
        last   = { "     ", "     " },
        middle = { "     ", "     " },
        down   = { "  ",    "  "    },
        space  = { " ",     " "     },
    }

    local unicode = {
        one    = { "└─┬──", "└────" },
        first  = { "├─┬──", "├────" },
        last   = { "└─┬──", "└────" },
        middle = { "├────", "├────" },
        down   = { "│ "   , "  "    },
        space  = { " "    , " "     },
    }

    local simple = {
        one    = { "    +", "    +" },
        first  = { "    +", "     " },
        last   = { "    +", "     " },
        middle = { "    +", "     " },
        down   = { "  "   , "  "    },
        space  = { " "    , " "     },
    }

    local private ; if buffers then

        local ps = fonts.helpers.privateslot
        local uc = utf.char

        private = {
            one    = { uc(ps("dirtree one yes"   )), uc(ps("dirtree one nop"   )) },
            first  = { uc(ps("dirtree first yes" )), uc(ps("dirtree first nop" )) },
            last   = { uc(ps("dirtree last yes"  )), uc(ps("dirtree last nop"  )) },
            middle = { uc(ps("dirtree middle yes")), uc(ps("dirtree middle nop")) },
            down   = { uc(ps("dirtree down yes"  )), uc(ps("dirtree down nop"  )) },
            space  = { uc(ps("dirtree space yes" )), uc(ps("dirtree space nop" )) },
        }

    end

    dirtree.symbols = {
        default  = default,
        unicode  = unicode,
        simple   = simple,
        private  = private,
    }

    -- Performance is okay, otherwise we would reuse the con table and do a selective
    -- concat here.

    local nameonly, suffix = file.nameonly, file.suffix

    dirtree.entries = {
        default = function(str)
            return str
        end,
        split = function(str)
            return "\\dirtreeentry{" .. nameonly(str) .. "}{" .. suffix(str) .. "}"
        end,
    }

    function dirtree.show(tree,symbolset,direntry)

        if type(tree) == "string" then
            tree = dir.glob(tree)
        end

        if type(tree) ~= "table" then
            return
        end

        if not direntry then
            direntry = "default"
        end
        direntry = dirtree.entries[direntry] or dirtree.entries.default

        local result = { }
        local output = { }

        if not symbolset then
            symbolset = "default"
        end

        local used = dirtree.symbols[symbolset] or dirtree.symbols.default

        table.setmetatableindex(used,dirtree.symbols.default)

        local yes_one   = used.one   [1] local nop_one   = used.one   [2]
        local yes_first = used.first [1] local nop_first = used.first [2]
        local yes_last  = used.last  [1] local nop_last  = used.last  [2]
        local yes_else  = used.middle[1] local nop_else  = used.middle[2]
        local yes_down  = used.down  [1] local nop_down  = used.down  [2]
        local yes_space = used.space [1] local nop_space = used.space [2]

        if #tree > 0 then
            for i=1,#tree do
                local d = false
                local r = result
                for s in gmatch(tree[i],"[^/]+") do
                    if d then
                        local rs = r[s]
                        if not rs then
                            rs = { }
                            r[s] = { }
                        end
                        r = rs
                    else
                        d = true
                    end
                end
            end
        else
            -- assume a hash
        end

        local level  = 0
        local con    = { }

        local function show(result,level)
            local list = sortedkeys(result)
            local size = #list
            for i=1,size do
                local l = list[i]
                local r = result[l]
                local sub = next(r)
                if level == 1 then
                    -- nothing special to do
                elseif size == 1 then
                    insert(con,sub and yes_one or nop_one)
                elseif i == 1 then
                    insert(con,sub and yes_first or nop_first)
                elseif i == size then
                    insert(con,sub and yes_last or nop_last)
                else
                    insert(con,sub and yes_else or nop_else)
                end
                if level == 1 then
                    output[#output+1] = direntry(l)
                else
                    output[#output+1] = concat(con) .. (sub and yes_space or nop_space) .. direntry(l)
                end
                if sub then
                    if size == 1 then
                        con[#con] = nop_down
                    elseif i == size then
                        con[#con] = nop_down
                    else
                        con[#con] = yes_down
                    end
                    show(r,level+1)
                end
                if level > 1 then
                    remove(con)
                end
            end
        end

        show(result,1)

        output = concat(output,"\r")

        if buffers then
            buffers.assign("dirtree",output)
        end

        return output

    end

\stopluacode

% \protected\def\ShowTree[#1]%
%   {\begingroup
%      \ctxlua{document.showtree(dir.glob("#1"))}
%      \setbox\scratchbox\hbox{\tttf │}
%      \expanded{
%           \setupinterlinespace
%            [line=\the\dimexpr\htdp\scratchbox\relax]
%      }
%      \lineskip-1pt
%      \typebuffer[dirtree]
%    \endgroup}

\let\dirtreeentry\firstofoneargument

\permanent\protected\def\showdirtree[#1]%
  {\begingroup
     \ctxlua{utilities.dirtree.show("#1","private","split")}
     \startlines
     \getbuffer[dirtree]
     \stoplines
   \endgroup}

\continueifinputfile{m-dirtree.mkxl}

\usemodule[article-basic]

\setuplayout[tight]

\starttext

    \definecolor[dirtree:lua] [darkred]
    \definecolor[dirtree:lmt] [darkred]
    \definecolor[dirtree:lfg] [darkblue]
    \definecolor[dirtree:llg] [darkblue]

  % \protected\def\dirtreeentry#1#2%
  %   {\doifelsesomething{#2}{\color[dirtree:#2]{#1.#2}}{#1}}

    \protected\def\dirtreeentry#1#2%
      {\ifparameter#2\or
         \color[dirtree:#2]{#1.#2}%
       \else
         #1%
       \fi}

    \startcolumns
       % \showdirtree[t:/texmf/tex/context/base/**]
         \showdirtree[./**]
    \stopcolumns

    \startluacode
     -- local list   = dir.glob("t:/texmf/tex/context/base/**")
        local list   = dir.glob("./**")
        local output = utilities.dirtree.show(list,"unicode","default")

        inspect(output)
    \stopluacode

\stoptext