Merge tag '1.4.2'
1.4 release LR
Julian Wollrath
10 years ago
0 | ## Version 1.4.2 | |
1 | ||
2 | ### Features | |
3 | ||
4 | * Can define fields/properties of objects; `readonly` modifier supported (#93) | |
5 | * Can switch off auto-linking to Lua manual with `no_lua_ref` | |
6 | * Module sorting is off by default, use `sort_modules=true` | |
7 | * References to 'classes' now work properly | |
8 | * Option to use first Markdown title instead of file names with `use_markdown_titles` | |
9 | * Automatic `Metamethods` and `Methods` sections generated for `classmod` classes | |
10 | * `unqualified=true` to strip package names on sidebar (#110) | |
11 | * Custom tags (which may be hidden) | |
12 | * Custom Display Name handlers | |
13 | ||
14 | ### Fixes | |
15 | ||
16 | * stricter about doc comments, now excludes common '----- XXXXX ----' pattern | |
17 | * no longer expects space after `##` in Markdown (#96) | |
18 | * Section lookup was broken | |
19 | * With `export` tag, decide whether method is static or not | |
20 | * `classmod` classes now respect custom sections (#113) | |
21 | * Minor issues with prettification | |
22 | * Command-line flags set explicitly take precendence over configuration file values. | |
23 | * Boilerplate Lua block comment ignored properly (#137) | |
24 | * Inline links with underscores sorted (#22) | |
25 | * Info section ordering is now consistent (#150) | |
26 | ||
27 | ## Version 1.4.0 | |
28 | ||
29 | ### Features | |
30 | ||
31 | * `sort=true` to sort items within sections alphabetically | |
32 | * `@set` tag in module comments; e.g, can say `@set sort=true` | |
33 | * `@classmod` tag for defining modules that export one class | |
34 | * can generate Markdown output | |
35 | * Can prettify C as well as Lua code with built-in prettifier | |
36 | * lfs and lpeg references understood | |
37 | * 'pale' template available | |
38 | * multiple return groups | |
39 | * experimental `@error` tag | |
40 | * Moonscript and plain C support | |
41 | ||
42 | ||
43 | ### Fixes | |
44 | ||
45 | * works with non-compatibily Lua 5.2, including `markdown.lua` | |
46 | * module names can not be types | |
47 | * all `builtin` Lua files are requirable without `module` | |
48 | * backticks expand in copyright and other 'info' tabs | |
49 | * `-m` tries harder to resolve methods | |
50 | * auto-scroll in navigation area to avoid breaking identifiers | |
51 | * better error message for non-luadoc-compatible behaviour | |
52 | * custom see references fixed | |
53 | ||
54 | ||
55 |
0 | project='LDoc' | |
1 | title='LDoc documentation' | |
2 | description='A Lua documentation tool' | |
3 | format='discount' | |
4 | file='ldoc.lua' | |
5 | dir='out' | |
6 | readme='doc/doc.md' |
5 | 5 | file='../ldoc.lua' |
6 | 6 | dir='../out' |
7 | 7 | readme='doc.md' |
8 | examples = {'../tests/styles/colon.lua','../tests/styles/four.lua','../tests/example/mylib.c'} | |
8 | style='!pale' | |
9 | kind_names={topic='Manual',script='Programs'} | |
10 | examples = { | |
11 | '../tests/styles/colon.lua', | |
12 | '../tests/styles/four.lua', | |
13 | '../tests/styles/three.lua', | |
14 | '../tests/styles/multiple.lua', | |
15 | '../tests/example/mylib.c', | |
16 | '../tests/moonscript/List.moon', | |
17 | } | |
9 | 18 |
3 | 3 | |
4 | 4 | ## Introduction |
5 | 5 | |
6 | LDoc is a second-generation documentation tool that can be used as a replacement for | |
7 | [LuaDoc](http://keplerproject.github.com/luadoc/). It arose out of my need to document my | |
8 | own projects and only depends on the [Penlight](https://github.com/stevedonovan/Penlight) | |
9 | libraries. | |
10 | ||
11 | It is mostly compatible with LuaDoc, except that certain workarounds are no longer needed. | |
6 | LDoc is a software documentation tool which automatically generates API documentation | |
7 | out of source code comments (doc comments). It is mainly targeted at Lua and documenting | |
8 | Lua APIs, but it can also parse C with according doc comments for documenting Lua modules | |
9 | implemented in C. | |
10 | ||
11 | It is mostly compatible with [LuaDoc](http://keplerproject.github.com/luadoc/), | |
12 | except that certain workarounds are no longer needed. | |
12 | 13 | For instance, it is not so married to the idea that Lua modules should be defined using the |
13 | 14 | `module` function; this is not only a matter of taste since this has been deprecated in Lua |
14 | 15 | 5.2. |
31 | 32 | |
32 | 33 | ## Commenting Conventions |
33 | 34 | |
34 | LDoc follows the conventions established by Javadoc and later by LuaDoc. | |
35 | LDoc follows the conventions established by Javadoc and later by LuaDoc to document the | |
36 | modules, functions, tables and types ("classes") of your API. | |
37 | ||
38 | ### Doc comments | |
35 | 39 | |
36 | 40 | Only 'doc comments' are parsed; these can be started with at least 3 hyphens, or by a empty |
37 | 41 | comment line with at least 3 hypens: |
54 | 58 | warning issued. The only exception is if the module starts with an explicit `module` |
55 | 59 | statement. |
56 | 60 | |
61 | If your coding standards require a boilerplate copyright notice, then the `-B` flag or | |
62 | `boilerplate=true` will make LDoc ignore the first comment of each module. | |
63 | ||
64 | Common commenting patterns like '---- (text) -----' are exempted, since they are often used | |
65 | for programmer-facing documentation. | |
66 | ||
67 | ||
68 | ### Tags | |
69 | ||
57 | 70 | All doc comments start with a summary sentence, that ends with a period or a question mark. |
58 | 71 | An optional description may follow. Normally the summary sentence will appear in the module |
59 | 72 | contents. |
60 | 73 | |
61 | After this descriptive text, there will typically be _tags_. These follow the convention | |
62 | established by Javadoc and widely used in tools for other languages. | |
74 | After this descriptive text, there will typically be _tags_ which are introduced with an @. | |
75 | These follow the convention established by Javadoc and widely used in tools for other languages. | |
76 | ||
77 | --- Some doc comment | |
78 | -- @tag1 parameters for first tag | |
79 | -- @tag2 parameters for the second tag | |
80 | ||
81 | The order of tags is not important, but as always, consistency is useful. | |
82 | ||
83 | Here are all the tags known to LDoc: | |
84 | ||
85 | * **@module** A Lua module containing functions and tables, which may be inside sections | |
86 | * **@classmod** Like **@module** but describing a class | |
87 | * **@submodule** A file containing definitions that you wish to put into the named _master_ module | |
88 | * **@script** A Lua program | |
89 | * **@author** (multiple), **copyright**, **@license**, **@release** only used for _project-level_ tags like **@module** | |
90 | * **@function**, **@lfunction**. Functions inside a module | |
91 | * **@param** formal arguments of a function (multiple) | |
92 | * **@return** returned values of a function (multiple) | |
93 | * **@raise** unhandled error thrown by this function | |
94 | * **@local** explicitly marks a function as not being exported (unless `--all`) | |
95 | * **@see** reference other documented items | |
96 | * **@usage** give an example of a function's use. (Has a somewhat different meaning when used | |
97 | with **@module**) | |
98 | * **@table** a Lua table | |
99 | * **@field** a named member of a table | |
100 | * **@section** starting a named section for grouping functions or tables together | |
101 | * **@type** a section which describes a class | |
102 | * **@within** puts the function or table into an implicit section | |
103 | * **@fixme**, **@todo** and **@warning** are _annotations_, which are doc comments that | |
104 | occur inside a function body. | |
105 | ||
106 | The first important tag to know is the module tag: | |
107 | ||
108 | #### Modules: naming and describing your API module | |
109 | ||
110 | The first thing in your API module should be a name and a description. | |
111 | This is how a module is commonly done in Lua 5.2 with a **@module** tag at the top | |
112 | which introduces the name: | |
113 | ||
114 | --- a test module | |
115 | -- @module test | |
116 | ||
117 | local test = {} | |
118 | ||
119 | function test.my_module_function_1() | |
120 | ... | |
121 | end | |
122 | ||
123 | ... | |
124 | ||
125 | return test | |
126 | ||
127 | This sets up a module named 'test' with the description 'a test module'. | |
128 | ||
129 | #### Functions | |
130 | ||
131 | The next thing to describe are the functions your module has. | |
132 | This is a simple example of a documented function: | |
63 | 133 | |
64 | 134 | --- foo explodes text. |
65 | 135 | -- It is a specialized splitting operation on a string. |
69 | 139 | .... |
70 | 140 | end |
71 | 141 | |
72 | There are also 'tparam' and 'treturn' which let you [specify a type](#Tag_Modifiers): | |
73 | ||
74 | -- @tparam string text the string | |
142 | You can also give the function name itself as an explicit tag, | |
143 | which is especially useful when documenting a Lua api exported by C code: | |
144 | ||
145 | /// A C function which is exported to Lua with another name, | |
146 | // because the ways of C can be mysterious! | |
147 | // @function our_nice_function | |
148 | int _some_function_for_lua(lua_State* l) { | |
149 | .... | |
150 | } | |
151 | ||
152 | The tags basically add all the detail that cannot be derived from the source code | |
153 | automatically. | |
154 | ||
155 | #### Function parameters and return values | |
156 | ||
157 | Common tags are the 'param' tag which takes a parameter name followed by a parameter | |
158 | description separated by a space, and the 'return' tag which is simply followed by | |
159 | a description for a return value: | |
160 | ||
161 | -- @param name_of_parameter the description of this parameter as verbose text | |
162 | -- @return the description of the return value | |
163 | ||
164 | If you want to [specify a type](#Tag_Modifiers) for a parameter or a return value, | |
165 | there are also 'tparam' and 'treturn': | |
166 | ||
167 | -- @tparam string text this parameter is named 'text' and has the fixed type 'string' | |
75 | 168 | -- @treturn {string,...} a table of substrings |
76 | 169 | |
77 | 170 | There may be multiple 'param' tags, which should document each formal parameter of the |
78 | 171 | function. For Lua, there can also be multiple 'return' tags |
79 | ||
80 | --- solvers for common equations. | |
81 | module("solvers", package.seeall) | |
82 | 172 | |
83 | 173 | --- solve a quadratic equation. |
84 | 174 | -- @param a first coeff |
98 | 188 | end |
99 | 189 | |
100 | 190 | ... |
101 | ||
102 | This is the common module style used in Lua 5.1, but it's increasingly common to see less | |
103 | 'magic' ways of creating modules in Lua. Since `module` is deprecated in Lua 5.2, any | |
104 | future-proof documentation tool needs to handle these styles gracefully: | |
105 | ||
106 | --- a test module | |
107 | -- @module test | |
108 | ||
109 | local test = {} | |
110 | ||
111 | --- first test. | |
112 | function test.one() | |
113 | ... | |
114 | end | |
115 | ||
116 | ... | |
117 | ||
118 | return test | |
119 | ||
120 | Here the name of the module is explicitly given using the 'module' tag. If you leave this | |
121 | out, then LDoc will infer the name of the module from the name of the file and its relative | |
122 | location in the filesystem; this logic is also used for the `module(...)` idiom. (How this | |
123 | works and when you need to provide extra information is discussed later.) | |
124 | ||
125 | It is common to use a local name for a module when declaring its contents. In this case the | |
126 | 'alias' tag can tell LDoc that these functions do belong to the module: | |
127 | ||
128 | --- another test. | |
129 | -- @module test2 | |
130 | -- @alias M | |
131 | ||
132 | local M = {} | |
133 | ||
134 | -- first test. | |
135 | function M.one() | |
136 | .. | |
137 | end | |
138 | ||
139 | return M | |
140 | ||
141 | `M` and `_M` are used commonly enough that LDoc will recognize them as aliases | |
142 | automatically, but 'alias' allows you to use any identifier. | |
143 | ||
144 | LDoc tries to deduce the function name and the formal parameter names from examining the | |
145 | code after the doc comment. It also recognizes the 'unsugared' way of defining functions as | |
146 | explicit assignment to a variable: | |
147 | ||
148 | --- second test. | |
149 | M.two = function(...) ... end | |
150 | ||
151 | Apart from exported functions, a module usually contains local functions. By default, LDoc | |
152 | does not include these in the documentation, but they can be enabled using the `--all` flag. | |
153 | They can be documented just like 'public' functions: | |
154 | ||
155 | --- it's clear that boo is local from context. | |
156 | local function boo(...) .. end | |
157 | ||
158 | local foo | |
159 | ||
160 | --- we need to give a hint here for foo | |
161 | -- @local here | |
162 | function foo(...) .. end | |
191 | Of course there is also the 'module' tag which you have already seen. | |
192 | ||
193 | #### Tables and constant values (fields) | |
163 | 194 | |
164 | 195 | Modules can of course export tables and other values. The classic way to document a table |
165 | 196 | looks like this: |
193 | 224 | |
194 | 225 | That is, a module may contain exported functions, local functions, tables and fields. |
195 | 226 | |
227 | #### Explicitly specifying a function or fields | |
228 | ||
196 | 229 | When the code analysis would lead to the wrong type, you can always be explicit. |
197 | 230 | |
198 | --- module contents. | |
231 | --- module contents with explicitly documented field _CONTENTS. | |
199 | 232 | -- @field _CONTENTS |
200 | 233 | M._CONTENTS = {constants=true,one=true,...} |
201 | 234 | |
202 | The order of tags is not important, but as always, consistency is useful. Tags like 'param' | |
203 | and 'return' can be specified multiple times, whereas a type tag like 'function' can only | |
204 | occur once in a comment. The basic rule is that a single doc comment can only document one | |
205 | entity. | |
235 | --- an explicitly named function. | |
236 | -- @function my_function | |
237 | function my_function() | |
238 | ... | |
239 | end | |
240 | ||
241 | As mentioned before, this is often especially useful in C where things | |
242 | may look different in the C code than they will in the final Lua api which | |
243 | you want to document. | |
244 | ||
245 | ### Doing modules the Lua 5.1 way | |
246 | ||
247 | As an alternative to using the 'module' tag as described before, you | |
248 | can still start your modules the Lua 5.1 way: | |
249 | ||
250 | --- solvers for common equations. | |
251 | module("solvers", package.seeall) | |
252 | ||
253 | However, the 'module' function is deprecated in Lua 5.2 and it is increasingly | |
254 | common to see less 'magic' ways of creating modules, as seen in the description | |
255 | of the 'module' tag previously with the explicitely returned module table. | |
256 | ||
257 | #### Repeating tags | |
258 | ||
259 | Tags like 'param' and 'return' can be specified multiple times, whereas a type | |
260 | tag like 'function' can only occur once in a comment. | |
261 | ||
262 | The basic rule is that a single doc comment can only document one entity. | |
263 | ||
264 | ### Local module name | |
265 | ||
266 | It is common to use a local name for a module when declaring its contents. In this case the | |
267 | 'alias' tag can tell LDoc that these functions do belong to the module: | |
268 | ||
269 | --- another test. | |
270 | -- @module test2 | |
271 | -- @alias M | |
272 | ||
273 | local M = {} | |
274 | ||
275 | -- first test. | |
276 | function M.one() | |
277 | .. | |
278 | end | |
279 | ||
280 | return M | |
281 | ||
282 | `M` and `_M` are used commonly enough that LDoc will recognize them as aliases | |
283 | automatically, but 'alias' allows you to use any identifier. | |
284 | ||
285 | LDoc tries to deduce the function name and the formal parameter names from examining the | |
286 | code after the doc comment. It also recognizes the 'unsugared' way of defining functions as | |
287 | explicit assignment to a variable: | |
288 | ||
289 | --- second test. | |
290 | M.two = function(...) ... end | |
291 | ||
292 | ### Local functions | |
293 | ||
294 | Apart from exported functions, a module usually contains local functions. By default, LDoc | |
295 | does not include these in the documentation, but they can be enabled using the `--all` flag. | |
296 | They can be documented just like 'public' functions: | |
297 | ||
298 | --- it's clear that boo is local from context. | |
299 | local function boo(...) .. end | |
300 | ||
301 | local foo | |
302 | ||
303 | --- we need to give a hint here for foo | |
304 | -- @local here | |
305 | function foo(...) .. end | |
306 | ||
307 | ### Alternative way of specifying tags | |
308 | ||
309 | Since 1.3, LDoc allows the use of _colons_ instead of @. | |
310 | ||
311 | --- a simple function. | |
312 | -- string name person's name | |
313 | -- int: age age of person | |
314 | -- !person: person object | |
315 | -- treturn: ?string | |
316 | -- function check(name,age) | |
317 | ||
318 | However, you must either use the `--colon` flag or set `colon=true` in your `config.ld`. | |
319 | ||
320 | In this style, types may be used directly if prefixed with '!' or '?' (for type-or-nil) | |
321 | ||
322 | (see @{colon.lua}, rendered [here](http://stevedonovan.github.io/ldoc/examples/colon)) | |
323 | ||
324 | ### Which files are processed | |
206 | 325 | |
207 | 326 | By default, LDoc will process any file ending in '.lua' or '.luadoc' in a specified |
208 | 327 | directory; you may point it to a single file as well. A 'project' usually consists of many |
217 | 336 | (containing code) and examples and 'topics' (containing documentation). These then contain |
218 | 337 | items like functions, tables, sections, and so forth. |
219 | 338 | |
220 | If you want to document scripts, then use `@script` instead of `@module`. New with 1.4 is | |
221 | `@classmod` which is a module which exports a single class. | |
339 | If you want to document scripts, then use **@script** instead of **@module**. New with 1.4 is | |
340 | **@classmod** which is a module which exports a single class. | |
222 | 341 | |
223 | 342 | |
224 | 343 | ## See References |
225 | 344 | |
226 | The tag 'see' is used to reference other parts of the documentation, and 'usage' can provide | |
345 | **@see** is used to reference other parts of the documentation, and **@usage** can provide | |
227 | 346 | examples of use; there can be multiple such tags: |
228 | 347 | |
229 | 348 | --------- |
240 | 359 | to a function in another module, then the reference has to be qualified. |
241 | 360 | |
242 | 361 | References to methods use a colon: `myclass:method`; this is for instance how you would |
243 | refer to members of a `@type` section. | |
244 | ||
245 | The example at `tests/complex` shows how @see references are interpreted: | |
362 | refer to members of a **@type** section. | |
363 | ||
364 | The example at `tests/complex` shows how **@see** references are interpreted: | |
246 | 365 | |
247 | 366 | complex.util.parse |
248 | 367 | complex.convert.basic |
263 | 382 | 'table.concat' are handled sensibly. |
264 | 383 | |
265 | 384 | References may be made inline using the `@{\ref}` syntax. This may appear anywhere in the |
266 | text, and is more flexible than `@see`. In particular, it provides one way to document the | |
385 | text, and is more flexible than **@see**. In particular, it provides one way to document the | |
267 | 386 | type of a parameter or return value when that type has a particular structure: |
268 | 387 | |
269 | 388 | ------ |
287 | 406 | |
288 | 407 | You can also put references in backticks, like `\`stdvars\``. This is commonly used in |
289 | 408 | Markdown to indicate code, so it comes naturally when writing documents. It is controlled by |
290 | the configuration variable `backtick_references`; the default is `true` if you use Markdown | |
291 | in your project, but can be specified explicitly in your `config.ld`. | |
292 | ||
293 | ### Custom @see References | |
409 | the configuration variable `backtick_references` or the `backtick` format; | |
410 | the default is `true` if you use Markdown in your project, but can be specified explicitly | |
411 | in `config.ld`. | |
412 | ||
413 | To quote such references so they won't be expanded, say @{\\ref}. | |
414 | ||
415 | #### Custom @see References | |
294 | 416 | |
295 | 417 | It's useful to define how to handle references external to a project. For instance, in the |
296 | 418 | [luaposix](https://github.com/luaposix/luaposix) project we wanted to have `man` references |
309 | 431 | |
310 | 432 | local upat = "http://www.kernel.org/doc/man-pages/online/pages/man%s/%s.%s.html" |
311 | 433 | |
312 | custom_see_handler('^(%a+)%((%d)%)$',function(name,section) | |
434 | custom_see_handler('^([%w_]+)%((%d)%)$',function(name,section) | |
313 | 435 | local url = upat:format(section,name,section) |
314 | 436 | local name = name .. '(' ..section..')' |
315 | 437 | return name, url |
316 | 438 | end) |
317 | 439 | |
318 | '^(%a+)%((%d)%)$' both matches the pattern and extracts the name and its section. Then it's | |
440 | `^([%w_]+)%((%d)%)$` both matches the pattern and extracts the name and its section. Then it's | |
319 | 441 | a simple matter of building up the appropriate URL. The function is expected to |
320 | 442 | return _link text_ and _link source_ and the patterns are checked before LDoc tries to resolve |
321 | 443 | project references. So it is best to make them match as exactly as possible. |
322 | 444 | |
445 | ## Module Tags | |
446 | ||
447 | LDoc requires you to have a module doc comment. If your code style requires | |
448 | license blocks that might look like doc comments, then set `boilerplate=true` in your | |
449 | configuration and they will be skipped. | |
450 | ||
451 | This comment does not have to have an explicit **@module** tag and LDoc continues to | |
452 | respect the use of `module()`. | |
453 | ||
454 | There are three types of 'modules' (i.e. 'project-level'); `module`, a library | |
455 | loadable with `require()`, `script`, a program, and `classmod` which is a class | |
456 | implemented in a single module. | |
457 | ||
458 | There are some tags which are only useful in module comments: `author`,`copyright`, | |
459 | `license` and `release`. These are presented in a special **Info** section in the | |
460 | default HTML output. | |
461 | ||
462 | The **@usage** tag has a somewhat different presentation when used in modules; the text | |
463 | is presented formatted as-is in a code font. If you look at the script `ldoc` in | |
464 | this documentation, you can see how the command-line usage is shown. Since coding | |
465 | is all about avoiding repetition and the out-of-sync issues that arise, | |
466 | the **@usage** tag can appear later in the module, before a long string. For instance, | |
467 | the main script of LDoc is [ldoc.lua](https://github.com/stevedonovan/LDoc/blob/master/ldoc.lua) | |
468 | and you will see that the usage tag appears on line 36 before the usage string | |
469 | presented as help. | |
470 | ||
471 | **@export** is another module tag that is usually 'detached'. It is for supporting | |
472 | modules that wish to explicitly export their functions @{three.lua|at the end}. | |
473 | In that example, both `question` and `answer` are local and therefore private to | |
474 | the module, but `answer` has been explicitly exported. (If you invoke LDoc with | |
475 | the `-a` flag on this file, you will see the documentation for the unexported | |
476 | function as well.) | |
477 | ||
478 | **@set** is a powerful tag which assigns a configuration variable to a value _just for this module_. | |
479 | Saying `@set no_summary=true` in a module comment will temporarily disable summary generation when | |
480 | the template is expanded. Generally configuration variables that effect template expansion | |
481 | are modifiable in this way. For instance, if you wish that the contents of a particular module | |
482 | be sorted, then `@set sort=true` will do it _just_ for that module. | |
483 | ||
323 | 484 | ## Sections |
324 | 485 | |
325 | 486 | LDoc supports _explicit_ sections. By default, the implicit sections correspond to the pre-existing |
326 | 487 | types in a module: 'Functions', 'Tables' and 'Fields' (There is another default section |
327 | 488 | 'Local Functions' which only appears if LDoc is invoked with the `--all` flag.) But new |
328 | sections can be added; the first mechanism is when you define a new type (say 'macro'). Then a new | |
329 | section ('Macros') is created to contain these types. | |
330 | ||
331 | There is also a way to declare ad-hoc sections using the `@section` tag. | |
489 | sections can be added; the first mechanism is when you @{Adding_new_Tags|define a new type} | |
490 | (say 'macro'). Then a new section ('Macros') is created to contain these types. | |
491 | ||
492 | There is also a way to declare ad-hoc sections using the **@section** tag. | |
332 | 493 | The need occurs when a module has a lot of functions that need to be put into logical |
333 | 494 | sections. |
334 | 495 | |
350 | 511 | |
351 | 512 | A section doc-comment has the same structure as a normal doc-comment; the summary is used as |
352 | 513 | the new section title, and the description will be output at the start of the function |
353 | details for that section. | |
354 | ||
355 | In any case, sections appear under 'Contents' on the left-hand side. See the | |
514 | details for that section; the name is not used, but must be unique. | |
515 | ||
516 | Sections appear under 'Contents' on the left-hand side. See the | |
356 | 517 | [winapi](http://stevedonovan.github.com/winapi/api.html) documentation for an example of how |
357 | 518 | this looks. |
358 | 519 | |
373 | 534 | end |
374 | 535 | |
375 | 536 | (In an ideal world, we would use the word 'class' instead of 'type', but this would conflict |
376 | with the LuaDoc usage.) | |
537 | with the LuaDoc `class` tag.) | |
377 | 538 | |
378 | 539 | A section continues until the next section is found, `@section end`, or end of file. |
379 | 540 | |
380 | You can put items into an implicit section using the @within tag. This allows you to put | |
541 | You can put items into an implicit section using **@within**. This allows you to put | |
381 | 542 | adjacent functions in different sections, so that you are not forced to order your code |
382 | 543 | in a particular way. |
383 | 544 | |
384 | Sometimes a module may logically span several files. There will be a master module with name | |
545 | With 1.4, there is another option for documenting classes, which is the top-level type | |
546 | `classmod`. It is intended for larger classes which are implemented within one module, | |
547 | and the advantage that methods can be put into sections. | |
548 | ||
549 | Sometimes a module may logically span several files, which can easily happen with large | |
550 | There will be a master module with name | |
385 | 551 | 'foo' and other files which when required add functions to that module. If these files have |
386 | a @submodule tag, their contents will be placed in the master module documentation. However, | |
552 | a **@submodule** tag, their contents will be placed in the master module documentation. However, | |
387 | 553 | a current limitation is that the master module must be processed before the submodules. |
388 | 554 | |
389 | 555 | See the `tests/submodule` example for how this works in practice. |
415 | 581 | LDoc will also work with C/C++ files, since extension writers clearly have the same |
416 | 582 | documentation needs as Lua module writers. |
417 | 583 | |
418 | LDoc allows you to attach a _type_ to a parameter or return value | |
419 | ||
420 | --- better mangler. | |
421 | -- @tparam string name | |
422 | -- @int max length | |
423 | -- @treturn string mangled name | |
424 | function strmangler(name,max) | |
425 | ... | |
426 | end | |
427 | ||
428 | `int` here is short for `tparam int` (see @{Tag_Modifiers}) | |
429 | ||
430 | It's common for types to be optional, or have different types, so the type can be like | |
431 | '?int|string' which renders as '(int or string)', or '?int', which renders as | |
432 | '(optional int)'. | |
433 | ||
434 | LDoc gives the documenter the option to use Markdown to parse the contents of comments. | |
435 | ||
436 | Since 1.3, LDoc allows the use of _colons_ instead of @. | |
437 | ||
438 | --- a simple function. | |
439 | -- string name person's name | |
440 | -- int: age age of person | |
441 | -- !person: person object | |
442 | -- treturn: ?string | |
443 | -- function check(name,age) | |
444 | ||
445 | However, you must either use the `--colon` flag or set `colon=true` in your `config.ld`. | |
446 | ||
447 | In this style, types may be used directly if prefixed with '!' or '?' (for type-or-nil) | |
448 | ||
449 | (see @{colon.lua}) | |
584 | LDoc allows you to attach a _type_ to a parameter or return value with `tparam` or `treturn`, | |
585 | and gives the documenter the option to use Markdown to parse the contents of comments. | |
586 | You may also include code examples which will be prettified, and readme files which will be | |
587 | rendered with Markdown and contain prettified code blocks. | |
450 | 588 | |
451 | 589 | ## Adding new Tags |
452 | 590 | |
541 | 679 | |
542 | 680 | LDoc can process C/C++ files: |
543 | 681 | |
544 | @plain | |
545 | /*** | |
546 | Create a table with given array and hash slots. | |
547 | @function createtable | |
548 | @param narr initial array slots, default 0 | |
549 | @param nrec initial hash slots, default 0 | |
550 | @return the new table | |
551 | */ | |
552 | static int l_createtable (lua_State *L) { | |
553 | .... | |
682 | ```c | |
683 | /*** | |
684 | Create a table with given array and hash slots. | |
685 | @function createtable | |
686 | @param narr initial array slots, default 0 | |
687 | @param nrec initial hash slots, default 0 | |
688 | @return the new table | |
689 | */ | |
690 | static int l_createtable (lua_State *L) { | |
691 | .... | |
692 | ``` | |
554 | 693 | |
555 | 694 | Both `/**` and `///` are recognized as starting a comment block. Otherwise, the tags are |
556 | 695 | processed in exactly the same way. It is necessary to specify that this is a function with a |
566 | 705 | the docs from the separate files. For this, use `merge=true`. |
567 | 706 | |
568 | 707 | See @{mylib.c} for the full example. |
708 | ||
709 | ## Moonscript Support | |
710 | ||
711 | 1.4 introduces basic support for [Moonscript](http://moonscript.org). Moonscript module | |
712 | conventions are just the same as Lua, except for an explicit class construct. | |
713 | @{list.moon} shows how **@classmod** can declare modules that export one class, with metamethods | |
714 | and methods put implicitly into a separate section. | |
569 | 715 | |
570 | 716 | ## Basic Usage |
571 | 717 | |
630 | 776 | - [lunamark](http://jgm.github.com/lunamark/), another pure Lua processor, faster than |
631 | 777 | markdown, and with extra features (`luarocks install lunamark`). |
632 | 778 | |
633 | You can request the processor you like with `format = 'markdown|discount|lunamark'`, and | |
779 | You can request the processor you like with `format = 'markdown|discount|lunamark|plain|backticks'`, and | |
634 | 780 | LDoc will attempt to use it. If it can't find it, it will look for one of the other |
635 | 781 | markdown processors; the original `markdown.lua` ships with LDoc, although it's slow |
636 | 782 | for larger documents. |
637 | 783 | |
638 | 784 | Even with the default of 'plain' some minimal processing takes place, in particular empty lines |
639 | are treated as line breaks. If 'process_backticks=true` then backticks will be | |
785 | are treated as line breaks. If the 'backticks' formatter is used, then it's equivalent to | |
786 | using 'process_backticks=true` in `config.ld` and backticks will be | |
640 | 787 | expanded into documentation links like `@{\ref}` and converted into `<code>ref</code>` |
641 | 788 | otherwise. |
642 | 789 | |
754 | 901 | |
755 | 902 | The _navigation section_ down the left has several parts: |
756 | 903 | |
757 | - The project name ('project' in the config) | |
758 | - A project description ('description') | |
759 | - ''Contents'' of the current page | |
760 | - ''Modules'' listing all the modules in this project | |
904 | - The project name (`project` in the config) | |
905 | - A project description (`description`) | |
906 | - **Contents** of the current page | |
907 | - **Modules** listing all the modules in this project | |
761 | 908 | |
762 | 909 | Note that `description` will be passed through Markdown, if it has been specified for the |
763 | 910 | project. This gives you an opportunity to make lists of links, etc; any '##' headers will be |
764 | 911 | formatted like the other top-level items on the navigation bar. |
765 | 912 | |
766 | 'Contents' is automatically generated. It will contain any explicit sections, if they have | |
767 | been used. Otherwise you will get the usual categories: 'Functions', 'Tables' and 'Fields'. | |
768 | ||
769 | 'Modules' will appear for any project providing Lua libraries; there may also be a 'Scripts' | |
913 | **Contents** is automatically generated. It will contain any explicit sections | |
914 | as well as the usual categories: 'Functions', 'Tables' and 'Fields'. For a documentation page, | |
915 | the subtitles become the sections shown here. | |
916 | ||
917 | **Modules** will appear for any project providing Lua libraries; there may also be a 'Scripts' | |
770 | 918 | section if the project contains Lua scripts. For example, |
771 | 919 | [LuaMacro](http://stevedonovan.github.com/LuaMacro/docs/api.html) has a driver script `luam` |
772 | 920 | in this section. The |
773 | 921 | [builtin](http://stevedonovan.github.com/LuaMacro/docs/modules/macro.builtin.html) module |
774 | only defines macros, which are defined as a _custom tag type_. | |
922 | only defines macros, which are defined as a _custom tag type[?]_. | |
775 | 923 | |
776 | 924 | The _content section_ on the right shows: |
777 | 925 | |
787 | 935 | 'see' in that order. (For tables, 'Fields' is used instead of 'Parameters' but internally |
788 | 936 | fields of a table are stored as the 'param' tag.) |
789 | 937 | |
938 | By default, the items appear in the order of declaration within their section. If `sort=true` | |
939 | then they will be sorted alphabetically. (This can be set per-module with @{Module_Tags|@set}.) | |
940 | ||
790 | 941 | You can of course customize the default template, but there are some parameters that can |
791 | control what the template will generate. Setting `one` to `true` in your configuration file | |
942 | control what the template will generate. Setting `one=true` in your configuration file | |
792 | 943 | will give a _one-column_ layout, which can be easier to use as a programming reference. You |
793 | 944 | can suppress the contents summary with `no_summary`. |
794 | 945 | |
796 | 947 | |
797 | 948 | A basic customization is to override the default UTF-8 encoding using `charset`. For instance, |
798 | 949 | Brazillian software would find it useful to put `charset='ISO-8859-1'` in `config.ld`, or use |
799 | the @charset tag for individual files. | |
950 | the **@charset** tag for individual files. | |
800 | 951 | |
801 | 952 | Setting `no_return_or_parms` to `true` will suppress the display of 'param' and 'return' |
802 | 953 | tags. This may appeal to programmers who dislike the traditional @tag soup xDoc style and |
838 | 989 | |
839 | 990 | LDoc allows for source examples to be included in the documentation. For example, see the |
840 | 991 | online documentation for [winapi](http://stevedonovan.github.com/winapi/api.html). The |
841 | function `utf8_expand` has a `@see` reference to 'testu.lua' and following that link gives | |
992 | function `utf8_expand` has a **@see** reference to 'testu.lua' and following that link gives | |
842 | 993 | you a pretty-printed version of the code. |
843 | 994 | |
844 | 995 | The line in the `config.ld` that enables this is: |
846 | 997 | examples = {'examples', exclude = {'examples/slow.lua'}} |
847 | 998 | |
848 | 999 | That is, all files in the `examples` folder are to be pretty-printed, except for `slow.lua` |
849 | which is meant to be called from one of the examples. Now a see-reference to `testu.lua` | |
850 | resolves to 'examples/testu.lua.html'. | |
1000 | which is meant to be called from one of the examples. | |
1001 | To link to an example, use a reference like `@{\testu.lua}` | |
1002 | which resolves to 'examples/testu.lua.html'. | |
851 | 1003 | |
852 | 1004 | Examples may link back to the API documentation, for instance the example `input.lua` has a |
853 | 1005 | `@{\spawn_process}` inline reference. |
854 | 1006 | |
855 | By default, LDoc uses a built-in Lua code 'prettifier'. See-references are allowed in comments, | |
856 | and also in code if they're enclosed in backticks. | |
1007 | By default, LDoc uses a built-in Lua code 'prettifier'. Reference links are allowed in comments, | |
1008 | and also in code if they're enclosed in backticks. Lua and C are known languages. | |
857 | 1009 | |
858 | 1010 | [lxsh](https://github.com/xolox/lua-lxsh) |
859 | can be used (available from LuaRocks) if you want support for C as well. `pretty='lxsh'` will | |
1011 | can be used (available from LuaRocks) if you want something more powerful. `pretty='lxsh'` will | |
860 | 1012 | cause `lxsh` to be used, if available. |
861 | 1013 | |
862 | 1014 | ## Readme files |
902 | 1054 | references, it is not an error if the reference cannot be found. |
903 | 1055 | |
904 | 1056 | The _sections_ of a document (the second-level headings) are also references. This |
905 | particular section can be refered to as `@{\doc.md.Resolving_References_in_Documents}` - the | |
1057 | particular section you are reading can be refered to as `@{\doc.md.Readme_files}` - the | |
906 | 1058 | rule is that any non-alphabetic character is replaced by an underscore. |
1059 | ||
1060 | Any indented blocks are assumed to be Lua, unless their first line is `@plain`. New | |
1061 | with 1.4 is github-markdown-style fenced code blocks, which start with three backticks | |
1062 | optionally followed by a language. The code continues until another three backticks | |
1063 | is found: the language can be `c`, `cpp` or `cxx` for C/C++, anything else is Lua. | |
907 | 1064 | |
908 | 1065 | ## Tag Modifiers |
909 | 1066 | |
923 | 1080 | |
924 | 1081 | tparam_alias('string','string') |
925 | 1082 | |
926 | That is, "@string" will now have the same meaning as "@tparam string"; this also applies | |
1083 | That is, **@string** will now have the same meaning as "@tparam string"; this also applies | |
927 | 1084 | to the optional type syntax "?|T1|T2". |
928 | 1085 | |
929 | 1086 | From 1.3, the following standard type aliases are predefined: |
954 | 1111 | |
955 | 1112 | alias('tparam',{'param',modifiers={type="$1"}}) |
956 | 1113 | |
957 | As an extension, you're allowed to use '@param' tags in table definitions. This makes it | |
958 | possible to use type alias like '@string' to describe fields, since they will expand to | |
1114 | As an extension, you're allowed to use **@param** tags in table definitions. This makes it | |
1115 | possible to use type aliases like **@string** to describe fields, since they will expand to | |
959 | 1116 | 'param'. |
960 | 1117 | |
961 | 1118 | Another modifier understood by LDoc is `opt`. For instance, |
984 | 1141 | end |
985 | 1142 | ----> displayed as: one (name, age [, calender='gregorian' [, offset=0]]) |
986 | 1143 | |
987 | Currently the `type` and `opt` modifiers are the only ones known and used by LDoc when generating HTML | |
1144 | ||
1145 | (See @{four.lua}, rendered [here](http://stevedonovan.github.io/ldoc/examples/four)) | |
1146 | ||
1147 | An experimental feature in 1.4 allows different 'return groups' to be defined. There may be | |
1148 | multiple **@return** tags, and the meaning of this is well-defined, since Lua functions may | |
1149 | return multiple values. However, being a dynamic language it may return a single value if | |
1150 | successful and two values (`nil`,an error message) if there is an error. This is in fact the | |
1151 | convention for returning 'normal' errors (like 'file not found') as opposed to parameter errors | |
1152 | (like 'file must be a string') that are often raised as errors. | |
1153 | ||
1154 | Return groups allow a documenter to specify the various possible return values of a function, | |
1155 | by specifying _number_ modifiers. All `return` tags with the same digit modifier belong together | |
1156 | as a group: | |
1157 | ||
1158 | ----- | |
1159 | -- function with return groups. | |
1160 | -- @return[1] result | |
1161 | -- @return[2] nil | |
1162 | -- @return[2] error message | |
1163 | function mul1() ... end | |
1164 | ||
1165 | This is the first function in @{multiple.lua}, and the [output](http://stevedonovan.github.io/ldoc/examples/multiple) | |
1166 | shows how return groups are presented, with an **Or** between the groups. | |
1167 | ||
1168 | This is rather clumsy, and so there is a shortcut, the **@error** tag which achieves the same result, | |
1169 | with helpful type information. | |
1170 | ||
1171 | Currently the `type`,`opt` and `<digit>` modifiers are the only ones known and used by LDoc when generating HTML | |
988 | 1172 | output. However, any other modifiers are allowed and are available for use with your own |
989 | 1173 | templates or for extraction by your own tools. |
990 | 1174 | |
991 | (See @{four.lua}) | |
992 | 1175 | |
993 | 1176 | ## Fields allowed in `config.ld` |
994 | 1177 | |
1011 | 1194 | template. In `config.ld` they may also be `true`, meaning use the same directory as the |
1012 | 1195 | configuration file. |
1013 | 1196 | - `merge` allow documentation from different files to be merged into modules without |
1014 | explicit @submodule tag | |
1015 | ||
1016 | _These only appear in config.ld:_ | |
1197 | explicit **@submodule** tag | |
1198 | ||
1199 | _These only appear in the configuration file:_ | |
1017 | 1200 | |
1018 | 1201 | - `description` a short project description used under the project title |
1019 | 1202 | - `full_description` when you _really_ need a longer project description |
1020 | 1203 | - `examples` a directory or file: can be a table |
1021 | 1204 | - `readme` or `topics` readme files (to be processed with Markdown) |
1022 | 1205 | - `pretty` code prettify 'lua' (default) or 'lxsh' |
1023 | - `charset` use if you want to override the UTF-8 default (also @charset in files) | |
1206 | - `charset` use if you want to override the UTF-8 default (also **@charset** in files) | |
1024 | 1207 | - `sort` set if you want all items in alphabetical order |
1025 | 1208 | - `no_return_or_parms` don't show parameters or return values in output |
1026 | 1209 | - `backtick_references` whether references in backticks will be resolved. Happens by default |
1031 | 1214 | 'file:///D:/dev/lua/projects/lua-5.1.4/doc/manual.html' |
1032 | 1215 | - `no_summary` suppress the Contents summary |
1033 | 1216 | - `custom_see_handler` function that filters see-references |
1217 | - `custom_display_name_handler` function that formats an item's name. The arguments are the item | |
1218 | and the default function used to format the name. For example, to show an icon or label beside any | |
1219 | function tagged with a certain tag: | |
1220 | -- define a @callback tag: | |
1221 | custom_tags = { { 'callback', hidden = true } } | |
1222 | ||
1223 | -- show a label beside functions tagged with @callback. | |
1224 | custom_display_name_handler = function(item, default_handler) | |
1225 | if item.type == 'function' and item.tags.callback then | |
1226 | return item.name .. ' [callback]' | |
1227 | end | |
1228 | return default_handler(item) | |
1229 | end | |
1230 | ||
1034 | 1231 | - `not_luadoc` set to `true` if the docs break LuaDoc compatibility |
1035 | 1232 | - `no_space_before_args` set to `true` if you do not want a space between a function's name and its arguments. |
1036 | 1233 | - `template_escape` overrides the usual '#' used for Lua code in templates. This needs to be changed if the output format is Markdown, for instance. |
1095 | 1292 | |
1096 | 1293 | This is then styled with `ldoc.css`. Currently the template and stylesheet is very much |
1097 | 1294 | based on LuaDoc, so the results are mostly equivalent; the main change that the template has |
1098 | been more generalized. The default location (indicated by '!') is the directory of `ldoc.lua`. | |
1295 | been more generalized. The default location (indicated by '!') is the directory of `ldoc_ltp.lua`. | |
1296 | ||
1297 | You will notice that the built-in templates and stylesheets end in `.lua`; this is simply to | |
1298 | make it easier for LDoc to find them. Where you are customizing one or both of the template | |
1299 | and stylesheet, they will have their usual extensions. | |
1099 | 1300 | |
1100 | 1301 | You may customize how you generate your documentation by specifying an alternative style |
1101 | 1302 | sheet and/or template, which can be deployed with your project. The parameters are `--style` |
1107 | 1308 | An example of fully customized documentation is `tests/example/style`: this is what you |
1108 | 1309 | could call 'minimal Markdown style' where there is no attempt to tag things (except |
1109 | 1310 | emphasizing parameter names). The narrative alone _can_ to be sufficient, if it is written |
1110 | appropriately. | |
1311 | well. | |
1312 | ||
1313 | There are two other stylesheets available in LDoc since 1.4; the first is `ldoc_one.css` which is what | |
1314 | you get from `one=true` and the second is `ldoc_pale.css`. This is a lighter theme which | |
1315 | might give some relief from the heavier colours of the default. You can use this style with | |
1316 | `style="!pale"` or `-s !pale`. | |
1317 | See the [Lake](http://stevedonovan.github.io/lake/modules/lakelibs.html) documentation | |
1318 | as an example of its use. | |
1111 | 1319 | |
1112 | 1320 | Of course, there's no reason why LDoc must always generate HTML. `--ext` defines what output |
1113 | 1321 | extension to use; this can also be set in the configuration file. So it's possible to write |
1115 | 1323 | and presentation makes this kind of new application possible with LDoc. |
1116 | 1324 | |
1117 | 1325 | From 1.4, LDoc has some limited support for generating Markdown output, although only |
1118 | for single files currently. Use `--ext md` for this. 'ldoc/html/ldoc_mdtp.lua' defines | |
1119 | the template for Markdown. | |
1326 | for single files currently. Use `--ext md` for this. 'ldoc/html/ldoc_md_ltp.lua' defines | |
1327 | the template for Markdown, but this can be overriden with `template` as above. It's another | |
1328 | example of minimal structure, and provides a better place to learn about these templates than the | |
1329 | rather elaborate default HTML template. | |
1120 | 1330 | |
1121 | 1331 | ## Internal Data Representation |
1122 | 1332 | |
1150 | 1360 | entities, including scripts) which each contain an `item` array (functions, tables and so |
1151 | 1361 | forth). |
1152 | 1362 | |
1153 | For instance, to find all functions which don't have a @return tag: | |
1363 | For instance, to find all functions which don't have a **@return** tag: | |
1154 | 1364 | |
1155 | 1365 | return { |
1156 | 1366 | filter = function (t) |
1164 | 1374 | end |
1165 | 1375 | } |
1166 | 1376 | |
1167 | The internal naming is not always so consistent; `ret` corresponds to @return, and `params` | |
1168 | corresponds to @param. `item.params` is an array of the function parameters, in order; it | |
1377 | The internal naming is not always so consistent; `ret` corresponds to **@return**, and `params` | |
1378 | corresponds to **@param**. `item.params` is an array of the function parameters, in order; it | |
1169 | 1379 | is also a map from these names to the individual descriptions of the parameters. |
1170 | 1380 | |
1171 | 1381 | `item.modifiers` is a table where the keys are the tags and the values are arrays of |
20 | 20 | -- - 'N' tags which have no associated value, like 'local` (TAG_FLAG) |
21 | 21 | -- - 'T' tags which represent a type, like 'function' (TAG_TYPE) |
22 | 22 | local known_tags = { |
23 | param = 'M', see = 'M', usage = 'ML', ['return'] = 'M', field = 'M', author='M',set='M'; | |
23 | param = 'M', see = 'M', comment = 'M', usage = 'ML', ['return'] = 'M', field = 'M', author='M',set='M'; | |
24 | 24 | class = 'id', name = 'id', pragma = 'id', alias = 'id', within = 'id', |
25 | 25 | copyright = 'S', summary = 'S', description = 'S', release = 'S', license = 'S', |
26 | 26 | fixme = 'S', todo = 'S', warning = 'S', raise = 'S', charset = 'S', |
120 | 120 | return List.iter(known_tags._module_info) |
121 | 121 | end |
122 | 122 | |
123 | ||
124 | 123 | -- annotation tags can appear anywhere in the code and may contain any of these tags: |
125 | 124 | known_tags._annotation_tags = { |
126 | 125 | fixme = true, todo = true, warning = true |
129 | 128 | local acount = 1 |
130 | 129 | |
131 | 130 | function doc.expand_annotation_item (tags, last_item) |
132 | if tags.summary ~= '' then return false end | |
131 | if tags.summary ~= '' or last_item == nil then return false end | |
132 | local item_name = last_item.tags.name | |
133 | 133 | for tag, value in pairs(tags) do |
134 | 134 | if known_tags._annotation_tags[tag] then |
135 | 135 | tags:add('class','annotation') |
136 | 136 | tags:add('summary',value) |
137 | local item_name = last_item and last_item.tags.name or '?' | |
138 | 137 | tags:add('name',item_name..'-'..tag..acount) |
139 | 138 | acount = acount + 1 |
140 | 139 | return true |
141 | end | |
142 | end | |
140 | elseif tag == 'return' then | |
141 | last_item:set_tag(tag,value) | |
142 | end | |
143 | end | |
144 | return false | |
143 | 145 | end |
144 | 146 | |
145 | 147 | -- we process each file, resulting in a File object, which has a list of Item objects. |
173 | 175 | for item in self.items:iter() do |
174 | 176 | local tags = item.tags |
175 | 177 | if tags.name == name then |
178 | tags.export = true | |
176 | 179 | if tags['local'] then |
177 | 180 | tags['local'] = nil |
178 | 181 | end |
204 | 207 | end |
205 | 208 | end |
206 | 209 | |
210 | local function init_within_section (mod,name) | |
211 | mod.kinds:add_kind(name, name) | |
212 | mod.enclosing_section = mod.section | |
213 | mod.section = nil | |
214 | return name | |
215 | end | |
216 | ||
207 | 217 | function File:finish() |
208 | 218 | local this_mod |
209 | 219 | local items = self.items |
210 | 220 | local tagged_inside |
211 | local function add_section (item, display_name) | |
212 | display_name = display_name or item.display_name | |
213 | this_mod.section = item | |
214 | this_mod.kinds:add_kind(display_name,display_name..' ',nil,item) | |
215 | this_mod.sections:append(item) | |
216 | this_mod.sections.by_name[display_name:gsub('%A','_')] = item | |
217 | end | |
221 | self.args = self.args or {} | |
218 | 222 | for item in items:iter() do |
219 | 223 | if mod_section_type(this_mod) == 'factory' and item.tags then |
220 | 224 | local klass = '@{'..this_mod.section.name..'}' |
226 | 230 | end |
227 | 231 | end |
228 | 232 | item:finish() |
229 | if doc.project_level(item.type) then | |
233 | -- the default is not to show local functions in the documentation. | |
234 | if not self.args.all and (item.type=='lfunction' or (item.tags and item.tags['local'])) then | |
235 | -- don't add to the module -- | |
236 | elseif doc.project_level(item.type) then | |
230 | 237 | this_mod = item |
231 | 238 | local package,mname,submodule |
232 | 239 | if item.type == 'module' then |
261 | 268 | if not submodule then |
262 | 269 | this_mod.package = package |
263 | 270 | this_mod.mod_name = mname |
264 | this_mod.kinds = ModuleMap() -- the iterator over the module contents | |
271 | this_mod.kinds = doc.ModuleMap() -- the iterator over the module contents | |
265 | 272 | self.modules:append(this_mod) |
266 | 273 | end |
267 | 274 | elseif doc.section_tag(item.type) then |
270 | 277 | this_mod.section = nil |
271 | 278 | else |
272 | 279 | local summary = item.summary:gsub('%.$','') |
280 | local lookup_name | |
273 | 281 | if doc.class_tag(item.type) then |
274 | 282 | display_name = 'Class '..item.name |
283 | lookup_name = item.name | |
275 | 284 | item.module = this_mod |
276 | 285 | this_mod.items.by_name[item.name] = item |
277 | 286 | else |
278 | 287 | display_name = summary |
288 | lookup_name = summary | |
279 | 289 | end |
280 | 290 | item.display_name = display_name |
281 | add_section(item) | |
291 | this_mod.section = item | |
292 | this_mod.kinds:add_kind(display_name,display_name..' ',nil,item) | |
293 | this_mod.sections:append(item) | |
294 | this_mod.sections.by_name[lookup_name:gsub('%A','_')] = item | |
282 | 295 | end |
283 | 296 | else |
284 | 297 | local to_be_removed |
285 | 298 | -- add the item to the module's item list |
286 | 299 | if this_mod then |
287 | 300 | -- new-style modules will have qualified names like 'mod.foo' |
301 | if item.name == nil then | |
302 | self:error("item's name is nil") | |
303 | end | |
288 | 304 | local mod,fname = split_dotted_name(item.name) |
289 | 305 | -- warning for inferred unqualified names in new style modules |
290 | 306 | -- (retired until we handle methods like Set:unset() properly) |
301 | 317 | item.name = fname |
302 | 318 | end |
303 | 319 | |
304 | local enclosing_section | |
305 | 320 | if tagged_inside then |
306 | 321 | item.tags.within = tagged_inside |
307 | 322 | end |
308 | 323 | if item.tags.within then |
309 | local name = item.tags.within | |
310 | this_mod.kinds:add_kind(name, name) | |
311 | enclosing_section = this_mod.section | |
312 | this_mod.section = nil | |
324 | init_within_section(this_mod,item.tags.within) | |
313 | 325 | end |
314 | 326 | |
315 | 327 | -- right, this item was within a section or a 'class' |
325 | 337 | -- if it was a class, then if the name is unqualified then it becomes |
326 | 338 | -- 'Class:foo' (unless flagged as being a constructor, static or not a function) |
327 | 339 | if doc.class_tag(stype) or classmod then |
328 | if not item.name:match '[:%.]' then -- not qualified | |
340 | if not item.name:match '[:%.]' then -- not qualified name! | |
341 | -- a class is either a @type section or a @classmod module. Is this a _method_? | |
329 | 342 | local class = classmod and this_mod.name or this_section.name |
330 | local lang = this_mod.file.lang | |
331 | 343 | local static = item.tags.constructor or item.tags.static or item.type ~= 'function' |
332 | item.name = class..(not static and lang.method_call or '.')..item.name | |
344 | -- methods and metamethods go into their own special sections... | |
345 | if classmod and item.type == 'function' then | |
346 | local inferred_section | |
347 | if item.name:match '^__' then | |
348 | inferred_section = 'Metamethods' | |
349 | elseif not static then | |
350 | inferred_section = 'Methods' | |
351 | end | |
352 | if inferred_section then | |
353 | item.tags.within = init_within_section(this_mod,inferred_section) | |
354 | end | |
355 | end | |
356 | -- Whether to use '.' or the language's version of ':' (e.g. \ for Moonscript) | |
357 | item.name = class..(not static and this_mod.file.lang.method_call or '.')..item.name | |
333 | 358 | end |
334 | 359 | if stype == 'factory' then |
335 | 360 | if item.tags.private then to_be_removed = true |
348 | 373 | section_description = item.tags.within |
349 | 374 | item.section = section_description |
350 | 375 | else |
351 | section_description = "Methods" | |
376 | if item.type == 'function' or item.type == 'lfunction' then | |
377 | section_description = "Methods" | |
378 | end | |
352 | 379 | item.section = item.type |
353 | 380 | end |
354 | elseif item.tags.within then | |
381 | elseif item.tags.within then -- ad-hoc section... | |
355 | 382 | section_description = item.tags.within |
356 | 383 | item.section = section_description |
357 | 384 | else -- otherwise, just goes into the default sections (Functions,Tables,etc) |
358 | item.section = item.type | |
385 | item.section = item.type; | |
359 | 386 | end |
360 | 387 | |
361 | 388 | item.module = this_mod |
367 | 394 | end |
368 | 395 | |
369 | 396 | -- restore current section after a 'within' |
370 | if enclosing_section then this_mod.section = enclosing_section end | |
397 | if this_mod.enclosing_section then | |
398 | this_mod.section = this_mod.enclosing_section | |
399 | this_mod.enclosing_section = nil | |
400 | end | |
371 | 401 | |
372 | 402 | else |
373 | 403 | -- must be a free-standing function (sometimes a problem...) |
404 | 434 | self.tags = {} |
405 | 435 | self.formal_args = tags.formal_args |
406 | 436 | tags.formal_args = nil |
407 | local iter = tags.iter | |
408 | if not iter then | |
409 | iter = Map.iter | |
410 | end | |
437 | local iter = tags.iter or Map.iter | |
411 | 438 | for tag in iter(tags) do |
412 | 439 | self:set_tag(tag,tags[tag]) |
413 | 440 | end |
421 | 448 | |
422 | 449 | function Item:trailing_warning (kind,tag,rest) |
423 | 450 | if type(rest)=='string' and #rest > 0 then |
424 | Item.warning(self,kind.." tag: '"..tag..'" has trailing text; use not_luadoc=true if you want description to continue between tags\n'..rest) | |
425 | end | |
451 | Item.warning(self,kind.." tag: '"..tag..'" has trailing text ; use not_luadoc=true if you want description to continue between tags\n"'..rest..'"') | |
452 | end | |
453 | end | |
454 | ||
455 | local function is_list (l) | |
456 | return getmetatable(l) == List | |
426 | 457 | end |
427 | 458 | |
428 | 459 | function Item:set_tag (tag,value) |
430 | 461 | local args = self.file.args |
431 | 462 | |
432 | 463 | if ttype == TAG_MULTI or ttype == TAG_MULTI_LINE then -- value is always a List! |
433 | if getmetatable(value) ~= List then | |
464 | local ovalue = self.tags[tag] | |
465 | if ovalue then -- already defined, must be a list | |
466 | --print(tag,ovalue,value) | |
467 | if is_list(value) then | |
468 | ovalue:extend(value) | |
469 | else | |
470 | ovalue:append(value) | |
471 | end | |
472 | value = ovalue | |
473 | end | |
474 | -- these multiple values are always represented as lists | |
475 | if not is_list(value) then | |
434 | 476 | value = List{value} |
435 | 477 | end |
436 | 478 | if ttype ~= TAG_MULTI_LINE and args and args.not_luadoc then |
447 | 489 | if type(value) == 'table' then |
448 | 490 | if value.append then -- it was a List! |
449 | 491 | -- such tags are _not_ multiple, e.g. name |
450 | self:error("'"..tag.."' cannot have multiple values") | |
492 | if tag == 'class' and value:contains 'example' then | |
493 | self:error("cannot use 'example' tag for functions or tables. Use 'usage'") | |
494 | else | |
495 | self:error("'"..tag.."' cannot have multiple values; "..tostring(value)) | |
496 | end | |
451 | 497 | end |
452 | 498 | value = value[1] |
453 | 499 | modifiers = value.modifiers |
481 | 527 | if alias then |
482 | 528 | if type(alias) == 'string' then |
483 | 529 | tag = alias |
484 | else | |
530 | elseif type(alias) == 'table' then --{ tag, value=, modifiers = } | |
485 | 531 | local avalue,amod |
486 | 532 | tag, avalue, amod = alias[1],alias.value,alias.modifiers |
487 | 533 | if avalue then value = avalue..' '..value end |
495 | 541 | modifiers[m] = v |
496 | 542 | end |
497 | 543 | end |
544 | else -- has to be a function that at least returns tag, value | |
545 | return alias(tags,value,modifiers) | |
498 | 546 | end |
499 | 547 | end |
500 | 548 | local ttype = known_tags[tag] |
546 | 594 | |
547 | 595 | local build_arg_list, split_iden -- forward declaration |
548 | 596 | |
597 | function Item:split_param (line) | |
598 | local name, comment = line:match('%s*([%w_%.:]+)(.*)') | |
599 | if not name then | |
600 | self:error("bad param name format '"..line.."'. Are you missing a parameter name?") | |
601 | end | |
602 | return name, comment | |
603 | end | |
549 | 604 | |
550 | 605 | function Item:finish() |
551 | 606 | local tags = self.tags |
594 | 649 | local param_names, comments = List(), List() |
595 | 650 | if params then |
596 | 651 | for line in params:iter() do |
597 | local name, comment = line:match('%s*([%w_%.:]+)(.*)') | |
598 | if not name then | |
599 | self:error("bad param name format '"..line.."'. Are you missing a parameter name?") | |
600 | end | |
652 | local name, comment = self:split_param(line) | |
601 | 653 | param_names:append(name) |
602 | 654 | comments:append(comment) |
603 | 655 | end |
613 | 665 | if fargs then |
614 | 666 | if #param_names == 0 then |
615 | 667 | --docs may be embedded in argument comments; in either case, use formal arg names |
616 | formal = List() | |
617 | if fargs.return_comment then | |
618 | local retc = self:parse_argument_comment(fargs.return_comment,'return') | |
619 | self.ret = List{retc} | |
620 | end | |
621 | for i, name in ipairs(fargs) do | |
622 | formal:append(name) | |
623 | comments:append(self:parse_argument_comment(fargs.comments[name],self.parameter)) | |
624 | end | |
625 | elseif #fargs > 0 then | |
668 | local ret | |
669 | formal,comments,ret = self:parse_formal_arguments(fargs) | |
670 | if ret and not self.ret then self.ret = ret end | |
671 | elseif #fargs > 0 then -- consistency check! | |
626 | 672 | local varargs = fargs[#fargs] == '...' |
627 | 673 | if varargs then table.remove(fargs) end |
674 | if tags.export then | |
675 | if fargs[1] == 'self' then | |
676 | table.remove(fargs,1) | |
677 | else | |
678 | tags.static = true | |
679 | end | |
680 | end | |
628 | 681 | local k = 0 |
629 | 682 | for _,pname in ipairs(param_names) do |
630 | 683 | local _,field = split_iden(pname) |
640 | 693 | end |
641 | 694 | end |
642 | 695 | if k < #fargs then |
643 | for i = k+1,#fargs do | |
644 | if fargs[i] ~= '...' then | |
645 | self:warning("undocumented formal argument: "..quote(fargs[i])) | |
646 | end | |
696 | for i = k+1,#fargs do if fargs[i] ~= '...' then | |
697 | self:warning("undocumented formal argument: "..quote(fargs[i])) | |
698 | end end | |
699 | end | |
700 | end -- #fargs > 0 | |
701 | -- formal arguments may come with types, inferred by the | |
702 | -- appropriate code in ldoc.lang | |
703 | if fargs.types then | |
704 | self.modifiers[field] = List() | |
705 | for t in fargs.types:iter() do | |
706 | self:add_type(field,t) | |
707 | end | |
708 | if fargs.return_type then | |
709 | if not self.ret then -- type, but no comment; no worries | |
710 | self.ret = List{''} | |
647 | 711 | end |
648 | end | |
649 | end | |
650 | end | |
712 | self.modifiers['return'] = List() | |
713 | self:add_type('return',fargs.return_type) | |
714 | end | |
715 | end | |
716 | end -- fargs | |
651 | 717 | |
652 | 718 | -- the comments are associated with each parameter by |
653 | 719 | -- adding name-value pairs to the params list (this is |
683 | 749 | self.params = params |
684 | 750 | self.args = build_arg_list (names,pmods) |
685 | 751 | end |
752 | if self.ret then | |
753 | self:build_return_groups() | |
754 | end | |
755 | end | |
756 | ||
757 | function Item:add_type(field,type) | |
758 | self.modifiers[field]:append {type = type} | |
686 | 759 | end |
687 | 760 | |
688 | 761 | -- ldoc allows comments in the formal arg list to be used, if they aren't specified with @param |
693 | 766 | comment = comment:gsub('^%-+%s*','') |
694 | 767 | local type,rest = comment:match '([^:]+):(.*)' |
695 | 768 | if type then |
696 | self.modifiers[field]:append {type = type} | |
769 | self:add_type(field,type) | |
697 | 770 | comment = rest |
698 | 771 | end |
699 | 772 | end |
700 | 773 | return comment or '' |
774 | end | |
775 | ||
776 | function Item:parse_formal_arguments (fargs) | |
777 | local formal, comments, ret = List(), List() | |
778 | if fargs.return_comment then | |
779 | local retc = self:parse_argument_comment(fargs.return_comment,'return') | |
780 | ret = List{retc} | |
781 | end | |
782 | for i, name in ipairs(fargs) do | |
783 | formal:append(name) | |
784 | comments:append(self:parse_argument_comment(fargs.comments[name],self.parameter)) | |
785 | end | |
786 | return formal, comments, ret | |
701 | 787 | end |
702 | 788 | |
703 | 789 | function split_iden (name) |
751 | 837 | return '('..table.concat(buffer)..')' |
752 | 838 | end |
753 | 839 | |
840 | ------ retrieving information about parameters ----- | |
841 | -- The template leans on these guys heavily.... | |
842 | ||
754 | 843 | function Item:param_modifiers (p) |
755 | 844 | local mods = self.modifiers[self.parameter] |
756 | 845 | if not mods then return '' end |
770 | 859 | return opt |
771 | 860 | end |
772 | 861 | |
773 | function Item:type_of_ret(idx) | |
774 | local rparam = self.modifiers['return'][idx] | |
775 | return rparam and rparam.type or '' | |
862 | function Item:readonly(p) | |
863 | local m = self:param_modifiers(p) | |
864 | if not m then return nil end | |
865 | return m.readonly | |
776 | 866 | end |
777 | 867 | |
778 | 868 | function Item:subparam(p) |
793 | 883 | end |
794 | 884 | end |
795 | 885 | |
886 | -------- return values and types ------- | |
887 | ||
888 | function Item:type_of_ret(idx) | |
889 | local rparam = self.modifiers['return'][idx] | |
890 | return rparam and rparam.type or '' | |
891 | end | |
892 | ||
893 | local function integer_keys(t) | |
894 | if type(t) ~= 'table' then return 0 end | |
895 | for k in pairs(t) do | |
896 | local num = tonumber(k) | |
897 | if num then return num end | |
898 | end | |
899 | return 0 | |
900 | end | |
901 | ||
902 | function Item:return_type(r) | |
903 | if not r.type then return '' end | |
904 | return r.type, r.ctypes | |
905 | end | |
906 | ||
907 | local struct_return_type = '*' | |
908 | ||
909 | function Item:build_return_groups() | |
910 | local modifiers = self.modifiers | |
911 | local retmod = modifiers['return'] | |
912 | local groups = List() | |
913 | local lastg, group | |
914 | for i,ret in ipairs(self.ret) do | |
915 | local mods = retmod[i] | |
916 | local g = integer_keys(mods) | |
917 | if g ~= lastg then | |
918 | group = List() | |
919 | group.g = g | |
920 | groups:append(group) | |
921 | lastg = g | |
922 | end | |
923 | --require 'pl.pretty'.dump(ret) | |
924 | if not mods then | |
925 | self:error(quote(self.name)..' had no return?') | |
926 | end | |
927 | group:append({text=ret, type = mods and (mods.type or '') or '',mods = mods}) | |
928 | end | |
929 | -- order by groups to force error groups to the end | |
930 | table.sort(groups,function(g1,g2) return g1.g < g2.g end) | |
931 | self.retgroups = groups | |
932 | --require 'pl.pretty'.dump(groups) | |
933 | -- cool, now see if there are any treturns that have tfields to associate with | |
934 | local fields = self.tags.field | |
935 | if fields then | |
936 | local fcomments = List() | |
937 | for i,f in ipairs(fields) do | |
938 | local name, comment = self:split_param(f) | |
939 | fields[i] = name | |
940 | fcomments[i] = comment | |
941 | end | |
942 | local fmods = modifiers.field | |
943 | for group in groups:iter() do for r in group:iter() do | |
944 | if r.mods and r.mods.type then | |
945 | local ctypes, T = List(), r.mods.type | |
946 | for i,f in ipairs(fields) do if fmods[i][T] then | |
947 | ctypes:append {name=f,type=fmods[i].type,comment=fcomments[i]} | |
948 | end end | |
949 | r.ctypes = ctypes | |
950 | --require 'pl.pretty'.dump(ctypes) | |
951 | end | |
952 | end end | |
953 | end | |
954 | end | |
955 | ||
956 | local ecount = 0 | |
957 | ||
958 | -- this alias macro implements @error. | |
959 | -- Alias macros need to return the same results as Item:check_tags... | |
960 | function doc.error_macro(tags,value,modifiers) | |
961 | local merge_groups = doc.ldoc.merge_error_groups | |
962 | local g = '2' -- our default group id | |
963 | -- Were we given an explicit group modifier? | |
964 | local key = integer_keys(modifiers) | |
965 | if key > 0 then | |
966 | g = tostring(key) | |
967 | else | |
968 | local l = tags:get 'return' | |
969 | if l then -- there were returns already...... | |
970 | -- maximum group of _existing_ error return | |
971 | local grp, lastr = 0 | |
972 | for r in l:iter() do if type(r) == 'table' then | |
973 | local rg = r.modifiers._err | |
974 | if rg then | |
975 | lastr = r | |
976 | grp = math.max(grp,rg) | |
977 | end | |
978 | end end | |
979 | if grp > 0 then -- cool, create new group | |
980 | if not merge_groups then | |
981 | g = tostring(grp+1) | |
982 | else | |
983 | local mods, text, T = lastr.modifiers | |
984 | local new = function(text) | |
985 | return mods._collected..' '..text,{type='string',[T]=true} | |
986 | end | |
987 | if not mods._collected then | |
988 | text = lastr[1] | |
989 | lastr[1] = merge_groups | |
990 | T = '@'..ecount | |
991 | mods.type = T | |
992 | mods._collected = 1 | |
993 | ecount = ecount + 1 | |
994 | tags:add('field',new(text)) | |
995 | else | |
996 | T = mods.type | |
997 | end | |
998 | mods._collected = mods._collected + 1 | |
999 | return 'field',new(value) | |
1000 | end | |
1001 | end | |
1002 | end | |
1003 | end | |
1004 | tags:add('return','',{[g]=true,type='nil'}) | |
1005 | -- note that this 'return' is tagged with _err! | |
1006 | return 'return', value, {[g]=true,_err=tonumber(g),type='string'} | |
1007 | end | |
1008 | ||
1009 | ---------- bothering the user -------------------- | |
796 | 1010 | |
797 | 1011 | function Item:warning(msg) |
798 | 1012 | local file = self.file and self.file.filename |
826 | 1040 | local err = io.stderr |
827 | 1041 | |
828 | 1042 | local function custom_see_references (s) |
829 | for pat, action in pairs(see_reference_handlers) do | |
830 | if s:match(pat) then | |
831 | local label, href = action(s:match(pat)) | |
832 | if not label then print('custom rule failed',s,pat,href) end | |
833 | return {href = href, label = label} | |
834 | end | |
835 | end | |
1043 | for pat, action in pairs(see_reference_handlers) do | |
1044 | if s:match(pat) then | |
1045 | local label, href = action(s:match(pat)) | |
1046 | if not label then print('custom rule failed',s,pat,href) end | |
1047 | return {href = href, label = label} | |
1048 | end | |
1049 | end | |
836 | 1050 | end |
837 | 1051 | |
838 | 1052 | local function reference (s, mod_ref, item_ref) |
839 | 1053 | local name = item_ref and item_ref.name or '' |
840 | 1054 | -- this is deeply hacky; classes have 'Class ' prepended. |
841 | if item_ref and doc.class_tag(item_ref.type) then | |
842 | name = 'Class_'..name | |
843 | end | |
1055 | --~ if item_ref and doc.class_tag(item_ref.type) then | |
1056 | --~ name = 'Class_'..name | |
1057 | --~ end | |
844 | 1058 | return {mod = mod_ref, name = name, label=s} |
845 | 1059 | end |
846 | 1060 | |
1061 | function Module:lookup_class_item (packmod, s) | |
1062 | local klass = packmod --"Class_"..packmod | |
1063 | local qs = klass..':'..s | |
1064 | local klass_section = self.sections.by_name[klass] | |
1065 | if not klass_section then return nil end -- no such class | |
1066 | for item in self.items:iter() do | |
1067 | --print('item',qs,item.name) | |
1068 | if s == item.name or qs == item.name then | |
1069 | return reference(s,self,item) | |
1070 | end | |
1071 | end | |
1072 | return nil | |
1073 | end | |
1074 | ||
847 | 1075 | function Module:process_see_reference (s,modules,istype) |
1076 | if s == nil then return nil end | |
848 | 1077 | local mod_ref,fun_ref,name,packmod |
849 | 1078 | local ref = custom_see_references(s) |
850 | 1079 | if ref then return ref end |
851 | 1080 | if not s:match '^[%w_%.%:%-]+$' or not s:match '[%w_]$' then |
852 | 1081 | return nil, "malformed see reference: '"..s..'"' |
853 | 1082 | end |
1083 | ||
1084 | -- `istype` means that we are looking up strictly in a _type_ context, so then only | |
1085 | -- allow `classmod` module references. | |
854 | 1086 | local function ismod(item) |
855 | 1087 | if item == nil then return false end |
856 | 1088 | if not istype then return true |
858 | 1090 | return item.type == 'classmod' |
859 | 1091 | end |
860 | 1092 | end |
1093 | ||
1094 | -- it is _entirely_ possible that someone does not want auto references for standard Lua libraries! | |
1095 | local lua_manual_ref | |
1096 | local ldoc = tools.item_ldoc(self) | |
1097 | if ldoc and ldoc.no_lua_ref then | |
1098 | lua_manual_ref = function(s) return false end | |
1099 | else | |
1100 | lua_manual_ref = global.lua_manual_ref | |
1101 | end | |
1102 | ||
861 | 1103 | -- is this a fully qualified module name? |
862 | 1104 | local mod_ref = modules.by_name[s] |
863 | 1105 | if ismod(mod_ref) then return reference(s, mod_ref,nil) end |
874 | 1116 | if not mod_ref then |
875 | 1117 | mod_ref = self:hunt_for_reference(packmod, modules) |
876 | 1118 | if not mod_ref then |
877 | local ref = global.lua_manual_ref(s) | |
1119 | local ref = self:lookup_class_item(packmod,s) | |
1120 | if ref then return ref end | |
1121 | local mod, klass = split_dotted_name(packmod) | |
1122 | mod_ref = modules.by_name[mod] | |
1123 | if mod_ref then | |
1124 | ref = mod_ref:lookup_class_item(klass,name) | |
1125 | if ref then return ref end | |
1126 | end | |
1127 | ref = lua_manual_ref(s) | |
878 | 1128 | if ref then return ref end |
879 | 1129 | return nil,"module not found: "..packmod |
880 | 1130 | end |
881 | 1131 | end |
882 | fun_ref = mod_ref.items.by_name[name] | |
1132 | fun_ref = mod_ref:get_fun_ref(name) | |
883 | 1133 | if fun_ref then |
884 | 1134 | return reference(s,mod_ref,fun_ref) |
885 | 1135 | else |
893 | 1143 | else -- plain jane name; module in this package, function in this module |
894 | 1144 | mod_ref = modules.by_name[self.package..'.'..s] |
895 | 1145 | if ismod(mod_ref) then return reference(s, mod_ref,nil) end |
896 | fun_ref = self.items.by_name[s] | |
897 | if fun_ref then return reference(s, self,fun_ref) | |
1146 | fun_ref = self:get_fun_ref(s) | |
1147 | if fun_ref then return reference(s,self,fun_ref) | |
898 | 1148 | else |
899 | local ref = global.lua_manual_ref (s) | |
1149 | local ref = lua_manual_ref (s) | |
900 | 1150 | if ref then return ref end |
901 | 1151 | return nil, "function not found: "..s.." in this module" |
902 | 1152 | end |
903 | 1153 | end |
904 | 1154 | end |
1155 | ||
1156 | function Module:get_fun_ref(s) | |
1157 | local fun_ref = self.items.by_name[s] | |
1158 | -- did not get an exact match, so try to match by the unqualified fun name | |
1159 | if not fun_ref then | |
1160 | local patt = '[.:]'..s..'$' | |
1161 | for qname,ref in pairs(self.items.by_name) do | |
1162 | if qname:match(patt) then | |
1163 | fun_ref = ref | |
1164 | break | |
1165 | end | |
1166 | end | |
1167 | end | |
1168 | return fun_ref | |
1169 | end | |
1170 | ||
905 | 1171 | |
906 | 1172 | -- resolving @see references. A word may be either a function in this module, |
907 | 1173 | -- or a module in this package. A MOD.NAME reference is within this package. |
934 | 1200 | end |
935 | 1201 | end |
936 | 1202 | |
937 | -- suppress the display of local functions and annotations. | |
938 | -- This is just a placeholder hack until we have a more general scheme | |
939 | -- for indicating 'private' content of a module. | |
940 | function Module:mask_locals () | |
941 | self.kinds['Local Functions'] = nil | |
942 | self.kinds['Annotations'] = nil | |
943 | end | |
944 | ||
945 | 1203 | function Item:dump_tags (taglist) |
946 | 1204 | for tag, value in pairs(self.tags) do |
947 | 1205 | if not taglist or taglist[tag] then |
28 | 28 | text-decoration: none; |
29 | 29 | } |
30 | 30 | li { |
31 | list-style: bullet; | |
31 | list-style: disc; | |
32 | 32 | margin-left: 20px; |
33 | 33 | } |
34 | 34 | caption,th { |
252 | 252 | border-style: solid; |
253 | 253 | border-color: #cccccc; |
254 | 254 | } |
255 | table.module_list td.name { background-color: #f0f0f0; ; min-width: 200px; } | |
255 | table.module_list td.name { background-color: #f0f0f0; min-width: 200px; } | |
256 | 256 | table.module_list td.summary { width: 100%; } |
257 | 257 | |
258 | 258 | |
268 | 268 | border-style: solid; |
269 | 269 | border-color: #cccccc; |
270 | 270 | } |
271 | table.function_list td.name { background-color: #f0f0f0; ; min-width: 200px; } | |
271 | table.function_list td.name { background-color: #f0f0f0; min-width: 200px; } | |
272 | 272 | table.function_list td.summary { width: 100%; } |
273 | ||
274 | ul.nowrap { | |
275 | overflow:auto; | |
276 | white-space:nowrap; | |
277 | } | |
273 | 278 | |
274 | 279 | dl.table dt, dl.function dt {border-top: 1px solid #ccc; padding-top: 1em;} |
275 | 280 | dl.table dd, dl.function dd {padding-bottom: 1em; margin: 10px 0 0 20px;} |
23 | 23 | # local use_li = ldoc.use_li |
24 | 24 | # local display_name = ldoc.display_name |
25 | 25 | # local iter = ldoc.modules.iter |
26 | # ---local M = ldoc.markup | |
27 | 26 | # local function M(txt,item) return ldoc.markup(txt,item,ldoc.plain) end |
28 | 27 | # local nowrap = ldoc.wrap and '' or 'nowrap' |
29 | 28 | |
53 | 52 | # if ldoc.no_summary and module and not ldoc.one then -- bang out the functions on the side |
54 | 53 | # for kind, items in module.kinds() do |
55 | 54 | <h2>$(kind)</h2> |
56 | <ul> | |
55 | <ul class="nowrap"> | |
57 | 56 | # for item in items() do |
58 | 57 | <li><a href="#$(item.name)">$(display_name(item))</a></li> |
59 | 58 | # end |
61 | 60 | # end |
62 | 61 | # end |
63 | 62 | # -------- contents of project ---------- |
64 | # -- if not ldoc.no_summary then | |
65 | 63 | # local this_mod = module and module.name |
66 | 64 | # for kind, mods, type in ldoc.kinds() do |
67 | 65 | # if not ldoc.kinds_allowed or ldoc.kinds_allowed[type] then |
68 | 66 | <h2>$(kind)</h2> |
69 | <ul> | |
70 | # for mod in mods() do | |
71 | # if mod.name == this_mod then -- highlight current module, link to others | |
72 | <li><strong>$(mod.name)</strong></li> | |
67 | <ul class="$(kind=='Topics' and '' or 'nowrap'"> | |
68 | # for mod in mods() do local name = ldoc.module_name(mod) | |
69 | # if mod.name == this_mod then | |
70 | <li><strong>$(name)</strong></li> | |
73 | 71 | # else |
74 | <li><a href="$(ldoc.ref_to_module(mod))">$(mod.name)</a></li> | |
72 | <li><a href="$(ldoc.ref_to_module(mod))">$(name)</a></li> | |
75 | 73 | # end |
76 | 74 | # end |
77 | 75 | # end |
78 | # -- end | |
79 | 76 | </ul> |
80 | 77 | # end |
81 | 78 | |
82 | 79 | </div> |
83 | 80 | |
84 | 81 | <div id="content"> |
85 | ||
86 | #if module then | |
87 | <h1>$(ldoc.module_typename(module)) <code>$(module.name)</code></h1> | |
88 | # end | |
89 | 82 | |
90 | 83 | # if ldoc.body then -- verbatim HTML as contents; 'non-code' entries |
91 | 84 | $(ldoc.body) |
92 | 85 | # elseif module then -- module documentation |
86 | <h1>$(ldoc.module_typename(module)) <code>$(module.name)</code></h1> | |
93 | 87 | <p>$(M(module.summary,module))</p> |
94 | 88 | <p>$(M(module.description,module))</p> |
95 | 89 | # if module.usage then |
104 | 98 | # if module.info then |
105 | 99 | <h3>Info:</h3> |
106 | 100 | <ul> |
107 | # for tag, value in ldoc.pairs(module.info) do | |
101 | # for tag, value in module.info:iter() do | |
108 | 102 | <li><strong>$(tag)</strong>: $(M(value,module))</li> |
109 | 103 | # end |
110 | 104 | </ul> |
154 | 148 | <dd> |
155 | 149 | $(M(ldoc.descript(item),item)) |
156 | 150 | |
151 | # if ldoc.custom_tags then | |
152 | # for custom in iter(ldoc.custom_tags) do | |
153 | # local tag = item.tags[custom[1]] | |
154 | # if tag and not custom.hidden then | |
155 | # local li,il = use_li(tag) | |
156 | <h3>$(custom.title or custom[1]):</h3> | |
157 | <ul> | |
158 | # for value in iter(tag) do | |
159 | $(li)$(custom.format and custom.format(value) or M(value))$(il) | |
160 | # end -- for | |
161 | # end -- if tag | |
162 | </ul> | |
163 | # end -- iter tags | |
164 | # end | |
165 | ||
157 | 166 | # if show_parms and item.params and #item.params > 0 then |
158 | <h3>$(module.kinds:type_of(item).subnames):</h3> | |
167 | # local subnames = module.kinds:type_of(item).subnames | |
168 | # if subnames then | |
169 | <h3>$(subnames):</h3> | |
170 | # end | |
159 | 171 | <ul> |
160 | 172 | # for parm in iter(item.params) do |
161 | 173 | # local param,sublist = item:subparam(parm) |
173 | 185 | # if def then |
174 | 186 | (<em>default</em> $(def)) |
175 | 187 | # end |
188 | # if item:readonly(p) then | |
189 | <em>readonly</em> | |
190 | # end | |
176 | 191 | </li> |
177 | 192 | # end |
178 | 193 | # if sublist then |
182 | 197 | </ul> |
183 | 198 | # end -- if params |
184 | 199 | |
185 | # if show_return and item.ret then | |
186 | # local li,il = use_li(item.ret) | |
200 | # if show_return and item.retgroups then local groups = item.retgroups | |
187 | 201 | <h3>Returns:</h3> |
202 | # for i,group in ldoc.ipairs(groups) do local li,il = use_li(group) | |
188 | 203 | <ol> |
189 | # for i,r in ldoc.ipairs(item.ret) do | |
204 | # for r in group:iter() do local type, ctypes = item:return_type(r); local rt = ldoc.typename(type) | |
190 | 205 | $(li) |
191 | # local tp = ldoc.typename(item:type_of_ret(i)) | |
192 | # if tp ~= '' then | |
193 | <span class="types">$(tp)</span> | |
194 | # end | |
195 | $(M(r,item))$(il) | |
196 | # end -- for | |
206 | # if rt ~= '' then | |
207 | <span class="types">$(rt)</span> | |
208 | # end | |
209 | $(M(r.text,item))$(il) | |
210 | # if ctypes then | |
211 | <ul> | |
212 | # for c in ctypes:iter() do | |
213 | <li><span class="parameter">$(c.name)</span> | |
214 | <span class="types">$(ldoc.typename(c.type))</span> | |
215 | $(M(c.comment,item))</li> | |
216 | # end | |
217 | </ul> | |
218 | # end -- if ctypes | |
219 | # end -- for r | |
197 | 220 | </ol> |
221 | # if i < #groups then | |
222 | <h3>Or</h3> | |
223 | # end | |
224 | # end -- for group | |
198 | 225 | # end -- if returns |
199 | 226 | |
200 | 227 | # if show_return and item.raise then |
253 | 280 | </div> <!-- id="content" --> |
254 | 281 | </div> <!-- id="main" --> |
255 | 282 | <div id="about"> |
256 | <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.0</a></i> | |
283 | <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.2</a></i> | |
257 | 284 | </div> <!-- id="about" --> |
258 | 285 | </div> <!-- id="container" --> |
259 | 286 | </body> |
0 | return [[ | |
1 | > local lev = ldoc.level or 2 | |
2 | > local lev1,lev2 = ('#'):rep(lev),('#'):rep(lev+1) | |
3 | > for kind, items in module.kinds() do | |
4 | > local kitem = module.kinds:get_item(kind) | |
5 | > if kitem then | |
6 | $(lev1) $(ldoc.descript(kitem)) | |
7 | ||
8 | > end | |
9 | > for item in items() do | |
10 | $(lev2) $(ldoc.display_name(item)) | |
11 | ||
12 | $(ldoc.descript(item)) | |
13 | ||
14 | > end | |
15 | > end | |
16 | ]] |
0 | return [[ | |
1 | > local lev = ldoc.level or 2 | |
2 | > local lev1,lev2 = ('#'):rep(lev),('#'):rep(lev+1) | |
3 | > for kind, items in module.kinds() do | |
4 | > local kitem = module.kinds:get_item(kind) | |
5 | > if kitem then | |
6 | $(lev1) $(ldoc.descript(kitem)) | |
7 | ||
8 | > end | |
9 | > for item in items() do | |
10 | $(lev2) $(ldoc.display_name(item)) | |
11 | ||
12 | $(ldoc.descript(item)) | |
13 | ||
14 | > end | |
15 | > end | |
16 | ]] |
0 | return [[/* BEGIN RESET | |
1 | ||
2 | Copyright (c) 2010, Yahoo! Inc. All rights reserved. | |
3 | Code licensed under the BSD License: | |
4 | http://developer.yahoo.com/yui/license.html | |
5 | version: 2.8.2r1 | |
6 | */ | |
7 | html { | |
8 | color: #000; | |
9 | background: #FFF; | |
10 | } | |
11 | body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,button,textarea,p,blockquote,th,td { | |
12 | margin: 0; | |
13 | padding: 0; | |
14 | } | |
15 | table { | |
16 | border-collapse: collapse; | |
17 | border-spacing: 0; | |
18 | } | |
19 | fieldset,img { | |
20 | border: 0; | |
21 | } | |
22 | address,caption,cite,code,dfn,em,strong,th,var,optgroup { | |
23 | font-style: inherit; | |
24 | font-weight: inherit; | |
25 | } | |
26 | del,ins { | |
27 | text-decoration: none; | |
28 | } | |
29 | li { | |
30 | list-style: bullet; | |
31 | margin-left: 20px; | |
32 | } | |
33 | caption,th { | |
34 | text-align: left; | |
35 | } | |
36 | h1,h2,h3,h4,h5,h6 { | |
37 | font-size: 100%; | |
38 | font-weight: bold; | |
39 | } | |
40 | q:before,q:after { | |
41 | content: ''; | |
42 | } | |
43 | abbr,acronym { | |
44 | border: 0; | |
45 | font-variant: normal; | |
46 | } | |
47 | sup { | |
48 | vertical-align: baseline; | |
49 | } | |
50 | sub { | |
51 | vertical-align: baseline; | |
52 | } | |
53 | legend { | |
54 | color: #000; | |
55 | } | |
56 | input,button,textarea,select,optgroup,option { | |
57 | font-family: inherit; | |
58 | font-size: inherit; | |
59 | font-style: inherit; | |
60 | font-weight: inherit; | |
61 | } | |
62 | input,button,textarea,select {*font-size:100%; | |
63 | } | |
64 | /* END RESET */ | |
65 | ||
66 | body { | |
67 | margin-left: 1em; | |
68 | margin-right: 1em; | |
69 | font-family: arial, helvetica, geneva, sans-serif; | |
70 | background-color: #ffffff; margin: 0px; | |
71 | } | |
72 | ||
73 | code, tt { font-family: monospace; } | |
74 | span.parameter { font-family:monospace; } | |
75 | span.parameter:after { content:":"; } | |
76 | span.types:before { content:"("; } | |
77 | span.types:after { content:")"; } | |
78 | .type { font-weight: bold; font-style:italic } | |
79 | ||
80 | body, p, td, th { font-size: .95em; line-height: 1.2em;} | |
81 | ||
82 | p, ul { margin: 10px 0 0 0px;} | |
83 | ||
84 | strong { font-weight: bold;} | |
85 | ||
86 | em { font-style: italic;} | |
87 | ||
88 | h1 { | |
89 | font-size: 1.5em; | |
90 | margin: 0 0 20px 0; | |
91 | } | |
92 | h2, h3, h4 { margin: 15px 0 10px 0; } | |
93 | h2 { font-size: 1.25em; } | |
94 | h3 { font-size: 1.15em; } | |
95 | h4 { font-size: 1.06em; } | |
96 | ||
97 | a:link { font-weight: bold; color: #004080; text-decoration: none; } | |
98 | a:visited { font-weight: bold; color: #006699; text-decoration: none; } | |
99 | a:link:hover { text-decoration: underline; } | |
100 | ||
101 | hr { | |
102 | color:#cccccc; | |
103 | background: #00007f; | |
104 | height: 1px; | |
105 | } | |
106 | ||
107 | blockquote { margin-left: 3em; } | |
108 | ||
109 | ul { list-style-type: disc; } | |
110 | ||
111 | p.name { | |
112 | font-family: "Andale Mono", monospace; | |
113 | padding-top: 1em; | |
114 | } | |
115 | ||
116 | pre.example { | |
117 | background-color: rgb(245, 245, 245); | |
118 | border: 1px solid silver; | |
119 | padding: 10px; | |
120 | margin: 10px 0 10px 0; | |
121 | font-family: "Andale Mono", monospace; | |
122 | font-size: .85em; | |
123 | } | |
124 | ||
125 | pre { | |
126 | background-color: rgb(245,245,255); // rgb(245, 245, 245); | |
127 | border: 1px solid #cccccc; //silver; | |
128 | padding: 10px; | |
129 | margin: 10px 0 10px 0; | |
130 | overflow: auto; | |
131 | font-family: "Andale Mono", monospace; | |
132 | } | |
133 | ||
134 | ||
135 | table.index { border: 1px #00007f; } | |
136 | table.index td { text-align: left; vertical-align: top; } | |
137 | ||
138 | #container { | |
139 | margin-left: 1em; | |
140 | margin-right: 1em; | |
141 | background-color: #f0f0f0; | |
142 | } | |
143 | ||
144 | #product { | |
145 | text-align: center; | |
146 | border-bottom: 1px solid #cccccc; | |
147 | background-color: #ffffff; | |
148 | } | |
149 | ||
150 | #product big { | |
151 | font-size: 2em; | |
152 | } | |
153 | ||
154 | #main { | |
155 | background-color:#FFFFFF; // #f0f0f0; | |
156 | //border-left: 2px solid #cccccc; | |
157 | } | |
158 | ||
159 | #navigation { | |
160 | float: left; | |
161 | width: 14em; | |
162 | vertical-align: top; | |
163 | background-color:#FFFFFF; // #f0f0f0; | |
164 | overflow: visible; | |
165 | } | |
166 | ||
167 | #navigation h2 { | |
168 | background-color:#FFFFFF;//:#e7e7e7; | |
169 | font-size:1.1em; | |
170 | color:#000000; | |
171 | text-align: left; | |
172 | padding:0.2em; | |
173 | //border-top:1px solid #dddddd; | |
174 | border-bottom:1px solid #dddddd; | |
175 | } | |
176 | ||
177 | #navigation ul | |
178 | { | |
179 | font-size:1em; | |
180 | list-style-type: none; | |
181 | margin: 1px 1px 10px 1px; | |
182 | } | |
183 | ||
184 | #navigation li { | |
185 | text-indent: -1em; | |
186 | display: block; | |
187 | margin: 3px 0px 0px 22px; | |
188 | } | |
189 | ||
190 | #navigation li li a { | |
191 | margin: 0px 3px 0px -1em; | |
192 | } | |
193 | ||
194 | #content { | |
195 | margin-left: 14em; | |
196 | padding: 1em; | |
197 | width: 700px; | |
198 | border-left: 2px solid #cccccc; | |
199 | // border-right: 2px solid #cccccc; | |
200 | background-color: #ffffff; | |
201 | } | |
202 | ||
203 | #about { | |
204 | clear: both; | |
205 | padding: 5px; | |
206 | border-top: 2px solid #cccccc; | |
207 | background-color: #ffffff; | |
208 | } | |
209 | ||
210 | @media print { | |
211 | body { | |
212 | font: 12pt "Times New Roman", "TimeNR", Times, serif; | |
213 | } | |
214 | a { font-weight: bold; color: #004080; text-decoration: underline; } | |
215 | ||
216 | #main { | |
217 | background-color: #ffffff; | |
218 | border-left: 0px; | |
219 | } | |
220 | ||
221 | #container { | |
222 | margin-left: 2%; | |
223 | margin-right: 2%; | |
224 | background-color: #ffffff; | |
225 | } | |
226 | ||
227 | #content { | |
228 | padding: 1em; | |
229 | background-color: #ffffff; | |
230 | } | |
231 | ||
232 | #navigation { | |
233 | display: none; | |
234 | } | |
235 | pre.example { | |
236 | font-family: "Andale Mono", monospace; | |
237 | font-size: 10pt; | |
238 | page-break-inside: avoid; | |
239 | } | |
240 | } | |
241 | ||
242 | table.module_list { | |
243 | border-width: 1px; | |
244 | border-style: solid; | |
245 | border-color: #cccccc; | |
246 | border-collapse: collapse; | |
247 | } | |
248 | table.module_list td { | |
249 | border-width: 1px; | |
250 | padding: 3px; | |
251 | border-style: solid; | |
252 | border-color: #cccccc; | |
253 | } | |
254 | table.module_list td.name { background-color: #f0f0f0; ; min-width: 200px; } | |
255 | table.module_list td.summary { width: 100%; } | |
256 | ||
257 | table.function_list { | |
258 | border-width: 1px; | |
259 | border-style: solid; | |
260 | border-color: #cccccc; | |
261 | border-collapse: collapse; | |
262 | } | |
263 | table.function_list td { | |
264 | border-width: 1px; | |
265 | padding: 3px; | |
266 | border-style: solid; | |
267 | border-color: #cccccc; | |
268 | } | |
269 | table.function_list td.name { background-color: #f6f6ff; ; min-width: 200px; } | |
270 | table.function_list td.summary { width: 100%; } | |
271 | ||
272 | dl.table dt, dl.function dt {border-top: 1px solid #ccc; padding-top: 1em;} | |
273 | dl.table dd, dl.function dd {padding-bottom: 1em; margin: 10px 0 0 20px;} | |
274 | dl.table h3, dl.function h3 {font-size: .95em;} | |
275 | ||
276 | ul.nowrap { | |
277 | overflow:auto; | |
278 | whitespace:nowrap; | |
279 | } | |
280 | ||
281 | /* stop sublists from having initial vertical space */ | |
282 | ul ul { margin-top: 0px; } | |
283 | ol ul { margin-top: 0px; } | |
284 | ol ol { margin-top: 0px; } | |
285 | ul ol { margin-top: 0px; } | |
286 | ||
287 | /* styles for prettification of source */ | |
288 | pre .comment { color: #558817; } | |
289 | pre .constant { color: #a8660d; } | |
290 | pre .escape { color: #844631; } | |
291 | pre .keyword { color: #2239a8; font-weight: bold; } | |
292 | pre .library { color: #0e7c6b; } | |
293 | pre .marker { color: #512b1e; background: #fedc56; font-weight: bold; } | |
294 | pre .string { color: #a8660d; } | |
295 | pre .number { color: #f8660d; } | |
296 | pre .operator { color: #2239a8; font-weight: bold; } | |
297 | pre .preprocessor, pre .prepro { color: #a33243; } | |
298 | pre .global { color: #800080; } | |
299 | pre .prompt { color: #558817; } | |
300 | pre .url { color: #272fc2; text-decoration: underline; } | |
301 | ]] |
18 | 18 | local stringx = require 'pl.stringx' |
19 | 19 | local template = require 'pl.template' |
20 | 20 | local tablex = require 'pl.tablex' |
21 | local OrderedMap = require 'pl.OrderedMap' | |
21 | 22 | local tools = require 'ldoc.tools' |
22 | 23 | local markup = require 'ldoc.markup' |
23 | 24 | local prettify = require 'ldoc.prettify' |
24 | 25 | local doc = require 'ldoc.doc' |
26 | local unpack = utils.unpack | |
25 | 27 | local html = {} |
26 | 28 | |
27 | 29 | |
37 | 39 | end |
38 | 40 | |
39 | 41 | local function get_module_info(m) |
40 | local info = {} | |
42 | local info = OrderedMap() | |
41 | 43 | for tag in doc.module_info_tags() do |
42 | 44 | local val = m.tags[tag] |
43 | 45 | if type(val)=='table' then |
44 | 46 | val = table.concat(val,',') |
45 | 47 | end |
46 | 48 | tag = stringx.title(tag) |
47 | info[tag] = val | |
48 | end | |
49 | if next(info) then | |
49 | info:set(tag,val) | |
50 | end | |
51 | if #info:keys() > 0 then | |
50 | 52 | return info |
51 | 53 | end |
52 | 54 | end |
91 | 93 | -- Item descriptions come from combining the summary and description fields |
92 | 94 | function ldoc.descript(item) |
93 | 95 | return (item.summary or '?')..' '..(item.description or '') |
96 | end | |
97 | ||
98 | function ldoc.module_name (mod) | |
99 | local name = mod.name | |
100 | if args.unqualified and (mod.type == 'module' or mod.type == 'classmod') then -- leave out package | |
101 | name = name:gsub('^.-%.','') | |
102 | elseif mod.type == 'topic' then | |
103 | if mod.display_name then | |
104 | name = mod.display_name | |
105 | else -- leave out md extension | |
106 | name = name:gsub('%..*$','') | |
107 | end | |
108 | end | |
109 | return name | |
94 | 110 | end |
95 | 111 | |
96 | 112 | -- this generates the internal module/function references |
140 | 156 | if #ls > 1 then return '<li>','</li>' else return '','' end |
141 | 157 | end |
142 | 158 | |
143 | function ldoc.display_name(item) | |
159 | function ldoc.default_display_name(item) | |
144 | 160 | local name = item.display_name or item.name |
145 | 161 | if item.type == 'function' or item.type == 'lfunction' then |
146 | 162 | if not ldoc.no_space_before_args then |
152 | 168 | end |
153 | 169 | end |
154 | 170 | |
171 | function ldoc.display_name(item) | |
172 | if ldoc.custom_display_name_handler then | |
173 | return ldoc.custom_display_name_handler(item, ldoc.default_display_name) | |
174 | else | |
175 | return ldoc.default_display_name(item) | |
176 | end | |
177 | end | |
178 | ||
155 | 179 | function ldoc.no_spaces(s) |
156 | 180 | s = s:gsub('%s*$','') |
157 | 181 | return (s:gsub('%W','_')) |
166 | 190 | end |
167 | 191 | |
168 | 192 | function ldoc.typename (tp) |
169 | if not tp or tp == '' then return '' end | |
193 | if not tp or tp == '' or tp:match '^@' then return '' end | |
170 | 194 | local optional |
171 | 195 | -- ?<type> is short for ?nil|<type> |
172 | 196 | if tp:match("^%?") and not tp:match '|' then |
177 | 201 | optional = true |
178 | 202 | tp = tp2 |
179 | 203 | end |
204 | ||
180 | 205 | local types = {} |
181 | 206 | for name in tp:gmatch("[^|]+") do |
182 | local ref,err = markup.process_reference(name,true) | |
207 | local sym = name:match '([%w%.%:]+)' | |
208 | local ref,err = markup.process_reference(sym,true) | |
183 | 209 | if ref then |
184 | types[#types+1] = ('<a class="type" href="%s">%s</a>'):format(ldoc.href(ref),ref.label or name) | |
210 | if ref.label and sym == name then | |
211 | name = ref.label | |
212 | end | |
213 | types[#types+1] = ('<a class="type" href="%s">%s</a>'):format(ldoc.href(ref),name) | |
185 | 214 | else |
186 | 215 | types[#types+1] = '<span class="type">'..name..'</span>' |
187 | 216 | end |
73 | 73 | self.line_comment = '^%-%-+' -- used for stripping |
74 | 74 | self.start_comment_ = '^%-%-%-+' -- used for doc comment line start |
75 | 75 | self.block_comment = '^%-%-%[=*%[%-+' -- used for block doc comments |
76 | self.end_comment_ = '[^%-]%-%-+\n$' ---- exclude --- this kind of comment --- | |
76 | self.end_comment_ = '[^%-]%-%-+[^-]*\n$' ---- exclude --- this kind of comment --- | |
77 | 77 | self.method_call = ':' |
78 | 78 | self:finalize() |
79 | 79 | end |
86 | 86 | |
87 | 87 | function Lua:grab_block_comment(v,tok) |
88 | 88 | local equals = v:match('^%-%-%[(=*)%[') |
89 | if not equals then return v end | |
89 | 90 | v = v:gsub(self.block_comment,'') |
90 | 91 | return tools.grab_block_comment(v,tok,'%]'..equals..'%]') |
91 | 92 | end |
261 | 262 | end |
262 | 263 | |
263 | 264 | function CC:grab_block_comment(v,tok) |
264 | v = v:gsub(self.block_comment,'') | |
265 | v = v:gsub(self.block_comment,''):gsub('\n%s*%*','\n') | |
265 | 266 | return 'comment',v:sub(1,-3) |
267 | end | |
268 | ||
269 | --- here the argument name is always last, and the type is composed of any tokens before | |
270 | function CC:extract_arg (tl,idx) | |
271 | idx = idx or 1 | |
272 | local res = List() | |
273 | for i = idx,#tl-1 do | |
274 | res:append(tl[i][2]) | |
275 | end | |
276 | local type = res:join ' ' | |
277 | return tl[#tl][2], type | |
278 | end | |
279 | ||
280 | function CC:item_follows (t,v,tok) | |
281 | if not self.extra.C then | |
282 | return false | |
283 | end | |
284 | if t == 'iden' or t == 'keyword' then -- | |
285 | if v == self.extra.export then -- this is not part of the return type! | |
286 | t,v = tnext(tok) | |
287 | end | |
288 | -- types may have multiple tokens: example, const char *bonzo(...) | |
289 | local return_type, name = v | |
290 | t,v = tnext(tok) | |
291 | name = v | |
292 | t,v = tnext(tok) | |
293 | while t ~= '(' do | |
294 | return_type = return_type .. ' ' .. name | |
295 | name = v | |
296 | t,v = tnext(tok) | |
297 | end | |
298 | --print ('got',name,t,v,return_type) | |
299 | return function(tags,tok) | |
300 | if not tags.name then | |
301 | tags:add('name',name) | |
302 | end | |
303 | tags:add('class','function') | |
304 | if t == '(' then | |
305 | tags.formal_args,t,v = tools.get_parameters(tok,')',',',self) | |
306 | if return_type ~= 'void' then | |
307 | tags.formal_args.return_type = return_type | |
308 | end | |
309 | end | |
310 | end | |
311 | end | |
312 | return false | |
266 | 313 | end |
267 | 314 | |
268 | 315 | local Moon = class(Lua) |
370 | 370 | {'^|=',tdump}, |
371 | 371 | {'^%^=',tdump}, |
372 | 372 | {'^::',tdump}, |
373 | {'^%.%.%.',tdump}, | |
373 | 374 | {'^.',tdump} |
374 | 375 | } |
375 | 376 | end |
0 | #!/usr/bin/env lua | |
1 | ||
2 | --[[ | |
3 | # markdown.lua -- version 0.32 | |
4 | ||
5 | <http://www.frykholm.se/files/markdown.lua> | |
6 | ||
7 | **Author:** Niklas Frykholm, <niklas@frykholm.se> | |
8 | **Date:** 31 May 2008 | |
9 | ||
10 | This is an implementation of the popular text markup language Markdown in pure Lua. | |
11 | Markdown can convert documents written in a simple and easy to read text format | |
12 | to well-formatted HTML. For a more thourough description of Markdown and the Markdown | |
13 | syntax, see <http://daringfireball.net/projects/markdown>. | |
14 | ||
15 | The original Markdown source is written in Perl and makes heavy use of advanced | |
16 | regular expression techniques (such as negative look-ahead, etc) which are not available | |
17 | in Lua's simple regex engine. Therefore this Lua port has been rewritten from the ground | |
18 | up. It is probably not completely bug free. If you notice any bugs, please report them to | |
19 | me. A unit test that exposes the error is helpful. | |
20 | ||
21 | ## Usage | |
22 | ||
23 | require "markdown" | |
24 | markdown(source) | |
25 | ||
26 | ``markdown.lua`` exposes a single global function named ``markdown(s)`` which applies the | |
27 | Markdown transformation to the specified string. | |
28 | ||
29 | ``markdown.lua`` can also be used directly from the command line: | |
30 | ||
31 | lua markdown.lua test.md | |
32 | ||
33 | Creates a file ``test.html`` with the converted content of ``test.md``. Run: | |
34 | ||
35 | lua markdown.lua -h | |
36 | ||
37 | For a description of the command-line options. | |
38 | ||
39 | ``markdown.lua`` uses the same license as Lua, the MIT license. | |
40 | ||
41 | ## License | |
42 | ||
43 | Copyright © 2008 Niklas Frykholm. | |
44 | ||
45 | Permission is hereby granted, free of charge, to any person obtaining a copy of this | |
46 | software and associated documentation files (the "Software"), to deal in the Software | |
47 | without restriction, including without limitation the rights to use, copy, modify, merge, | |
48 | publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons | |
49 | to whom the Software is furnished to do so, subject to the following conditions: | |
50 | ||
51 | The above copyright notice and this permission notice shall be included in all copies | |
52 | or substantial portions of the Software. | |
53 | ||
54 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
55 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
56 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
57 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
58 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
59 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
60 | THE SOFTWARE. | |
61 | ||
62 | ## Version history | |
63 | ||
64 | - **0.32** -- 31 May 2008 | |
65 | - Fix for links containing brackets | |
66 | - **0.31** -- 1 Mar 2008 | |
67 | - Fix for link definitions followed by spaces | |
68 | - **0.30** -- 25 Feb 2008 | |
69 | - Consistent behavior with Markdown when the same link reference is reused | |
70 | - **0.29** -- 24 Feb 2008 | |
71 | - Fix for <pre> blocks with spaces in them | |
72 | - **0.28** -- 18 Feb 2008 | |
73 | - Fix for link encoding | |
74 | - **0.27** -- 14 Feb 2008 | |
75 | - Fix for link database links with () | |
76 | - **0.26** -- 06 Feb 2008 | |
77 | - Fix for nested italic and bold markers | |
78 | - **0.25** -- 24 Jan 2008 | |
79 | - Fix for encoding of naked < | |
80 | - **0.24** -- 21 Jan 2008 | |
81 | - Fix for link behavior. | |
82 | - **0.23** -- 10 Jan 2008 | |
83 | - Fix for a regression bug in longer expressions in italic or bold. | |
84 | - **0.22** -- 27 Dec 2007 | |
85 | - Fix for crash when processing blocks with a percent sign in them. | |
86 | - **0.21** -- 27 Dec 2007 | |
87 | - Fix for combined strong and emphasis tags | |
88 | - **0.20** -- 13 Oct 2007 | |
89 | - Fix for < as well in image titles, now matches Dingus behavior | |
90 | - **0.19** -- 28 Sep 2007 | |
91 | - Fix for quotation marks " and ampersands & in link and image titles. | |
92 | - **0.18** -- 28 Jul 2007 | |
93 | - Does not crash on unmatched tags (behaves like standard markdown) | |
94 | - **0.17** -- 12 Apr 2007 | |
95 | - Fix for links with %20 in them. | |
96 | - **0.16** -- 12 Apr 2007 | |
97 | - Do not require arg global to exist. | |
98 | - **0.15** -- 28 Aug 2006 | |
99 | - Better handling of links with underscores in them. | |
100 | - **0.14** -- 22 Aug 2006 | |
101 | - Bug for *`foo()`* | |
102 | - **0.13** -- 12 Aug 2006 | |
103 | - Added -l option for including stylesheet inline in document. | |
104 | - Fixed bug in -s flag. | |
105 | - Fixed emphasis bug. | |
106 | - **0.12** -- 15 May 2006 | |
107 | - Fixed several bugs to comply with MarkdownTest 1.0 <http://six.pairlist.net/pipermail/markdown-discuss/2004-December/000909.html> | |
108 | - **0.11** -- 12 May 2006 | |
109 | - Fixed bug for escaping `*` and `_` inside code spans. | |
110 | - Added license terms. | |
111 | - Changed join() to table.concat(). | |
112 | - **0.10** -- 3 May 2006 | |
113 | - Initial public release. | |
114 | ||
115 | // Niklas | |
116 | ]] | |
117 | ||
118 | ||
119 | -- Set up a table for holding local functions to avoid polluting the global namespace | |
120 | -- Penlight 1.2 defines compatible 5.1 setfenv in utils table | |
121 | local M = {} | |
122 | local MT = {__index = _G} | |
123 | setmetatable(M, MT) | |
124 | ||
125 | ---------------------------------------------------------------------- | |
126 | -- Utility functions | |
127 | ---------------------------------------------------------------------- | |
128 | ||
129 | -- Locks table t from changes, writes an error if someone attempts to change the table. | |
130 | -- This is useful for detecting variables that have "accidently" been made global. Something | |
131 | -- I tend to do all too much. | |
132 | function M.lock(t) | |
133 | local function lock_new_index(t, k, v) | |
134 | error("module has been locked -- " .. k .. " must be declared local", 2) | |
135 | end | |
136 | ||
137 | local mt = {__newindex = lock_new_index} | |
138 | if getmetatable(t) then | |
139 | mt.__index = getmetatable(t).__index | |
140 | end | |
141 | setmetatable(t, mt) | |
142 | end | |
143 | ||
144 | -- Returns the result of mapping the values in table t through the function f | |
145 | local function map(t, f) | |
146 | local out = {} | |
147 | for k,v in pairs(t) do out[k] = f(v,k) end | |
148 | return out | |
149 | end | |
150 | ||
151 | -- The identity function, useful as a placeholder. | |
152 | local function identity(text) return text end | |
153 | ||
154 | -- Functional style if statement. (NOTE: no short circuit evaluation) | |
155 | local function iff(t, a, b) if t then return a else return b end end | |
156 | ||
157 | -- Splits the text into an array of separate lines. | |
158 | local function split(text, sep) | |
159 | sep = sep or "\n" | |
160 | local lines = {} | |
161 | local pos = 1 | |
162 | while true do | |
163 | local b,e = text:find(sep, pos) | |
164 | if not b then table.insert(lines, text:sub(pos)) break end | |
165 | table.insert(lines, text:sub(pos, b-1)) | |
166 | pos = e + 1 | |
167 | end | |
168 | return lines | |
169 | end | |
170 | ||
171 | -- Converts tabs to spaces | |
172 | local function detab(text) | |
173 | local tab_width = 4 | |
174 | local function rep(match) | |
175 | local spaces = -match:len() | |
176 | while spaces<1 do spaces = spaces + tab_width end | |
177 | return match .. string.rep(" ", spaces) | |
178 | end | |
179 | text = text:gsub("([^\n]-)\t", rep) | |
180 | return text | |
181 | end | |
182 | ||
183 | -- Applies string.find for every pattern in the list and returns the first match | |
184 | local function find_first(s, patterns, index) | |
185 | local res = {} | |
186 | for _,p in ipairs(patterns) do | |
187 | local match = {s:find(p, index)} | |
188 | if #match>0 and (#res==0 or match[1] < res[1]) then res = match end | |
189 | end | |
190 | return unpack(res) | |
191 | end | |
192 | ||
193 | -- If a replacement array is specified, the range [start, stop] in the array is replaced | |
194 | -- with the replacement array and the resulting array is returned. Without a replacement | |
195 | -- array the section of the array between start and stop is returned. | |
196 | local function splice(array, start, stop, replacement) | |
197 | if replacement then | |
198 | local n = stop - start + 1 | |
199 | while n > 0 do | |
200 | table.remove(array, start) | |
201 | n = n - 1 | |
202 | end | |
203 | for i,v in ipairs(replacement) do | |
204 | table.insert(array, start, v) | |
205 | end | |
206 | return array | |
207 | else | |
208 | local res = {} | |
209 | for i = start,stop do | |
210 | table.insert(res, array[i]) | |
211 | end | |
212 | return res | |
213 | end | |
214 | end | |
215 | ||
216 | -- Outdents the text one step. | |
217 | local function outdent(text) | |
218 | text = "\n" .. text | |
219 | text = text:gsub("\n ? ? ?", "\n") | |
220 | text = text:sub(2) | |
221 | return text | |
222 | end | |
223 | ||
224 | -- Indents the text one step. | |
225 | local function indent(text) | |
226 | text = text:gsub("\n", "\n ") | |
227 | return text | |
228 | end | |
229 | ||
230 | -- Does a simple tokenization of html data. Returns the data as a list of tokens. | |
231 | -- Each token is a table with a type field (which is either "tag" or "text") and | |
232 | -- a text field (which contains the original token data). | |
233 | local function tokenize_html(html) | |
234 | local tokens = {} | |
235 | local pos = 1 | |
236 | while true do | |
237 | local start = find_first(html, {"<!%-%-", "<[a-z/!$]", "<%?"}, pos) | |
238 | if not start then | |
239 | table.insert(tokens, {type="text", text=html:sub(pos)}) | |
240 | break | |
241 | end | |
242 | if start ~= pos then table.insert(tokens, {type="text", text = html:sub(pos, start-1)}) end | |
243 | ||
244 | local _, stop | |
245 | if html:match("^<!%-%-", start) then | |
246 | _,stop = html:find("%-%->", start) | |
247 | elseif html:match("^<%?", start) then | |
248 | _,stop = html:find("?>", start) | |
249 | else | |
250 | _,stop = html:find("%b<>", start) | |
251 | end | |
252 | if not stop then | |
253 | -- error("Could not match html tag " .. html:sub(start,start+30)) | |
254 | table.insert(tokens, {type="text", text=html:sub(start, start)}) | |
255 | pos = start + 1 | |
256 | else | |
257 | table.insert(tokens, {type="tag", text=html:sub(start, stop)}) | |
258 | pos = stop + 1 | |
259 | end | |
260 | end | |
261 | return tokens | |
262 | end | |
263 | ||
264 | ---------------------------------------------------------------------- | |
265 | -- Hash | |
266 | ---------------------------------------------------------------------- | |
267 | ||
268 | -- This is used to "hash" data into alphanumeric strings that are unique | |
269 | -- in the document. (Note that this is not cryptographic hash, the hash | |
270 | -- function is not one-way.) The hash procedure is used to protect parts | |
271 | -- of the document from further processing. | |
272 | ||
273 | local HASH = { | |
274 | -- Has the hash been inited. | |
275 | inited = false, | |
276 | ||
277 | -- The unique string prepended to all hash values. This is to ensure | |
278 | -- that hash values do not accidently coincide with an actual existing | |
279 | -- string in the document. | |
280 | identifier = "", | |
281 | ||
282 | -- Counter that counts up for each new hash instance. | |
283 | counter = 0, | |
284 | ||
285 | -- Hash table. | |
286 | table = {} | |
287 | } | |
288 | ||
289 | -- Inits hashing. Creates a hash_identifier that doesn't occur anywhere | |
290 | -- in the text. | |
291 | local function init_hash(text) | |
292 | HASH.inited = true | |
293 | HASH.identifier = "" | |
294 | HASH.counter = 0 | |
295 | HASH.table = {} | |
296 | ||
297 | local s = "HASH" | |
298 | local counter = 0 | |
299 | local id | |
300 | while true do | |
301 | id = s .. counter | |
302 | if not text:find(id, 1, true) then break end | |
303 | counter = counter + 1 | |
304 | end | |
305 | HASH.identifier = id | |
306 | end | |
307 | ||
308 | -- Returns the hashed value for s. | |
309 | local function hash(s) | |
310 | assert(HASH.inited) | |
311 | if not HASH.table[s] then | |
312 | HASH.counter = HASH.counter + 1 | |
313 | local id = HASH.identifier .. HASH.counter .. "X" | |
314 | HASH.table[s] = id | |
315 | end | |
316 | return HASH.table[s] | |
317 | end | |
318 | ||
319 | ---------------------------------------------------------------------- | |
320 | -- Protection | |
321 | ---------------------------------------------------------------------- | |
322 | ||
323 | -- The protection module is used to "protect" parts of a document | |
324 | -- so that they are not modified by subsequent processing steps. | |
325 | -- Protected parts are saved in a table for later unprotection | |
326 | ||
327 | -- Protection data | |
328 | local PD = { | |
329 | -- Saved blocks that have been converted | |
330 | blocks = {}, | |
331 | ||
332 | -- Block level tags that will be protected | |
333 | tags = {"p", "div", "h1", "h2", "h3", "h4", "h5", "h6", "blockquote", | |
334 | "pre", "table", "dl", "ol", "ul", "script", "noscript", "form", "fieldset", | |
335 | "iframe", "math", "ins", "del"} | |
336 | } | |
337 | ||
338 | -- Pattern for matching a block tag that begins and ends in the leftmost | |
339 | -- column and may contain indented subtags, i.e. | |
340 | -- <div> | |
341 | -- A nested block. | |
342 | -- <div> | |
343 | -- Nested data. | |
344 | -- </div> | |
345 | -- </div> | |
346 | local function block_pattern(tag) | |
347 | return "\n<" .. tag .. ".-\n</" .. tag .. ">[ \t]*\n" | |
348 | end | |
349 | ||
350 | -- Pattern for matching a block tag that begins and ends with a newline | |
351 | local function line_pattern(tag) | |
352 | return "\n<" .. tag .. ".-</" .. tag .. ">[ \t]*\n" | |
353 | end | |
354 | ||
355 | -- Protects the range of characters from start to stop in the text and | |
356 | -- returns the protected string. | |
357 | local function protect_range(text, start, stop) | |
358 | local s = text:sub(start, stop) | |
359 | local h = hash(s) | |
360 | PD.blocks[h] = s | |
361 | text = text:sub(1,start) .. h .. text:sub(stop) | |
362 | return text | |
363 | end | |
364 | ||
365 | -- Protect every part of the text that matches any of the patterns. The first | |
366 | -- matching pattern is protected first, etc. | |
367 | local function protect_matches(text, patterns) | |
368 | while true do | |
369 | local start, stop = find_first(text, patterns) | |
370 | if not start then break end | |
371 | text = protect_range(text, start, stop) | |
372 | end | |
373 | return text | |
374 | end | |
375 | ||
376 | -- Protects blocklevel tags in the specified text | |
377 | local function protect(text) | |
378 | -- First protect potentially nested block tags | |
379 | text = protect_matches(text, map(PD.tags, block_pattern)) | |
380 | -- Then protect block tags at the line level. | |
381 | text = protect_matches(text, map(PD.tags, line_pattern)) | |
382 | -- Protect <hr> and comment tags | |
383 | text = protect_matches(text, {"\n<hr[^>]->[ \t]*\n"}) | |
384 | text = protect_matches(text, {"\n<!%-%-.-%-%->[ \t]*\n"}) | |
385 | return text | |
386 | end | |
387 | ||
388 | -- Returns true if the string s is a hash resulting from protection | |
389 | local function is_protected(s) | |
390 | return PD.blocks[s] | |
391 | end | |
392 | ||
393 | -- Unprotects the specified text by expanding all the nonces | |
394 | local function unprotect(text) | |
395 | for k,v in pairs(PD.blocks) do | |
396 | v = v:gsub("%%", "%%%%") | |
397 | text = text:gsub(k, v) | |
398 | end | |
399 | return text | |
400 | end | |
401 | ||
402 | ||
403 | ---------------------------------------------------------------------- | |
404 | -- Block transform | |
405 | ---------------------------------------------------------------------- | |
406 | ||
407 | -- The block transform functions transform the text on the block level. | |
408 | -- They work with the text as an array of lines rather than as individual | |
409 | -- characters. | |
410 | ||
411 | -- Returns true if the line is a ruler of (char) characters. | |
412 | -- The line must contain at least three char characters and contain only spaces and | |
413 | -- char characters. | |
414 | local function is_ruler_of(line, char) | |
415 | if not line:match("^[ %" .. char .. "]*$") then return false end | |
416 | if not line:match("%" .. char .. ".*%" .. char .. ".*%" .. char) then return false end | |
417 | return true | |
418 | end | |
419 | ||
420 | -- Identifies the block level formatting present in the line | |
421 | local function classify(line) | |
422 | local info = {line = line, text = line} | |
423 | ||
424 | if line:match("^ ") then | |
425 | info.type = "indented" | |
426 | info.outdented = line:sub(5) | |
427 | return info | |
428 | end | |
429 | ||
430 | for _,c in ipairs({'*', '-', '_', '='}) do | |
431 | if is_ruler_of(line, c) then | |
432 | info.type = "ruler" | |
433 | info.ruler_char = c | |
434 | return info | |
435 | end | |
436 | end | |
437 | ||
438 | if line == "" then | |
439 | info.type = "blank" | |
440 | return info | |
441 | end | |
442 | ||
443 | if line:match("^(#+)[ \t]*(.-)[ \t]*#*[ \t]*$") then | |
444 | local m1, m2 = line:match("^(#+)[ \t]*(.-)[ \t]*#*[ \t]*$") | |
445 | info.type = "header" | |
446 | info.level = m1:len() | |
447 | info.text = m2 | |
448 | return info | |
449 | end | |
450 | ||
451 | if line:match("^ ? ? ?(%d+)%.[ \t]+(.+)") then | |
452 | local number, text = line:match("^ ? ? ?(%d+)%.[ \t]+(.+)") | |
453 | info.type = "list_item" | |
454 | info.list_type = "numeric" | |
455 | info.number = 0 + number | |
456 | info.text = text | |
457 | return info | |
458 | end | |
459 | ||
460 | if line:match("^ ? ? ?([%*%+%-])[ \t]+(.+)") then | |
461 | local bullet, text = line:match("^ ? ? ?([%*%+%-])[ \t]+(.+)") | |
462 | info.type = "list_item" | |
463 | info.list_type = "bullet" | |
464 | info.bullet = bullet | |
465 | info.text= text | |
466 | return info | |
467 | end | |
468 | ||
469 | if line:match("^>[ \t]?(.*)") then | |
470 | info.type = "blockquote" | |
471 | info.text = line:match("^>[ \t]?(.*)") | |
472 | return info | |
473 | end | |
474 | ||
475 | if is_protected(line) then | |
476 | info.type = "raw" | |
477 | info.html = unprotect(line) | |
478 | return info | |
479 | end | |
480 | ||
481 | info.type = "normal" | |
482 | return info | |
483 | end | |
484 | ||
485 | -- Find headers constisting of a normal line followed by a ruler and converts them to | |
486 | -- header entries. | |
487 | local function headers(array) | |
488 | local i = 1 | |
489 | while i <= #array - 1 do | |
490 | if array[i].type == "normal" and array[i+1].type == "ruler" and | |
491 | (array[i+1].ruler_char == "-" or array[i+1].ruler_char == "=") then | |
492 | local info = {line = array[i].line} | |
493 | info.text = info.line | |
494 | info.type = "header" | |
495 | info.level = iff(array[i+1].ruler_char == "=", 1, 2) | |
496 | table.remove(array, i+1) | |
497 | array[i] = info | |
498 | end | |
499 | i = i + 1 | |
500 | end | |
501 | return array | |
502 | end | |
503 | ||
504 | local block_transform, blocks_to_html, encode_code, span_transform, encode_backslash_escapes | |
505 | ||
506 | -- Find list blocks and convert them to protected data blocks | |
507 | local function lists(array, sublist) | |
508 | local function process_list(arr) | |
509 | local function any_blanks(arr) | |
510 | for i = 1, #arr do | |
511 | if arr[i].type == "blank" then return true end | |
512 | end | |
513 | return false | |
514 | end | |
515 | ||
516 | local function split_list_items(arr) | |
517 | local acc = {arr[1]} | |
518 | local res = {} | |
519 | for i=2,#arr do | |
520 | if arr[i].type == "list_item" then | |
521 | table.insert(res, acc) | |
522 | acc = {arr[i]} | |
523 | else | |
524 | table.insert(acc, arr[i]) | |
525 | end | |
526 | end | |
527 | table.insert(res, acc) | |
528 | return res | |
529 | end | |
530 | ||
531 | local function process_list_item(lines, block) | |
532 | while lines[#lines].type == "blank" do | |
533 | table.remove(lines) | |
534 | end | |
535 | ||
536 | local itemtext = lines[1].text | |
537 | for i=2,#lines do | |
538 | itemtext = itemtext .. "\n" .. outdent(lines[i].line) | |
539 | end | |
540 | if block then | |
541 | itemtext = block_transform(itemtext, true) | |
542 | if not itemtext:find("<pre>") then itemtext = indent(itemtext) end | |
543 | return " <li>" .. itemtext .. "</li>" | |
544 | else | |
545 | local lines = split(itemtext) | |
546 | lines = map(lines, classify) | |
547 | lines = lists(lines, true) | |
548 | lines = blocks_to_html(lines, true) | |
549 | itemtext = table.concat(lines, "\n") | |
550 | if not itemtext:find("<pre>") then itemtext = indent(itemtext) end | |
551 | return " <li>" .. itemtext .. "</li>" | |
552 | end | |
553 | end | |
554 | ||
555 | local block_list = any_blanks(arr) | |
556 | local items = split_list_items(arr) | |
557 | local out = "" | |
558 | for _, item in ipairs(items) do | |
559 | out = out .. process_list_item(item, block_list) .. "\n" | |
560 | end | |
561 | if arr[1].list_type == "numeric" then | |
562 | return "<ol>\n" .. out .. "</ol>" | |
563 | else | |
564 | return "<ul>\n" .. out .. "</ul>" | |
565 | end | |
566 | end | |
567 | ||
568 | -- Finds the range of lines composing the first list in the array. A list | |
569 | -- starts with (^ list_item) or (blank list_item) and ends with | |
570 | -- (blank* $) or (blank normal). | |
571 | -- | |
572 | -- A sublist can start with just (list_item) does not need a blank... | |
573 | local function find_list(array, sublist) | |
574 | local function find_list_start(array, sublist) | |
575 | if array[1].type == "list_item" then return 1 end | |
576 | if sublist then | |
577 | for i = 1,#array do | |
578 | if array[i].type == "list_item" then return i end | |
579 | end | |
580 | else | |
581 | for i = 1, #array-1 do | |
582 | if array[i].type == "blank" and array[i+1].type == "list_item" then | |
583 | return i+1 | |
584 | end | |
585 | end | |
586 | end | |
587 | return nil | |
588 | end | |
589 | local function find_list_end(array, start) | |
590 | local pos = #array | |
591 | for i = start, #array-1 do | |
592 | if array[i].type == "blank" and array[i+1].type ~= "list_item" | |
593 | and array[i+1].type ~= "indented" and array[i+1].type ~= "blank" then | |
594 | pos = i-1 | |
595 | break | |
596 | end | |
597 | end | |
598 | while pos > start and array[pos].type == "blank" do | |
599 | pos = pos - 1 | |
600 | end | |
601 | return pos | |
602 | end | |
603 | ||
604 | local start = find_list_start(array, sublist) | |
605 | if not start then return nil end | |
606 | return start, find_list_end(array, start) | |
607 | end | |
608 | ||
609 | while true do | |
610 | local start, stop = find_list(array, sublist) | |
611 | if not start then break end | |
612 | local text = process_list(splice(array, start, stop)) | |
613 | local info = { | |
614 | line = text, | |
615 | type = "raw", | |
616 | html = text | |
617 | } | |
618 | array = splice(array, start, stop, {info}) | |
619 | end | |
620 | ||
621 | -- Convert any remaining list items to normal | |
622 | for _,line in ipairs(array) do | |
623 | if line.type == "list_item" then line.type = "normal" end | |
624 | end | |
625 | ||
626 | return array | |
627 | end | |
628 | ||
629 | -- Find and convert blockquote markers. | |
630 | local function blockquotes(lines) | |
631 | local function find_blockquote(lines) | |
632 | local start | |
633 | for i,line in ipairs(lines) do | |
634 | if line.type == "blockquote" then | |
635 | start = i | |
636 | break | |
637 | end | |
638 | end | |
639 | if not start then return nil end | |
640 | ||
641 | local stop = #lines | |
642 | for i = start+1, #lines do | |
643 | if lines[i].type == "blank" or lines[i].type == "blockquote" then | |
644 | elseif lines[i].type == "normal" then | |
645 | if lines[i-1].type == "blank" then stop = i-1 break end | |
646 | else | |
647 | stop = i-1 break | |
648 | end | |
649 | end | |
650 | while lines[stop].type == "blank" do stop = stop - 1 end | |
651 | return start, stop | |
652 | end | |
653 | ||
654 | local function process_blockquote(lines) | |
655 | local raw = lines[1].text | |
656 | for i = 2,#lines do | |
657 | raw = raw .. "\n" .. lines[i].text | |
658 | end | |
659 | local bt = block_transform(raw) | |
660 | if not bt:find("<pre>") then bt = indent(bt) end | |
661 | return "<blockquote>\n " .. bt .. | |
662 | "\n</blockquote>" | |
663 | end | |
664 | ||
665 | while true do | |
666 | local start, stop = find_blockquote(lines) | |
667 | if not start then break end | |
668 | local text = process_blockquote(splice(lines, start, stop)) | |
669 | local info = { | |
670 | line = text, | |
671 | type = "raw", | |
672 | html = text | |
673 | } | |
674 | lines = splice(lines, start, stop, {info}) | |
675 | end | |
676 | return lines | |
677 | end | |
678 | ||
679 | -- Find and convert codeblocks. | |
680 | local function codeblocks(lines) | |
681 | local function find_codeblock(lines) | |
682 | local start | |
683 | for i,line in ipairs(lines) do | |
684 | if line.type == "indented" then start = i break end | |
685 | end | |
686 | if not start then return nil end | |
687 | ||
688 | local stop = #lines | |
689 | for i = start+1, #lines do | |
690 | if lines[i].type ~= "indented" and lines[i].type ~= "blank" then | |
691 | stop = i-1 | |
692 | break | |
693 | end | |
694 | end | |
695 | while lines[stop].type == "blank" do stop = stop - 1 end | |
696 | return start, stop | |
697 | end | |
698 | ||
699 | local function process_codeblock(lines) | |
700 | local raw = detab(encode_code(outdent(lines[1].line))) | |
701 | for i = 2,#lines do | |
702 | raw = raw .. "\n" .. detab(encode_code(outdent(lines[i].line))) | |
703 | end | |
704 | return "<pre><code>" .. raw .. "\n</code></pre>" | |
705 | end | |
706 | ||
707 | while true do | |
708 | local start, stop = find_codeblock(lines) | |
709 | if not start then break end | |
710 | local text = process_codeblock(splice(lines, start, stop)) | |
711 | local info = { | |
712 | line = text, | |
713 | type = "raw", | |
714 | html = text | |
715 | } | |
716 | lines = splice(lines, start, stop, {info}) | |
717 | end | |
718 | return lines | |
719 | end | |
720 | ||
721 | -- Convert lines to html code | |
722 | function blocks_to_html(lines, no_paragraphs) | |
723 | local out = {} | |
724 | local i = 1 | |
725 | while i <= #lines do | |
726 | local line = lines[i] | |
727 | if line.type == "ruler" then | |
728 | table.insert(out, "<hr/>") | |
729 | elseif line.type == "raw" then | |
730 | table.insert(out, line.html) | |
731 | elseif line.type == "normal" then | |
732 | local s = line.line | |
733 | ||
734 | while i+1 <= #lines and lines[i+1].type == "normal" do | |
735 | i = i + 1 | |
736 | s = s .. "\n" .. lines[i].line | |
737 | end | |
738 | ||
739 | if no_paragraphs then | |
740 | table.insert(out, span_transform(s)) | |
741 | else | |
742 | table.insert(out, "<p>" .. span_transform(s) .. "</p>") | |
743 | end | |
744 | elseif line.type == "header" then | |
745 | local s = "<h" .. line.level .. ">" .. span_transform(line.text) .. "</h" .. line.level .. ">" | |
746 | table.insert(out, s) | |
747 | else | |
748 | table.insert(out, line.line) | |
749 | end | |
750 | i = i + 1 | |
751 | end | |
752 | return out | |
753 | end | |
754 | ||
755 | -- Perform all the block level transforms | |
756 | function block_transform(text, sublist) | |
757 | local lines = split(text) | |
758 | lines = map(lines, classify) | |
759 | lines = headers(lines) | |
760 | lines = lists(lines, sublist) | |
761 | lines = codeblocks(lines) | |
762 | lines = blockquotes(lines) | |
763 | lines = blocks_to_html(lines) | |
764 | local text = table.concat(lines, "\n") | |
765 | return text | |
766 | end | |
767 | ||
768 | -- Debug function for printing a line array to see the result | |
769 | -- of partial transforms. | |
770 | local function print_lines(lines) | |
771 | for i, line in ipairs(lines) do | |
772 | print(i, line.type, line.text or line.line) | |
773 | end | |
774 | end | |
775 | ||
776 | ---------------------------------------------------------------------- | |
777 | -- Span transform | |
778 | ---------------------------------------------------------------------- | |
779 | ||
780 | -- Functions for transforming the text at the span level. | |
781 | ||
782 | -- These characters may need to be escaped because they have a special | |
783 | -- meaning in markdown. | |
784 | local escape_chars = "'\\`*_{}[]()>#+-.!'" | |
785 | local escape_table = {} | |
786 | ||
787 | local function init_escape_table() | |
788 | escape_table = {} | |
789 | for i = 1,#escape_chars do | |
790 | local c = escape_chars:sub(i,i) | |
791 | escape_table[c] = hash(c) | |
792 | end | |
793 | end | |
794 | ||
795 | -- Adds a new escape to the escape table. | |
796 | local function add_escape(text) | |
797 | if not escape_table[text] then | |
798 | escape_table[text] = hash(text) | |
799 | end | |
800 | return escape_table[text] | |
801 | end | |
802 | ||
803 | -- Escape characters that should not be disturbed by markdown. | |
804 | local function escape_special_chars(text) | |
805 | local tokens = tokenize_html(text) | |
806 | ||
807 | local out = "" | |
808 | for _, token in ipairs(tokens) do | |
809 | local t = token.text | |
810 | if token.type == "tag" then | |
811 | -- In tags, encode * and _ so they don't conflict with their use in markdown. | |
812 | t = t:gsub("%*", escape_table["*"]) | |
813 | t = t:gsub("%_", escape_table["_"]) | |
814 | else | |
815 | t = encode_backslash_escapes(t) | |
816 | end | |
817 | out = out .. t | |
818 | end | |
819 | return out | |
820 | end | |
821 | ||
822 | -- Encode backspace-escaped characters in the markdown source. | |
823 | function encode_backslash_escapes(t) | |
824 | for i=1,escape_chars:len() do | |
825 | local c = escape_chars:sub(i,i) | |
826 | t = t:gsub("\\%" .. c, escape_table[c]) | |
827 | end | |
828 | return t | |
829 | end | |
830 | ||
831 | -- Unescape characters that have been encoded. | |
832 | local function unescape_special_chars(t) | |
833 | local tin = t | |
834 | for k,v in pairs(escape_table) do | |
835 | k = k:gsub("%%", "%%%%") | |
836 | t = t:gsub(v,k) | |
837 | end | |
838 | if t ~= tin then t = unescape_special_chars(t) end | |
839 | return t | |
840 | end | |
841 | ||
842 | -- Encode/escape certain characters inside Markdown code runs. | |
843 | -- The point is that in code, these characters are literals, | |
844 | -- and lose their special Markdown meanings. | |
845 | function encode_code(s) | |
846 | s = s:gsub("%&", "&") | |
847 | s = s:gsub("<", "<") | |
848 | s = s:gsub(">", ">") | |
849 | for k,v in pairs(escape_table) do | |
850 | s = s:gsub("%"..k, v) | |
851 | end | |
852 | return s | |
853 | end | |
854 | ||
855 | -- Handle backtick blocks. | |
856 | local function code_spans(s) | |
857 | s = s:gsub("\\\\", escape_table["\\"]) | |
858 | s = s:gsub("\\`", escape_table["`"]) | |
859 | ||
860 | local pos = 1 | |
861 | while true do | |
862 | local start, stop = s:find("`+", pos) | |
863 | if not start then return s end | |
864 | local count = stop - start + 1 | |
865 | -- Find a matching numbert of backticks | |
866 | local estart, estop = s:find(string.rep("`", count), stop+1) | |
867 | local brstart = s:find("\n", stop+1) | |
868 | if estart and (not brstart or estart < brstart) then | |
869 | local code = s:sub(stop+1, estart-1) | |
870 | code = code:gsub("^[ \t]+", "") | |
871 | code = code:gsub("[ \t]+$", "") | |
872 | code = code:gsub(escape_table["\\"], escape_table["\\"] .. escape_table["\\"]) | |
873 | code = code:gsub(escape_table["`"], escape_table["\\"] .. escape_table["`"]) | |
874 | code = "<code>" .. encode_code(code) .. "</code>" | |
875 | code = add_escape(code) | |
876 | s = s:sub(1, start-1) .. code .. s:sub(estop+1) | |
877 | pos = start + code:len() | |
878 | else | |
879 | pos = stop + 1 | |
880 | end | |
881 | end | |
882 | return s | |
883 | end | |
884 | ||
885 | -- Encode alt text... enodes &, and ". | |
886 | local function encode_alt(s) | |
887 | if not s then return s end | |
888 | s = s:gsub('&', '&') | |
889 | s = s:gsub('"', '"') | |
890 | s = s:gsub('<', '<') | |
891 | return s | |
892 | end | |
893 | ||
894 | local link_database | |
895 | ||
896 | -- Handle image references | |
897 | local function images(text) | |
898 | local function reference_link(alt, id) | |
899 | alt = encode_alt(alt:match("%b[]"):sub(2,-2)) | |
900 | id = id:match("%[(.*)%]"):lower() | |
901 | if id == "" then id = text:lower() end | |
902 | link_database[id] = link_database[id] or {} | |
903 | if not link_database[id].url then return nil end | |
904 | local url = link_database[id].url or id | |
905 | url = encode_alt(url) | |
906 | local title = encode_alt(link_database[id].title) | |
907 | if title then title = " title=\"" .. title .. "\"" else title = "" end | |
908 | return add_escape ('<img src="' .. url .. '" alt="' .. alt .. '"' .. title .. "/>") | |
909 | end | |
910 | ||
911 | local function inline_link(alt, link) | |
912 | alt = encode_alt(alt:match("%b[]"):sub(2,-2)) | |
913 | local url, title = link:match("%(<?(.-)>?[ \t]*['\"](.+)['\"]") | |
914 | url = url or link:match("%(<?(.-)>?%)") | |
915 | url = encode_alt(url) | |
916 | title = encode_alt(title) | |
917 | if title then | |
918 | return add_escape('<img src="' .. url .. '" alt="' .. alt .. '" title="' .. title .. '"/>') | |
919 | else | |
920 | return add_escape('<img src="' .. url .. '" alt="' .. alt .. '"/>') | |
921 | end | |
922 | end | |
923 | ||
924 | text = text:gsub("!(%b[])[ \t]*\n?[ \t]*(%b[])", reference_link) | |
925 | text = text:gsub("!(%b[])(%b())", inline_link) | |
926 | return text | |
927 | end | |
928 | ||
929 | -- Handle anchor references | |
930 | local function anchors(text) | |
931 | local function reference_link(text, id) | |
932 | text = text:match("%b[]"):sub(2,-2) | |
933 | id = id:match("%b[]"):sub(2,-2):lower() | |
934 | if id == "" then id = text:lower() end | |
935 | link_database[id] = link_database[id] or {} | |
936 | if not link_database[id].url then return nil end | |
937 | local url = link_database[id].url or id | |
938 | url = encode_alt(url) | |
939 | local title = encode_alt(link_database[id].title) | |
940 | if title then title = " title=\"" .. title .. "\"" else title = "" end | |
941 | return add_escape("<a href=\"" .. url .. "\"" .. title .. ">") .. text .. add_escape("</a>") | |
942 | end | |
943 | ||
944 | local function inline_link(text, link) | |
945 | text = text:match("%b[]"):sub(2,-2) | |
946 | local url, title = link:match("%(<?(.-)>?[ \t]*['\"](.+)['\"]") | |
947 | title = encode_alt(title) | |
948 | url = url or link:match("%(<?(.-)>?%)") or "" | |
949 | url = encode_alt(url) | |
950 | if title then | |
951 | return add_escape("<a href=\"" .. url .. "\" title=\"" .. title .. "\">") .. text .. "</a>" | |
952 | else | |
953 | return add_escape("<a href=\"" .. url .. "\">") .. text .. add_escape("</a>") | |
954 | end | |
955 | end | |
956 | ||
957 | text = text:gsub("(%b[])[ \t]*\n?[ \t]*(%b[])", reference_link) | |
958 | text = text:gsub("(%b[])(%b())", inline_link) | |
959 | return text | |
960 | end | |
961 | ||
962 | -- Handle auto links, i.e. <http://www.google.com/>. | |
963 | local function auto_links(text) | |
964 | local function link(s) | |
965 | return add_escape("<a href=\"" .. s .. "\">") .. s .. "</a>" | |
966 | end | |
967 | -- Encode chars as a mix of dec and hex entitites to (perhaps) fool | |
968 | -- spambots. | |
969 | local function encode_email_address(s) | |
970 | -- Use a deterministic encoding to make unit testing possible. | |
971 | -- Code 45% hex, 45% dec, 10% plain. | |
972 | local hex = {code = function(c) return "&#x" .. string.format("%x", c:byte()) .. ";" end, count = 1, rate = 0.45} | |
973 | local dec = {code = function(c) return "&#" .. c:byte() .. ";" end, count = 0, rate = 0.45} | |
974 | local plain = {code = function(c) return c end, count = 0, rate = 0.1} | |
975 | local codes = {hex, dec, plain} | |
976 | local function swap(t,k1,k2) local temp = t[k2] t[k2] = t[k1] t[k1] = temp end | |
977 | ||
978 | local out = "" | |
979 | for i = 1,s:len() do | |
980 | for _,code in ipairs(codes) do code.count = code.count + code.rate end | |
981 | if codes[1].count < codes[2].count then swap(codes,1,2) end | |
982 | if codes[2].count < codes[3].count then swap(codes,2,3) end | |
983 | if codes[1].count < codes[2].count then swap(codes,1,2) end | |
984 | ||
985 | local code = codes[1] | |
986 | local c = s:sub(i,i) | |
987 | -- Force encoding of "@" to make email address more invisible. | |
988 | if c == "@" and code == plain then code = codes[2] end | |
989 | out = out .. code.code(c) | |
990 | code.count = code.count - 1 | |
991 | end | |
992 | return out | |
993 | end | |
994 | local function mail(s) | |
995 | s = unescape_special_chars(s) | |
996 | local address = encode_email_address("mailto:" .. s) | |
997 | local text = encode_email_address(s) | |
998 | return add_escape("<a href=\"" .. address .. "\">") .. text .. "</a>" | |
999 | end | |
1000 | -- links | |
1001 | text = text:gsub("<(https?:[^'\">%s]+)>", link) | |
1002 | text = text:gsub("<(ftp:[^'\">%s]+)>", link) | |
1003 | ||
1004 | ||
1005 | text = text:gsub("<mailto:([^'\">%s]+)>", mail) | |
1006 | text = text:gsub("<([-.%w]+%@[-.%w]+)>", mail) | |
1007 | return text | |
1008 | end | |
1009 | ||
1010 | -- Encode free standing amps (&) and angles (<)... note that this does not | |
1011 | -- encode free >. | |
1012 | local function amps_and_angles(s) | |
1013 | -- encode amps not part of &..; expression | |
1014 | local pos = 1 | |
1015 | while true do | |
1016 | local amp = s:find("&", pos) | |
1017 | if not amp then break end | |
1018 | local semi = s:find(";", amp+1) | |
1019 | local stop = s:find("[ \t\n&]", amp+1) | |
1020 | if not semi or (stop and stop < semi) or (semi - amp) > 15 then | |
1021 | s = s:sub(1,amp-1) .. "&" .. s:sub(amp+1) | |
1022 | pos = amp+1 | |
1023 | else | |
1024 | pos = amp+1 | |
1025 | end | |
1026 | end | |
1027 | ||
1028 | -- encode naked <'s | |
1029 | s = s:gsub("<([^a-zA-Z/?$!])", "<%1") | |
1030 | s = s:gsub("<$", "<") | |
1031 | ||
1032 | -- what about >, nothing done in the original markdown source to handle them | |
1033 | return s | |
1034 | end | |
1035 | ||
1036 | -- Handles emphasis markers (* and _) in the text. | |
1037 | local function emphasis(text) | |
1038 | for _, s in ipairs {"%*%*", "%_%_"} do | |
1039 | text = text:gsub(s .. "([^%s][%*%_]?)" .. s, "<strong>%1</strong>") | |
1040 | text = text:gsub(s .. "([^%s][^<>]-[^%s][%*%_]?)" .. s, "<strong>%1</strong>") | |
1041 | end | |
1042 | for _, s in ipairs {"%*", "%_"} do | |
1043 | text = text:gsub(s .. "([^%s_])" .. s, "<em>%1</em>") | |
1044 | text = text:gsub(s .. "(<strong>[^%s_]</strong>)" .. s, "<em>%1</em>") | |
1045 | text = text:gsub(s .. "([^%s_][^<>_]-[^%s_])" .. s, "<em>%1</em>") | |
1046 | text = text:gsub(s .. "([^<>_]-<strong>[^<>_]-</strong>[^<>_]-)" .. s, "<em>%1</em>") | |
1047 | end | |
1048 | return text | |
1049 | end | |
1050 | ||
1051 | -- Handles line break markers in the text. | |
1052 | local function line_breaks(text) | |
1053 | return text:gsub(" +\n", " <br/>\n") | |
1054 | end | |
1055 | ||
1056 | -- Perform all span level transforms. | |
1057 | function span_transform(text) | |
1058 | text = code_spans(text) | |
1059 | text = escape_special_chars(text) | |
1060 | text = images(text) | |
1061 | text = anchors(text) | |
1062 | text = auto_links(text) | |
1063 | text = amps_and_angles(text) | |
1064 | text = emphasis(text) | |
1065 | text = line_breaks(text) | |
1066 | return text | |
1067 | end | |
1068 | ||
1069 | ---------------------------------------------------------------------- | |
1070 | -- Markdown | |
1071 | ---------------------------------------------------------------------- | |
1072 | ||
1073 | -- Cleanup the text by normalizing some possible variations to make further | |
1074 | -- processing easier. | |
1075 | local function cleanup(text) | |
1076 | -- Standardize line endings | |
1077 | text = text:gsub("\r\n", "\n") -- DOS to UNIX | |
1078 | text = text:gsub("\r", "\n") -- Mac to UNIX | |
1079 | ||
1080 | -- Convert all tabs to spaces | |
1081 | text = detab(text) | |
1082 | ||
1083 | -- Strip lines with only spaces and tabs | |
1084 | while true do | |
1085 | local subs | |
1086 | text, subs = text:gsub("\n[ \t]+\n", "\n\n") | |
1087 | if subs == 0 then break end | |
1088 | end | |
1089 | ||
1090 | return "\n" .. text .. "\n" | |
1091 | end | |
1092 | ||
1093 | -- Strips link definitions from the text and stores the data in a lookup table. | |
1094 | local function strip_link_definitions(text) | |
1095 | local linkdb = {} | |
1096 | ||
1097 | local function link_def(id, url, title) | |
1098 | id = id:match("%[(.+)%]"):lower() | |
1099 | linkdb[id] = linkdb[id] or {} | |
1100 | linkdb[id].url = url or linkdb[id].url | |
1101 | linkdb[id].title = title or linkdb[id].title | |
1102 | return "" | |
1103 | end | |
1104 | ||
1105 | local def_no_title = "\n ? ? ?(%b[]):[ \t]*\n?[ \t]*<?([^%s>]+)>?[ \t]*" | |
1106 | local def_title1 = def_no_title .. "[ \t]+\n?[ \t]*[\"'(]([^\n]+)[\"')][ \t]*" | |
1107 | local def_title2 = def_no_title .. "[ \t]*\n[ \t]*[\"'(]([^\n]+)[\"')][ \t]*" | |
1108 | local def_title3 = def_no_title .. "[ \t]*\n?[ \t]+[\"'(]([^\n]+)[\"')][ \t]*" | |
1109 | ||
1110 | text = text:gsub(def_title1, link_def) | |
1111 | text = text:gsub(def_title2, link_def) | |
1112 | text = text:gsub(def_title3, link_def) | |
1113 | text = text:gsub(def_no_title, link_def) | |
1114 | return text, linkdb | |
1115 | end | |
1116 | ||
1117 | link_database = {} | |
1118 | ||
1119 | -- Main markdown processing function | |
1120 | local function markdown(text) | |
1121 | init_hash(text) | |
1122 | init_escape_table() | |
1123 | ||
1124 | text = cleanup(text) | |
1125 | text = protect(text) | |
1126 | text, link_database = strip_link_definitions(text) | |
1127 | text = block_transform(text) | |
1128 | text = unescape_special_chars(text) | |
1129 | return text | |
1130 | end | |
1131 | ||
1132 | ---------------------------------------------------------------------- | |
1133 | -- End of module | |
1134 | ---------------------------------------------------------------------- | |
1135 | ||
1136 | M.lock(M) | |
1137 | ||
1138 | -- Expose markdown function to the world | |
1139 | _G.markdown = M.markdown | |
1140 | ||
1141 | -- Class for parsing command-line options | |
1142 | local OptionParser = {} | |
1143 | OptionParser.__index = OptionParser | |
1144 | ||
1145 | -- Creates a new option parser | |
1146 | function OptionParser:new() | |
1147 | local o = {short = {}, long = {}} | |
1148 | setmetatable(o, self) | |
1149 | return o | |
1150 | end | |
1151 | ||
1152 | -- Calls f() whenever a flag with specified short and long name is encountered | |
1153 | function OptionParser:flag(short, long, f) | |
1154 | local info = {type = "flag", f = f} | |
1155 | if short then self.short[short] = info end | |
1156 | if long then self.long[long] = info end | |
1157 | end | |
1158 | ||
1159 | -- Calls f(param) whenever a parameter flag with specified short and long name is encountered | |
1160 | function OptionParser:param(short, long, f) | |
1161 | local info = {type = "param", f = f} | |
1162 | if short then self.short[short] = info end | |
1163 | if long then self.long[long] = info end | |
1164 | end | |
1165 | ||
1166 | -- Calls f(v) for each non-flag argument | |
1167 | function OptionParser:arg(f) | |
1168 | self.arg = f | |
1169 | end | |
1170 | ||
1171 | -- Runs the option parser for the specified set of arguments. Returns true if all arguments | |
1172 | -- where successfully parsed and false otherwise. | |
1173 | function OptionParser:run(args) | |
1174 | local pos = 1 | |
1175 | local param | |
1176 | while pos <= #args do | |
1177 | local arg = args[pos] | |
1178 | if arg == "--" then | |
1179 | for i=pos+1,#args do | |
1180 | if self.arg then self.arg(args[i]) end | |
1181 | return true | |
1182 | end | |
1183 | end | |
1184 | if arg:match("^%-%-") then | |
1185 | local info = self.long[arg:sub(3)] | |
1186 | if not info then print("Unknown flag: " .. arg) return false end | |
1187 | if info.type == "flag" then | |
1188 | info.f() | |
1189 | pos = pos + 1 | |
1190 | else | |
1191 | param = args[pos+1] | |
1192 | if not param then print("No parameter for flag: " .. arg) return false end | |
1193 | info.f(param) | |
1194 | pos = pos+2 | |
1195 | end | |
1196 | elseif arg:match("^%-") then | |
1197 | for i=2,arg:len() do | |
1198 | local c = arg:sub(i,i) | |
1199 | local info = self.short[c] | |
1200 | if not info then print("Unknown flag: -" .. c) return false end | |
1201 | if info.type == "flag" then | |
1202 | info.f() | |
1203 | else | |
1204 | if i == arg:len() then | |
1205 | param = args[pos+1] | |
1206 | if not param then print("No parameter for flag: -" .. c) return false end | |
1207 | info.f(param) | |
1208 | pos = pos + 1 | |
1209 | else | |
1210 | param = arg:sub(i+1) | |
1211 | info.f(param) | |
1212 | end | |
1213 | break | |
1214 | end | |
1215 | end | |
1216 | pos = pos + 1 | |
1217 | else | |
1218 | if self.arg then self.arg(arg) end | |
1219 | pos = pos + 1 | |
1220 | end | |
1221 | end | |
1222 | return true | |
1223 | end | |
1224 | ||
1225 | -- Handles the case when markdown is run from the command line | |
1226 | local function run_command_line(arg) | |
1227 | -- Generate output for input s given options | |
1228 | local function run(s, options) | |
1229 | s = markdown(s) | |
1230 | if not options.wrap_header then return s end | |
1231 | local header = "" | |
1232 | if options.header then | |
1233 | local f = io.open(options.header) or error("Could not open file: " .. options.header) | |
1234 | header = f:read("*a") | |
1235 | f:close() | |
1236 | else | |
1237 | header = [[ | |
1238 | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> | |
1239 | <html> | |
1240 | <head> | |
1241 | <meta http-equiv="content-type" content="text/html; charset=CHARSET" /> | |
1242 | <title>TITLE</title> | |
1243 | <link rel="stylesheet" type="text/css" href="STYLESHEET" /> | |
1244 | </head> | |
1245 | <body> | |
1246 | ]] | |
1247 | local title = options.title or s:match("<h1>(.-)</h1>") or s:match("<h2>(.-)</h2>") or | |
1248 | s:match("<h3>(.-)</h3>") or "Untitled" | |
1249 | header = header:gsub("TITLE", title) | |
1250 | if options.inline_style then | |
1251 | local style = "" | |
1252 | local f = io.open(options.stylesheet) | |
1253 | if f then | |
1254 | style = f:read("*a") f:close() | |
1255 | else | |
1256 | error("Could not include style sheet " .. options.stylesheet .. ": File not found") | |
1257 | end | |
1258 | header = header:gsub('<link rel="stylesheet" type="text/css" href="STYLESHEET" />', | |
1259 | "<style type=\"text/css\"><!--\n" .. style .. "\n--></style>") | |
1260 | else | |
1261 | header = header:gsub("STYLESHEET", options.stylesheet) | |
1262 | end | |
1263 | header = header:gsub("CHARSET", options.charset) | |
1264 | end | |
1265 | local footer = "</body></html>" | |
1266 | if options.footer then | |
1267 | local f = io.open(options.footer) or error("Could not open file: " .. options.footer) | |
1268 | footer = f:read("*a") | |
1269 | f:close() | |
1270 | end | |
1271 | return header .. s .. footer | |
1272 | end | |
1273 | ||
1274 | -- Generate output path name from input path name given options. | |
1275 | local function outpath(path, options) | |
1276 | if options.append then return path .. ".html" end | |
1277 | local m = path:match("^(.+%.html)[^/\\]+$") if m then return m end | |
1278 | m = path:match("^(.+%.)[^/\\]*$") if m and path ~= m .. "html" then return m .. "html" end | |
1279 | return path .. ".html" | |
1280 | end | |
1281 | ||
1282 | -- Default commandline options | |
1283 | local options = { | |
1284 | wrap_header = true, | |
1285 | header = nil, | |
1286 | footer = nil, | |
1287 | charset = "utf-8", | |
1288 | title = nil, | |
1289 | stylesheet = "default.css", | |
1290 | inline_style = false | |
1291 | } | |
1292 | local help = [[ | |
1293 | Usage: markdown.lua [OPTION] [FILE] | |
1294 | Runs the markdown text markup to HTML converter on each file specified on the | |
1295 | command line. If no files are specified, runs on standard input. | |
1296 | ||
1297 | No header: | |
1298 | -n, --no-wrap Don't wrap the output in <html>... tags. | |
1299 | Custom header: | |
1300 | -e, --header FILE Use content of FILE for header. | |
1301 | -f, --footer FILE Use content of FILE for footer. | |
1302 | Generated header: | |
1303 | -c, --charset SET Specifies charset (default utf-8). | |
1304 | -i, --title TITLE Specifies title (default from first <h1> tag). | |
1305 | -s, --style STYLE Specifies style sheet file (default default.css). | |
1306 | -l, --inline-style Include the style sheet file inline in the header. | |
1307 | Generated files: | |
1308 | -a, --append Append .html extension (instead of replacing). | |
1309 | Other options: | |
1310 | -h, --help Print this help text. | |
1311 | -t, --test Run the unit tests. | |
1312 | ]] | |
1313 | ||
1314 | local run_stdin = true | |
1315 | local op = OptionParser:new() | |
1316 | op:flag("n", "no-wrap", function () options.wrap_header = false end) | |
1317 | op:param("e", "header", function (x) options.header = x end) | |
1318 | op:param("f", "footer", function (x) options.footer = x end) | |
1319 | op:param("c", "charset", function (x) options.charset = x end) | |
1320 | op:param("i", "title", function(x) options.title = x end) | |
1321 | op:param("s", "style", function(x) options.stylesheet = x end) | |
1322 | op:flag("l", "inline-style", function(x) options.inline_style = true end) | |
1323 | op:flag("a", "append", function() options.append = true end) | |
1324 | op:flag("t", "test", function() | |
1325 | local n = arg[0]:gsub("markdown.lua", "markdown-tests.lua") | |
1326 | local f = io.open(n) | |
1327 | if f then | |
1328 | f:close() dofile(n) | |
1329 | else | |
1330 | error("Cannot find markdown-tests.lua") | |
1331 | end | |
1332 | run_stdin = false | |
1333 | end) | |
1334 | op:flag("h", "help", function() print(help) run_stdin = false end) | |
1335 | op:arg(function(path) | |
1336 | local file = io.open(path) or error("Could not open file: " .. path) | |
1337 | local s = file:read("*a") | |
1338 | file:close() | |
1339 | s = run(s, options) | |
1340 | file = io.open(outpath(path, options), "w") or error("Could not open output file: " .. outpath(path, options)) | |
1341 | file:write(s) | |
1342 | file:close() | |
1343 | run_stdin = false | |
1344 | end | |
1345 | ) | |
1346 | ||
1347 | if not op:run(arg) then | |
1348 | print(help) | |
1349 | run_stdin = false | |
1350 | end | |
1351 | ||
1352 | if run_stdin then | |
1353 | local s = io.read("*a") | |
1354 | s = run(s, options) | |
1355 | io.write(s) | |
1356 | end | |
1357 | end | |
1358 | ||
1359 | -- If we are being run from the command-line, act accordingly | |
1360 | if arg and arg[0]:find("markdown%.lua$") then | |
1361 | run_command_line(arg) | |
1362 | else | |
1363 | return markdown | |
1364 | end | |
0 | #!/usr/bin/env lua | |
1 | ||
2 | --[[ | |
3 | # markdown.lua -- version 0.32 | |
4 | ||
5 | <http://www.frykholm.se/files/markdown.lua> | |
6 | ||
7 | **Author:** Niklas Frykholm, <niklas@frykholm.se> | |
8 | **Date:** 31 May 2008 | |
9 | ||
10 | This is an implementation of the popular text markup language Markdown in pure Lua. | |
11 | Markdown can convert documents written in a simple and easy to read text format | |
12 | to well-formatted HTML. For a more thourough description of Markdown and the Markdown | |
13 | syntax, see <http://daringfireball.net/projects/markdown>. | |
14 | ||
15 | The original Markdown source is written in Perl and makes heavy use of advanced | |
16 | regular expression techniques (such as negative look-ahead, etc) which are not available | |
17 | in Lua's simple regex engine. Therefore this Lua port has been rewritten from the ground | |
18 | up. It is probably not completely bug free. If you notice any bugs, please report them to | |
19 | me. A unit test that exposes the error is helpful. | |
20 | ||
21 | ## Usage | |
22 | ||
23 | require "markdown" | |
24 | markdown(source) | |
25 | ||
26 | ``markdown.lua`` exposes a single global function named ``markdown(s)`` which applies the | |
27 | Markdown transformation to the specified string. | |
28 | ||
29 | ``markdown.lua`` can also be used directly from the command line: | |
30 | ||
31 | lua markdown.lua test.md | |
32 | ||
33 | Creates a file ``test.html`` with the converted content of ``test.md``. Run: | |
34 | ||
35 | lua markdown.lua -h | |
36 | ||
37 | For a description of the command-line options. | |
38 | ||
39 | ``markdown.lua`` uses the same license as Lua, the MIT license. | |
40 | ||
41 | ## License | |
42 | ||
43 | Copyright © 2008 Niklas Frykholm. | |
44 | ||
45 | Permission is hereby granted, free of charge, to any person obtaining a copy of this | |
46 | software and associated documentation files (the "Software"), to deal in the Software | |
47 | without restriction, including without limitation the rights to use, copy, modify, merge, | |
48 | publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons | |
49 | to whom the Software is furnished to do so, subject to the following conditions: | |
50 | ||
51 | The above copyright notice and this permission notice shall be included in all copies | |
52 | or substantial portions of the Software. | |
53 | ||
54 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
55 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
56 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
57 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
58 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
59 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
60 | THE SOFTWARE. | |
61 | ||
62 | ## Version history | |
63 | ||
64 | - **0.32** -- 31 May 2008 | |
65 | - Fix for links containing brackets | |
66 | - **0.31** -- 1 Mar 2008 | |
67 | - Fix for link definitions followed by spaces | |
68 | - **0.30** -- 25 Feb 2008 | |
69 | - Consistent behavior with Markdown when the same link reference is reused | |
70 | - **0.29** -- 24 Feb 2008 | |
71 | - Fix for <pre> blocks with spaces in them | |
72 | - **0.28** -- 18 Feb 2008 | |
73 | - Fix for link encoding | |
74 | - **0.27** -- 14 Feb 2008 | |
75 | - Fix for link database links with () | |
76 | - **0.26** -- 06 Feb 2008 | |
77 | - Fix for nested italic and bold markers | |
78 | - **0.25** -- 24 Jan 2008 | |
79 | - Fix for encoding of naked < | |
80 | - **0.24** -- 21 Jan 2008 | |
81 | - Fix for link behavior. | |
82 | - **0.23** -- 10 Jan 2008 | |
83 | - Fix for a regression bug in longer expressions in italic or bold. | |
84 | - **0.22** -- 27 Dec 2007 | |
85 | - Fix for crash when processing blocks with a percent sign in them. | |
86 | - **0.21** -- 27 Dec 2007 | |
87 | - Fix for combined strong and emphasis tags | |
88 | - **0.20** -- 13 Oct 2007 | |
89 | - Fix for < as well in image titles, now matches Dingus behavior | |
90 | - **0.19** -- 28 Sep 2007 | |
91 | - Fix for quotation marks " and ampersands & in link and image titles. | |
92 | - **0.18** -- 28 Jul 2007 | |
93 | - Does not crash on unmatched tags (behaves like standard markdown) | |
94 | - **0.17** -- 12 Apr 2007 | |
95 | - Fix for links with %20 in them. | |
96 | - **0.16** -- 12 Apr 2007 | |
97 | - Do not require arg global to exist. | |
98 | - **0.15** -- 28 Aug 2006 | |
99 | - Better handling of links with underscores in them. | |
100 | - **0.14** -- 22 Aug 2006 | |
101 | - Bug for *`foo()`* | |
102 | - **0.13** -- 12 Aug 2006 | |
103 | - Added -l option for including stylesheet inline in document. | |
104 | - Fixed bug in -s flag. | |
105 | - Fixed emphasis bug. | |
106 | - **0.12** -- 15 May 2006 | |
107 | - Fixed several bugs to comply with MarkdownTest 1.0 <http://six.pairlist.net/pipermail/markdown-discuss/2004-December/000909.html> | |
108 | - **0.11** -- 12 May 2006 | |
109 | - Fixed bug for escaping `*` and `_` inside code spans. | |
110 | - Added license terms. | |
111 | - Changed join() to table.concat(). | |
112 | - **0.10** -- 3 May 2006 | |
113 | - Initial public release. | |
114 | ||
115 | // Niklas | |
116 | ]] | |
117 | ||
118 | ||
119 | -- Set up a table for holding local functions to avoid polluting the global namespace | |
120 | local M = {} | |
121 | local unpack = unpack or table.unpack | |
122 | local MT = {__index = _G} | |
123 | setmetatable(M, MT) | |
124 | ||
125 | ---------------------------------------------------------------------- | |
126 | -- Utility functions | |
127 | ---------------------------------------------------------------------- | |
128 | ||
129 | -- Locks table t from changes, writes an error if someone attempts to change the table. | |
130 | -- This is useful for detecting variables that have "accidently" been made global. Something | |
131 | -- I tend to do all too much. | |
132 | function M.lock(t) | |
133 | local function lock_new_index(t, k, v) | |
134 | error("module has been locked -- " .. k .. " must be declared local", 2) | |
135 | end | |
136 | ||
137 | local mt = {__newindex = lock_new_index} | |
138 | if getmetatable(t) then | |
139 | mt.__index = getmetatable(t).__index | |
140 | end | |
141 | setmetatable(t, mt) | |
142 | end | |
143 | ||
144 | -- Returns the result of mapping the values in table t through the function f | |
145 | local function map(t, f) | |
146 | local out = {} | |
147 | for k,v in pairs(t) do out[k] = f(v,k) end | |
148 | return out | |
149 | end | |
150 | ||
151 | -- The identity function, useful as a placeholder. | |
152 | local function identity(text) return text end | |
153 | ||
154 | -- Functional style if statement. (NOTE: no short circuit evaluation) | |
155 | local function iff(t, a, b) if t then return a else return b end end | |
156 | ||
157 | -- Splits the text into an array of separate lines. | |
158 | local function split(text, sep) | |
159 | sep = sep or "\n" | |
160 | local lines = {} | |
161 | local pos = 1 | |
162 | while true do | |
163 | local b,e = text:find(sep, pos) | |
164 | if not b then table.insert(lines, text:sub(pos)) break end | |
165 | table.insert(lines, text:sub(pos, b-1)) | |
166 | pos = e + 1 | |
167 | end | |
168 | return lines | |
169 | end | |
170 | ||
171 | -- Converts tabs to spaces | |
172 | local function detab(text) | |
173 | local tab_width = 4 | |
174 | local function rep(match) | |
175 | local spaces = -match:len() | |
176 | while spaces<1 do spaces = spaces + tab_width end | |
177 | return match .. string.rep(" ", spaces) | |
178 | end | |
179 | text = text:gsub("([^\n]-)\t", rep) | |
180 | return text | |
181 | end | |
182 | ||
183 | -- Applies string.find for every pattern in the list and returns the first match | |
184 | local function find_first(s, patterns, index) | |
185 | local res = {} | |
186 | for _,p in ipairs(patterns) do | |
187 | local match = {s:find(p, index)} | |
188 | if #match>0 and (#res==0 or match[1] < res[1]) then res = match end | |
189 | end | |
190 | return unpack(res) | |
191 | end | |
192 | ||
193 | -- If a replacement array is specified, the range [start, stop] in the array is replaced | |
194 | -- with the replacement array and the resulting array is returned. Without a replacement | |
195 | -- array the section of the array between start and stop is returned. | |
196 | local function splice(array, start, stop, replacement) | |
197 | if replacement then | |
198 | local n = stop - start + 1 | |
199 | while n > 0 do | |
200 | table.remove(array, start) | |
201 | n = n - 1 | |
202 | end | |
203 | for i,v in ipairs(replacement) do | |
204 | table.insert(array, start, v) | |
205 | end | |
206 | return array | |
207 | else | |
208 | local res = {} | |
209 | for i = start,stop do | |
210 | table.insert(res, array[i]) | |
211 | end | |
212 | return res | |
213 | end | |
214 | end | |
215 | ||
216 | -- Outdents the text one step. | |
217 | local function outdent(text) | |
218 | text = "\n" .. text | |
219 | text = text:gsub("\n ? ? ?", "\n") | |
220 | text = text:sub(2) | |
221 | return text | |
222 | end | |
223 | ||
224 | -- Indents the text one step. | |
225 | local function indent(text) | |
226 | text = text:gsub("\n", "\n ") | |
227 | return text | |
228 | end | |
229 | ||
230 | -- Does a simple tokenization of html data. Returns the data as a list of tokens. | |
231 | -- Each token is a table with a type field (which is either "tag" or "text") and | |
232 | -- a text field (which contains the original token data). | |
233 | local function tokenize_html(html) | |
234 | local tokens = {} | |
235 | local pos = 1 | |
236 | while true do | |
237 | local start = find_first(html, {"<!%-%-", "<[a-z/!$]", "<%?"}, pos) | |
238 | if not start then | |
239 | table.insert(tokens, {type="text", text=html:sub(pos)}) | |
240 | break | |
241 | end | |
242 | if start ~= pos then table.insert(tokens, {type="text", text = html:sub(pos, start-1)}) end | |
243 | ||
244 | local _, stop | |
245 | if html:match("^<!%-%-", start) then | |
246 | _,stop = html:find("%-%->", start) | |
247 | elseif html:match("^<%?", start) then | |
248 | _,stop = html:find("?>", start) | |
249 | else | |
250 | _,stop = html:find("%b<>", start) | |
251 | end | |
252 | if not stop then | |
253 | -- error("Could not match html tag " .. html:sub(start,start+30)) | |
254 | table.insert(tokens, {type="text", text=html:sub(start, start)}) | |
255 | pos = start + 1 | |
256 | else | |
257 | table.insert(tokens, {type="tag", text=html:sub(start, stop)}) | |
258 | pos = stop + 1 | |
259 | end | |
260 | end | |
261 | return tokens | |
262 | end | |
263 | ||
264 | ---------------------------------------------------------------------- | |
265 | -- Hash | |
266 | ---------------------------------------------------------------------- | |
267 | ||
268 | -- This is used to "hash" data into alphanumeric strings that are unique | |
269 | -- in the document. (Note that this is not cryptographic hash, the hash | |
270 | -- function is not one-way.) The hash procedure is used to protect parts | |
271 | -- of the document from further processing. | |
272 | ||
273 | local HASH = { | |
274 | -- Has the hash been inited. | |
275 | inited = false, | |
276 | ||
277 | -- The unique string prepended to all hash values. This is to ensure | |
278 | -- that hash values do not accidently coincide with an actual existing | |
279 | -- string in the document. | |
280 | identifier = "", | |
281 | ||
282 | -- Counter that counts up for each new hash instance. | |
283 | counter = 0, | |
284 | ||
285 | -- Hash table. | |
286 | table = {} | |
287 | } | |
288 | ||
289 | -- Inits hashing. Creates a hash_identifier that doesn't occur anywhere | |
290 | -- in the text. | |
291 | local function init_hash(text) | |
292 | HASH.inited = true | |
293 | HASH.identifier = "" | |
294 | HASH.counter = 0 | |
295 | HASH.table = {} | |
296 | ||
297 | local s = "HASH" | |
298 | local counter = 0 | |
299 | local id | |
300 | while true do | |
301 | id = s .. counter | |
302 | if not text:find(id, 1, true) then break end | |
303 | counter = counter + 1 | |
304 | end | |
305 | HASH.identifier = id | |
306 | end | |
307 | ||
308 | -- Returns the hashed value for s. | |
309 | local function hash(s) | |
310 | assert(HASH.inited) | |
311 | if not HASH.table[s] then | |
312 | HASH.counter = HASH.counter + 1 | |
313 | local id = HASH.identifier .. HASH.counter .. "X" | |
314 | HASH.table[s] = id | |
315 | end | |
316 | return HASH.table[s] | |
317 | end | |
318 | ||
319 | ---------------------------------------------------------------------- | |
320 | -- Protection | |
321 | ---------------------------------------------------------------------- | |
322 | ||
323 | -- The protection module is used to "protect" parts of a document | |
324 | -- so that they are not modified by subsequent processing steps. | |
325 | -- Protected parts are saved in a table for later unprotection | |
326 | ||
327 | -- Protection data | |
328 | local PD = { | |
329 | -- Saved blocks that have been converted | |
330 | blocks = {}, | |
331 | ||
332 | -- Block level tags that will be protected | |
333 | tags = {"p", "div", "h1", "h2", "h3", "h4", "h5", "h6", "blockquote", | |
334 | "pre", "table", "dl", "ol", "ul", "script", "noscript", "form", "fieldset", | |
335 | "iframe", "math", "ins", "del"} | |
336 | } | |
337 | ||
338 | -- Pattern for matching a block tag that begins and ends in the leftmost | |
339 | -- column and may contain indented subtags, i.e. | |
340 | -- <div> | |
341 | -- A nested block. | |
342 | -- <div> | |
343 | -- Nested data. | |
344 | -- </div> | |
345 | -- </div> | |
346 | local function block_pattern(tag) | |
347 | return "\n<" .. tag .. ".-\n</" .. tag .. ">[ \t]*\n" | |
348 | end | |
349 | ||
350 | -- Pattern for matching a block tag that begins and ends with a newline | |
351 | local function line_pattern(tag) | |
352 | return "\n<" .. tag .. ".-</" .. tag .. ">[ \t]*\n" | |
353 | end | |
354 | ||
355 | -- Protects the range of characters from start to stop in the text and | |
356 | -- returns the protected string. | |
357 | local function protect_range(text, start, stop) | |
358 | local s = text:sub(start, stop) | |
359 | local h = hash(s) | |
360 | PD.blocks[h] = s | |
361 | text = text:sub(1,start) .. h .. text:sub(stop) | |
362 | return text | |
363 | end | |
364 | ||
365 | -- Protect every part of the text that matches any of the patterns. The first | |
366 | -- matching pattern is protected first, etc. | |
367 | local function protect_matches(text, patterns) | |
368 | while true do | |
369 | local start, stop = find_first(text, patterns) | |
370 | if not start then break end | |
371 | text = protect_range(text, start, stop) | |
372 | end | |
373 | return text | |
374 | end | |
375 | ||
376 | -- Protects blocklevel tags in the specified text | |
377 | local function protect(text) | |
378 | -- First protect potentially nested block tags | |
379 | text = protect_matches(text, map(PD.tags, block_pattern)) | |
380 | -- Then protect block tags at the line level. | |
381 | text = protect_matches(text, map(PD.tags, line_pattern)) | |
382 | -- Protect <hr> and comment tags | |
383 | text = protect_matches(text, {"\n<hr[^>]->[ \t]*\n"}) | |
384 | text = protect_matches(text, {"\n<!%-%-.-%-%->[ \t]*\n"}) | |
385 | return text | |
386 | end | |
387 | ||
388 | -- Returns true if the string s is a hash resulting from protection | |
389 | local function is_protected(s) | |
390 | return PD.blocks[s] | |
391 | end | |
392 | ||
393 | -- Unprotects the specified text by expanding all the nonces | |
394 | local function unprotect(text) | |
395 | for k,v in pairs(PD.blocks) do | |
396 | v = v:gsub("%%", "%%%%") | |
397 | text = text:gsub(k, v) | |
398 | end | |
399 | return text | |
400 | end | |
401 | ||
402 | ||
403 | ---------------------------------------------------------------------- | |
404 | -- Block transform | |
405 | ---------------------------------------------------------------------- | |
406 | ||
407 | -- The block transform functions transform the text on the block level. | |
408 | -- They work with the text as an array of lines rather than as individual | |
409 | -- characters. | |
410 | ||
411 | -- Returns true if the line is a ruler of (char) characters. | |
412 | -- The line must contain at least three char characters and contain only spaces and | |
413 | -- char characters. | |
414 | local function is_ruler_of(line, char) | |
415 | if not line:match("^[ %" .. char .. "]*$") then return false end | |
416 | if not line:match("%" .. char .. ".*%" .. char .. ".*%" .. char) then return false end | |
417 | return true | |
418 | end | |
419 | ||
420 | -- Identifies the block level formatting present in the line | |
421 | local function classify(line) | |
422 | local info = {line = line, text = line} | |
423 | ||
424 | if line:match("^ ") then | |
425 | info.type = "indented" | |
426 | info.outdented = line:sub(5) | |
427 | return info | |
428 | end | |
429 | ||
430 | for _,c in ipairs({'*', '-', '_', '='}) do | |
431 | if is_ruler_of(line, c) then | |
432 | info.type = "ruler" | |
433 | info.ruler_char = c | |
434 | return info | |
435 | end | |
436 | end | |
437 | ||
438 | if line == "" then | |
439 | info.type = "blank" | |
440 | return info | |
441 | end | |
442 | ||
443 | if line:match("^(#+)[ \t]*(.-)[ \t]*#*[ \t]*$") then | |
444 | local m1, m2 = line:match("^(#+)[ \t]*(.-)[ \t]*#*[ \t]*$") | |
445 | info.type = "header" | |
446 | info.level = m1:len() | |
447 | info.text = m2 | |
448 | return info | |
449 | end | |
450 | ||
451 | if line:match("^ ? ? ?(%d+)%.[ \t]+(.+)") then | |
452 | local number, text = line:match("^ ? ? ?(%d+)%.[ \t]+(.+)") | |
453 | info.type = "list_item" | |
454 | info.list_type = "numeric" | |
455 | info.number = 0 + number | |
456 | info.text = text | |
457 | return info | |
458 | end | |
459 | ||
460 | if line:match("^ ? ? ?([%*%+%-])[ \t]+(.+)") then | |
461 | local bullet, text = line:match("^ ? ? ?([%*%+%-])[ \t]+(.+)") | |
462 | info.type = "list_item" | |
463 | info.list_type = "bullet" | |
464 | info.bullet = bullet | |
465 | info.text= text | |
466 | return info | |
467 | end | |
468 | ||
469 | if line:match("^>[ \t]?(.*)") then | |
470 | info.type = "blockquote" | |
471 | info.text = line:match("^>[ \t]?(.*)") | |
472 | return info | |
473 | end | |
474 | ||
475 | if is_protected(line) then | |
476 | info.type = "raw" | |
477 | info.html = unprotect(line) | |
478 | return info | |
479 | end | |
480 | ||
481 | info.type = "normal" | |
482 | return info | |
483 | end | |
484 | ||
485 | -- Find headers constisting of a normal line followed by a ruler and converts them to | |
486 | -- header entries. | |
487 | local function headers(array) | |
488 | local i = 1 | |
489 | while i <= #array - 1 do | |
490 | if array[i].type == "normal" and array[i+1].type == "ruler" and | |
491 | (array[i+1].ruler_char == "-" or array[i+1].ruler_char == "=") then | |
492 | local info = {line = array[i].line} | |
493 | info.text = info.line | |
494 | info.type = "header" | |
495 | info.level = iff(array[i+1].ruler_char == "=", 1, 2) | |
496 | table.remove(array, i+1) | |
497 | array[i] = info | |
498 | end | |
499 | i = i + 1 | |
500 | end | |
501 | return array | |
502 | end | |
503 | ||
504 | local block_transform, blocks_to_html, encode_code, span_transform, encode_backslash_escapes | |
505 | ||
506 | -- Find list blocks and convert them to protected data blocks | |
507 | local function lists(array, sublist) | |
508 | local function process_list(arr) | |
509 | local function any_blanks(arr) | |
510 | for i = 1, #arr do | |
511 | if arr[i].type == "blank" then return true end | |
512 | end | |
513 | return false | |
514 | end | |
515 | ||
516 | local function split_list_items(arr) | |
517 | local acc = {arr[1]} | |
518 | local res = {} | |
519 | for i=2,#arr do | |
520 | if arr[i].type == "list_item" then | |
521 | table.insert(res, acc) | |
522 | acc = {arr[i]} | |
523 | else | |
524 | table.insert(acc, arr[i]) | |
525 | end | |
526 | end | |
527 | table.insert(res, acc) | |
528 | return res | |
529 | end | |
530 | ||
531 | local function process_list_item(lines, block) | |
532 | while lines[#lines].type == "blank" do | |
533 | table.remove(lines) | |
534 | end | |
535 | ||
536 | local itemtext = lines[1].text | |
537 | for i=2,#lines do | |
538 | itemtext = itemtext .. "\n" .. outdent(lines[i].line) | |
539 | end | |
540 | if block then | |
541 | itemtext = block_transform(itemtext, true) | |
542 | if not itemtext:find("<pre>") then itemtext = indent(itemtext) end | |
543 | return " <li>" .. itemtext .. "</li>" | |
544 | else | |
545 | local lines = split(itemtext) | |
546 | lines = map(lines, classify) | |
547 | lines = lists(lines, true) | |
548 | lines = blocks_to_html(lines, true) | |
549 | itemtext = table.concat(lines, "\n") | |
550 | if not itemtext:find("<pre>") then itemtext = indent(itemtext) end | |
551 | return " <li>" .. itemtext .. "</li>" | |
552 | end | |
553 | end | |
554 | ||
555 | local block_list = any_blanks(arr) | |
556 | local items = split_list_items(arr) | |
557 | local out = "" | |
558 | for _, item in ipairs(items) do | |
559 | out = out .. process_list_item(item, block_list) .. "\n" | |
560 | end | |
561 | if arr[1].list_type == "numeric" then | |
562 | return "<ol>\n" .. out .. "</ol>" | |
563 | else | |
564 | return "<ul>\n" .. out .. "</ul>" | |
565 | end | |
566 | end | |
567 | ||
568 | -- Finds the range of lines composing the first list in the array. A list | |
569 | -- starts with (^ list_item) or (blank list_item) and ends with | |
570 | -- (blank* $) or (blank normal). | |
571 | -- | |
572 | -- A sublist can start with just (list_item) does not need a blank... | |
573 | local function find_list(array, sublist) | |
574 | local function find_list_start(array, sublist) | |
575 | if array[1].type == "list_item" then return 1 end | |
576 | if sublist then | |
577 | for i = 1,#array do | |
578 | if array[i].type == "list_item" then return i end | |
579 | end | |
580 | else | |
581 | for i = 1, #array-1 do | |
582 | if array[i].type == "blank" and array[i+1].type == "list_item" then | |
583 | return i+1 | |
584 | end | |
585 | end | |
586 | end | |
587 | return nil | |
588 | end | |
589 | local function find_list_end(array, start) | |
590 | local pos = #array | |
591 | for i = start, #array-1 do | |
592 | if array[i].type == "blank" and array[i+1].type ~= "list_item" | |
593 | and array[i+1].type ~= "indented" and array[i+1].type ~= "blank" then | |
594 | pos = i-1 | |
595 | break | |
596 | end | |
597 | end | |
598 | while pos > start and array[pos].type == "blank" do | |
599 | pos = pos - 1 | |
600 | end | |
601 | return pos | |
602 | end | |
603 | ||
604 | local start = find_list_start(array, sublist) | |
605 | if not start then return nil end | |
606 | return start, find_list_end(array, start) | |
607 | end | |
608 | ||
609 | while true do | |
610 | local start, stop = find_list(array, sublist) | |
611 | if not start then break end | |
612 | local text = process_list(splice(array, start, stop)) | |
613 | local info = { | |
614 | line = text, | |
615 | type = "raw", | |
616 | html = text | |
617 | } | |
618 | array = splice(array, start, stop, {info}) | |
619 | end | |
620 | ||
621 | -- Convert any remaining list items to normal | |
622 | for _,line in ipairs(array) do | |
623 | if line.type == "list_item" then line.type = "normal" end | |
624 | end | |
625 | ||
626 | return array | |
627 | end | |
628 | ||
629 | -- Find and convert blockquote markers. | |
630 | local function blockquotes(lines) | |
631 | local function find_blockquote(lines) | |
632 | local start | |
633 | for i,line in ipairs(lines) do | |
634 | if line.type == "blockquote" then | |
635 | start = i | |
636 | break | |
637 | end | |
638 | end | |
639 | if not start then return nil end | |
640 | ||
641 | local stop = #lines | |
642 | for i = start+1, #lines do | |
643 | if lines[i].type == "blank" or lines[i].type == "blockquote" then | |
644 | elseif lines[i].type == "normal" then | |
645 | if lines[i-1].type == "blank" then stop = i-1 break end | |
646 | else | |
647 | stop = i-1 break | |
648 | end | |
649 | end | |
650 | while lines[stop].type == "blank" do stop = stop - 1 end | |
651 | return start, stop | |
652 | end | |
653 | ||
654 | local function process_blockquote(lines) | |
655 | local raw = lines[1].text | |
656 | for i = 2,#lines do | |
657 | raw = raw .. "\n" .. lines[i].text | |
658 | end | |
659 | local bt = block_transform(raw) | |
660 | if not bt:find("<pre>") then bt = indent(bt) end | |
661 | return "<blockquote>\n " .. bt .. | |
662 | "\n</blockquote>" | |
663 | end | |
664 | ||
665 | while true do | |
666 | local start, stop = find_blockquote(lines) | |
667 | if not start then break end | |
668 | local text = process_blockquote(splice(lines, start, stop)) | |
669 | local info = { | |
670 | line = text, | |
671 | type = "raw", | |
672 | html = text | |
673 | } | |
674 | lines = splice(lines, start, stop, {info}) | |
675 | end | |
676 | return lines | |
677 | end | |
678 | ||
679 | -- Find and convert codeblocks. | |
680 | local function codeblocks(lines) | |
681 | local function find_codeblock(lines) | |
682 | local start | |
683 | for i,line in ipairs(lines) do | |
684 | if line.type == "indented" then start = i break end | |
685 | end | |
686 | if not start then return nil end | |
687 | ||
688 | local stop = #lines | |
689 | for i = start+1, #lines do | |
690 | if lines[i].type ~= "indented" and lines[i].type ~= "blank" then | |
691 | stop = i-1 | |
692 | break | |
693 | end | |
694 | end | |
695 | while lines[stop].type == "blank" do stop = stop - 1 end | |
696 | return start, stop | |
697 | end | |
698 | ||
699 | local function process_codeblock(lines) | |
700 | local raw = detab(encode_code(outdent(lines[1].line))) | |
701 | for i = 2,#lines do | |
702 | raw = raw .. "\n" .. detab(encode_code(outdent(lines[i].line))) | |
703 | end | |
704 | return "<pre><code>" .. raw .. "\n</code></pre>" | |
705 | end | |
706 | ||
707 | while true do | |
708 | local start, stop = find_codeblock(lines) | |
709 | if not start then break end | |
710 | local text = process_codeblock(splice(lines, start, stop)) | |
711 | local info = { | |
712 | line = text, | |
713 | type = "raw", | |
714 | html = text | |
715 | } | |
716 | lines = splice(lines, start, stop, {info}) | |
717 | end | |
718 | return lines | |
719 | end | |
720 | ||
721 | -- Convert lines to html code | |
722 | function blocks_to_html(lines, no_paragraphs) | |
723 | local out = {} | |
724 | local i = 1 | |
725 | while i <= #lines do | |
726 | local line = lines[i] | |
727 | if line.type == "ruler" then | |
728 | table.insert(out, "<hr/>") | |
729 | elseif line.type == "raw" then | |
730 | table.insert(out, line.html) | |
731 | elseif line.type == "normal" then | |
732 | local s = line.line | |
733 | ||
734 | while i+1 <= #lines and lines[i+1].type == "normal" do | |
735 | i = i + 1 | |
736 | s = s .. "\n" .. lines[i].line | |
737 | end | |
738 | ||
739 | if no_paragraphs then | |
740 | table.insert(out, span_transform(s)) | |
741 | else | |
742 | table.insert(out, "<p>" .. span_transform(s) .. "</p>") | |
743 | end | |
744 | elseif line.type == "header" then | |
745 | local s = "<h" .. line.level .. ">" .. span_transform(line.text) .. "</h" .. line.level .. ">" | |
746 | table.insert(out, s) | |
747 | else | |
748 | table.insert(out, line.line) | |
749 | end | |
750 | i = i + 1 | |
751 | end | |
752 | return out | |
753 | end | |
754 | ||
755 | -- Perform all the block level transforms | |
756 | function block_transform(text, sublist) | |
757 | local lines = split(text) | |
758 | lines = map(lines, classify) | |
759 | lines = headers(lines) | |
760 | lines = lists(lines, sublist) | |
761 | lines = codeblocks(lines) | |
762 | lines = blockquotes(lines) | |
763 | lines = blocks_to_html(lines) | |
764 | local text = table.concat(lines, "\n") | |
765 | return text | |
766 | end | |
767 | ||
768 | -- Debug function for printing a line array to see the result | |
769 | -- of partial transforms. | |
770 | local function print_lines(lines) | |
771 | for i, line in ipairs(lines) do | |
772 | print(i, line.type, line.text or line.line) | |
773 | end | |
774 | end | |
775 | ||
776 | ---------------------------------------------------------------------- | |
777 | -- Span transform | |
778 | ---------------------------------------------------------------------- | |
779 | ||
780 | -- Functions for transforming the text at the span level. | |
781 | ||
782 | -- These characters may need to be escaped because they have a special | |
783 | -- meaning in markdown. | |
784 | local escape_chars = "'\\`*_{}[]()>#+-.!'" | |
785 | local escape_table = {} | |
786 | ||
787 | local function init_escape_table() | |
788 | escape_table = {} | |
789 | for i = 1,#escape_chars do | |
790 | local c = escape_chars:sub(i,i) | |
791 | escape_table[c] = hash(c) | |
792 | end | |
793 | end | |
794 | ||
795 | -- Adds a new escape to the escape table. | |
796 | local function add_escape(text) | |
797 | if not escape_table[text] then | |
798 | escape_table[text] = hash(text) | |
799 | end | |
800 | return escape_table[text] | |
801 | end | |
802 | ||
803 | -- Escape characters that should not be disturbed by markdown. | |
804 | local function escape_special_chars(text) | |
805 | local tokens = tokenize_html(text) | |
806 | ||
807 | local out = "" | |
808 | for _, token in ipairs(tokens) do | |
809 | local t = token.text | |
810 | if token.type == "tag" then | |
811 | -- In tags, encode * and _ so they don't conflict with their use in markdown. | |
812 | t = t:gsub("%*", escape_table["*"]) | |
813 | t = t:gsub("%_", escape_table["_"]) | |
814 | else | |
815 | t = encode_backslash_escapes(t) | |
816 | end | |
817 | out = out .. t | |
818 | end | |
819 | return out | |
820 | end | |
821 | ||
822 | -- Encode backspace-escaped characters in the markdown source. | |
823 | function encode_backslash_escapes(t) | |
824 | for i=1,escape_chars:len() do | |
825 | local c = escape_chars:sub(i,i) | |
826 | t = t:gsub("\\%" .. c, escape_table[c]) | |
827 | end | |
828 | return t | |
829 | end | |
830 | ||
831 | -- Unescape characters that have been encoded. | |
832 | local function unescape_special_chars(t) | |
833 | local tin = t | |
834 | for k,v in pairs(escape_table) do | |
835 | k = k:gsub("%%", "%%%%") | |
836 | t = t:gsub(v,k) | |
837 | end | |
838 | if t ~= tin then t = unescape_special_chars(t) end | |
839 | return t | |
840 | end | |
841 | ||
842 | -- Encode/escape certain characters inside Markdown code runs. | |
843 | -- The point is that in code, these characters are literals, | |
844 | -- and lose their special Markdown meanings. | |
845 | function encode_code(s) | |
846 | s = s:gsub("%&", "&") | |
847 | s = s:gsub("<", "<") | |
848 | s = s:gsub(">", ">") | |
849 | for k,v in pairs(escape_table) do | |
850 | s = s:gsub("%"..k, v) | |
851 | end | |
852 | return s | |
853 | end | |
854 | ||
855 | -- Handle backtick blocks. | |
856 | local function code_spans(s) | |
857 | s = s:gsub("\\\\", escape_table["\\"]) | |
858 | s = s:gsub("\\`", escape_table["`"]) | |
859 | ||
860 | local pos = 1 | |
861 | while true do | |
862 | local start, stop = s:find("`+", pos) | |
863 | if not start then return s end | |
864 | local count = stop - start + 1 | |
865 | -- Find a matching numbert of backticks | |
866 | local estart, estop = s:find(string.rep("`", count), stop+1) | |
867 | local brstart = s:find("\n", stop+1) | |
868 | if estart and (not brstart or estart < brstart) then | |
869 | local code = s:sub(stop+1, estart-1) | |
870 | code = code:gsub("^[ \t]+", "") | |
871 | code = code:gsub("[ \t]+$", "") | |
872 | code = code:gsub(escape_table["\\"], escape_table["\\"] .. escape_table["\\"]) | |
873 | code = code:gsub(escape_table["`"], escape_table["\\"] .. escape_table["`"]) | |
874 | code = "<code>" .. encode_code(code) .. "</code>" | |
875 | code = add_escape(code) | |
876 | s = s:sub(1, start-1) .. code .. s:sub(estop+1) | |
877 | pos = start + code:len() | |
878 | else | |
879 | pos = stop + 1 | |
880 | end | |
881 | end | |
882 | return s | |
883 | end | |
884 | ||
885 | -- Encode alt text... enodes &, and ". | |
886 | local function encode_alt(s) | |
887 | if not s then return s end | |
888 | s = s:gsub('&', '&') | |
889 | s = s:gsub('"', '"') | |
890 | s = s:gsub('<', '<') | |
891 | return s | |
892 | end | |
893 | ||
894 | local link_database | |
895 | ||
896 | -- Handle image references | |
897 | local function images(text) | |
898 | local function reference_link(alt, id) | |
899 | alt = encode_alt(alt:match("%b[]"):sub(2,-2)) | |
900 | id = id:match("%[(.*)%]"):lower() | |
901 | if id == "" then id = text:lower() end | |
902 | link_database[id] = link_database[id] or {} | |
903 | if not link_database[id].url then return nil end | |
904 | local url = link_database[id].url or id | |
905 | url = encode_alt(url) | |
906 | local title = encode_alt(link_database[id].title) | |
907 | if title then title = " title=\"" .. title .. "\"" else title = "" end | |
908 | return add_escape ('<img src="' .. url .. '" alt="' .. alt .. '"' .. title .. "/>") | |
909 | end | |
910 | ||
911 | local function inline_link(alt, link) | |
912 | alt = encode_alt(alt:match("%b[]"):sub(2,-2)) | |
913 | local url, title = link:match("%(<?(.-)>?[ \t]*['\"](.+)['\"]") | |
914 | url = url or link:match("%(<?(.-)>?%)") | |
915 | url = encode_alt(url) | |
916 | title = encode_alt(title) | |
917 | if title then | |
918 | return add_escape('<img src="' .. url .. '" alt="' .. alt .. '" title="' .. title .. '"/>') | |
919 | else | |
920 | return add_escape('<img src="' .. url .. '" alt="' .. alt .. '"/>') | |
921 | end | |
922 | end | |
923 | ||
924 | text = text:gsub("!(%b[])[ \t]*\n?[ \t]*(%b[])", reference_link) | |
925 | text = text:gsub("!(%b[])(%b())", inline_link) | |
926 | return text | |
927 | end | |
928 | ||
929 | -- Handle anchor references | |
930 | local function anchors(text) | |
931 | local function reference_link(text, id) | |
932 | text = text:match("%b[]"):sub(2,-2) | |
933 | id = id:match("%b[]"):sub(2,-2):lower() | |
934 | if id == "" then id = text:lower() end | |
935 | link_database[id] = link_database[id] or {} | |
936 | if not link_database[id].url then return nil end | |
937 | local url = link_database[id].url or id | |
938 | url = encode_alt(url) | |
939 | local title = encode_alt(link_database[id].title) | |
940 | if title then title = " title=\"" .. title .. "\"" else title = "" end | |
941 | return add_escape("<a href=\"" .. url .. "\"" .. title .. ">") .. text .. add_escape("</a>") | |
942 | end | |
943 | ||
944 | local function inline_link(text, link) | |
945 | text = text:match("%b[]"):sub(2,-2) | |
946 | local url, title = link:match("%(<?(.-)>?[ \t]*['\"](.+)['\"]") | |
947 | title = encode_alt(title) | |
948 | url = url or link:match("%(<?(.-)>?%)") or "" | |
949 | url = encode_alt(url) | |
950 | if title then | |
951 | return add_escape("<a href=\"" .. url .. "\" title=\"" .. title .. "\">") .. text .. "</a>" | |
952 | else | |
953 | return add_escape("<a href=\"" .. url .. "\">") .. text .. add_escape("</a>") | |
954 | end | |
955 | end | |
956 | ||
957 | text = text:gsub("(%b[])[ \t]*\n?[ \t]*(%b[])", reference_link) | |
958 | text = text:gsub("(%b[])(%b())", inline_link) | |
959 | return text | |
960 | end | |
961 | ||
962 | -- Handle auto links, i.e. <http://www.google.com/>. | |
963 | local function auto_links(text) | |
964 | local function link(s) | |
965 | return add_escape("<a href=\"" .. s .. "\">") .. s .. "</a>" | |
966 | end | |
967 | -- Encode chars as a mix of dec and hex entitites to (perhaps) fool | |
968 | -- spambots. | |
969 | local function encode_email_address(s) | |
970 | -- Use a deterministic encoding to make unit testing possible. | |
971 | -- Code 45% hex, 45% dec, 10% plain. | |
972 | local hex = {code = function(c) return "&#x" .. string.format("%x", c:byte()) .. ";" end, count = 1, rate = 0.45} | |
973 | local dec = {code = function(c) return "&#" .. c:byte() .. ";" end, count = 0, rate = 0.45} | |
974 | local plain = {code = function(c) return c end, count = 0, rate = 0.1} | |
975 | local codes = {hex, dec, plain} | |
976 | local function swap(t,k1,k2) local temp = t[k2] t[k2] = t[k1] t[k1] = temp end | |
977 | ||
978 | local out = "" | |
979 | for i = 1,s:len() do | |
980 | for _,code in ipairs(codes) do code.count = code.count + code.rate end | |
981 | if codes[1].count < codes[2].count then swap(codes,1,2) end | |
982 | if codes[2].count < codes[3].count then swap(codes,2,3) end | |
983 | if codes[1].count < codes[2].count then swap(codes,1,2) end | |
984 | ||
985 | local code = codes[1] | |
986 | local c = s:sub(i,i) | |
987 | -- Force encoding of "@" to make email address more invisible. | |
988 | if c == "@" and code == plain then code = codes[2] end | |
989 | out = out .. code.code(c) | |
990 | code.count = code.count - 1 | |
991 | end | |
992 | return out | |
993 | end | |
994 | local function mail(s) | |
995 | s = unescape_special_chars(s) | |
996 | local address = encode_email_address("mailto:" .. s) | |
997 | local text = encode_email_address(s) | |
998 | return add_escape("<a href=\"" .. address .. "\">") .. text .. "</a>" | |
999 | end | |
1000 | -- links | |
1001 | text = text:gsub("<(https?:[^'\">%s]+)>", link) | |
1002 | text = text:gsub("<(ftp:[^'\">%s]+)>", link) | |
1003 | ||
1004 | ||
1005 | text = text:gsub("<mailto:([^'\">%s]+)>", mail) | |
1006 | text = text:gsub("<([-.%w]+%@[-.%w]+)>", mail) | |
1007 | return text | |
1008 | end | |
1009 | ||
1010 | -- Encode free standing amps (&) and angles (<)... note that this does not | |
1011 | -- encode free >. | |
1012 | local function amps_and_angles(s) | |
1013 | -- encode amps not part of &..; expression | |
1014 | local pos = 1 | |
1015 | while true do | |
1016 | local amp = s:find("&", pos) | |
1017 | if not amp then break end | |
1018 | local semi = s:find(";", amp+1) | |
1019 | local stop = s:find("[ \t\n&]", amp+1) | |
1020 | if not semi or (stop and stop < semi) or (semi - amp) > 15 then | |
1021 | s = s:sub(1,amp-1) .. "&" .. s:sub(amp+1) | |
1022 | pos = amp+1 | |
1023 | else | |
1024 | pos = amp+1 | |
1025 | end | |
1026 | end | |
1027 | ||
1028 | -- encode naked <'s | |
1029 | s = s:gsub("<([^a-zA-Z/?$!])", "<%1") | |
1030 | s = s:gsub("<$", "<") | |
1031 | ||
1032 | -- what about >, nothing done in the original markdown source to handle them | |
1033 | return s | |
1034 | end | |
1035 | ||
1036 | -- Handles emphasis markers (* and _) in the text. | |
1037 | local function emphasis(text) | |
1038 | for _, s in ipairs {"%*%*", "%_%_"} do | |
1039 | text = text:gsub(s .. "([^%s][%*%_]?)" .. s, "<strong>%1</strong>") | |
1040 | text = text:gsub(s .. "([^%s][^<>]-[^%s][%*%_]?)" .. s, "<strong>%1</strong>") | |
1041 | end | |
1042 | for _, s in ipairs {"%*", "%_"} do | |
1043 | text = text:gsub(s .. "([^%s_])" .. s, "<em>%1</em>") | |
1044 | text = text:gsub(s .. "(<strong>[^%s_]</strong>)" .. s, "<em>%1</em>") | |
1045 | text = text:gsub(s .. "([^%s_][^<>_]-[^%s_])" .. s, "<em>%1</em>") | |
1046 | text = text:gsub(s .. "([^<>_]-<strong>[^<>_]-</strong>[^<>_]-)" .. s, "<em>%1</em>") | |
1047 | end | |
1048 | return text | |
1049 | end | |
1050 | ||
1051 | -- Handles line break markers in the text. | |
1052 | local function line_breaks(text) | |
1053 | return text:gsub(" +\n", " <br/>\n") | |
1054 | end | |
1055 | ||
1056 | -- Perform all span level transforms. | |
1057 | function span_transform(text) | |
1058 | text = code_spans(text) | |
1059 | text = escape_special_chars(text) | |
1060 | text = images(text) | |
1061 | text = anchors(text) | |
1062 | text = auto_links(text) | |
1063 | text = amps_and_angles(text) | |
1064 | text = emphasis(text) | |
1065 | text = line_breaks(text) | |
1066 | return text | |
1067 | end | |
1068 | ||
1069 | ---------------------------------------------------------------------- | |
1070 | -- Markdown | |
1071 | ---------------------------------------------------------------------- | |
1072 | ||
1073 | -- Cleanup the text by normalizing some possible variations to make further | |
1074 | -- processing easier. | |
1075 | local function cleanup(text) | |
1076 | -- Standardize line endings | |
1077 | text = text:gsub("\r\n", "\n") -- DOS to UNIX | |
1078 | text = text:gsub("\r", "\n") -- Mac to UNIX | |
1079 | ||
1080 | -- Convert all tabs to spaces | |
1081 | text = detab(text) | |
1082 | ||
1083 | -- Strip lines with only spaces and tabs | |
1084 | while true do | |
1085 | local subs | |
1086 | text, subs = text:gsub("\n[ \t]+\n", "\n\n") | |
1087 | if subs == 0 then break end | |
1088 | end | |
1089 | ||
1090 | return "\n" .. text .. "\n" | |
1091 | end | |
1092 | ||
1093 | -- Strips link definitions from the text and stores the data in a lookup table. | |
1094 | local function strip_link_definitions(text) | |
1095 | local linkdb = {} | |
1096 | ||
1097 | local function link_def(id, url, title) | |
1098 | id = id:match("%[(.+)%]"):lower() | |
1099 | linkdb[id] = linkdb[id] or {} | |
1100 | linkdb[id].url = url or linkdb[id].url | |
1101 | linkdb[id].title = title or linkdb[id].title | |
1102 | return "" | |
1103 | end | |
1104 | ||
1105 | local def_no_title = "\n ? ? ?(%b[]):[ \t]*\n?[ \t]*<?([^%s>]+)>?[ \t]*" | |
1106 | local def_title1 = def_no_title .. "[ \t]+\n?[ \t]*[\"'(]([^\n]+)[\"')][ \t]*" | |
1107 | local def_title2 = def_no_title .. "[ \t]*\n[ \t]*[\"'(]([^\n]+)[\"')][ \t]*" | |
1108 | local def_title3 = def_no_title .. "[ \t]*\n?[ \t]+[\"'(]([^\n]+)[\"')][ \t]*" | |
1109 | ||
1110 | text = text:gsub(def_title1, link_def) | |
1111 | text = text:gsub(def_title2, link_def) | |
1112 | text = text:gsub(def_title3, link_def) | |
1113 | text = text:gsub(def_no_title, link_def) | |
1114 | return text, linkdb | |
1115 | end | |
1116 | ||
1117 | link_database = {} | |
1118 | ||
1119 | -- Main markdown processing function | |
1120 | local function markdown(text) | |
1121 | init_hash(text) | |
1122 | init_escape_table() | |
1123 | ||
1124 | text = cleanup(text) | |
1125 | text = protect(text) | |
1126 | text, link_database = strip_link_definitions(text) | |
1127 | text = block_transform(text) | |
1128 | text = unescape_special_chars(text) | |
1129 | return text | |
1130 | end | |
1131 | ||
1132 | ---------------------------------------------------------------------- | |
1133 | -- End of module | |
1134 | ---------------------------------------------------------------------- | |
1135 | ||
1136 | M.lock(M) | |
1137 | ||
1138 | -- Expose markdown function to the world | |
1139 | _G.markdown = M.markdown | |
1140 | ||
1141 | -- Class for parsing command-line options | |
1142 | local OptionParser = {} | |
1143 | OptionParser.__index = OptionParser | |
1144 | ||
1145 | -- Creates a new option parser | |
1146 | function OptionParser:new() | |
1147 | local o = {short = {}, long = {}} | |
1148 | setmetatable(o, self) | |
1149 | return o | |
1150 | end | |
1151 | ||
1152 | -- Calls f() whenever a flag with specified short and long name is encountered | |
1153 | function OptionParser:flag(short, long, f) | |
1154 | local info = {type = "flag", f = f} | |
1155 | if short then self.short[short] = info end | |
1156 | if long then self.long[long] = info end | |
1157 | end | |
1158 | ||
1159 | -- Calls f(param) whenever a parameter flag with specified short and long name is encountered | |
1160 | function OptionParser:param(short, long, f) | |
1161 | local info = {type = "param", f = f} | |
1162 | if short then self.short[short] = info end | |
1163 | if long then self.long[long] = info end | |
1164 | end | |
1165 | ||
1166 | -- Calls f(v) for each non-flag argument | |
1167 | function OptionParser:arg(f) | |
1168 | self.arg = f | |
1169 | end | |
1170 | ||
1171 | -- Runs the option parser for the specified set of arguments. Returns true if all arguments | |
1172 | -- where successfully parsed and false otherwise. | |
1173 | function OptionParser:run(args) | |
1174 | local pos = 1 | |
1175 | local param | |
1176 | while pos <= #args do | |
1177 | local arg = args[pos] | |
1178 | if arg == "--" then | |
1179 | for i=pos+1,#args do | |
1180 | if self.arg then self.arg(args[i]) end | |
1181 | return true | |
1182 | end | |
1183 | end | |
1184 | if arg:match("^%-%-") then | |
1185 | local info = self.long[arg:sub(3)] | |
1186 | if not info then print("Unknown flag: " .. arg) return false end | |
1187 | if info.type == "flag" then | |
1188 | info.f() | |
1189 | pos = pos + 1 | |
1190 | else | |
1191 | param = args[pos+1] | |
1192 | if not param then print("No parameter for flag: " .. arg) return false end | |
1193 | info.f(param) | |
1194 | pos = pos+2 | |
1195 | end | |
1196 | elseif arg:match("^%-") then | |
1197 | for i=2,arg:len() do | |
1198 | local c = arg:sub(i,i) | |
1199 | local info = self.short[c] | |
1200 | if not info then print("Unknown flag: -" .. c) return false end | |
1201 | if info.type == "flag" then | |
1202 | info.f() | |
1203 | else | |
1204 | if i == arg:len() then | |
1205 | param = args[pos+1] | |
1206 | if not param then print("No parameter for flag: -" .. c) return false end | |
1207 | info.f(param) | |
1208 | pos = pos + 1 | |
1209 | else | |
1210 | param = arg:sub(i+1) | |
1211 | info.f(param) | |
1212 | end | |
1213 | break | |
1214 | end | |
1215 | end | |
1216 | pos = pos + 1 | |
1217 | else | |
1218 | if self.arg then self.arg(arg) end | |
1219 | pos = pos + 1 | |
1220 | end | |
1221 | end | |
1222 | return true | |
1223 | end | |
1224 | ||
1225 | -- Handles the case when markdown is run from the command line | |
1226 | local function run_command_line(arg) | |
1227 | -- Generate output for input s given options | |
1228 | local function run(s, options) | |
1229 | s = markdown(s) | |
1230 | if not options.wrap_header then return s end | |
1231 | local header = "" | |
1232 | if options.header then | |
1233 | local f = io.open(options.header) or error("Could not open file: " .. options.header) | |
1234 | header = f:read("*a") | |
1235 | f:close() | |
1236 | else | |
1237 | header = [[ | |
1238 | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> | |
1239 | <html> | |
1240 | <head> | |
1241 | <meta http-equiv="content-type" content="text/html; charset=CHARSET" /> | |
1242 | <title>TITLE</title> | |
1243 | <link rel="stylesheet" type="text/css" href="STYLESHEET" /> | |
1244 | </head> | |
1245 | <body> | |
1246 | ]] | |
1247 | local title = options.title or s:match("<h1>(.-)</h1>") or s:match("<h2>(.-)</h2>") or | |
1248 | s:match("<h3>(.-)</h3>") or "Untitled" | |
1249 | header = header:gsub("TITLE", title) | |
1250 | if options.inline_style then | |
1251 | local style = "" | |
1252 | local f = io.open(options.stylesheet) | |
1253 | if f then | |
1254 | style = f:read("*a") f:close() | |
1255 | else | |
1256 | error("Could not include style sheet " .. options.stylesheet .. ": File not found") | |
1257 | end | |
1258 | header = header:gsub('<link rel="stylesheet" type="text/css" href="STYLESHEET" />', | |
1259 | "<style type=\"text/css\"><!--\n" .. style .. "\n--></style>") | |
1260 | else | |
1261 | header = header:gsub("STYLESHEET", options.stylesheet) | |
1262 | end | |
1263 | header = header:gsub("CHARSET", options.charset) | |
1264 | end | |
1265 | local footer = "</body></html>" | |
1266 | if options.footer then | |
1267 | local f = io.open(options.footer) or error("Could not open file: " .. options.footer) | |
1268 | footer = f:read("*a") | |
1269 | f:close() | |
1270 | end | |
1271 | return header .. s .. footer | |
1272 | end | |
1273 | ||
1274 | -- Generate output path name from input path name given options. | |
1275 | local function outpath(path, options) | |
1276 | if options.append then return path .. ".html" end | |
1277 | local m = path:match("^(.+%.html)[^/\\]+$") if m then return m end | |
1278 | m = path:match("^(.+%.)[^/\\]*$") if m and path ~= m .. "html" then return m .. "html" end | |
1279 | return path .. ".html" | |
1280 | end | |
1281 | ||
1282 | -- Default commandline options | |
1283 | local options = { | |
1284 | wrap_header = true, | |
1285 | header = nil, | |
1286 | footer = nil, | |
1287 | charset = "utf-8", | |
1288 | title = nil, | |
1289 | stylesheet = "default.css", | |
1290 | inline_style = false | |
1291 | } | |
1292 | local help = [[ | |
1293 | Usage: markdown.lua [OPTION] [FILE] | |
1294 | Runs the markdown text markup to HTML converter on each file specified on the | |
1295 | command line. If no files are specified, runs on standard input. | |
1296 | ||
1297 | No header: | |
1298 | -n, --no-wrap Don't wrap the output in <html>... tags. | |
1299 | Custom header: | |
1300 | -e, --header FILE Use content of FILE for header. | |
1301 | -f, --footer FILE Use content of FILE for footer. | |
1302 | Generated header: | |
1303 | -c, --charset SET Specifies charset (default utf-8). | |
1304 | -i, --title TITLE Specifies title (default from first <h1> tag). | |
1305 | -s, --style STYLE Specifies style sheet file (default default.css). | |
1306 | -l, --inline-style Include the style sheet file inline in the header. | |
1307 | Generated files: | |
1308 | -a, --append Append .html extension (instead of replacing). | |
1309 | Other options: | |
1310 | -h, --help Print this help text. | |
1311 | -t, --test Run the unit tests. | |
1312 | ]] | |
1313 | ||
1314 | local run_stdin = true | |
1315 | local op = OptionParser:new() | |
1316 | op:flag("n", "no-wrap", function () options.wrap_header = false end) | |
1317 | op:param("e", "header", function (x) options.header = x end) | |
1318 | op:param("f", "footer", function (x) options.footer = x end) | |
1319 | op:param("c", "charset", function (x) options.charset = x end) | |
1320 | op:param("i", "title", function(x) options.title = x end) | |
1321 | op:param("s", "style", function(x) options.stylesheet = x end) | |
1322 | op:flag("l", "inline-style", function(x) options.inline_style = true end) | |
1323 | op:flag("a", "append", function() options.append = true end) | |
1324 | op:flag("t", "test", function() | |
1325 | local n = arg[0]:gsub("markdown.lua", "markdown-tests.lua") | |
1326 | local f = io.open(n) | |
1327 | if f then | |
1328 | f:close() dofile(n) | |
1329 | else | |
1330 | error("Cannot find markdown-tests.lua") | |
1331 | end | |
1332 | run_stdin = false | |
1333 | end) | |
1334 | op:flag("h", "help", function() print(help) run_stdin = false end) | |
1335 | op:arg(function(path) | |
1336 | local file = io.open(path) or error("Could not open file: " .. path) | |
1337 | local s = file:read("*a") | |
1338 | file:close() | |
1339 | s = run(s, options) | |
1340 | file = io.open(outpath(path, options), "w") or error("Could not open output file: " .. outpath(path, options)) | |
1341 | file:write(s) | |
1342 | file:close() | |
1343 | run_stdin = false | |
1344 | end | |
1345 | ) | |
1346 | ||
1347 | if not op:run(arg) then | |
1348 | print(help) | |
1349 | run_stdin = false | |
1350 | end | |
1351 | ||
1352 | if run_stdin then | |
1353 | local s = io.read("*a") | |
1354 | s = run(s, options) | |
1355 | io.write(s) | |
1356 | end | |
1357 | end | |
1358 | ||
1359 | -- If we are being run from the command-line, act accordingly | |
1360 | if arg and arg[0]:find("markdown%.lua$") then | |
1361 | run_command_line(arg) | |
1362 | else | |
1363 | return markdown | |
1364 | end |
19 | 19 | if not qname then |
20 | 20 | qname = name |
21 | 21 | end |
22 | local ref,err = markup.process_reference(qname) | |
22 | local ref, err | |
23 | local custom_ref, refname = utils.splitv(qname,':') | |
24 | if custom_ref and ldoc.custom_references then | |
25 | custom_ref = ldoc.custom_references[custom_ref] | |
26 | if custom_ref then | |
27 | ref,err = custom_ref(refname) | |
28 | end | |
29 | end | |
30 | if not ref then | |
31 | ref,err = markup.process_reference(qname) | |
32 | end | |
23 | 33 | if not ref then |
24 | 34 | err = err .. ' ' .. qname |
25 | if item then item:warning(err) | |
35 | if item and item.warning then item:warning(err) | |
26 | 36 | else |
27 | 37 | io.stderr:write('nofile error: ',err,'\n') |
28 | 38 | end |
43 | 53 | res = res:gsub('`([^`]+)`',function(name) |
44 | 54 | local ref,err = markup.process_reference(name) |
45 | 55 | if ref then |
56 | if not plain and name then | |
57 | name = name:gsub('_', '\\_') | |
58 | end | |
46 | 59 | return ('<a href="%s">%s</a> '):format(ldoc.href(ref),name) |
47 | 60 | else |
48 | 61 | return '<code>'..name..'</code>' |
56 | 69 | -- they can appear in the contents list as a ToC. |
57 | 70 | function markup.add_sections(F, txt) |
58 | 71 | local sections, L, first = {}, 1, true |
59 | local title_pat_end, title_pat = '[^#]%s*(.+)' | |
72 | local title_pat | |
73 | local lstrip = stringx.lstrip | |
60 | 74 | for line in stringx.lines(txt) do |
61 | 75 | if first then |
62 | 76 | local level,header = line:match '^(#+)%s*(.+)' |
65 | 79 | else |
66 | 80 | level = '##' |
67 | 81 | end |
68 | title_pat = '^'..level..title_pat_end | |
82 | title_pat = '^'..level..'([^#]%s*.+)' | |
83 | title_pat = lstrip(title_pat) | |
69 | 84 | first = false |
85 | F.display_name = header | |
70 | 86 | end |
71 | 87 | local title = line:match (title_pat) |
72 | 88 | if title then |
73 | -- Markdown does allow this pattern | |
89 | -- Markdown allows trailing '#'... | |
74 | 90 | title = title:gsub('%s*#+$','') |
75 | sections[L] = F:add_document_section(title) | |
91 | sections[L] = F:add_document_section(lstrip(title)) | |
76 | 92 | end |
77 | 93 | L = L + 1 |
78 | 94 | end |
86 | 102 | return indent,line |
87 | 103 | end |
88 | 104 | |
89 | local function non_blank (line) | |
90 | return line:find '%S' | |
105 | local function blank (line) | |
106 | return not line:find '%S' | |
91 | 107 | end |
92 | 108 | |
93 | 109 | local global_context, local_context |
115 | 131 | code = concat(code,'\n') |
116 | 132 | if code ~= '' then |
117 | 133 | local err |
134 | -- If we omit the following '\n', a '--' (or '//') comment on the | |
135 | -- last line won't be recognized. | |
118 | 136 | code, err = prettify.code(lang,filename,code..'\n',L,false) |
119 | 137 | append(res,'<pre>') |
120 | 138 | append(res, code) |
152 | 170 | if indent >= 4 then -- indented code block |
153 | 171 | local code = {} |
154 | 172 | local plain |
155 | while indent >= 4 or not non_blank(line) do | |
173 | while indent >= 4 or blank(line) do | |
156 | 174 | if not start_indent then |
157 | 175 | start_indent = indent |
158 | 176 | if line:match '^%s*@plain%s*$' then |
161 | 179 | end |
162 | 180 | end |
163 | 181 | if not plain then |
164 | append(code,line:sub(start_indent)) | |
182 | append(code,line:sub(start_indent + 1)) | |
165 | 183 | else |
166 | 184 | append(res,line) |
167 | 185 | end |
170 | 188 | indent, line = indent_line(line) |
171 | 189 | end |
172 | 190 | start_indent = nil |
173 | if #code > 1 then table.remove(code) end | |
191 | while #code > 1 and blank(code[#code]) do -- trim blank lines. | |
192 | table.remove(code) | |
193 | end | |
174 | 194 | pretty_code (code,'lua') |
175 | 195 | else |
176 | 196 | local section = F.sections[L] |
0 | 0 | -- parsing code for doc comments |
1 | 1 | |
2 | local utils = require 'pl.utils' | |
2 | 3 | local List = require 'pl.List' |
3 | 4 | local Map = require 'pl.Map' |
4 | 5 | local stringio = require 'pl.stringio' |
6 | 7 | local tools = require 'ldoc.tools' |
7 | 8 | local doc = require 'ldoc.doc' |
8 | 9 | local Item,File = doc.Item,doc.File |
10 | local unpack = utils.unpack | |
9 | 11 | |
10 | 12 | ------ Parsing the Source -------------- |
11 | 13 | -- This uses the lexer from PL, but it should be possible to use Peter Odding's |
69 | 71 | return preamble,tag_items |
70 | 72 | end |
71 | 73 | |
72 | -- Tags are stored as an ordered map | |
74 | -- Tags are stored as an ordered multi map from strings to strings | |
75 | -- If the same key is used, then the value becomes a list | |
73 | 76 | local Tags = {} |
74 | 77 | Tags.__index = Tags |
75 | 78 | |
88 | 91 | return tags |
89 | 92 | end |
90 | 93 | |
91 | function Tags:add (tag,value) | |
94 | function Tags:add (tag,value,modifiers) | |
95 | if modifiers then -- how modifiers are encoded | |
96 | value = {value,modifiers=modifiers} | |
97 | end | |
98 | local ovalue = self:get(tag) | |
99 | if ovalue then | |
100 | ovalue:append(value) | |
101 | value = ovalue | |
102 | end | |
92 | 103 | rawset(self,tag,value) |
93 | self._order:append(tag) | |
104 | if not ovalue then | |
105 | self._order:append(tag) | |
106 | end | |
107 | end | |
108 | ||
109 | function Tags:get (tag) | |
110 | local ovalue = rawget(self,tag) | |
111 | if ovalue then -- previous value? | |
112 | if getmetatable(ovalue) ~= List then | |
113 | ovalue = List{ovalue} | |
114 | end | |
115 | return ovalue | |
116 | end | |
94 | 117 | end |
95 | 118 | |
96 | 119 | function Tags:iter () |
128 | 151 | value = strip(value) |
129 | 152 | end |
130 | 153 | |
131 | if modifiers then value = { value, modifiers=modifiers } end | |
132 | local old_value = tags[tag] | |
133 | ||
134 | if not old_value then -- first element | |
135 | tags:add(tag,value) | |
136 | elseif type(old_value)=='table' and old_value.append then -- append to existing list | |
137 | old_value :append (value) | |
138 | else -- upgrade string->list | |
139 | tags:add(tag,List{old_value, value}) | |
140 | end | |
154 | tags:add(tag,value,modifiers) | |
141 | 155 | end |
142 | 156 | return tags --Map(tags) |
143 | 157 | end |
199 | 213 | local t,v = tnext(tok) |
200 | 214 | -- with some coding styles first comment is standard boilerplate; option to ignore this. |
201 | 215 | if args.boilerplate and t == 'comment' then |
216 | -- hack to deal with boilerplate inside Lua block comments | |
217 | if v:match '%s*%-%-%[%[' then lang:grab_block_comment(v,tok) end | |
202 | 218 | t,v = tnext(tok) |
203 | 219 | end |
204 | 220 | if t == '#' then -- skip Lua shebang line, if present |
349 | 365 | if is_local or tags['local'] then |
350 | 366 | tags:add('local',true) |
351 | 367 | end |
368 | -- support for standalone fields/properties of classes/modules | |
369 | if (tags.field or tags.param) and not tags.class then | |
370 | -- the hack is to take a subfield and pull out its name, | |
371 | -- (see Tag:add above) but let the subfield itself go through | |
372 | -- with any modifiers. | |
373 | local fp = tags.field or tags.param | |
374 | if type(fp) == 'table' then fp = fp[1] end | |
375 | fp = tools.extract_identifier(fp) | |
376 | tags:add('name',fp) | |
377 | tags:add('class','field') | |
378 | end | |
352 | 379 | if tags.name then |
353 | 380 | current_item = F:new_item(tags,line) |
354 | 381 | current_item.inferred = item_follows ~= nil |
356 | 383 | if module_item then |
357 | 384 | F:error("Module already declared!") |
358 | 385 | end |
359 | if tags.class == 'classmod' then | |
360 | tags = tags.new('section','methods') | |
361 | tags:add('summary','Methods') | |
362 | F:new_item(tags,line) | |
363 | end | |
364 | 386 | module_item = current_item |
365 | 387 | end |
366 | 388 | end |
3 | 3 | -- A module reference to an example `test-fun.lua` would look like |
4 | 4 | -- `@{example:test-fun}`. |
5 | 5 | local List = require 'pl.List' |
6 | local lexer = require 'ldoc.lexer' | |
7 | 6 | local globals = require 'ldoc.builtin.globals' |
8 | local tnext = lexer.skipws | |
9 | 7 | local prettify = {} |
10 | 8 | |
11 | 9 | local escaped_chars = { |
25 | 23 | |
26 | 24 | local spans = {keyword=true,number=true,string=true,comment=true,global=true,backtick=true} |
27 | 25 | |
28 | function prettify.lua (fname, code, initial_lineno, pre) | |
29 | local res = List() | |
26 | local cpp_lang = {c = true, cpp = true, cxx = true, h = true} | |
27 | ||
28 | function prettify.lua (lang, fname, code, initial_lineno, pre) | |
29 | local res, lexer, tokenizer = List(), require 'ldoc.lexer' | |
30 | local tnext = lexer.skipws | |
31 | if not cpp_lang[lang] then | |
32 | tokenizer = lexer.lua | |
33 | else | |
34 | tokenizer = lexer.cpp | |
35 | end | |
36 | ||
30 | 37 | if pre then |
31 | 38 | res:append '<pre>\n' |
32 | 39 | end |
33 | 40 | initial_lineno = initial_lineno or 0 |
34 | 41 | |
35 | local tok = lexer.lua(code,{},{}) | |
42 | local tok = tokenizer(code,{},{}) | |
36 | 43 | local error_reporter = { |
37 | 44 | warning = function (self,msg) |
38 | 45 | io.stderr:write(fname..':'..tok:lineno()+initial_lineno..': '..msg,'\n') |
71 | 78 | |
72 | 79 | function prettify.code (lang,fname,code,initial_lineno,pre) |
73 | 80 | if not lxsh then |
74 | return prettify.lua (fname, code, initial_lineno, pre) | |
81 | return prettify.lua (lang,fname, code, initial_lineno, pre) | |
75 | 82 | else |
76 | 83 | if not lxsh_highlighers[lang] then |
77 | 84 | lang = 'lua' |
81 | 88 | external = true |
82 | 89 | }) |
83 | 90 | if not pre then |
84 | code = code:gsub("^<pre*.->(.*)</pre>$", '%1') | |
91 | code = code:gsub("^<pre.->(.-)%s*</pre>$", '%1') | |
85 | 92 | end |
86 | 93 | return code |
87 | 94 | end |
15 | 15 | local quit = utils.quit |
16 | 16 | local lfs = require 'lfs' |
17 | 17 | |
18 | -- at rendering time, can access the ldoc table from any module item, | |
19 | -- or the item itself if it's a module | |
20 | function M.item_ldoc (item) | |
21 | local mod = item and (item.module or item) | |
22 | return mod and mod.ldoc | |
23 | end | |
24 | ||
18 | 25 | -- this constructs an iterator over a list of objects which returns only |
19 | 26 | -- those objects where a field has a certain value. It's used to iterate |
20 | 27 | -- only over functions or tables, etc. If the list of item has a module |
25 | 32 | local fls = list:filter(function(item) |
26 | 33 | return item[field] == value |
27 | 34 | end) |
28 | local mod = fls[1] and fls[1].module | |
29 | local ldoc = mod and mod.ldoc | |
35 | local ldoc = M.item_ldoc(fls[1]) | |
30 | 36 | if ldoc and ldoc.sort then |
31 | 37 | fls:sort(function(ia,ib) |
32 | 38 | return ia.name < ib.name |
130 | 136 | end |
131 | 137 | end |
132 | 138 | |
133 | ||
134 | 139 | ----- some useful utility functions ------ |
135 | 140 | |
136 | 141 | function M.module_basepath() |
144 | 149 | end |
145 | 150 | |
146 | 151 | -- split a qualified name into the module part and the name part, |
147 | -- e.g 'pl.utils.split' becomes 'pl.utils' and 'split' | |
152 | -- e.g 'pl.utils.split' becomes 'pl.utils' and 'split'. Also | |
153 | -- must understand colon notation! | |
148 | 154 | function M.split_dotted_name (s) |
149 | local s1,s2 = path.splitext(s) | |
150 | if s2=='' then return nil | |
151 | else return s1,s2:sub(2) | |
152 | end | |
155 | local s1,s2 = s:match '^(.+)[%.:](.+)$' | |
156 | if s1 then -- we can split | |
157 | return s1,s2 | |
158 | else | |
159 | return nil | |
160 | end | |
161 | --~ local s1,s2 = path.splitext(s) | |
162 | --~ if s2=='' then return nil | |
163 | --~ else return s1,s2:sub(2) | |
164 | --~ end | |
153 | 165 | end |
154 | 166 | |
155 | 167 | -- grab lines from a line iterator `iter` until the line matches the pattern. |
283 | 295 | -- following the arguments. ldoc will use these in addition to explicit |
284 | 296 | -- param tags. |
285 | 297 | |
286 | function M.get_parameters (tok,endtoken,delim) | |
298 | function M.get_parameters (tok,endtoken,delim,lang) | |
287 | 299 | tok = M.space_skip_getter(tok) |
288 | 300 | local args = List() |
289 | 301 | args.comments = {} |
291 | 303 | |
292 | 304 | if not ltl or not ltl[1] or #ltl[1] == 0 then return args end -- no arguments |
293 | 305 | |
294 | local function strip_comment (text) | |
295 | return text:match("%s*%-%-+%s*(.*)") | |
306 | local strip_comment, extract_arg | |
307 | ||
308 | if lang then | |
309 | strip_comment = utils.bind1(lang.trim_comment,lang) | |
310 | extract_arg = utils.bind1(lang.extract_arg,lang) | |
311 | else | |
312 | strip_comment = function(text) | |
313 | return text:match("%s*%-%-+%s*(.*)") | |
314 | end | |
315 | extract_arg = function(tl,idx) | |
316 | return value_of(tl[idx or 1]) | |
317 | end | |
296 | 318 | end |
297 | 319 | |
298 | 320 | local function set_comment (idx,tok) |
304 | 326 | text = current_comment .. " " .. text |
305 | 327 | end |
306 | 328 | args.comments[arg] = text |
329 | end | |
330 | ||
331 | local function add_arg (tl,idx) | |
332 | local name, type = extract_arg(tl,idx) | |
333 | args:append(name) | |
334 | if type then | |
335 | if not args.types then args.types = List() end | |
336 | args.types:append(type) | |
337 | end | |
307 | 338 | end |
308 | 339 | |
309 | 340 | for i = 1,#ltl do |
322 | 353 | j = j + 1 |
323 | 354 | end |
324 | 355 | if #tl > 1 then |
325 | args:append(value_of(tl[j])) | |
356 | add_arg(tl,j) | |
326 | 357 | end |
327 | 358 | else |
328 | args:append(value_of(tl[1])) | |
359 | add_arg(tl,1) | |
329 | 360 | end |
330 | 361 | if i == #ltl and #tl > 1 then |
331 | 362 | while j <= #tl and type_of(tl[j]) ~= 'comment' do |
0 | package = "ldoc" | |
1 | version = "scm-2" | |
2 | ||
3 | source = { | |
4 | dir="LDoc", | |
5 | url = "git://github.com/stevedonovan/LDoc.git" | |
6 | } | |
7 | ||
8 | description = { | |
9 | summary = "A Lua Documentation Tool", | |
10 | detailed = [[ | |
11 | LDoc is a LuaDoc-compatible documentation generator which can also | |
12 | process C extension source. Markdown may be optionally used to | |
13 | render comments, as well as integrated readme documentation and | |
14 | pretty-printed example files | |
15 | ]], | |
16 | homepage='http://stevedonovan.github.com/ldoc', | |
17 | maintainer='steve.j.donovan@gmail.com', | |
18 | license = "MIT/X11", | |
19 | } | |
20 | ||
21 | dependencies = { | |
22 | "penlight","markdown" | |
23 | } | |
24 | ||
25 | build = { | |
26 | type = "builtin", | |
27 | modules = { | |
28 | ["ldoc.tools"] = "ldoc/tools.lua", | |
29 | ["ldoc.lang"] = "ldoc/lang.lua", | |
30 | ["ldoc.parse"] = "ldoc/parse.lua", | |
31 | ["ldoc.html"] = "ldoc/html.lua", | |
32 | ["ldoc.lexer"] = "ldoc/lexer.lua", | |
33 | ["ldoc.markup"] = "ldoc/markup.lua", | |
34 | ["ldoc.prettify"] = "ldoc/prettify.lua", | |
35 | ["ldoc.markdown"] = "ldoc/markdown.lua", | |
36 | ["ldoc.doc"] = "ldoc/doc.lua", | |
37 | ["ldoc.html.ldoc_css"] = "ldoc/html/ldoc_css.lua", | |
38 | ["ldoc.html.ldoc_ltp"] = "ldoc/html/ldoc_ltp.lua", | |
39 | ["ldoc.html.ldoc_one_css"] = "ldoc/html/ldoc_one_css.lua", | |
40 | ["ldoc.html.ldoc_pale_css"] = "ldoc/html/ldoc_pale_css.lua", | |
41 | ["ldoc.builtin.globals"] = "ldoc/builtin/globals.lua", | |
42 | ["ldoc.builtin.coroutine"] = "ldoc/builtin/coroutine.lua", | |
43 | ["ldoc.builtin.global"] = "ldoc/builtin/global.lua", | |
44 | ["ldoc.builtin.debug"] = "ldoc/builtin/debug.lua", | |
45 | ["ldoc.builtin.io"] = "ldoc/builtin/io.lua", | |
46 | ["ldoc.builtin.lfs"] = "ldoc/builtin/lfs.lua", | |
47 | ["ldoc.builtin.lpeg"] = "ldoc/builtin/lpeg.lua", | |
48 | ["ldoc.builtin.math"] = "ldoc/builtin/math.lua", | |
49 | ["ldoc.builtin.os"] = "ldoc/builtin/os.lua", | |
50 | ["ldoc.builtin.package"] = "ldoc/builtin/package.lua", | |
51 | ["ldoc.builtin.string"] = "ldoc/builtin/string.lua", | |
52 | ["ldoc.builtin.table"] = "ldoc/builtin/table.lua", | |
53 | }, | |
54 | copy_directories = {'doc','tests'}, | |
55 | install = { | |
56 | bin = { | |
57 | ldoc = "ldoc.lua" | |
58 | } | |
59 | } | |
60 | } | |
61 |
6 | 6 | -- |
7 | 7 | -- C/C++ support for Lua extensions is provided. |
8 | 8 | -- |
9 | -- Available from LuaRocks as 'ldoc' and as a [Zip file](http://stevedonovan.github.com/files/ldoc-1.3.9.zip) | |
9 | -- Available from LuaRocks as 'ldoc' and as a [Zip file](http://stevedonovan.github.com/files/ldoc-1.4.2.zip) | |
10 | 10 | -- |
11 | 11 | -- [Github Page](https://github.com/stevedonovan/ldoc) |
12 | 12 | -- |
24 | 24 | local stringx = require 'pl.stringx' |
25 | 25 | local tablex = require 'pl.tablex' |
26 | 26 | |
27 | -- Penlight compatibility | |
28 | utils.unpack = utils.unpack or unpack or table.unpack | |
27 | 29 | |
28 | 30 | local append = table.insert |
29 | 31 | |
34 | 36 | |
35 | 37 | --- @usage |
36 | 38 | local usage = [[ |
37 | ldoc, a documentation generator for Lua, vs 1.4.0 | |
39 | ldoc, a documentation generator for Lua, vs 1.4.2 | |
38 | 40 | -d,--dir (default doc) output directory |
39 | 41 | -o,--output (default 'index') output name |
40 | 42 | -v,--verbose verbose |
49 | 51 | -b,--package (default .) top-level package basename (needed for module(...)) |
50 | 52 | -x,--ext (default html) output file extension |
51 | 53 | -c,--config (default config.ld) configuration name |
54 | -u,--unqualified don't show package name in sidebar links | |
52 | 55 | -i,--ignore ignore any 'no doc comment or no module' warnings |
53 | 56 | -X,--not_luadoc break LuaDoc compatibility. Descriptions may continue after tags. |
54 | 57 | -D,--define (default none) set a flag to be used in config.ld |
79 | 82 | local quit = utils.quit |
80 | 83 | |
81 | 84 | |
82 | class.ModuleMap(KindMap) | |
83 | local ModuleMap = ModuleMap | |
85 | local ModuleMap = class(KindMap) | |
86 | doc.ModuleMap = ModuleMap | |
84 | 87 | |
85 | 88 | function ModuleMap:_init () |
86 | 89 | self.klass = ModuleMap |
87 | 90 | self.fieldname = 'section' |
88 | 91 | end |
89 | 92 | |
90 | ModuleMap:add_kind('function','Functions','Parameters') | |
91 | ModuleMap:add_kind('table','Tables','Fields') | |
92 | ModuleMap:add_kind('field','Fields') | |
93 | ModuleMap:add_kind('lfunction','Local Functions','Parameters') | |
94 | ModuleMap:add_kind('annotation','Issues') | |
95 | ||
96 | ||
97 | class.ProjectMap(KindMap) | |
93 | local ProjectMap = class(KindMap) | |
98 | 94 | ProjectMap.project_level = true |
99 | 95 | |
100 | 96 | function ProjectMap:_init () |
102 | 98 | self.fieldname = 'type' |
103 | 99 | end |
104 | 100 | |
105 | ProjectMap:add_kind('module','Modules') | |
106 | ProjectMap:add_kind('script','Scripts') | |
107 | ProjectMap:add_kind('classmod','Classes') | |
108 | ProjectMap:add_kind('topic','Topics') | |
109 | ProjectMap:add_kind('example','Examples') | |
101 | ||
110 | 102 | |
111 | 103 | local lua, cc = lang.lua, lang.cc |
112 | 104 | |
121 | 113 | ['.mm'] = cc, |
122 | 114 | ['.moon'] = lang.moon, |
123 | 115 | } |
124 | ||
125 | 116 | ------- ldoc external API ------------ |
126 | 117 | |
127 | 118 | -- the ldoc table represents the API available in `config.ld`. |
128 | 119 | local ldoc = { charset = 'UTF-8' } |
120 | ||
121 | local known_types, kind_names = {} | |
122 | ||
123 | local function lookup (itype,igroup,isubgroup) | |
124 | local kn = kind_names[itype] | |
125 | known_types[itype] = true | |
126 | if kn then | |
127 | if type(kn) == 'string' then | |
128 | igroup = kn | |
129 | else | |
130 | igroup = kn[1] | |
131 | isubgroup = kn[2] | |
132 | end | |
133 | end | |
134 | return itype, igroup, isubgroup | |
135 | end | |
136 | ||
137 | local function setup_kinds () | |
138 | kind_names = ldoc.kind_names or {} | |
139 | ||
140 | ModuleMap:add_kind(lookup('function','Functions','Parameters')) | |
141 | ModuleMap:add_kind(lookup('table','Tables','Fields')) | |
142 | ModuleMap:add_kind(lookup('field','Fields')) | |
143 | ModuleMap:add_kind(lookup('lfunction','Local Functions','Parameters')) | |
144 | ModuleMap:add_kind(lookup('annotation','Issues')) | |
145 | ||
146 | ProjectMap:add_kind(lookup('module','Modules')) | |
147 | ProjectMap:add_kind(lookup('script','Scripts')) | |
148 | ProjectMap:add_kind(lookup('classmod','Classes')) | |
149 | ProjectMap:add_kind(lookup('topic','Topics')) | |
150 | ProjectMap:add_kind(lookup('example','Examples')) | |
151 | ||
152 | for k in pairs(kind_names) do | |
153 | if not known_types[k] then | |
154 | quit("unknown item type "..tools.quote(k).." in kind_names") | |
155 | end | |
156 | end | |
157 | end | |
158 | ||
159 | ||
129 | 160 | local add_language_extension |
130 | ||
131 | local function override (field) | |
132 | if ldoc[field] ~= nil then args[field] = ldoc[field] end | |
161 | -- hacky way for doc module to be passed options... | |
162 | doc.ldoc = ldoc | |
163 | ||
164 | -- if the corresponding argument was the default, then any ldoc field overrides | |
165 | local function override (field,defval) | |
166 | defval = defval or false | |
167 | if args[field] == defval and ldoc[field] ~= nil then args[field] = ldoc[field] end | |
133 | 168 | end |
134 | 169 | |
135 | 170 | -- aliases to existing tags can be defined. E.g. just 'p' for 'param' |
147 | 182 | type = type or name |
148 | 183 | ldoc.alias(name,{'param',modifiers={type=type}}) |
149 | 184 | end |
185 | ||
186 | ldoc.alias ('error',doc.error_macro) | |
150 | 187 | |
151 | 188 | ldoc.tparam_alias 'string' |
152 | 189 | ldoc.tparam_alias 'number' |
185 | 222 | end |
186 | 223 | |
187 | 224 | local ldoc_contents = { |
188 | 'alias','add_language_extension','new_type','add_section', 'tparam_alias', | |
225 | 'alias','add_language_extension','custom_tags','new_type','add_section', 'tparam_alias', | |
189 | 226 | 'file','project','title','package','format','output','dir','ext', 'topics', |
190 | 227 | 'one','style','template','description','examples', 'pretty', 'charset', 'plain', |
191 | 'readme','all','manual_url', 'ignore', 'colon', 'sort', 'module_file', | |
192 | 'boilerplate','merge', 'wrap', 'not_luadoc', 'template_escape', | |
228 | 'readme','all','manual_url', 'ignore', 'colon', 'sort', 'module_file','vars', | |
229 | 'boilerplate','merge', 'wrap', 'not_luadoc', 'template_escape','merge_error_groups', | |
193 | 230 | 'no_return_or_parms','no_summary','full_description','backtick_references', 'custom_see_handler', |
194 | 'no_space_before_args', | |
231 | 'no_space_before_args','parse_extra','no_lua_ref','sort_modules','use_markdown_titles', | |
232 | 'unqualified', 'custom_display_name_handler', 'kind_names', 'custom_references', | |
195 | 233 | } |
196 | 234 | ldoc_contents = tablex.makeset(ldoc_contents) |
197 | 235 | |
298 | 336 | args.file = abspath(args.file) |
299 | 337 | end |
300 | 338 | |
339 | if type(ldoc.custom_tags) == 'table' then -- custom tags | |
340 | for i, custom in ipairs(ldoc.custom_tags) do | |
341 | if type(custom) == 'string' then | |
342 | custom = {custom} | |
343 | ldoc.custom_tags[i] = custom | |
344 | end | |
345 | doc.add_tag(custom[1], 'ML') | |
346 | end | |
347 | end -- custom tags | |
348 | ||
301 | 349 | local source_dir = args.file |
302 | 350 | if type(source_dir) == 'table' then |
303 | 351 | source_dir = source_dir[1] |
316 | 364 | -- * '..' the path given points to the source directory |
317 | 365 | -- * 'NAME' explicitly give the base module package name |
318 | 366 | -- |
367 | ||
368 | override ('package','.') | |
319 | 369 | |
320 | 370 | local function setup_package_base() |
321 | 371 | if ldoc.package then args.package = ldoc.package end |
348 | 398 | local ftype = file_types[ext] |
349 | 399 | if ftype then |
350 | 400 | if args.verbose then print(f) end |
401 | ftype.extra = ldoc.parse_extra or {} | |
351 | 402 | local F,err = parse.file(f,ftype,args) |
352 | 403 | if err then |
353 | 404 | if F then |
367 | 418 | override 'merge' |
368 | 419 | override 'not_luadoc' |
369 | 420 | override 'module_file' |
421 | override 'boilerplate' | |
422 | ||
423 | setup_kinds() | |
424 | ||
425 | -- LDoc is doing plain ole C, don't want random Lua references! | |
426 | if ldoc.parse_extra and ldoc.parse_extra.C then | |
427 | ldoc.no_lua_ref = true | |
428 | end | |
429 | ||
430 | if ldoc.merge_error_groups == nil then | |
431 | ldoc.merge_error_groups = 'Error Message' | |
432 | end | |
370 | 433 | |
371 | 434 | -- ldoc.module_file establishes a partial ordering where the |
372 | 435 | -- master module files are processed first. |
428 | 491 | quit ("file or directory does not exist: "..quote(args.file)) |
429 | 492 | end |
430 | 493 | |
494 | ||
431 | 495 | -- create the function that renders text (descriptions and summaries) |
432 | 496 | -- (this also will initialize the code prettifier used) |
433 | override 'format' | |
497 | override ('format','plain') | |
434 | 498 | override 'pretty' |
435 | 499 | ldoc.markup = markup.create(ldoc, args.format,args.pretty) |
436 | 500 | |
468 | 532 | }) |
469 | 533 | -- wrap prettify for this example so it knows which file to blame |
470 | 534 | -- if there's a problem |
471 | item.postprocess = function(code) return prettify.lua(f,code,0,true) end | |
535 | local ext = path.extension(f):sub(2) | |
536 | item.postprocess = function(code) return prettify.lua(ext,f,code,0,true) end | |
472 | 537 | end) |
473 | 538 | end |
474 | 539 | |
490 | 555 | -- headers in the readme, which are attached to the File. So |
491 | 556 | -- we pass the File to the postprocesser, which will insert the section markers |
492 | 557 | -- and resolve inline @ references. |
558 | if ldoc.use_markdown_titles then | |
559 | item.display_name = F.display_name | |
560 | end | |
493 | 561 | item.postprocess = function(txt) return ldoc.markup(txt,F) end |
494 | 562 | end) |
495 | 563 | end |
519 | 587 | project:add(mod,module_list) |
520 | 588 | end |
521 | 589 | |
522 | -- the default is not to show local functions in the documentation. | |
523 | if not args.all and not ldoc.all then | |
524 | for mod in module_list:iter() do | |
525 | mod:mask_locals() | |
526 | end | |
527 | end | |
528 | ||
529 | table.sort(module_list,function(m1,m2) | |
530 | return m1.name < m2.name | |
531 | end) | |
590 | override 'all' | |
591 | ||
592 | if ldoc.sort_modules then | |
593 | table.sort(module_list,function(m1,m2) | |
594 | return m1.name < m2.name | |
595 | end) | |
596 | end | |
532 | 597 | |
533 | 598 | ldoc.single = modcount == 1 and first_module or nil |
534 | 599 | |
600 | --do return end | |
535 | 601 | |
536 | 602 | -------- three ways to dump the object graph after processing ----- |
537 | 603 | |
542 | 608 | if args.module == true then |
543 | 609 | file_list[1]:dump(args.verbose) |
544 | 610 | else |
545 | local fun = module_list[1].items.by_name[args.module] | |
546 | if not fun then quit(quote(args.module).." is not part of "..quote(args.file)) end | |
611 | local M,name = module_list[1], args.module | |
612 | local fun = M.items.by_name[name] | |
613 | if not fun then | |
614 | fun = M.items.by_name[M.mod_name..':'..name] | |
615 | end | |
616 | if not fun then quit(quote(name).." is not part of "..quote(args.file)) end | |
547 | 617 | fun:dump(true) |
548 | 618 | end |
549 | 619 | return |
574 | 644 | os.exit() |
575 | 645 | end |
576 | 646 | |
647 | -- can specify format, output, dir and ext in config.ld | |
648 | override ('output','index') | |
649 | override ('dir','doc') | |
650 | override ('ext','html') | |
651 | override 'one' | |
652 | ||
653 | -- handling styling and templates -- | |
577 | 654 | ldoc.css, ldoc.templ = 'ldoc.css','ldoc.ltp' |
578 | 655 | |
656 | -- special case: user wants to generate a .md file from a .lua file | |
579 | 657 | if args.ext == 'md' then |
580 | ldoc.templ = 'ldoc.mdtp' | |
658 | if #module_list ~= 1 then | |
659 | quit("can currently only generate Markdown output from one module only") | |
660 | end | |
661 | if ldoc.template == '!' then | |
662 | ldoc.template = '!md' | |
663 | end | |
664 | args.output = module_list[1].name | |
665 | args.dir = '.' | |
581 | 666 | ldoc.template_escape = '>' |
582 | 667 | ldoc.style = false |
583 | 668 | args.ext = '.md' |
669 | end | |
670 | ||
671 | local function match_bang (s) | |
672 | if type(s) ~= 'string' then return end | |
673 | return s:match '^!(.*)' | |
584 | 674 | end |
585 | 675 | |
586 | 676 | local function style_dir (sname) |
593 | 683 | if style then |
594 | 684 | if style == true then |
595 | 685 | dir = config_dir |
596 | elseif type(style) == 'string' and path.isdir(style) then | |
686 | elseif type(style) == 'string' and (path.isdir(style) or match_bang(style)) then | |
597 | 687 | dir = style |
598 | 688 | else |
599 | 689 | quit(quote(tostring(style)).." is not a directory") |
601 | 691 | args[sname] = dir |
602 | 692 | end |
603 | 693 | end |
604 | ||
605 | 694 | |
606 | 695 | -- the directories for template and stylesheet can be specified |
607 | 696 | -- either by command-line '--template','--style' arguments or by 'template and |
613 | 702 | style_dir 'style' |
614 | 703 | style_dir 'template' |
615 | 704 | |
616 | -- can specify format, output, dir and ext in config.ld | |
617 | override 'output' | |
618 | override 'dir' | |
619 | override 'ext' | |
620 | override 'one' | |
621 | override 'boilerplate' | |
622 | ||
623 | 705 | if not args.ext:find '^%.' then |
624 | 706 | args.ext = '.'..args.ext |
625 | 707 | end |
626 | 708 | |
627 | 709 | if args.one then |
628 | ldoc.css = 'ldoc_one.css' | |
629 | end | |
630 | ||
631 | if args.style == '!' or args.template == '!' then | |
710 | ldoc.style = '!one' | |
711 | end | |
712 | ||
713 | local builtin_style, builtin_template = match_bang(args.style),match_bang(args.template) | |
714 | if builtin_style or builtin_template then | |
632 | 715 | -- '!' here means 'use built-in templates' |
633 | 716 | local tmpdir = path.join(path.is_windows and os.getenv('TMP') or '/tmp','ldoc') |
634 | 717 | if not path.isdir(tmpdir) then |
635 | 718 | lfs.mkdir(tmpdir) |
636 | 719 | end |
637 | 720 | local function tmpwrite (name) |
638 | utils.writefile(path.join(tmpdir,name),require('ldoc.html.'..name:gsub('%.','_'))) | |
639 | end | |
640 | if args.style == '!' then | |
721 | local ok,text = pcall(require,'ldoc.html.'..name:gsub('%.','_')) | |
722 | if not ok then | |
723 | quit("cannot find builtin template "..name..": "..text) | |
724 | end | |
725 | if not utils.writefile(path.join(tmpdir,name),text) then | |
726 | quit("cannot write to temp directory "..tmpdir) | |
727 | end | |
728 | end | |
729 | if builtin_style then | |
730 | if builtin_style ~= '' then | |
731 | ldoc.css = 'ldoc_'..builtin_style..'.css' | |
732 | end | |
641 | 733 | tmpwrite(ldoc.css) |
642 | 734 | args.style = tmpdir |
643 | 735 | end |
644 | if args.template == '!' then | |
736 | if builtin_template then | |
737 | if builtin_template ~= '' then | |
738 | ldoc.templ = 'ldoc_'..builtin_template..'.ltp' | |
739 | end | |
645 | 740 | tmpwrite(ldoc.templ) |
646 | 741 | args.template = tmpdir |
647 | 742 | end |
10 | 10 | The [API documentation](http://stevedonovan.github.com/Penlight/api/index.html) of Penlight |
11 | 11 | is an example of a project using plain LuaDoc markup processed using LDoc. |
12 | 12 | |
13 | LDoc is intended to be compatible with [LuaDoc](http://luadoc.luaforge.net/manual.htm) and | |
13 | LDoc is intended to be compatible with [LuaDoc](http://keplerproject.github.io/luadoc/) and | |
14 | 14 | thus follows the pattern set by the various *Doc tools: |
15 | 15 | |
16 | 16 | --- Summary ends with a period. |
22 | 22 | len: => #@ls |
23 | 23 | |
24 | 24 | --- string representation |
25 | -- @within Metamethods | |
26 | 25 | __tostring: => '['..(concat @ls,',')..']' |
27 | 26 | |
28 | 27 | --- return idx of first occurence of `item` |
53 | 52 | self |
54 | 53 | |
55 | 54 | --- concatenate two lists, giving a new list |
56 | -- @within Metamethods | |
57 | 55 | __concat: (l1,l2) -> l1\copy!\extend l2 |
58 | 56 | |
59 | 57 | --- an iterator over all items |
0 | ------ | |
1 | -- Various ways of indicating errors | |
2 | -- @module multiple | |
3 | ||
4 | ----- | |
5 | -- function with return groups. | |
6 | -- @treturn[1] string result | |
7 | -- @return[2] nil | |
8 | -- @return[2] error message | |
9 | function mul1 () end | |
10 | ||
11 | ----- | |
12 | -- function with return and error tag | |
13 | -- @return result | |
14 | -- @error message | |
15 | function mul2 () end | |
16 | ||
17 | ----- | |
18 | -- function with multiple error tags | |
19 | -- @return result | |
20 | -- @error not found | |
21 | -- @error bad format | |
22 | function mul3 () end | |
23 | ||
24 | ---- | |
25 | -- function with inline return and errors | |
26 | -- @string name | |
27 | function mul4 (name) | |
28 | if type(name) ~= 'string' then | |
29 | --- @error[1] not a string | |
30 | return nil, 'not a string' | |
31 | end | |
32 | if #name == 0 then | |
33 | --- @error[2] zero-length string | |
34 | return nil, 'zero-length string' | |
35 | end | |
36 | --- @treturn string converted to uppercase | |
37 | return name:upper() | |
38 | end | |
39 | ----- | |
40 | -- function that raises an error. | |
41 | -- @string filename | |
42 | -- @treturn string result | |
43 | -- @raise 'file not found' | |
44 | function mul5(filename) end |
0 | 0 | -------------------------------------------------------------------------------- |
1 | --- Queue of objects sorted by priority | |
1 | --- Queue of objects sorted by priority. | |
2 | 2 | -- @module lua-nucleo.priority_queue |
3 | -- This file is a part of lua-nucleo library | |
3 | -- This file is a part of lua-nucleo library. Note that if you wish to spread | |
4 | -- the description after tags, then invoke with `not_luadoc=true`. | |
5 | -- The flags here are `ldoc -X -f backtick priority_queue.lua`, which | |
6 | -- also expands backticks. | |
4 | 7 | -- @copyright lua-nucleo authors (see file `COPYRIGHT` for the license) |
5 | 8 | -------------------------------------------------------------------------------- |
6 | 9 | |
33 | 36 | |
34 | 37 | local insert = function(self, priority, value) |
35 | 38 | method_arguments( |
36 | self, | |
39 | s | |
37 | 40 | "number", priority |
38 | 41 | ) |
39 | 42 | assert(value ~= nil, "value can't be nil") -- value may be of any type, except nil |
0 | ------ | |
1 | -- functions returning compound types | |
2 | -- @module struct | |
3 | ||
4 | ----- | |
5 | -- returns a 'struct'. | |
6 | -- @string name your name dammit | |
7 | -- @tfield string arb stuff | |
8 | -- @treturn st details of person | |
9 | -- @tfield[st] string name of person | |
10 | -- @tfield[st] int age of person | |
11 | function struct(name) end | |
12 |
0 | ----- | |
1 | -- module containing a class | |
2 | -- @module type | |
3 | ||
4 | ---- | |
5 | -- Our class. | |
6 | -- @type Bonzo | |
7 | ||
8 | ---- | |
9 | -- make a new Bonzo. | |
10 | -- @see Bonzo:dog | |
11 | -- @string s name of Bonzo | |
12 | function Bonzo.new(s) | |
13 | end | |
14 | ||
15 | ----- | |
16 | -- get a string representation. | |
17 | -- works with `tostring` | |
18 | function Bonzo:__tostring() | |
19 | end | |
20 | ||
21 | ---- | |
22 | -- Another method. | |
23 | function Bonzo:dog () | |
24 | ||
25 | end | |
26 | ||
27 | ---- | |
28 | -- Private method. | |
29 | -- You need -a flag or 'all=true' to see these | |
30 | -- @local | |
31 | function Bonzo:cat () | |
32 | ||
33 | end | |
34 | ||
35 | ||
36 | ---- | |
37 | -- A subtable with fields. | |
38 | -- @table Details | |
39 | -- @string[readonly] name | |
40 | -- @int[readonly] age | |
41 | ||
42 | --- | |
43 | -- This is a simple field/property of the class. | |
44 | -- @string[opt="Bilbo",readonly] frodo direct access to text |