1local gio = require("lgi").Gio
2local gobject = require("lgi").GObject
3local glib = require("lgi").GLib
4
5local name_attr = gio.FILE_ATTRIBUTE_STANDARD_NAME
6local type_attr = gio.FILE_ATTRIBUTE_STANDARD_TYPE
7
8local module = {}
9
10-- Like pairs(), but iterate over keys in a sorted manner. Does not support
11-- modifying the table while iterating.
12local function sorted_pairs(t)
13    -- Collect all keys
14    local keys = {}
15    for k in pairs(t) do
16        table.insert(keys, k)
17    end
18
19    table.sort(keys)
20
21    -- return iterator function
22    local i = 0
23    return function()
24        i = i + 1
25        if keys[i] then
26            return keys[i], t[keys[i]]
27        end
28    end
29end
30
31-- Recursive file scanner
32local function get_all_files(path, ext, ret)
33    ret = ret or {}
34    local enumerator = gio.File.new_for_path(path):enumerate_children(
35        table.concat({name_attr, type_attr}, ",") , 0, nil, nil
36    )
37
38    for file in function() return enumerator:next_file() end do
39        local file_name = file:get_attribute_as_string(name_attr)
40        local file_type = file:get_file_type()
41        local match_ext = file_name:match("[.]"..ext.."$" or "")
42        local fpath     = enumerator:get_child(file):get_path()
43        local is_test   = fpath:match("/tests/")
44        if file_type == "REGULAR" and match_ext and not is_test then
45            table.insert(ret, fpath)
46        elseif file_type == "DIRECTORY" then
47            get_all_files(enumerator:get_child(file):get_path(), ext, ret)
48        end
49    end
50
51    return ret
52end
53
54local function path_to_module(path)
55    if path:match("[.]c$") then
56        return path:gmatch("/([^./]+)[.]c$")()
57    end
58
59    for _, module in ipairs {
60        "awful", "wibox", "gears", "naughty", "menubar", "beautiful"
61    } do
62        local match = path:match("/"..module.."/([^.]+).lua")
63        if match then
64            return module.."."..match:gsub("/",".")
65        end
66    end
67
68    error("Cannot figure out module for " .. tostring(path))
69end
70
71function module.path_to_html(path)
72    local mod = path_to_module(path):gsub(".init", "")
73    local f = assert(io.open(path))
74    while true do
75        local line = f:read()
76        if not line then break end
77        if line:match("@classmod") then
78            f:close()
79            return "../classes/".. mod ..".html#"
80        end
81        if line:match("@module") or line:match("@submodule") then
82            f:close()
83            return "../libraries/".. mod ..".html#"
84        end
85    end
86    f:close()
87
88    error("Cannot figure out if module or class: " .. tostring(path))
89end
90
91local function get_link(file, element, element_name)
92    return table.concat {
93        "<a href='",
94        module.path_to_html(file),
95        element,
96        "'>",
97        element_name,
98        "</a>"
99    }
100end
101
102local function parse_files(paths, property_name, matcher, name_matcher)
103    local exp1 = "[-*]*[ ]*@".. property_name .." ([^ \n]*)"
104    local exp2 = matcher or "[-*]*[ ]*".. property_name ..".(.+)"
105    local exp3 = name_matcher or "[. ](.+)"
106
107    local ret = {}
108
109    table.sort(paths)
110
111    -- Find all @beautiful doc entries
112    for _,file in ipairs(paths) do
113        local f = io.open(file)
114
115        local buffer = ""
116
117        for line in f:lines() do
118
119            local var = line:gmatch(exp1)()
120
121            -- There is no backward/forward pattern in lua
122            if #line <= 1 then
123                buffer = ""
124            elseif #buffer and not var then
125                buffer = buffer.."\n"..line
126            elseif line:sub(1,3) == "---" or line:sub(1,3) == "/**" then
127                buffer = line
128            end
129
130            if var then
131                -- Get the @param, @see and @usage
132                local params = ""
133                for line in f:lines() do
134                    if line:sub(1,2) ~= "--" and line:sub(1,2) ~= " *" then
135                        break
136                    else
137                        params = params.."\n"..line
138                    end
139                end
140
141                local name = var:gmatch(exp2)()
142                if not name then
143                    print("WARNING:", var,
144                        "seems to be misformatted. Use `beautiful.namespace_name`"
145                    )
146                else
147                    table.insert(ret, {
148                        file = file,
149                        name = name:gsub("_", "\\_"),
150                        link = get_link(file, var, var:match(exp3):gsub("_", "\\_")),
151                        desc = buffer:gmatch("[-*/ \n]+([^\n.]*)")() or "",
152                        mod  = path_to_module(file),
153                    })
154                end
155
156                buffer = ""
157            end
158        end
159    end
160
161    return ret
162end
163
164local function create_table(entries, columns, prefix)
165    prefix = prefix or ""
166    local lines = {}
167
168    for _, entry in ipairs(entries) do
169        local line = "  <tr>"
170
171        for _, column in ipairs(columns) do
172            line = line.."<td>"..entry[column].."</td>"
173        end
174
175        table.insert(lines, prefix..line.."</tr>\n")
176    end
177
178    return [[--<table class='widget_list' border=1>
179]]..prefix..[[<tr>
180]]..prefix..[[ <th align='center'>Name</th>
181]]..prefix..[[ <th align='center'>Description</th>
182]]..prefix..[[</tr>
183]] .. table.concat(lines) .. prefix .."</table>\n"
184end
185
186module.create_table  = create_table
187module.parse_files   = parse_files
188module.sorted_pairs  = sorted_pairs
189module.get_all_files = get_all_files
190
191return module
192