-
Notifications
You must be signed in to change notification settings - Fork 11
/
locco.lua
executable file
·266 lines (247 loc) · 9.58 KB
/
locco.lua
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
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
#!/usr/bin/env lua
-- __Locco__ is a Lua port of [Docco](http://jashkenas.github.com/docco/),
-- the quick-and-dirty, hundred-line-long, literate-programming-style
-- documentation generator. It produces HTML that displays your comments
-- alongside your code. Comments are passed through
-- [Markdown](http://daringfireball.net/projects/markdown/), and code is
-- syntax highlighted.
-- This page is the result of running Locco against its own source file:
-- locco.lua locco.lua
--
-- For its syntax highlighting Locco relies on the help of
-- [David Manura](http://lua-users.org/wiki/DavidManura)'s
-- [Lua Balanced](https://github.com/davidm/lua-balanced) to split
-- up the code. As a markdown engine it ships with
-- [Niklas Frykholm](http://www.frykholm.se/)'s
-- [markdown.lua](http://www.frykholm.se/files/markdown.lua) in the [Lua 5.2
-- compatible version](https://github.com/speedata/luamarkdown) from
-- [Patrick Gundlach](https://github.com/pgundlach). Otherwise there
-- are no external dependencies.
--
-- The generated HTML documentation for the given source files is saved
-- into a `docs` directory. If you have Locco on your path you can run it from
-- the command-line:
-- locco.lua project/*.lua
--
-- Locco is monolingual, but there are many projects written in
-- and with support for other languages, see the
-- [Docco](http://jashkenas.github.com/docco/) page for a list.<br>
-- The [source for Locco](https://github.com/rgieseke/locco) is available on
-- GitHub, and released under the MIT
-- license.
-- ### Setup & Helpers
-- Add script path to package path to find submodules.
local script_path = arg[0]:match('(.+)/.+')
package.path = table.concat({
script_path..'/?.lua',
package.path
}, ';')
-- Load markdown.lua.
local md = require 'markdown'
-- Load Lua Balanced.
local lb = require 'luabalanced'
-- Load HTML templates.
local template = require 'template'
-- Ensure the `docs` directory exists and return the _path_ of the source file.<br>
-- Parameter:<br>
-- _source_: The source file for which documentation is generated.<br>
local function ensure_directory(source)
local path = source:match('(.+)/.+$')
if not path then path = '.' end
os.execute('mkdir -p '..path..'/docs')
return path
end
-- Insert HTML entities in a string.<br>
-- Parameter:<br>
-- _s_: String to escape.<br>
local function escape(s)
s = s:gsub('&', '&')
s = s:gsub('<', '<')
s = s:gsub('>', '>')
s = s:gsub('%%', '%')
return s
end
--local function replace_percent(s)
-- s = s:gsub('%%', '%%%%')
-- return s
--end
-- Define the Lua keywords, built-in functions and operators that should
-- be highlighted.
local keywords = { 'break', 'do', 'else', 'elseif', 'end', 'false', 'for',
'function', 'if', 'in', 'local', 'nil', 'repeat', 'return',
'then', 'true', 'until', 'while' }
local functions = { 'assert', 'collectgarbage', 'dofile', 'error', 'getfenv',
'getmetatable', 'ipairs', 'load', 'loadfile', 'loadstring',
'module', 'next', 'pairs', 'pcall', 'print', 'rawequal',
'rawget', 'rawset', 'require', 'setfenv', 'setmetatable',
'tonumber', 'tostring', 'type', 'unpack', 'xpcall' }
local operators = { 'and', 'not', 'or' }
-- Wrap an item from a list of Lua keywords in a span template or return the
-- unchanged item.<br>
-- Parameters:<br>
-- _item_: An item of a code snippet.<br>
-- _item\_list_: List of keywords or functions.<br>
-- _span\_class_: Style sheet class.<br>
local function wrap_in_span(item, item_list, span_class)
for i=1, #item_list do
if item_list[i] == item then
item = '<span class="'..span_class..'">'..item..'</span>'
break
end
end
return item
end
-- Quick and dirty source code highlighting. A chunk of code is split into
-- comments (at the end of a line), strings and code using the
-- [Lua Balanced](https://github.com/davidm/lua-balanced/blob/master/luabalanced.lua)
-- module. The code is then split again and matched against lists
-- of Lua keywords, functions or operators. All Lua items are wrapped into
-- a span having one of the classes defined in the Locco style sheet.<br>
-- Parameter:<br>
-- _code_: Chunk of code to highlight.<br>
local function highlight_lua(code)
local out = lb.gsub(code,
function(u, s)
local sout
if u == 'c' then -- Comments.
sout = '<span class="c">'..escape(s)..'</span>'
elseif u == 's' then -- Strings.
sout = '<span class="s">'..escape(s)..'</span>'
elseif u == 'e' then -- Code.
s = escape(s)
-- First highlight function names.
s = s:gsub('function ([%w_:%.]+)', 'function <span class="nf">%1</span>')
-- There might be a non-keyword at the beginning of the snippet.
sout = s:match('^(%A+)') or ''
-- Iterate through Lua items and try to wrap operators,
-- keywords and built-in functions in span elements.
-- If nothing was highlighted go to the next category.
for item, sep in s:gmatch('([%a_]+)(%A+)') do
local span, n = wrap_in_span(item, operators, 'o')
if span == item then
span, n = wrap_in_span(item, keywords, 'k')
end
if span == item then
span, n = wrap_in_span(item, functions, 'nt')
end
sout = sout..span..sep
end
end
return sout
end)
out = '<div class="highlight"><pre>'..out..'</pre></div>'
return out
end
-- ### Main Documentation Generation Functions
-- Given a string of source code, parse out each comment and the code that
-- follows it, and create an individual section for it. Sections take the form:
--
-- {
-- docs_text = ...,
-- docs_html = ...,
-- code_text = ...,
-- code_html = ...,
-- }
--
-- Parameter:<br>
-- _source_: The source file to process.<br>
local function parse(source)
local sections = {}
local has_code = false
local docs_text, code_text = '', ''
for line in io.lines(source) do
if line:match('^%s*%-%-') then
if has_code then
code_text = code_text:gsub('\n\n$', '\n') -- remove empty trailing line
sections[#sections + 1] = { ['docs_text'] = docs_text,
['code_text'] = code_text }
has_code = false
docs_text, code_text = '', ''
end
docs_text = docs_text..line:gsub('%s*(%-%-%s?)', '', 1)..'\n'
else
if not line:match('^#!') then -- ignore #!/usr/bin/lua
has_code = true
code_text = code_text..line..'\n'
end
end
end
sections[#sections + 1] = { ['docs_text'] = docs_text,
['code_text'] = code_text }
return sections
end
-- Loop through a table of split sections and convert the documentation
-- from Markdown to HTML and pass the code through Locco's syntax
-- highlighting. Add _docs\_html_ and _code\_html_ elements to the sections
-- table.<br>
-- Parameter:<br>
-- _sections_: A table with split sections.<br>
local function highlight(sections)
for i=1, #sections do
sections[i]['docs_html'] = md.markdown(sections[i]['docs_text'])
sections[i]['code_html'] = highlight_lua(sections[i]['code_text'])
end
return sections
end
-- After the highlighting is done, the template is filled with the documentation
-- and code snippets and an HTML file is written.<br>
-- Parameters:<br>
-- _source_: The source file.<br>
-- _path_: Path of the source file.<br>
-- _filename_: The filename of the source file.<br>
-- _sections_: A table with the original sections and rendered as HTML.<br>
-- _jump\_to_: A HTML chunk with links to other documentation files.
local function generate_html(source, path, filename, sections, jump_to)
local f, err = io.open(path..'/'..'docs/'..filename:gsub('lua$', 'html'), 'wb')
if err then print(err) end
local h = template.header:gsub('%%title%%', source)
h = h:gsub('%%jump%%', jump_to)
f:write(h)
for i=1, #sections do
local t = template.table_entry:gsub('%%index%%', i..'')
t = t:gsub('%%docs_html%%', sections[i]['docs_html'])
t = t:gsub('%%code_html%%', sections[i]['code_html'])
f:write(t)
end
f:write(template.footer)
f:close()
end
-- Generate the documentation for a source file by reading it in,
-- splitting it up into comment/code sections, highlighting and merging
-- them into an HTML template.<br>
-- Parameters:<br>
-- _source_: The source file to process.<br>
-- _path_: Path of the source file.<br>
-- _filename_: The filename of the source file.<br>
-- _jump\_to_: A HTML chunk with links to other documentation files.
local function generate_documentation(source, path, filename, jump_to)
local sections = parse(source)
local sections = highlight(sections)
generate_html(source, path, filename, sections, jump_to)
end
-- Run the script.
-- Generate HTML links to other files in the documentation.
local jump_to = ''
if #arg > 1 then
jump_to = template.jump_start
for i=1, #arg do
local link = arg[i]:gsub('lua$', 'html')
link = link:match('.+/(.+)$') or link
local t = template.jump:gsub('%%jump_html%%', link)
t = t:gsub('%%jump_lua%%', arg[i])
jump_to = jump_to..t
end
jump_to = jump_to..template.jump_end
end
-- Make sure the output directory exists, generate the HTML files for each
-- source file, print what's happening and write the style sheet.
local path = ensure_directory(arg[1])
for i=1, #arg do
local filename = arg[i]:match('.+/(.+)$') or arg[i]
generate_documentation(arg[i], path, filename, jump_to)
print(arg[i]..' --> '..path..'/docs/'..filename:gsub('lua$', 'html'))
end
local f, err = io.open(path..'/'..'docs/locco.css', 'wb')
if err then print(err) end
f:write(template.css)
f:close()