├── fir
├── version.yue
├── generic
│ ├── index.md
│ ├── backend.yue
│ ├── emit
│ │ ├── markdown.yue
│ │ └── tests.yue
│ └── parser.yue
├── error.yue
└── file.yue
├── .gitignore
├── docs
├── fir-logo.png
├── generic
│ ├── index.md
│ ├── backend.md
│ ├── parser.md
│ └── emit
│ │ ├── markdown.md
│ │ └── tests.md
├── stylesheets
│ └── extra.css
├── tutorial
│ ├── alfons.md
│ ├── tests.md
│ ├── CLI.md
│ └── index.md
├── index.md
└── examples
│ └── generic-backend.html
├── .gitattributes
├── examples
├── Fir.docs.moon
├── testable.moon
├── Fir.test.moon
└── testable.test.moon
├── TODO.md
├── Fir.moon
├── rockspecs
├── fir-1.0-1.rockspec
├── fir-1.1-1.rockspec
├── fir-1.2-1.rockspec
├── fir-1.3-1.rockspec
├── fir-1.1.1-1.rockspec
├── fir-1.1.2-1.rockspec
├── fir-1.2.1-1.rockspec
├── fir-1.3.1-1.rockspec
├── fir-2.0.0-1.rockspec
└── fir-2.0.0-2.rockspec
├── rock.yml
├── ROADMAP.md
├── LICENSE.md
├── mkdocs.yml
├── Alfons.yue
├── README.md
├── alfons
└── tasks
│ └── fir.yue
└── bin
└── fir.yue
/fir/version.yue:
--------------------------------------------------------------------------------
1 | "2.0.0"
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .vscode
2 | .cache
3 |
--------------------------------------------------------------------------------
/docs/fir-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daelvn/fir/HEAD/docs/fir-logo.png
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.lua linguist-generated
2 | *.rockspec linguist-generated
3 | *.yue linguist-language=MoonScript
4 | docs/* linguist-documentation
5 |
--------------------------------------------------------------------------------
/examples/Fir.docs.moon:
--------------------------------------------------------------------------------
1 | config:
2 | -- general config
3 | format: "markdown" -- fir.generic.emit.markdown
4 | name: "Fir/tests"
5 |
6 | -- language (moon/yue)
7 | language: {single: "--"}
8 |
9 | -- files
10 | input: {"examples/*.test.lua"}
11 | transform: (path) -> (path\match "examples/(.+)%.test.lua") .. ".md"
12 | output: "examples"
13 |
--------------------------------------------------------------------------------
/TODO.md:
--------------------------------------------------------------------------------
1 | ## To-do
2 |
3 | ```
4 | ├─ /home/daelvn/dev/fir/bin/fir.mp line 12: TODO Integrate this into alfons
5 | ├─ /home/daelvn/dev/fir/docs/cli.md line 19: TODO include backend and emit options
6 | ├─ /home/daelvn/dev/fir/docs/index.html line 5: TODO add opengraph meta tags
7 | └─ /home/daelvn/dev/fir/fir/generic/parser.mp line 76: TODO luassert tests (-- %?^?[TtFfenu:=~] .+)
8 |
9 | ```
10 |
--------------------------------------------------------------------------------
/examples/testable.moon:
--------------------------------------------------------------------------------
1 | ---# examples.testable #---
2 | -- Example document to generate tests and documentation from them
3 |
4 | --# main #--
5 |
6 | --- @function id :: a -> a
7 | --- Identity function
8 | --
9 | -- ?? 2 == id 1
10 | export id = (a) -> a
11 |
12 | --- @function map :: (a -> b) -> [a] -> [b]
13 | --- Map function
14 | --
15 | -- !! array = { 1, 2, 3 }
16 | -- ??~ ((map id) array), array
17 | export map = (fn) -> (l) -> [fn a for a in *l]
18 |
--------------------------------------------------------------------------------
/examples/Fir.test.moon:
--------------------------------------------------------------------------------
1 | config:
2 | -- general config
3 | format: "tests" --
4 | name: "Fir/tests"
5 | emit:
6 | print: 'tap'
7 | auto_unpack: true
8 |
9 | -- language (moon/yue)
10 | language: {single: "--"}
11 |
12 | -- files
13 | input: {"examples/testable.moon"}
14 | transform: (path) -> (path\match "examples/(.+)%.moon") .. ".test.moon"
15 | output: "examples"
16 | ignore: { -- formatted for filekit.fromGlob
17 | "*.test.lua"
18 | "module/*"
19 | }
20 |
21 | -- tests
22 | -- emit:
23 | -- unpack: { "field" }
24 | -- docs: false
25 |
--------------------------------------------------------------------------------
/Fir.moon:
--------------------------------------------------------------------------------
1 | config:
2 | name: "Fir"
3 | -- general config
4 | format: "markdown" -- fir.generic.emit.markdown
5 | emit:
6 | symbols_in_code: true
7 | inline_type: true
8 | tabs:
9 | use: "pymdownx"
10 |
11 | -- language (moon/yue)
12 | language: {single: "--"}
13 |
14 | -- files
15 | input: {
16 | "fir/**.yue"
17 | { "README.md", verbatim: true }
18 | { "fir/**.md", verbatim: true }
19 | } -- formatted for lpath.fs.glob
20 |
21 | transform: (path) ->
22 | if path\match "yue$"
23 | (path\match "fir/(.+)%.yue") .. ".md"
24 | elseif path\match "README.md"
25 | "index.md"
26 | else
27 | path\match "fir/(.+)"
28 |
29 | output: "docs"
30 |
31 | ignore: {
32 | "fir/version.yue"
33 | "fir/file.yue"
34 | }
35 |
--------------------------------------------------------------------------------
/rockspecs/fir-1.0-1.rockspec:
--------------------------------------------------------------------------------
1 | build = {
2 | install = {
3 | bin = {
4 | fir = "bin/fir.lua"
5 | }
6 | },
7 | modules = {
8 | ["fir.file"] = "fir/file.lua",
9 | ["fir.generic.backend"] = "fir/generic/backend.lua",
10 | ["fir.generic.emit.markdown"] = "fir/generic/emit/markdown.lua",
11 | ["fir.generic.parser"] = "fir/generic/parser.lua",
12 | ["fir.version"] = "fir/version.lua"
13 | },
14 | type = "builtin"
15 | }
16 | dependencies = {
17 | "lua >= 5.1",
18 | "argparse",
19 | "ansikit",
20 | "filekit"
21 | }
22 | description = {
23 | detailed = "fir extracts documentation comments from source code and builds trees out of them, so you can turn them into any other format.\n",
24 | homepage = "https://github.com/daelvn/fir",
25 | summary = "A language-agnostic documentation generator."
26 | }
27 | package = "fir"
28 | rockspec_format = "3.0"
29 | source = {
30 | tag = "v1.0",
31 | url = "git://github.com/daelvn/fir.git"
32 | }
33 | version = "1.0-1"
34 |
--------------------------------------------------------------------------------
/rockspecs/fir-1.1-1.rockspec:
--------------------------------------------------------------------------------
1 | build = {
2 | install = {
3 | bin = {
4 | fir = "bin/fir.lua"
5 | }
6 | },
7 | modules = {
8 | ["fir.file"] = "fir/file.lua",
9 | ["fir.generic.backend"] = "fir/generic/backend.lua",
10 | ["fir.generic.emit.markdown"] = "fir/generic/emit/markdown.lua",
11 | ["fir.generic.parser"] = "fir/generic/parser.lua",
12 | ["fir.version"] = "fir/version.lua"
13 | },
14 | type = "builtin"
15 | }
16 | dependencies = {
17 | "lua >= 5.1",
18 | "argparse",
19 | "ansikit",
20 | "filekit"
21 | }
22 | description = {
23 | detailed = "fir extracts documentation comments from source code and builds trees out of them, so you can turn them into any other format.\n",
24 | homepage = "https://github.com/daelvn/fir",
25 | summary = "A language-agnostic documentation generator."
26 | }
27 | package = "fir"
28 | rockspec_format = "3.0"
29 | source = {
30 | tag = "v1.1",
31 | url = "git://github.com/daelvn/fir.git"
32 | }
33 | version = "1.1-1"
34 |
--------------------------------------------------------------------------------
/rockspecs/fir-1.2-1.rockspec:
--------------------------------------------------------------------------------
1 | build = {
2 | install = {
3 | bin = {
4 | fir = "bin/fir.lua"
5 | }
6 | },
7 | modules = {
8 | ["fir.file"] = "fir/file.lua",
9 | ["fir.generic.backend"] = "fir/generic/backend.lua",
10 | ["fir.generic.emit.markdown"] = "fir/generic/emit/markdown.lua",
11 | ["fir.generic.parser"] = "fir/generic/parser.lua",
12 | ["fir.version"] = "fir/version.lua"
13 | },
14 | type = "builtin"
15 | }
16 | dependencies = {
17 | "lua >= 5.1",
18 | "argparse",
19 | "ansikit",
20 | "filekit"
21 | }
22 | description = {
23 | detailed = "fir extracts documentation comments from source code and builds trees out of them, so you can turn them into any other format.\n",
24 | homepage = "https://github.com/daelvn/fir",
25 | summary = "A language-agnostic documentation generator."
26 | }
27 | package = "fir"
28 | rockspec_format = "3.0"
29 | source = {
30 | tag = "v1.2",
31 | url = "git://github.com/daelvn/fir.git"
32 | }
33 | version = "1.2-1"
34 |
--------------------------------------------------------------------------------
/rockspecs/fir-1.3-1.rockspec:
--------------------------------------------------------------------------------
1 | build = {
2 | install = {
3 | bin = {
4 | fir = "bin/fir.lua"
5 | }
6 | },
7 | modules = {
8 | ["fir.file"] = "fir/file.lua",
9 | ["fir.generic.backend"] = "fir/generic/backend.lua",
10 | ["fir.generic.emit.markdown"] = "fir/generic/emit/markdown.lua",
11 | ["fir.generic.parser"] = "fir/generic/parser.lua",
12 | ["fir.version"] = "fir/version.lua"
13 | },
14 | type = "builtin"
15 | }
16 | dependencies = {
17 | "lua >= 5.1",
18 | "argparse",
19 | "ansikit",
20 | "filekit"
21 | }
22 | description = {
23 | detailed = "fir extracts documentation comments from source code and builds trees out of them, so you can turn them into any other format.\n",
24 | homepage = "https://github.com/daelvn/fir",
25 | summary = "A language-agnostic documentation generator."
26 | }
27 | package = "fir"
28 | rockspec_format = "3.0"
29 | source = {
30 | tag = "v1.3",
31 | url = "git://github.com/daelvn/fir.git"
32 | }
33 | version = "1.3-1"
34 |
--------------------------------------------------------------------------------
/rockspecs/fir-1.1.1-1.rockspec:
--------------------------------------------------------------------------------
1 | build = {
2 | install = {
3 | bin = {
4 | fir = "bin/fir.lua"
5 | }
6 | },
7 | modules = {
8 | ["fir.file"] = "fir/file.lua",
9 | ["fir.generic.backend"] = "fir/generic/backend.lua",
10 | ["fir.generic.emit.markdown"] = "fir/generic/emit/markdown.lua",
11 | ["fir.generic.parser"] = "fir/generic/parser.lua",
12 | ["fir.version"] = "fir/version.lua"
13 | },
14 | type = "builtin"
15 | }
16 | dependencies = {
17 | "lua >= 5.1",
18 | "argparse",
19 | "ansikit",
20 | "filekit"
21 | }
22 | description = {
23 | detailed = "fir extracts documentation comments from source code and builds trees out of them, so you can turn them into any other format.\n",
24 | homepage = "https://github.com/daelvn/fir",
25 | summary = "A language-agnostic documentation generator."
26 | }
27 | package = "fir"
28 | rockspec_format = "3.0"
29 | source = {
30 | tag = "v1.1.1",
31 | url = "git://github.com/daelvn/fir.git"
32 | }
33 | version = "1.1.1-1"
34 |
--------------------------------------------------------------------------------
/rockspecs/fir-1.1.2-1.rockspec:
--------------------------------------------------------------------------------
1 | build = {
2 | install = {
3 | bin = {
4 | fir = "bin/fir.lua"
5 | }
6 | },
7 | modules = {
8 | ["fir.file"] = "fir/file.lua",
9 | ["fir.generic.backend"] = "fir/generic/backend.lua",
10 | ["fir.generic.emit.markdown"] = "fir/generic/emit/markdown.lua",
11 | ["fir.generic.parser"] = "fir/generic/parser.lua",
12 | ["fir.version"] = "fir/version.lua"
13 | },
14 | type = "builtin"
15 | }
16 | dependencies = {
17 | "lua >= 5.1",
18 | "argparse",
19 | "ansikit",
20 | "filekit"
21 | }
22 | description = {
23 | detailed = "fir extracts documentation comments from source code and builds trees out of them, so you can turn them into any other format.\n",
24 | homepage = "https://github.com/daelvn/fir",
25 | summary = "A language-agnostic documentation generator."
26 | }
27 | package = "fir"
28 | rockspec_format = "3.0"
29 | source = {
30 | tag = "v1.1.2",
31 | url = "git://github.com/daelvn/fir.git"
32 | }
33 | version = "1.1.2-1"
34 |
--------------------------------------------------------------------------------
/rockspecs/fir-1.2.1-1.rockspec:
--------------------------------------------------------------------------------
1 | build = {
2 | install = {
3 | bin = {
4 | fir = "bin/fir.lua"
5 | }
6 | },
7 | modules = {
8 | ["fir.file"] = "fir/file.lua",
9 | ["fir.generic.backend"] = "fir/generic/backend.lua",
10 | ["fir.generic.emit.markdown"] = "fir/generic/emit/markdown.lua",
11 | ["fir.generic.parser"] = "fir/generic/parser.lua",
12 | ["fir.version"] = "fir/version.lua"
13 | },
14 | type = "builtin"
15 | }
16 | dependencies = {
17 | "lua >= 5.1",
18 | "argparse",
19 | "ansikit",
20 | "filekit"
21 | }
22 | description = {
23 | detailed = "fir extracts documentation comments from source code and builds trees out of them, so you can turn them into any other format.\n",
24 | homepage = "https://github.com/daelvn/fir",
25 | summary = "A language-agnostic documentation generator."
26 | }
27 | package = "fir"
28 | rockspec_format = "3.0"
29 | source = {
30 | tag = "v1.2.1",
31 | url = "git://github.com/daelvn/fir.git"
32 | }
33 | version = "1.2.1-1"
34 |
--------------------------------------------------------------------------------
/rockspecs/fir-1.3.1-1.rockspec:
--------------------------------------------------------------------------------
1 | build = {
2 | install = {
3 | bin = {
4 | fir = "bin/fir.lua"
5 | }
6 | },
7 | modules = {
8 | ["fir.file"] = "fir/file.lua",
9 | ["fir.generic.backend"] = "fir/generic/backend.lua",
10 | ["fir.generic.emit.markdown"] = "fir/generic/emit/markdown.lua",
11 | ["fir.generic.parser"] = "fir/generic/parser.lua",
12 | ["fir.version"] = "fir/version.lua"
13 | },
14 | type = "builtin"
15 | }
16 | dependencies = {
17 | "lua >= 5.1",
18 | "argparse",
19 | "ansikit",
20 | "filekit"
21 | }
22 | description = {
23 | detailed = "fir extracts documentation comments from source code and builds trees out of them, so you can turn them into any other format.\n",
24 | homepage = "https://github.com/daelvn/fir",
25 | summary = "A language-agnostic documentation generator."
26 | }
27 | package = "fir"
28 | rockspec_format = "3.0"
29 | source = {
30 | tag = "v1.3.1",
31 | url = "git://github.com/daelvn/fir.git"
32 | }
33 | version = "1.3.1-1"
34 |
--------------------------------------------------------------------------------
/rock.yml:
--------------------------------------------------------------------------------
1 | package: fir
2 | source:
3 | url: git://github.com/daelvn/fir.git
4 | description:
5 | summary: A language-agnostic documentation generator.
6 | detailed: >
7 | fir extracts documentation comments from source code and builds
8 | trees out of them, so you can turn them into any other format.
9 | homepage: https://github.com/daelvn/fir
10 | dependencies:
11 | - lua >= 5.1
12 | - argparse
13 | - ansikit
14 | - lpath
15 | - lrexlib-pcre2
16 | build:
17 | type: builtin
18 | modules:
19 | fir.generic.backend: fir/generic/backend.lua
20 | fir.generic.parser: fir/generic/parser.lua
21 | fir.generic.emit.markdown: fir/generic/emit/markdown.lua
22 | fir.generic.emit.tests: fir/generic/emit/tests.lua
23 | fir.file: fir/file.lua
24 | fir.version: fir/version.lua
25 | fir.error: fir/error.lua
26 | alfons.tasks.fir: alfons/tasks/fir.lua
27 | install:
28 | bin:
29 | fir: bin/fir.lua
30 |
--------------------------------------------------------------------------------
/ROADMAP.md:
--------------------------------------------------------------------------------
1 | # Roadmap
2 |
3 | ## General
4 |
5 | - Alfons taskfiles.
6 |
7 | ## Backend
8 |
9 | - `source` backend.
10 | - Backend that just returns all the source.
11 |
12 | - `generic+` backend (iterative).
13 | - Reads using `io.lines()`.
14 |
15 | ## Parser
16 |
17 | - ~~`generic` parser.~~
18 | - ~~Add `@table`~~
19 | - ~~Add `@field`~~
20 | - ~~Add `@variable`~~
21 | - ~~Add `@test`~~
22 |
23 | - `generic+` backend (iterative).
24 | - Reads using an iterator and is an iterator itself.
25 | - Parses line-by-line for efficiency (can hold on to lines).
26 |
27 | - `passthrough` parser.
28 | - `parse` = `id`
29 |
30 | - `source` parser.
31 | - Haddock can lex, highlight and link source files and do static type analysis on them. This will probably never get added to Fir but I would love to see it.
32 |
33 | ## Emitters
34 |
35 | - `highlight` emitter.
36 | - Highlights source code.
37 |
38 | - `json` emitter.
39 | - Emits documentation as JSON.
40 |
41 | - `haddock` emitter.
42 | - Emits HTML styled like the modern haddock.
43 |
44 | ## Frontends
45 |
46 | - `docsify` frontend.
47 | - Included by default.
--------------------------------------------------------------------------------
/rockspecs/fir-2.0.0-1.rockspec:
--------------------------------------------------------------------------------
1 | build = {
2 | install = {
3 | bin = {
4 | fir = "bin/fir.lua"
5 | }
6 | },
7 | modules = {
8 | ["alfons.tasks.fir"] = "alfons/tasks/fir.lua",
9 | ["fir.error"] = "fir/error.lua",
10 | ["fir.file"] = "fir/file.lua",
11 | ["fir.generic.backend"] = "fir/generic/backend.lua",
12 | ["fir.generic.emit.markdown"] = "fir/generic/emit/markdown.lua",
13 | ["fir.generic.emit.tests"] = "fir/generic/emit/tests.lua",
14 | ["fir.generic.parser"] = "fir/generic/parser.lua",
15 | ["fir.version"] = "fir/version.lua"
16 | },
17 | type = "builtin"
18 | }
19 | dependencies = {
20 | "lua >= 5.1",
21 | "argparse",
22 | "ansikit",
23 | "lpath"
24 | }
25 | description = {
26 | detailed = "fir extracts documentation comments from source code and builds trees out of them, so you can turn them into any other format.\n",
27 | homepage = "https://github.com/daelvn/fir",
28 | summary = "A language-agnostic documentation generator."
29 | }
30 | package = "fir"
31 | rockspec_format = "3.0"
32 | source = {
33 | tag = "v2.0.0",
34 | url = "git://github.com/daelvn/fir.git"
35 | }
36 | version = "2.0.0-1"
37 |
--------------------------------------------------------------------------------
/rockspecs/fir-2.0.0-2.rockspec:
--------------------------------------------------------------------------------
1 | build = {
2 | install = {
3 | bin = {
4 | fir = "bin/fir.lua"
5 | }
6 | },
7 | modules = {
8 | ["alfons.tasks.fir"] = "alfons/tasks/fir.lua",
9 | ["fir.error"] = "fir/error.lua",
10 | ["fir.file"] = "fir/file.lua",
11 | ["fir.generic.backend"] = "fir/generic/backend.lua",
12 | ["fir.generic.emit.markdown"] = "fir/generic/emit/markdown.lua",
13 | ["fir.generic.emit.tests"] = "fir/generic/emit/tests.lua",
14 | ["fir.generic.parser"] = "fir/generic/parser.lua",
15 | ["fir.version"] = "fir/version.lua"
16 | },
17 | type = "builtin"
18 | }
19 | dependencies = {
20 | "lua >= 5.1",
21 | "argparse",
22 | "ansikit",
23 | "lpath",
24 | "lrexlib-pcre2"
25 | }
26 | description = {
27 | detailed = "fir extracts documentation comments from source code and builds trees out of them, so you can turn them into any other format.\n",
28 | homepage = "https://github.com/daelvn/fir",
29 | summary = "A language-agnostic documentation generator."
30 | }
31 | package = "fir"
32 | rockspec_format = "3.0"
33 | source = {
34 | tag = "v2.0.0",
35 | url = "git://github.com/daelvn/fir.git"
36 | }
37 | version = "2.0.0-2"
38 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | This is free and unencumbered software released into the public domain.
2 |
3 | Anyone is free to copy, modify, publish, use, compile, sell, or
4 | distribute this software, either in source code form or as a compiled
5 | binary, for any purpose, commercial or non-commercial, and by any
6 | means.
7 |
8 | In jurisdictions that recognize copyright laws, the author or authors
9 | of this software dedicate any and all copyright interest in the
10 | software to the public domain. We make this dedication for the benefit
11 | of the public at large and to the detriment of our heirs and
12 | successors. We intend this dedication to be an overt act of
13 | relinquishment in perpetuity of all present and future rights to this
14 | software under copyright law.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
24 | For more information, please refer to
--------------------------------------------------------------------------------
/mkdocs.yml:
--------------------------------------------------------------------------------
1 | # yaml-language-server: $schema=https://squidfunk.github.io/mkdocs-material/schema.json
2 | site_name: Fir
3 | site_url: https://daelvn.github.io/fir
4 |
5 | repo_url: https://github.com/daelvn/fir
6 | repo_name: daelvn/fir
7 |
8 | extra_css:
9 | - stylesheets/extra.css
10 |
11 | extra:
12 | social:
13 | - icon: fontawesome/brands/github
14 | link: https://github.com/daelvn
15 |
16 | # FIXME: https://github.com/squidfunk/mkdocs-material/issues/7599
17 | plugins:
18 | - search
19 | - social:
20 | cards_layout_options:
21 | background_color: "#2d6934"
22 |
23 | markdown_extensions:
24 | - toc:
25 | permalink: true
26 | permalink_leading: true
27 | slugify: !!python/object/apply:pymdownx.slugs.slugify {}
28 | - admonition
29 | - pymdownx.details
30 | - pymdownx.superfences
31 | - attr_list
32 | - md_in_html
33 | - pymdownx.tabbed:
34 | alternate_style: true
35 |
36 | theme:
37 | name: material
38 | palette:
39 | primary: custom
40 | icon:
41 | logo: material/pine-tree
42 | annotation: material/star-four-points-circle-outline
43 | features:
44 | - navigation.tabs
45 | - navigation.indexes
46 | - navigation.footer
47 | - content.tabs.link
48 | - content.code.annotate
49 | # - toc.integrate
50 |
--------------------------------------------------------------------------------
/docs/generic/index.md:
--------------------------------------------------------------------------------
1 | # Generic module
2 |
3 | The generic module comes with Fir by default to provide a standard backend for parsing and emitting. By default, it can emit Markdown documentation, as well as [luassert](https://github.com/lunarmodules/luassert) tests.
4 |
5 | If you're looking for the tutorial to document your code using the default backend, it can be found [here](/fir/tutorial).
6 |
7 | ## The generic pipeline
8 |
9 | - The comments are extracted through `fir.generic.backend`. [Documentation here](/fir/generic/backend).
10 | - Then, they are parsed by `fir.generic.parser`. [Documentation here](/fir/generic/parser).
11 | - Lastly, they are passed through a language emitter (`fir.generic.emit.*`). [Documentation here](/fir/generic/emit/markdown).
12 |
13 | All the moving parts and data types are documented in their respective places.
14 |
15 | ## Integrating
16 |
17 | By creating functions that work with the data types specified in the other parts of the documentation, you're able to replace parts of the generic pipeline with your own parts. This is:want useful, for example, if you want to change the way the comments get parsed.
18 |
19 | !!! warning "About the CLI"
20 | The CLI was made to work only with the generic backend. You may have to roll your own CLI or adapt the existing one to use your own backend.
21 |
--------------------------------------------------------------------------------
/fir/generic/index.md:
--------------------------------------------------------------------------------
1 | # Generic module
2 |
3 | The generic module comes with Fir by default to provide a standard backend for parsing and emitting. By default, it can emit Markdown documentation, as well as [luassert](https://github.com/lunarmodules/luassert) tests.
4 |
5 | If you're looking for the tutorial to document your code using the default backend, it can be found [here](/fir/tutorial).
6 |
7 | ## The generic pipeline
8 |
9 | - The comments are extracted through `fir.generic.backend`. [Documentation here](/fir/generic/backend).
10 | - Then, they are parsed by `fir.generic.parser`. [Documentation here](/fir/generic/parser).
11 | - Lastly, they are passed through a language emitter (`fir.generic.emit.*`). [Documentation here](/fir/generic/emit/markdown).
12 |
13 | All the moving parts and data types are documented in their respective places.
14 |
15 | ## Integrating
16 |
17 | By creating functions that work with the data types specified in the other parts of the documentation, you're able to replace parts of the generic pipeline with your own parts. This is:want useful, for example, if you want to change the way the comments get parsed.
18 |
19 | !!! warning "About the CLI"
20 | The CLI was made to work only with the generic backend. You may have to roll your own CLI or adapt the existing one to use your own backend.
21 |
--------------------------------------------------------------------------------
/docs/stylesheets/extra.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --md-primary-fg-color: #2d6934;
3 | --md-primary-fg-color--light: #8dcc94;
4 | --md-primary-fg-color--dark: #1b2e1d;
5 | }
6 |
7 | .fancy-scrollbar {
8 | --sb-track-color: var(--md-primary-fg-color--light);
9 | --sb-thumb-color: var(--md-primary-fg-color--dark);
10 | --sb-size: 3px;
11 | }
12 |
13 | .fancy-scrollbar::-webkit-scrollbar {
14 | width: 3px;
15 | scrollbar-width: thin;
16 | }
17 |
18 | .fancy-scrollbar::-webkit-scrollbar-track {
19 | background: var(--sb-track-color);
20 | border-radius: 8px;
21 | }
22 |
23 | .fancy-scrollbar::-webkit-scrollbar-thumb {
24 | background: var(--sb-thumb-color);
25 | border-radius: 8px;
26 |
27 | }
28 |
29 | @supports not selector(::-webkit-scrollbar) {
30 | .fancy-scrollbar {
31 | scrollbar-color: var(--sb-thumb-color)
32 | var(--sb-track-color);
33 | }
34 | }
35 |
36 | .md-typeset .fir-symbol {
37 | display: flex;
38 | align-items: center;
39 | font-size: 0.8em;
40 | font-family: monospace, monospace;
41 | overflow-x: auto;
42 | white-space: nowrap;
43 | & h3 {
44 | display: flex;
45 | align-items: center;
46 | margin: 0;
47 | }
48 | & span {
49 | margin: 0;
50 | margin-top: 0.4em;
51 | line-height: 1.4;
52 | }
53 | }
54 |
55 | .md-grid {
56 | max-width: 71rem;
57 | }
58 |
--------------------------------------------------------------------------------
/fir/error.yue:
--------------------------------------------------------------------------------
1 | ---# fir.error #---
2 | -- @internal
3 | -- Error reporting utilities
4 | import style from 'ansikit.style'
5 |
6 | --# main #--
7 |
8 | --- @type Context
9 | --- All context that can be used to build a context message
10 | --:moon Context
11 | -- Context {
12 | -- source: string?
13 | -- tag: string?
14 | -- tag_type: string?
15 | -- section: string?
16 | -- tree: GenericAST.title?
17 | -- extra: [string]?
18 | -- }
19 |
20 | --- @function buildContext :: context:@@@Context@@@
21 | --- Builds a context array out of a context object
22 | export buildContext = =>
23 | lines = {
24 | (@tag and @tag_type and "in #{@tag_type}: %{magenta}#{@tag}%{reset}" or false)
25 | (@section and "in section: %{magenta}#{@section}%{reset}" or false)
26 | (@tree and "in tree: %{magenta}#{@tree}%{reset}" or false)
27 | ...(@extra or {})
28 | }
29 | return [a for a in *lines when a] -- filter
30 |
31 | --- @function errorOut :: msg:string, context:[string]
32 | --- Prints an error and exits the program with an error code
33 | export errorOut = (msg, context) ->
34 | print style "%{red}!!! #{context.source}: #{msg}"
35 | -- XXX: Yuescript currently eliminates the function call when results are not assigned.
36 | -- `_=` is a workaround while I check if this is intended behavior.
37 | do _= [print style "%{red} #{ctx_line}" for ctx_line in *buildContext context]
38 | os.exit 1
39 |
--------------------------------------------------------------------------------
/fir/file.yue:
--------------------------------------------------------------------------------
1 | -- fir.file (originally alfons.file, alfons.env)
2 | -- Gets the contents of a firfile
3 | fs = require "filekit"
4 |
5 | readMoon = (file) ->
6 | local content
7 | with fs.safeOpen file, "r"
8 | -- check that we could open correctly
9 | if .error
10 | return nil, "Could not open #{file}: #{.error}"
11 | -- read and compile
12 | import to_lua from require "moonscript.base"
13 | content, err = to_lua \read "*a"
14 | -- check that we could compile correctly
15 | unless content
16 | return nil, "Could not read or parse #{file}: #{err}"
17 | \close!
18 | -- return
19 | return content
20 |
21 | readLua = (file) ->
22 | local content
23 | with fs.safeOpen file, "r"
24 | -- check that we could open correctly
25 | if .error
26 | return nil, "Could not open #{file}: #{.error}"
27 | -- read and compile
28 | content = \read "*a"
29 | -- check that we could compile correctly
30 | unless content
31 | return nil, "Could not read #{file}: #{content}"
32 | \close!
33 | -- return
34 | return content
35 |
36 | loadEnv = (content, env) ->
37 | local fn
38 | switch _VERSION
39 | -- use loadstring on 5.1
40 | when "Lua 5.1"
41 | fn, err = loadstring content
42 | unless fn
43 | return nil, "Could not load Firfile content (5.1): #{err}"
44 | setfenv fn, env
45 | -- use load otherwise
46 | when "Lua 5.2", "Lua 5.3", "Lua 5.4"
47 | fn, err = load content, "Fir", "t", env
48 | unless fn
49 | return nil, "Could not load Firfile content (5.2+): #{err}"
50 | -- return
51 | return fn
52 |
53 | { :readMoon, :readLua, :loadEnv }
--------------------------------------------------------------------------------
/docs/generic/backend.md:
--------------------------------------------------------------------------------
1 | # fir.generic.backend
2 |
3 | A generic implementation of a backend for the Fir
4 | documentation generator.
5 |
6 | This specific implementation uses a `language` module (defaults to `fir.generic.languages`) to
7 | parse comments from any file.
8 |
9 | Check out an example output of this backend [here](/fir/examples/generic-backend.html).
10 |
11 | ## API
12 |
13 | This is the API provided to work with the generic backend.
14 |
15 | | Element | Summary |
16 | |---------|---------|
17 | | **Functions** | |
18 | | [extract](#extract) | Extracts comment from a string separated by newlines. |
19 | | **Types** | |
20 | | [GenericComment](#GenericComment) | Comment returned by [`extract`](#extract). |
21 | | [Language](#Language) | Language type accepted by [`extract`](#extract). |
22 |
23 |
24 | ### extract
25 | :: input:string, language?:Language, options?:table -> comments:[[GenericComment](/fir/generic/backend#GenericComment)]
26 |
27 |
28 |
29 | Extracts comment from a string separated by newlines.
30 |
31 | - `patterns:boolean` (`false`): Whether to use patterns for the language fields and ignore string or not.
32 | - `ignore:string` (`"///"`): String used to determine when to start or stop ignoring comments.
33 | - `merge:boolean` (`true`): Whether to merge adjacent single-line comments.
34 | - `paragraphs:boolean` (`true`): Whether to split multi-line comments by empty strings (`""`).
35 |
36 |
37 | ### GenericComment
38 |
39 |
40 | Comment returned by [`extract`](#extract).
41 |
42 |
43 | === "Format"
44 |
45 | ```moon
46 | GenericComment {
47 | start :: number
48 | end :: number
49 | content :: [string]
50 | }
51 | ```
52 |
53 |
54 |
55 | ### Language
56 |
57 |
58 | Language type accepted by [`extract`](#extract).
59 |
60 |
61 | === "Format"
62 |
63 | ```moon
64 | Language {
65 | single :: string
66 | multi :: [string]
67 | extensions :: [string]
68 | }
69 | ```
70 |
71 |
--------------------------------------------------------------------------------
/examples/testable.test.moon:
--------------------------------------------------------------------------------
1 | ---# Tests for examples.testable #---
2 | -- This test suite was automatically generated from documentation comments,
3 | -- the tests are embedded in the code itself. Do not edit this file.
4 | assert = require 'luassert'
5 | M = require 'examples.testable'
6 | setmetatable _ENV, {['__index']: M}
7 |
8 | --///--
9 | -- argument and tag initialization
10 | args, tags = {...}, {}
11 | for _, v in ipairs args do tags[v] = true
12 | --///--
13 | print 'TAP version 14'
14 | print '1..2'
15 | --///--
16 | -- counters
17 | FIR_SUCCESSES, FIR_FAILURES, FIR_PENDINGS = 0, 0, 0
18 | -- incrementing functions
19 | FIR_SINCR, FIR_FINCR, FIR_PINCR = (() -> FIR_SUCCESSES = FIR_SUCCESSES + 1), (() -> FIR_FAILURES = FIR_FAILURES + 1), (() -> FIR_PENDINGS = FIR_PENDINGS + 1)
20 | -- other functions
21 | FIR_SUCCEED = (current, count) -> print 'ok ' .. count .. ' - ' .. current
22 | FIR_FAIL = (current, count, err) -> print 'not ok ' .. count .. ' - ' .. current .. ': ' .. (string.gsub err, '[\r\n]+', '')
23 | FIR_PEND = (current, count) -> print 'not ok ' .. count .. ' - ' .. current .. ' # SKIP'
24 | FIR_RESULT = (current) ->
25 | -- marker for pending tests
26 | FIR_NEXT_PENDING = false
27 | --///--
28 |
29 | --# General #--
30 | -- Tests for the whole file are placed here.
31 |
32 | --# main #--
33 | -- Tests for main.
34 |
35 | --- @test main#1
36 | --- Test for element main #1
37 | -- - **Type:** `test`
38 | --:moon Test
39 | -- 2 == id 1
40 | xpcall (() ->
41 | if FIR_NEXT_PENDING
42 | FIR_PINCR!
43 | FIR_NEXT_PENDING = false
44 | FIR_PEND 'main#1', '1'
45 | return
46 | assert.truthy 2 == id 1
47 | FIR_SUCCEED 'main#1', '1'
48 | FIR_SINCR!
49 | ), ((err) ->
50 | FIR_FAIL 'main#1', '1', err
51 | FIR_FINCR!
52 | )
53 |
54 | array = { 1, 2, 3 }
55 | --- @test main#2
56 | --- Test for element main #2
57 | -- - **Type:** `luassert-test` (same)
58 | --:moon Test
59 | -- ((map id) array), array
60 | xpcall (() ->
61 | if FIR_NEXT_PENDING
62 | FIR_PINCR!
63 | FIR_NEXT_PENDING = false
64 | FIR_PEND 'main#2', '2'
65 | return
66 | assert.same ((map id) array), array
67 | FIR_SUCCEED 'main#2', '2'
68 | FIR_SINCR!
69 | ), ((err) ->
70 | FIR_FAIL 'main#2', '2', err
71 | FIR_FINCR!
72 | )
73 |
74 | --# `id`
75 |
76 | --# `map`
77 |
78 | FIR_RESULT!
--------------------------------------------------------------------------------
/Alfons.yue:
--------------------------------------------------------------------------------
1 | tasks:
2 | always: => load 'fir'
3 | --- @task make Install a local version
4 | --- @option make [v] Version to make
5 | make: => sh "rockbuild -m --delete #{@v}" if @v
6 | --- @task release Create and upload a release of Alfons using %{magenta}`rockbuild`%{reset}.
7 | --- @option release [v] Version to release
8 | release: => sh "rockbuild -m -t #{@v} u" if @v
9 | --- @task compile Compile all MoonScript and Yuescript
10 | compile: =>
11 | sh "moonc #{file}" for file in wildcard "fir/**.moon"
12 | sh "yue bin"
13 | sh "yue fir"
14 | sh "yue alfons/tasks"
15 | --- @task clean Clean all generated Lua files
16 | clean: =>
17 | show "Cleaning files"
18 | for file in wildcard "**.lua"
19 | fs.remove file
20 | --- @task docs Manage documentation
21 | --- @option docs [serve] Open a preview of the documentation
22 | --- @option docs [build] Builds a static site from the documentation
23 | docs: =>
24 | logo_path = '/fir/fir-logo.png'
25 | if @generate
26 | tasks.fir
27 | generate: true
28 | writer: (_path, content) ->
29 | if _path\match 'docs/index.md'
30 | writefile _path, content
31 | \gsub '> %[!TIP%]\n> ', '!!! tip\n '
32 | \gsub '%]%(#([a-z])(.-)%)', (initial, rest) -> "](##{initial\upper!}#{rest})"
33 | \gsub '
', "{ width=128 height=128 align=left }"
34 | else
35 | writefile _path, content
36 | config:
37 | name: "Fir"
38 | -- general config
39 | format: "markdown"
40 | emit:
41 | symbols_in_code: true
42 | inline_type: true
43 | tabs:
44 | use: "pymdownx"
45 |
46 | -- language (moon/yue)
47 | language: {single: "--"}
48 |
49 | -- files
50 | input: {
51 | "fir/**.yue"
52 | { "README.md", verbatim: true }
53 | { "fir/**.md", verbatim: true }
54 | }
55 |
56 | transform: (path) ->
57 | if path\match "yue$"
58 | (path\match "fir/(.+)%.yue") .. ".md"
59 | elseif path\match "README.md"
60 | "index.md"
61 | else
62 | path\match "fir/(.+)"
63 |
64 | output: "docs"
65 |
66 | ignore: {
67 | "fir/version.yue"
68 | "fir/file.yue"
69 | }
70 | if @serve
71 | sh "mkdocs serve"
72 | if @build
73 | sh "mkdocs build"
74 |
--------------------------------------------------------------------------------
/docs/tutorial/alfons.md:
--------------------------------------------------------------------------------
1 | # Using from Alfons
2 |
3 | As of Fir 2, you can use Fir directly from Alfons without having to go through a shell interface or have a separate configuration file.
4 | The integration is installed automatically when you install the `fir` LuaRocks package.
5 |
6 | ## Loading
7 |
8 | It is recommended that you load external tasks in the `always` task. In rare cases (using the Alfons API) this might be disabled.
9 |
10 | === "Lua"
11 | ```lua
12 | function always()
13 | load "fir"
14 | end
15 | ```
16 |
17 | === "MoonScript"
18 | ```moon
19 | tasks:
20 | always: => load 'fir'
21 | ```
22 |
23 | ## Using
24 |
25 | You can use Fir from Alfons by calling `tasks.fir` once it is loaded (which should be always). The task takes the following arguments:
26 |
27 | | **Argument** | **Type** | **Default** | **Meaning** |
28 | |--|--|--|--|
29 | | `generate` | Boolean | None | Equivalent to CLI `generate` command |
30 | | `dump` | Boolean | None | Equivalent to CLI `dump` command |
31 | | `reader` | `path:string -> content:string` | Alfons `readfile` | Function that reads the content of a file |
32 | | `writer` | `path:string, content:string -> void` | Alfons `writefile` | Function that writes the content of a file |
33 | | `config` | Table | None | A configuration as described for `Fir.moon`/`Fir.lua` |
34 |
35 | If you have a Firfile that looks like the following:
36 |
37 | === "Lua"
38 | ```lua
39 | name = "Fir"
40 | format = "markdown"
41 | ```
42 |
43 | === "MoonScript"
44 | ```moon
45 | config:
46 | name: 'Fir'
47 | format: 'markdown'
48 | ```
49 |
50 | Then you would call Fir in Alfons as such:
51 |
52 | === "Lua"
53 | ```lua
54 | function use_fir()
55 | tasks.fir {
56 | generate = true,
57 | config = {
58 | name = "Fir",
59 | format = "markdown"
60 | }
61 | }
62 | end
63 | ```
64 |
65 | === "MoonScript"
66 | ```moon
67 | tasks:
68 | use_fir: =>
69 | tasks.fir generate: true, config:
70 | name: 'Fir'
71 | format: 'markdown'
72 | ```
73 |
74 | ### `reader`/`writer`
75 |
76 | It is possible to configure `reader` and `writer` so that you can apply transformations to the files being read or written. For example, if you are copying over your `README.md` to an `index.md` like Fir does, you might want to change the syntax of some components like admonitions. This is how you could do that:
77 |
78 | === "Lua"
79 | ```lua
80 | function generate_docs()
81 | tasks.fir {
82 | generate = true,
83 | writer = function(path, content)
84 | if path == "README.md" then
85 | writefile(path, content:gsub('> [!TIP]\n> ', '!!! tip\n '))
86 | else
87 | writefile(path, content)
88 | end
89 | end,
90 | config = {
91 | name = "Fir",
92 | format = "markdown"
93 | }
94 | }
95 | end
96 | ```
97 |
98 | === "MoonScript"
99 | ```moon
100 | tasks:
101 | generate_docs: =>
102 | tasks.fir generate: true,
103 | writer: (path, content) =>
104 | if path == 'README.md'
105 | writefile path, content\gsub '> [!TIP]\n> ', '!!! tip\n '
106 | else
107 | writefile path, content
108 | config:
109 | name: 'Fir'
110 | format: 'markdown'
111 | ```
112 |
--------------------------------------------------------------------------------
/docs/generic/parser.md:
--------------------------------------------------------------------------------
1 | # fir.generic.parser
2 |
3 | A parser that works with the format provided by the [generic backend](/fir/generic/backend).
4 |
5 | You can see an example of parser output [here](/fir/examples/generic-parser.html).
6 |
7 | ## Helpers
8 |
9 | Several functions that aid in the process of parsing.
10 |
11 | | Element | Summary |
12 | |---------|---------|
13 | | **Functions** | |
14 | | [determineSummaryBoundary](#determineSummaryBoundary) | Gets the boundary line where a summary ends and the description begins |
15 | | [parseDescription](#parseDescription) | Parses codeblocks, tags, headers and normal text in descriptions. |
16 | | **Types** | |
17 | | [DescriptionLine](#DescriptionLine) | A single element in a description returned by `parseDescription` |
18 |
19 |
20 | ### determineSummaryBoundary
21 | :: content:[GenericComment](/fir/generic/backend#GenericComment).content, lead:string -> boundary:number
22 |
23 |
24 |
25 | Gets the boundary line where a summary ends and the description begins
26 |
27 | - Gets the boundary line where a summary ends and the description begins
28 |
29 |
30 | ### parseDescription
31 | :: description:[string] -> description:[[DescriptionLine](/fir/generic/parser#DescriptionLine)], tags:[string]
32 |
33 |
34 |
35 | Parses codeblocks, tags, headers and normal text in descriptions.
36 |
37 |
38 | #### Notes
39 |
40 | - In a codeblock, the first character of every line is removed (for a space).
41 |
42 | #### Supported tags
43 |
44 | - `@internal` - Adds an `internal` true flag to the element.
45 |
46 |
47 | ### DescriptionLine
48 |
49 |
50 | A single element in a description returned by `parseDescription`
51 |
52 |
53 | === "Format"
54 |
55 | ```moon
56 | DescriptionLine {
57 | type :: string (text|snippet|header)
58 | content :: [string]
59 | language? :: string -- only when type is snippet
60 | title? :: string -- only when type is snippet
61 | n? :: number -- only when type is header
62 | }
63 | ```
64 |
65 |
66 | ## API
67 |
68 | This is the API provided to work with the generic parser.
69 |
70 | | Element | Summary |
71 | |---------|---------|
72 | | **Functions** | |
73 | | [parse](#parse) | Parses a list of GenericComments into a GenericAST |
74 | | **Types** | |
75 | | [GenericAST](#GenericAST) | The AST produced by `parse`. |
76 |
77 |
78 | ### parse
79 | :: comments:[[GenericComment](/fir/generic/backend#GenericComment)], language:Language -> ast:[GenericAST](/fir/generic/parser#GenericAST)
80 |
81 |
82 |
83 | Parses a list of GenericComments into a GenericAST
84 |
85 | - Parses a list of GenericComments into a GenericAST
86 |
87 |
88 | ### GenericAST
89 |
90 |
91 | **Aliases:** `GenericSection, GenericSectionContent, GenericElement`
92 |
93 | The AST produced by `parse`.
94 |
95 |
96 | === "Format"
97 |
98 | ```moon
99 | GenericAST {
100 | title :: string
101 | description :: [DescriptionLine]
102 | [n] :: GenericSection { -- where n is the ID of the section
103 | section :: GenericSectionDetails {
104 | id :: number
105 | name :: string
106 | description :: [DescriptionLine]
107 | }
108 | content :: GenericSectionContent {
109 | [name] :: GenericElement {
110 | is :: string (type|function|constant|class)
111 | description :: [DescriptionLine]
112 | name :: [string] -- an array, meant to also contain aliases
113 | summary :: string
114 | type :: string -- only when `is` is `"function"` or `"constant"`
115 | }
116 | }
117 | }
118 | }
119 | ```
120 |
121 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Fir 2
2 |
3 | 
4 | 
5 | 
6 | 
7 |
8 |
9 |
10 | Fir is a language-agnostic documentation generator framework. It is fully modular, which means you can mix a comment extractor with a different comment parser and emitter (but you aren't going to go through the hassle of writing anything). A set of generic modules for generating Markdown documentation is provided, don't expect much more (unless you press me to write more).
11 |
12 |
13 |
14 | > [!TIP]
15 | > Check out the new Fir 2 update! Renewed, with symbol support, verbatim inputs, and a little more. [Changelog below](#2).
16 |
17 | ## Table of contents
18 |
19 |
20 | - [Fir 2](#fir-2)
21 | - [Table of contents](#table-of-contents)
22 | - [Install](#install)
23 | - [Compiling from source](#compiling-from-source)
24 | - [Changelog](#changelog)
25 | - [Documentation](#documentation)
26 | - [Extra features](#extra-features)
27 | - [License](#license)
28 |
29 |
30 | ## Install
31 |
32 | You can install Fir via LuaRocks:
33 |
34 | ```
35 | $ luarocks install fir
36 | ```
37 |
38 | ### Compiling from source
39 |
40 | If you want to compile from source, you will need to install the following dependencies from LuaRocks:
41 | - `argparse` (CLI)
42 | - `ansikit` (CLI, optional if using CLI with `--silent`)
43 | - `lpath` (Library, CLI)
44 | - `lrexlib-pcre2` (Library, CLI)
45 | - `yuescript` (Compiling)
46 | - `alfons` (Compiling)
47 | - `rockbuild` (Compiling)
48 |
49 | Then, simply run `alfons compile make -v 1.0`. To clean the project, simply `alfons clean`.
50 |
51 | ## Changelog
52 |
53 | ### 2.x
54 |
55 | - **2.0.0** (04.10.2024)
56 |
57 | Fir 2 is being released! Out of the necessity to use a more modern documentation for my other, better project, [Alfons](https://github.com/daelvn/alfons), I decided to update this project. Here's the main changes:
58 |
59 | - **Revamped test suite generator.** The `tests` format now actually works, and generates robust tests from your documentation that can check for many conditions. It's even [TAP](https://testanything.org/tap-version-14-specification.html) compliant for your CI needs!
60 | - **Symbol linking.** Fir CLI using the Generic backend will now collect all symbols and let you link to them with `@@@` syntax.
61 | - **Verbatim inputs.** Those inputs will be copied over directly to the output folder.
62 | - **Alfons integration.** Fir automatically installs a loadable [Alfons](https://github.com/daelvn/alfons) task, so you can integrate it into your Alfons workflows. Use `load 'fir'` to load it.
63 | - **Symbol summaries can now be longer than one line.** The summary ends on the first line that does not begin with a \ character.
64 | - **Aliases are deranked.** Now (under the Generic backend) aliases are not put beside the name.
65 | - **Better error reporting.** No more unparsable errors.
66 | - **Output for MkDocs.** The Generic Markdown emitter now has specific MkDocs options.
67 | - **Updated documentation.** It's easier to understand how the fuck to use this.
68 | - **Documentation now uses [Material for MkDocs](https://squidfunk.github.io/mkdocs-material).** For a more clean, updated and powerful look and feel.
69 | - **Switched to more robust libraries.** [lpath](https://github.com/starwing/lpath) for FS operations, [lrexlib on PCRE2 backend](https://github.com/rrthomas/lrexlib) for symbol syntax.
70 | - **Updated from MoonPlus to Yuescript.** MoonPlus has not been a thing for years and instead transitioned into being [Yuescript](https://yuescript.org).
71 |
72 | ## Documentation
73 |
74 | Check out the documentation [here](//daelvn.github.io/fir/).
75 |
76 | ## Extra features
77 |
78 | I kindly welcome new features that anyone wants to suggest or add! Feel free to open an issue or PR.
79 |
80 | ## License
81 |
82 | This project is [Unlicensed](LICENSE.md), and released to the public domain. Do whatever you wish with it!
83 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | # Fir 2
2 |
3 | 
4 | 
5 | 
6 | 
7 |
8 | { width=128 height=128 align=left }
9 |
10 | Fir is a language-agnostic documentation generator framework. It is fully modular, which means you can mix a comment extractor with a different comment parser and emitter (but you aren't going to go through the hassle of writing anything). A set of generic modules for generating Markdown documentation is provided, don't expect much more (unless you press me to write more).
11 |
12 |
13 |
14 | !!! tip
15 | Check out the new Fir 2 update! Renewed, with symbol support, verbatim inputs, and a little more. [Changelog below](#2).
16 |
17 | ## Table of contents
18 |
19 |
20 | - [Fir 2](#Fir-2)
21 | - [Table of contents](#Table-of-contents)
22 | - [Install](#Install)
23 | - [Compiling from source](#Compiling-from-source)
24 | - [Changelog](#Changelog)
25 | - [Documentation](#Documentation)
26 | - [Extra features](#Extra-features)
27 | - [License](#License)
28 |
29 |
30 | ## Install
31 |
32 | You can install Fir via LuaRocks:
33 |
34 | ```
35 | $ luarocks install fir
36 | ```
37 |
38 | ### Compiling from source
39 |
40 | If you want to compile from source, you will need to install the following dependencies from LuaRocks:
41 | - `argparse` (CLI)
42 | - `ansikit` (CLI, optional if using CLI with `--silent`)
43 | - `lpath` (Library, CLI)
44 | - `lrexlib-pcre2` (Library, CLI)
45 | - `yuescript` (Compiling)
46 | - `alfons` (Compiling)
47 | - `rockbuild` (Compiling)
48 |
49 | Then, simply run `alfons compile make -v 1.0`. To clean the project, simply `alfons clean`.
50 |
51 | ## Changelog
52 |
53 | ### 2.x
54 |
55 | - **2.0.0** (04.10.2024)
56 |
57 | Fir 2 is being released! Out of the necessity to use a more modern documentation for my other, better project, [Alfons](https://github.com/daelvn/alfons), I decided to update this project. Here's the main changes:
58 |
59 | - **Revamped test suite generator.** The `tests` format now actually works, and generates robust tests from your documentation that can check for many conditions. It's even [TAP](https://testanything.org/tap-version-14-specification.html) compliant for your CI needs!
60 | - **Symbol linking.** Fir CLI using the Generic backend will now collect all symbols and let you link to them with `@@@` syntax.
61 | - **Verbatim inputs.** Those inputs will be copied over directly to the output folder.
62 | - **Alfons integration.** Fir automatically installs a loadable [Alfons](https://github.com/daelvn/alfons) task, so you can integrate it into your Alfons workflows. Use `load 'fir'` to load it.
63 | - **Symbol summaries can now be longer than one line.** The summary ends on the first line that does not begin with a \ character.
64 | - **Aliases are deranked.** Now (under the Generic backend) aliases are not put beside the name.
65 | - **Better error reporting.** No more unparsable errors.
66 | - **Output for MkDocs.** The Generic Markdown emitter now has specific MkDocs options.
67 | - **Updated documentation.** It's easier to understand how the fuck to use this.
68 | - **Documentation now uses [Material for MkDocs](https://squidfunk.github.io/mkdocs-material).** For a more clean, updated and powerful look and feel.
69 | - **Switched to more robust libraries.** [lpath](https://github.com/starwing/lpath) for FS operations, [lrexlib on PCRE2 backend](https://github.com/rrthomas/lrexlib) for symbol syntax.
70 | - **Updated from MoonPlus to Yuescript.** MoonPlus has not been a thing for years and instead transitioned into being [Yuescript](https://yuescript.org).
71 |
72 | ## Documentation
73 |
74 | Check out the documentation [here](//daelvn.github.io/fir/).
75 |
76 | ## Extra features
77 |
78 | I kindly welcome new features that anyone wants to suggest or add! Feel free to open an issue or PR.
79 |
80 | ## License
81 |
82 | This project is [Unlicensed](LICENSE.md), and released to the public domain. Do whatever you wish with it!
83 |
--------------------------------------------------------------------------------
/docs/generic/emit/markdown.md:
--------------------------------------------------------------------------------
1 | # fir.generic.emit.markdown
2 |
3 | An emitter that works with a [`GenericAST`](/fir/generic/parser#GenericAST) and turns it into markdown.
4 |
5 | ## API
6 |
7 | This is the API provided to work with the generic markdown emitter.
8 |
9 | | Element | Summary |
10 | |---------|---------|
11 | | **Functions** | |
12 | | [emit](#emit) | Emits Markdown from a [`GenericAST`](/fir/generic/parser#GenericAST) |
13 | | [emitDescription](#emitDescription) | Emits Markdown from the description of an element. |
14 | | [emitSection](#emitSection) | Emits Markdown from a [`GenericSection`](/fir/generic/parser#GenericAST) |
15 | | [listAllSymbols](#listAllSymbols) | Locates all symbols in an AST |
16 | | [replaceSymbols](#replaceSymbols) | Returns a string with the symbol syntax replaced with its documentation location |
17 |
18 |
19 | ### emit
20 | :: ast:[GenericAST](/fir/generic/parser#GenericAST), options:table -> markdown:string
21 |
22 |
23 |
24 | Emits Markdown from a [`GenericAST`](/fir/generic/parser#GenericAST)
25 |
26 | - `tabs:table`: Adds tabbed syntax to the final output.
27 | - `use:boolean|"docsify"|"pymdownx"` (`false`): Outputs tab syntax, either [docsify-tabs](https://jhildenbiddle.github.io/docsify-tabs/#/) or [pymdownx.tabbed](https://facelessuser.github.io/pymdown-extensions/extensions/tabbed/#syntax).
28 | - `docsify_header:string` (`"####"`): **Unused**. Headers to use for docsify-tabs.
29 | - `all:boolean` (`false`): Also emits hidden elements.
30 | - `sections:table`: Settings for sections.
31 | - `columns:table`: Settings for columns.
32 | - `[n]:table`: (where `n` can be any number or `"*"`). Specifies the column names for section `n` (with fallback).
33 | - `[1]:string` (`"Element"`) - Left column
34 | - `[2]:string` (`"Summary"`) - Right column
35 | - `types:table`: Aliases for types.
36 | - `[type]:string`: (where `type` can be any of the types supported by [GenericAST](generic/parser.md#GenericAST)). Default values include `type=Types` and `function=Functions` among others.
37 | - `symbols:{string:string}`: Map of symbols to their documentation locations
38 | - `symbols_in_code:boolean|'mkdocs'` (false): Whether to link symbols inside code. Only supports MkDocs.
39 |
40 |
41 | ### emitDescription
42 | :: desc:[[DescriptionLine](/fir/generic/parser#DescriptionLine)], options:table -> markdown:string
43 |
44 |
45 |
46 | Emits Markdown from the description of an element.
47 |
48 | - `tabs:table`: Adds tabbed syntax to the final output.
49 | - `use:boolean|"docsify"|"pymdownx"` (`false`): Outputs tab syntax, either [docsify-tabs](https://jhildenbiddle.github.io/docsify-tabs/#/) or [pymdownx.tabbed](https://facelessuser.github.io/pymdown-extensions/extensions/tabbed/#syntax).
50 | - `docsify_header:string` (`"####"`): Headers to use for docsify-tabs.
51 | - `symbols:{string:string}`: Map of symbols to their documentation location
52 | - `symbols_in_code:boolean|'mkdocs'` (false): Whether to link symbols inside code. Only supports MkDocs.
53 |
54 | #### Recognized options
55 |
56 | - `headerOffset:number` (`1`): Offsets the header levels by n
57 |
58 |
59 | ### emitSection
60 | :: section:[GenericSection](/fir/generic/parser#GenericAST), options:table -> markdown:string
61 |
62 |
63 |
64 | Emits Markdown from a [`GenericSection`](/fir/generic/parser#GenericAST)
65 |
66 |
67 |
68 |
69 | ### listAllSymbols
70 | :: ast:[GenericAST](/fir/generic/parser#GenericAST) -> {[symbol: string]: location:string}
71 |
72 |
73 |
74 | Locates all symbols in an AST
75 |
76 | - `module:string`: Module string to use
77 | - `url_prefix:string`: Prefix of all documentation urls
78 |
79 |
80 | ### replaceSymbols
81 | :: str:string, symbols:{string:string} -> str:string
82 |
83 |
84 |
85 | Returns a string with the symbol syntax replaced with its documentation location
86 |
87 | ``
88 |
--------------------------------------------------------------------------------
/alfons/tasks/fir.yue:
--------------------------------------------------------------------------------
1 | tasks:
2 | fir: =>
3 | import 'fir.version' as FIR_VERSION
4 | prints "%{bold blue}Fir #{FIR_VERSION} (in Alfons mode)"
5 |
6 | -- set reader/writer
7 | @reader or= readfile
8 | @writer or= writefile
9 |
10 | -- arrow
11 | printArrow = (txt) -> prints "%{blue}==>%{white} #{txt}" unless @s or @silent
12 | printError = (txt) -> prints "%{red}#{txt}" unless @s or @silent
13 | printPlus = (txt) -> prints "%{green}+ #{txt}" unless @s or @silent
14 | printMini = (txt) -> prints "%{yellow}-> #{txt}" unless @s or @silent
15 |
16 | -- mkdir
17 | mkdirFor = (_path) ->
18 | _path = _path\match "(.+)/.-"
19 | sep, pStr = fsinfo.sep, ""
20 | for dir in _path\gmatch "[^" .. sep .. "]+" do
21 | pStr = pStr .. dir .. sep
22 | fs.mkdir pStr
23 | -- fromGlob reimplementation
24 | fromGlob = (pattern) -> [node for node in fs.glob pattern]
25 | -- reduce
26 | reduce = (array, callback, initial) ->
27 | accumulator = initial and initial or array[1]
28 | start = initial and 1 or 2
29 | for i = start, #array
30 | accumulator = callback accumulator, array[i]
31 | return accumulator
32 | -- flatten
33 | flatten = (arr) -> arr |> reduce ((acc, val) -> [ ...acc, ...val ]), []
34 | -- makes a glob pattern filekit-compatible
35 | globCompat = (pattern) -> pattern\gsub "%*%*([^#{fsinfo.sep}])", "**#{fsinfo.sep}*%1"
36 |
37 | -- Generate docs
38 | if @generate or @dump
39 | printArrow "Generating docs#{@config?.name and (' for ' .. @config.name) or ''}"
40 | --- files ---
41 | -- check input
42 | unless @config.input?
43 | printError "No input specified."
44 | printError "Please set an 'input' field (table) in #{FILE}"
45 | os.exit 1
46 | -- compile ignore list
47 | ignore = flatten [fromGlob globCompat ig for ig in *@config.ignore] if @config.ignore
48 | -- iterate files
49 | cwd = path.cwd!
50 | files = {}
51 | emitted = {}
52 | for inp in *@config.input
53 | nodes = fromGlob globCompat (inp.verbatim and inp[1] or inp)
54 | for node in *nodes
55 | if fs.isfile node
56 | -- take ignores into account
57 | doIgnore = false
58 | if @config.ignore
59 | for ig in *ignore
60 | doIgnore = true if node\match ig
61 | -- add file
62 | unless doIgnore
63 | fname = (path.abs node)\match "#{cwd}#{fsinfo.sep}(.+)"
64 | if inp.verbatim
65 | emitted[fname] = @.reader fname
66 | else
67 | files[] = fname
68 | printPlus fname
69 | --- check language ---
70 | unless @config.language?
71 | printError "No language specified."
72 | printError "Please set a 'language' field (table) in your Taskfile"
73 | os.exit 1
74 | --- extract comments ---
75 | import extract from require "fir.generic.backend"
76 | extracted = {}
77 | for file in *files
78 | --printMini file
79 | extracted[file] = (@.reader file) |> extract _, @config.language, @config.backend or {}
80 | --- parse comments ---
81 | import parse from require "fir.generic.parser"
82 | --printArrow "Parsing comments"
83 | --
84 | parsed = {}
85 | for file in *files
86 | --printMini file
87 | parsed[file] = extracted[file] |> parse _, @config.language
88 | --- dump? ---
89 | if args.dump
90 | import generate from "tableview"
91 | for file in *files
92 | print generate parsed[file]
93 | os.exit!
94 | --- emit ---
95 | unless @config.format?
96 | printError "No format specified."
97 | printError "Please set a 'format' field (string) in #{FILE}"
98 | os.exit 1
99 | -- option to collect symbols
100 | symbols = []
101 | if @config.format == 'markdown'
102 | import listAllSymbols from require "fir.generic.emit.markdown"
103 | for file, parsed in pairs parsed
104 | this_symbols = listAllSymbols parsed, {url_prefix: @config.url_prefix}
105 | symbols = {...symbols, ...this_symbols}
106 | -- printMini "Symbols: #{(require 'inspect') symbols}"
107 | -- actual emitting
108 | import emit from require "fir.generic.emit.#{@config.format}"
109 | for file in *files
110 | --printMini file
111 | emitted[file] = parsed[file] |> emit _, {...(@config.emit or {}), :symbols}
112 | --- write ---
113 | unless @config.output?
114 | printError "No output folder specified."
115 | printError "Please set an 'output' field (string) in #{FILE}"
116 | os.exit 1
117 | -- printArrow "Writing documentation..."
118 | -- make docs dir
119 | fs.mkdir @config.output unless fs.isdir @config.output
120 | -- write
121 | for oldfile, content in pairs emitted
122 | -- transform if exists
123 | file = oldfile
124 | if @config.transform
125 | file = path @config.output, @config.transform oldfile
126 | --printMini file
127 | --
128 | mkdirFor file
129 | @.writer file, content
130 |
--------------------------------------------------------------------------------
/docs/generic/emit/tests.md:
--------------------------------------------------------------------------------
1 | # fir.generic.emit.tests
2 |
3 | An emitter that works with a [GenericAST](/generic/parser.md#GenericAST) and turns it into runnable tests.
4 |
5 | ## API
6 |
7 | This is the API provided to work with the test emitter.
8 |
9 | | Element | Summary |
10 | |---------|---------|
11 | | **Functions** | |
12 | | [emit](#emit) | Emits Lua tests from a `GenericAST` using `test` and `tagged-test` nodes. |
13 | | [emitFunctionCall](#emitFunctionCall) | Emits a function call across languages |
14 | | [emitIfStatement](#emitIfStatement) | Emits an if statement across languages |
15 | | [emitInlineFunctionDefinition](#emitInlineFunctionDefinition) | Emits an inline function declaration across languages |
16 | | [emitInternal](#emitInternal) | Emits tests, as an internal function to be used in several parts. |
17 | | [emitLocalDeclaration](#emitLocalDeclaration) | Emits a local declaration across languages |
18 | | [emitPairsForStatement](#emitPairsForStatement) | Emits a for statement across languages |
19 | | [emitParentheses](#emitParentheses) | Emits an expression in parentheses across langauges |
20 | | [emitString](#emitString) | Emits a string across languages |
21 | | [emitTableIndex](#emitTableIndex) | Emits a table index across languages |
22 | | [emitTableLiteral](#emitTableLiteral) | Emits a table literal across languages |
23 | | [emitTestHeader](#emitTestHeader) | Emits test headers. |
24 | | [emitTestWrapper](#emitTestWrapper) | Wraps a test line in counters and error protectors |
25 |
26 |
27 | ### emit
28 | :: ast:GenericAST, options:table -> code:string
29 |
30 |
31 |
32 | Emits Lua tests from a `GenericAST` using `test` and `tagged-test` nodes.
33 |
34 | - Emits Lua tests from a `GenericAST` using `test` and `tagged-test` nodes.
35 |
36 |
37 | ### emitFunctionCall
38 | :: fn:string, args:[string], options:table
39 |
40 |
41 | **Aliases:** `efn`
42 |
43 | Emits a function call across languages
44 |
45 |
46 |
47 |
48 | ### emitIfStatement
49 | :: condition:string, block:[string], options:table
50 |
51 |
52 | **Aliases:** `eif`
53 |
54 | Emits an if statement across languages
55 |
56 | - Emits an if statement across languages
57 |
58 |
59 | ### emitInlineFunctionDefinition
60 | :: args:[string], body:string, options:table
61 |
62 |
63 | **Aliases:** `eidef`
64 |
65 | Emits an inline function declaration across languages
66 |
67 |
68 |
69 |
70 | ### emitInternal
71 | :: description:[DescriptionLine], options:table, append:function, prepend:function, placement:string -> nil
72 |
73 |
74 |
75 | Emits tests, as an internal function to be used in several parts.
76 |
77 |
78 |
79 |
80 | ### emitLocalDeclaration
81 | :: name:string, lhs:string, options:table
82 |
83 |
84 | **Aliases:** `elocal`
85 |
86 | Emits a local declaration across languages
87 |
88 |
89 |
90 |
91 | ### emitPairsForStatement
92 | :: k:string, v:string, iterator:string, body:string, options:string
93 |
94 |
95 | **Aliases:** `eforp`
96 |
97 | Emits a for statement across languages
98 |
99 | - Emits a for statement across languages
100 |
101 |
102 | ### emitParentheses
103 | :: content:string
104 |
105 |
106 | **Aliases:** `eparen`
107 |
108 | Emits an expression in parentheses across langauges
109 |
110 |
111 |
112 |
113 | ### emitString
114 | :: content:string
115 |
116 |
117 | **Aliases:** `estring`
118 |
119 | Emits a string across languages
120 |
121 |
122 |
123 |
124 | ### emitTableIndex
125 | :: table:string, index:string
126 |
127 |
128 | **Aliases:** `eindex`
129 |
130 | Emits a table index across languages
131 |
132 | - Emits a table index across languages
133 |
134 |
135 | ### emitTableLiteral
136 | :: tbl:table, options:table
137 |
138 |
139 | **Aliases:** `etable`
140 |
141 | Emits a table literal across languages
142 |
143 |
144 |
145 |
146 | ### emitTestHeader
147 | :: node:table, count:number, options:table, append:function, prepend:function, placement:string -> nil
148 |
149 |
150 |
151 | Emits test headers.
152 |
153 |
154 |
155 |
156 | ### emitTestWrapper
157 | :: name:string, count:number, body:string, options:table -> code:string
158 |
159 |
160 | **Aliases:** `ewrap`
161 |
162 | Wraps a test line in counters and error protectors
163 |
164 | - Wraps a test line in counters and error protectors
165 |
--------------------------------------------------------------------------------
/docs/tutorial/tests.md:
--------------------------------------------------------------------------------
1 | # Tests
2 |
3 | This page explains how to use the `tests` Generic emitter to generate tests. The emitter generates a Lua file that is, re-parsable by Fir to generate Markdown information about the test. So, if desired, a test pipeline can be run twice, once to generate the tests, and another to generate the test documentation.
4 |
5 | A lot more functionality can be enabled if using the [`luassert`](https://github.com/lunarmodules/luassert) module.
6 |
7 | ## Configuring test emitting
8 |
9 | Below is the configuration for test emitting. Format must be set to `"emit"` for these options to apply.
10 |
11 | | **Option** | **Type** | **Default** | **Meaning** |
12 | |--|--|--|--|
13 | | `module` | String | [GenericAST](/fir/generic/parser#GenericAST).title | Name of the module to `require` |
14 | | `header` | String | None | Code or comments to append to the comment header |
15 | | `subheader` | String | `'General'` | Name of the documentation section for tests |
16 | | `trim` | Boolean | `true` | Whether to trim spaces on test lines |
17 | | `newline` | String | `\n` | Newline character |
18 | | `luassert` | Boolean | `true` | Whether to use `luassert` or not |
19 | | `print` | `'pretty'`\|`'plain'`\|`'plain-verbose'`\|`'tap'`\|`false` | `'pretty'` | Whether to print test parts and results |
20 | | `testHeaders` | Boolean | `true` | Whether to emit documentation headers for each test |
21 | | `docs` | Boolean | `true` | Whether to add comments to the tests at all |
22 | | `auto_unpack` | Boolean | `false` | Whether to unpack each symbol automatically |
23 | | `unpack` | \[String\] | None | List of symbols to unpack, making them accessible directly without prefixing with `M` |
24 | | `snippet` | String | None | Snippet of code to add before all the tests |
25 | | `all` | Boolean | None | Whether to set all tags to `true` (Execute all tests) |
26 |
27 | ### Printing modes
28 |
29 | Fir now offers different printing modes depending on your needs.
30 |
31 | - `pretty` will print pending and failed tests, as well as results, using colors.
32 | - `plain` does exactly the same with no color.
33 | - The `-verbose` variants will also print successes.
34 | - `tap` tries to output according to the [TAP protocol](https://testanything.org/tap-version-14-specification.html)
35 | - Setting it to `false` will not print anything
36 |
37 |
38 | ## Structure of a test
39 |
40 | - Header
41 | - `option.header`
42 | - Imports (`luassert` and your module)
43 | - Unpack `options.unpack`
44 | - `options.snippet`
45 | - Parse arguments (`{...}`) into a tag list (`tags`)
46 | - `options.all` setup
47 | - Insert docs description and `options.subheader` as its header
48 | - Iterate sections
49 | - Emit each test
50 |
51 | ## How unpacking works
52 |
53 | There are two kinds of unpacking: through `options.unpack` or by setting `options.auto_unpack`.
54 |
55 | ### Explicit unpacking
56 |
57 | All the symbols listed in `options.unpack` will be unpacked in the form `(local) = M['']` where `M` is your required module.
58 |
59 | ### Automatic unpacking
60 |
61 | The metatable of the environment is set so that `M` is a fallback table. This way, all functions and symbols can be accessed automatically without the need to list them in an unpack.
62 |
63 | ## Test lines
64 |
65 | ### Test configuration
66 |
67 | These configuration comments should go before the test you want to mark.
68 |
69 | Mark a test as pending: `?$ pending`
70 |
71 | ### Verbatim tests
72 |
73 | These kinds of tests are pasted into the code verbatim.
74 |
75 | === "Lua"
76 | ```lua
77 | -- ?! print(VALUE)
78 | ```
79 |
80 | ### Verbatim setup
81 |
82 | These comments will embed code lines directly into the output, as to setup tests.
83 |
84 | === "Lua"
85 | ```lua
86 | -- !! array = {1, 2, 3}
87 | ```
88 |
89 | ### Truthy tests
90 |
91 | These kinds of tests check for a truthy result. The contents are placed inside `assert`, or `assert.truthy` in the case of using `luassert`.
92 |
93 |
94 | === "Lua"
95 | ```lua
96 | -- ?? VALUE
97 | ```
98 |
99 | ### Tagged tests
100 |
101 | Tagged tests are tests that are only run if a certain tag is passed to the runner file. Otherwise equivalent to [Truthy tests](#Truthy-tests)
102 |
103 | === "Lua"
104 | ```lua
105 | -- ?? tag: VALUE
106 | ```
107 |
108 | ### `luassert` tests
109 |
110 | These tests directly leverage `luassert` to check for different conditions. The error message `err` is always optional.
111 |
112 | | **Letter** | **`luassert`** | **Meaning** |
113 | |--------|----------|---------|
114 | | `T` | `is_true(value, err)` | Check for `true` |
115 | | `t` | `truthy(value, err)` | Check for a truthy value |
116 | | `F` | `is_false(value, err)` | Check for `false` |
117 | | `f` | `falsy(value, err)` | Check for a falsy value |
118 | | `E` | `has_no.errors(fn, err)` | Check that a function has no errors |
119 | | `e` | `errors(fn, expected_err, err)` | Checks that a function errors |
120 | | `n` | `near(num, expected_num, tolerance)` | Checks that a number is within tolerance of expectation |
121 | | `u` | `unique(list, err, deep?)` | Checks that every value in a list is unique |
122 | | `:` | `matches(value, pattern, err)` | Checks that a value matches a Lua pattern |
123 | | `=` | `equals(value, expected, err)` | Checks that two values are equal (`==`) |
124 | | `~` | `same(value, expected, err)` | Checks that two values are similar through a deep compare |
125 | | `#` | type | Using the tag, checks if the passed expression is (type) |
126 |
127 | You can negate the test result by putting a caret (`^`) between the two interrogation signs.
128 |
129 | === "Lua"
130 | ```lua
131 | -- ??t truthy_value, "Not truthy"
132 | -- ?^?= value, expected, "They are, but shouldn't be equal"
133 | -- ??# string: stringy_value, "Not a string"
134 | ```
135 |
136 | ### `luassert` tagged tests
137 |
138 | Equivalent to [`luassert` tests](#luassert-tests), but with tags as in [Tagged test](#Tagged-tests) syntax.
139 |
140 | !!! warning
141 | The type `luassert` test (`??#`) does not work with tagged tests because they use the tag as the type to test for.
142 |
--------------------------------------------------------------------------------
/docs/tutorial/CLI.md:
--------------------------------------------------------------------------------
1 | # CLI
2 |
3 | This page describes the CLI to the Fir framework. It can only use the [generic](generic/documenting.md) documentation format (but it can use different output formats).
4 |
5 | ## Installing
6 |
7 | The Fir CLI comes with the Fir framework via LuaRocks automatically.
8 |
9 | ## Configuration
10 |
11 | The most important part to maintaining the documentation of a project with Fir is the configuration. The configuration takes form as a Lua or MoonScript file and it defines the inputs, outputs, formats and other things related to documentation.
12 |
13 | The file must be named `Fir.lua` or `Fir.moon` for it to be detected automatically, but you can also use `-c` or `--config` to change the configuration file, and `--config-language` to set the language it should be loaded as (only supports `lua` and `moon`).
14 |
15 | !!! note "About MoonScript"
16 | **Note:** To use a Fir.moon configuration file, or any configuration file written in MoonScript, you need to install MoonScript via LuaRocks with the following command: `luarocks install moonscript`
17 |
18 | Here follows a complete description of all the options that the Fir CLI accepts.
19 |
20 | === "Lua"
21 | ```lua
22 | --- General config
23 | name = "Fir" -- name of your project, not mandatory
24 | --- Format
25 | -- turns into fir.generic.emit.
26 | format = "markdown" -- only markdown is included by default
27 | --- Emit options
28 | emit = {
29 | tabs = {
30 | use = false, -- Whether to use Tab syntax in Markdown or not
31 | docsify_header = "####" -- Header to use in the Docsify Tab syntax
32 | },
33 | all = false, -- Also emit `@internal` sections
34 | columns = {}, -- Modify the TOC
35 | types = { -- TOC section titles
36 | type = "Types",
37 | function = "Functions",
38 | constant = "Constants",
39 | class = "Classes",
40 | test = "Tests",
41 | table = "Tables",
42 | field = "Fields",
43 | variable = "Variables"
44 | }
45 | }
46 | --- Backend options
47 | backend = {
48 | patterns = false, -- Whether to use patterns for the language strings
49 | ignore = "///", -- String used to determine when to start or stop ignoring comments
50 | merge = true, -- Whether to merge adjacent single-line comments
51 | paragraphs = true, -- Whether to split multi-line comments by empty strings
52 | }
53 | --- Language
54 | -- defines the way comments should be extracted
55 | -- with support for single-line and multiline comments.
56 | language = {
57 | single = "--",
58 | multi = {
59 | "--[[",
60 | "]]--"
61 | }
62 | }
63 | --- Files
64 | -- Input
65 | -- Every entry is passed through lpath.fs.glob, so you can use
66 | -- wildcards. Reference below.
67 | input = {
68 | "fir/**.yue",
69 | -- Verbatim files will be placed in the output folder as they
70 | -- are.
71 | { "fir/**.md", verbatim: true }
72 | }
73 | -- Transform
74 | -- Function that changes the filepath to be written.
75 | -- If the input is fir/generic/emit/markdown.yue, this function
76 | -- will transform it into `generic/emit/markdown.md`, for example.
77 | transform = function (path)
78 | if (path:match "yue$") then
79 | return (path:match "fir/(.+)%.yue") .. ".md"
80 | else
81 | return (path:match "fir/(.+)")
82 | end
83 | end
84 | -- Output folder
85 | output = "docs"
86 | -- Ignore
87 | -- List of files to ignore. Also supports wildcards (reference below).
88 | ignore = {
89 | "fir/version.mp",
90 | "fir/file.mp"
91 | }
92 | ```
93 |
94 | === "MoonScript"
95 | ```moon
96 | config:
97 | --- General config
98 | name: "Fir" -- name of your project, not mandatory
99 | --- Format
100 | -- turns into fir.generic.emit.
101 | format: "markdown" -- only markdown is included by default
102 | --- Emit options
103 | emit: {
104 | tabs: {
105 | use: false, -- Whether to use Tab syntax in Markdown or not
106 | docsify_header: "####" -- Header to use in the Docsify Tab syntax
107 | },
108 | all: false, -- Also emit `@internal` sections
109 | columns: {}, -- Modify the TOC
110 | types: { -- TOC section titles
111 | type: "Types",
112 | function: "Functions",
113 | constant: "Constants",
114 | class: "Classes",
115 | test: "Tests",
116 | table: "Tables",
117 | field: "Fields",
118 | variable: "Variables"
119 | }
120 | }
121 | --- Backend options
122 | backend: {
123 | patterns: false, -- Whether to use patterns for the language strings
124 | ignore: "///", -- String used to determine when to start or stop ignoring comments
125 | merge: true, -- Whether to merge adjacent single-line comments
126 | paragraphs: true, -- Whether to split multi-line comments by empty strings
127 | }
128 | --- Language
129 | -- defines the way comments should be extracted
130 | -- with support for single-line and multiline comments.
131 | language:
132 | single: "--"
133 | multi: {
134 | "--[["
135 | "]]--"
136 | }
137 | }
138 | -- Verbatim files will be placed in the output folder as they
139 | -- are.
140 | --- Files
141 | -- Input
142 | -- Every entry is passed through lpath.fs.glob (reference below),
143 | -- so you can use wildcards.
144 | input: {
145 | "fir/**.yue"
146 | -- Verbatim files will be placed in the output folder as they
147 | -- are.
148 | { "fir/**.md", verbatim: true }
149 | }
150 | -- Transform
151 | -- Function that changes the filepath to be written.
152 | -- If the input is fir/generic/emit/markdown.yue, this function
153 | -- will transform it into `generic/emit/markdown.md`
154 | transform: (path) ->
155 | if path\match "yue$"
156 | (path\match "fir/(.+)%.mp") .. ".md"
157 | else
158 | path\match "fir/(.+)"
159 | -- Output folder
160 | output: "docs"
161 | -- Ignore
162 | -- List of files to ignore. Also supports wildcards (reference below).
163 | ignore: {
164 | "fir/version.mp"
165 | "fir/file.mp"
166 | }
167 | ```
168 |
169 | ### Wildcards
170 |
171 | Wildcards work as described in the [lpath `fs.glob`](https://github.com/starwing/lpath#fsdirfsscandirfsglob) function documentation, with a modification that has been made by Fir.
172 |
173 | `**` not followed by the path separator will be turned into `**/*`. This is because `fs.glob` does not work with `**.txt` directly, but needs to have `**/*.txt`.
174 |
175 | ## Generating
176 |
177 | Then you can simply generate your docs with `fir generate` (or `fir g` for shory). This will automatically create all folders and files necessary.
178 |
179 | ## Viewing
180 |
181 | You may only want the Markdown files, but I recommend using a project like [Docsify](https://docsify.js.org/#/) or [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/) to make your documentation look better as a webpage. The Markdown generic emitter has some facilities to integrate with both Docsify (like comments for the [`docsify-tabs` plugin](https://jhildenbiddle.github.io/docsify-tabs/#/)) and MkDocs.
182 |
183 | This page is currently using Material for MkDocs.
184 |
--------------------------------------------------------------------------------
/fir/generic/backend.yue:
--------------------------------------------------------------------------------
1 | ---# fir.generic.backend #---
2 | -- A generic implementation of a backend for the Fir
3 | -- documentation generator.
4 | --
5 | -- This specific implementation uses a `language` module (defaults to `fir.generic.languages`) to
6 | -- parse comments from any file.
7 | --
8 | -- Check out an example output of this backend [here](/fir/examples/generic-backend.html).
9 | {:find, :sub, :match} = string
10 |
11 | --# Utils #--
12 | -- @internal
13 | -- Several utils used internally.
14 |
15 | --- @function sanitize :: input:string -> escaped:string
16 | --- Takes any string and escapes it to work with patterns.
17 | sanitize = (input) -> if "string" == type input then return input\gsub "[%(%)%.%%%+%-%*%?%[%]%^%$]", "%%%0" else return input
18 |
19 | --- @function trim :: input:string -> trimmed:string, n:number
20 | --- Trims the whitespace at each end of a string.
21 | -- Taken from [Lua-users wiki's StringTrim (`trim11`)](http://lua-users.org/wiki/StringTrim).
22 | trim = (input) -> return if n := find input, "%S" then match input, ".*%S", n else ""
23 |
24 | --- @function lines :: input:string -> lines:table
25 | --- Splits a table into lines.
26 | -- Taken from [Penlight's `stringx` (`splitlines`)](https://stevedonovan.github.io/Penlight/api/libraries/pl.stringx.html#splitlines).
27 | lines = (s) ->
28 | res, pos = {}, 1
29 | while true
30 | lep = string.find s, "[\r\n]", pos
31 | break unless lep
32 | le = sub s, lep, lep
33 | le = "\r\n" if (le == "\r") and ((sub s, lep+1, lep+1) == "\n")
34 | ln = sub s, pos, lep-1
35 | res[#res+1] = ln
36 | pos = lep + #le
37 | if pos <= #s
38 | res[#res+1] = sub s, pos
39 | return res
40 |
41 | --- @function lconcat :: la:[*], lb:[*] -> merged:[*]
42 | --- Concatenates two lists
43 | lconcat = (ta, tb) ->
44 | tc = {}
45 | for elem in *ta do tc[#tc+1] = elem
46 | for elem in *tb do tc[#tc+1] = elem
47 | return tc
48 |
49 | --# API #--
50 | -- This is the API provided to work with the generic backend.
51 |
52 | --- @type Language
53 | --- Language type accepted by [`extract`](#extract).
54 | --:moon Format
55 | -- Language {
56 | -- single :: string
57 | -- multi :: [string]
58 | -- extensions :: [string]
59 | -- }
60 | --:
61 |
62 | --- @type GenericComment
63 | --- Comment returned by [`extract`](#extract).
64 | --:moon Format
65 | -- GenericComment {
66 | -- start :: number
67 | -- end :: number
68 | -- content :: [string]
69 | -- }
70 | --:
71 |
72 | --- @function extract :: input:string, language?:Language, options?:table -> comments:[@@@GenericComment@@@]
73 | --- Extracts comment from a string separated by newlines.
74 | --# Available options
75 | -- - `patterns:boolean` (`false`): Whether to use patterns for the language fields and ignore string or not.
76 | -- - `ignore:string` (`"///"`): String used to determine when to start or stop ignoring comments.
77 | -- - `merge:boolean` (`true`): Whether to merge adjacent single-line comments.
78 | -- - `paragraphs:boolean` (`true`): Whether to split multi-line comments by empty strings (`""`).
79 |
80 | --///--
81 | export extract = (input="", language={}, options={}) ->
82 | -- lt:table - (l)ines (t)able, lines in the file
83 | -- comments:table - comments that were parsed
84 | -- comment:table - current comment
85 | -- start:number - line start
86 | -- end:number - line end
87 | -- type:string - type of comment (multi|single)
88 | -- content:[string] - comment strings
89 | -- tracking:table - table for variables that are getting tracked
90 | -- multi:boolean - whether we are in multiline mode
91 | -- ignore:boolean - whether comments are currently being ignored
92 | lt = lines input
93 | comments = {}
94 | comment = {content:{}}
95 | tracking = {
96 | multi: false
97 | ignore: false
98 | }
99 | -- options
100 | options.patterns = false if options.patterns == nil
101 | options.ignore = "///" if options.ignore == nil
102 | options.merge = true if options.merge == nil
103 | options.paragraphs = true if options.paragraphs == nil
104 | -- sanitize language
105 | igstring = options.patterns and options.ignore or sanitize options.ignore
106 | local single, multis, multie
107 | if options.patterns
108 | single = language.single or ""
109 | multis = language.multi?[1]
110 | multie = language.multi?[2]
111 | else
112 | single = (sanitize language.single or "")
113 | multis = (sanitize language.multi?[1] or false)
114 | multie = (sanitize language.multi?[2] or false)
115 | -- line loop
116 | -- lc:number - (l)ine (c)ount
117 | for lc=1, #lt
118 | line = lt[lc]
119 | -- (single)/// signals to start ignoring all contents
120 | if match line, "^#{single}#{igstring}"
121 | tracking.ignore = not tracking.ignore
122 | -- continue if currently ignoring
123 | elseif tracking.ignore
124 | continue
125 | -- close multiline comment
126 | elseif tracking.multi and multie and match line, "#{multie}"
127 | tracking.multi = false
128 | comment.end = lc
129 | comment.content[#comment.content] = match line, "^(.-)#{multie}"
130 | comments[#comments+1] = comment
131 | comment = content: {}
132 | -- if multiline, shove into current comment
133 | elseif tracking.multi
134 | comment.content[#comment.content] = line
135 | -- start multiline comment
136 | elseif multis and match line, "^#{multis}"
137 | tracking.multi = true
138 | comment.start = lc
139 | comment.content[#comment.content] = match line, "^#{multis}(.+)"
140 | -- add single line comment
141 | elseif single and match line, "^#{single}"
142 | comment.start, comment.end = lc, lc
143 | comment.content[1] = match line, "^#{single}(.-)$"
144 | comments[#comments+1] = comment
145 | comment = content: {}
146 | -- merge adjacent single comments
147 | if options.merge
148 | comments[0] = {type: false, start: 0, end: -1}
149 | for i=#comments, 1, -1
150 | curr = comments[i]
151 | prev = comments[i-1]
152 | continue unless curr.start == prev.end+1
153 | comments[i-1] = {content: (lconcat prev.content, curr.content), start: prev.start, end: curr.end}
154 | table.remove comments, i
155 | comments[0] = nil
156 | -- split in paragraphs
157 | lastcomments = {}
158 | if options.paragraphs
159 | -- iterate comments
160 | for comment in *comments
161 | -- iterate contents
162 | parts = {}
163 | part = {start: 1}
164 | for lc=1, #comment.content
165 | -- split by empty line
166 | if comment.content[lc] == ""
167 | part.end = lc - 1
168 | parts[#parts+1] = part
169 | part = {start: lc+1}
170 | else
171 | part[#part+1] = comment.content[lc]
172 | part.end = #comment.content
173 | parts[#parts+1] = part
174 | -- split parts into several comments
175 | --print generate parts
176 | for part in *parts
177 | lastcomments[#lastcomments+1] = {start: comment.start+part.start-1, end: comment.start+part.end-1, content: [l for l in *part]}
178 | comments = lastcomments
179 | -- return
180 | return comments
181 | --///--
182 |
183 | --///--
184 | -- readFile = (f) ->
185 | -- local content
186 | -- with io.open f, "r"
187 | -- content = \read "*a"
188 | -- \close!
189 | -- return content
190 | --print generate extract (readFile "fir/generic/backend.moon"), {single: "--"}, {}
191 | --///--
192 |
--------------------------------------------------------------------------------
/bin/fir.yue:
--------------------------------------------------------------------------------
1 | ---# bin/fir #---
2 | -- The command line interface to Fir.
3 | --
4 | -- It only works with the `generic` pipeline. If you wish to
5 | -- use your custom functions that are not in `fir.generic.*`
6 | -- you need to roll out your own script.
7 | import "argparse"
8 | -- import "filekit" as fs
9 | import "path.fs" as fs
10 | import "path" as fspath
11 | import "path.info" as fsinfo
12 | import "ansikit.style" as {:style}
13 |
14 | --///--
15 | -- TODO Integrate this into alfons
16 | --
17 | -- wtf did old me mean by this?
18 | --///--
19 | local args
20 | with argparse!
21 | \name "fir"
22 | \description "Language-agnostic documentation generator"
23 | \epilog "fir v#{require 'fir.version'} - https://github.com/daelvn/fir"
24 |
25 | \help_description_margin 30
26 |
27 | -- CONFIG
28 | c = with \option "-c --config"
29 | \description "Configuration file for this project"
30 | cl = with \option "--config-language"
31 | \description "Uses an explicit language for the config file"
32 | \group "Configuration", c, cl
33 |
34 | -- DOCUMENTATION
35 | g = with \command "generate gen g"
36 | \description "Generates documentation for a project"
37 | d = with \command "dump d"
38 | \description "Dumps AST to stdout using tableview (does not generate docs)"
39 | \group "Documentation", g
40 |
41 | -- FLAGS
42 | sl = with \flag "-s --silent"
43 | \description "Does not print to stderr"
44 | \group "Flags", sl
45 |
46 | args = \parse!
47 |
48 | -- load config file
49 | FILE = do
50 | if args.c then args.c
51 | elseif args.config then args.config
52 | elseif fs.exists "Fir.lua" then "Fir.lua"
53 | elseif fs.exists "Fir.moon" then "Fir.moon"
54 | else error "No Firfile found."
55 |
56 | -- Also accept a custom language
57 | LANGUAGE = do
58 | if FILE\match "moon$" then "moon"
59 | elseif FILE\match "lua$" then "lua"
60 | elseif args.config_language then args.config_language
61 | else error "Cannot resolve format for Firfile."
62 | io.stderr\write "Using #{FILE} (#{LANGUAGE})\n" unless arg.silent
63 |
64 | -- Load string
65 | import readMoon, readLua, loadEnv from require "fir.file"
66 | content, contentErr = switch LANGUAGE
67 | when "moon" then readMoon FILE
68 | when "lua" then readLua FILE
69 | else error "Cannot resolve format '#{LANGUAGE}' for Firfile."
70 | unless content then error contentErr, 1
71 |
72 | -- Load with environment
73 | firEnv = {}
74 | fir, firErr = loadEnv content, firEnv
75 | if firErr then error "Could not load Firfile: #{firErr}"
76 |
77 | -- Run and get contents
78 | list = fir!
79 | rconfig = list and (list.config and list.config or {}) or {}
80 | project = firEnv
81 | for k, v in pairs rconfig do project[k] = v
82 |
83 | -- arrow
84 | printArrow = (txt) -> io.stderr\write style "%{blue}==>%{white} #{txt}\n" unless args.silent
85 | printError = (txt) -> io.stderr\write style "%{red}#{txt}\n" unless args.silent
86 | printPlus = (txt) -> io.stderr\write style "%{green}+ #{txt}\n" unless args.silent
87 | printMini = (txt) -> io.stderr\write style "%{yellow}-> #{txt}\n" unless args.silent
88 |
89 | -- readfile
90 | readFile = (f) ->
91 | local content
92 | with io.open f, "r"
93 | content = \read "*a"
94 | \close!
95 | return content
96 | -- writefile
97 | writeFile = (f, t) ->
98 | with io.open f, "w"
99 | \write t
100 | \close!
101 | -- mkdir
102 | mkdirFor = (path) ->
103 | path = path\match "(.+)/.-"
104 | sep, pStr = (package.config\sub 1, 1), ""
105 | for dir in path\gmatch "[^" .. sep .. "]+" do
106 | pStr = pStr .. dir .. sep
107 | fs.mkdir pStr
108 | -- fromGlob reimplementation
109 | fromGlob = (pattern) -> [node for node in fs.glob pattern]
110 | -- reduce
111 | reduce = (array, callback, initial) ->
112 | accumulator = initial and initial or array[1]
113 | start = initial and 1 or 2
114 | for i = start, #array
115 | accumulator = callback accumulator, array[i]
116 | return accumulator
117 | -- flatten
118 | flatten = (arr) -> arr |> reduce ((acc, val) -> [ ...acc, ...val ]), []
119 | -- makes a glob pattern filekit-compatible
120 | globCompat = (pattern) -> pattern\gsub "%*%*([^#{fsinfo.sep}])", "**#{fsinfo.sep}*%1"
121 |
122 | -- Generate docs
123 | if args.generate or args.dump
124 | printArrow "Generating docs#{project.name? and (' for ' .. project.name) or ''}"
125 | --- files ---
126 | -- check input
127 | unless project.input?
128 | printError "No input specified."
129 | printError "Please set an 'input' field (table) in #{FILE}"
130 | os.exit 1
131 | -- compile ignore list
132 | ignore = flatten [fromGlob globCompat ig for ig in *project.ignore] if project.ignore
133 | -- iterate files
134 | cwd = fspath.cwd!
135 | files = {}
136 | emitted = {}
137 | for inp in *project.input
138 | nodes = fromGlob globCompat (inp.verbatim and inp[1] or inp)
139 | for node in *nodes
140 | if fs.isfile node
141 | -- take ignores into account
142 | doIgnore = false
143 | if project.ignore
144 | for ig in *ignore
145 | doIgnore = true if node\match ig
146 | -- add file
147 | unless doIgnore
148 | fname = (fspath.abs node)\match "#{cwd}#{fsinfo.sep}(.+)"
149 | if inp.verbatim
150 | emitted[fname] = readFile fname
151 | else
152 | files[] = fname
153 | printPlus fname
154 | --- check language ---
155 | unless project.language?
156 | printError "No language specified."
157 | printError "Please set a 'language' field (table) in #{FILE}"
158 | os.exit 1
159 | --- extract comments ---
160 | import extract from require "fir.generic.backend"
161 | --printArrow "Extracting comments"
162 | --
163 | extracted = {}
164 | for file in *files
165 | --printMini file
166 | extracted[file] = (readFile file) |> extract _, project.language, project.backend or {}
167 | --- parse comments ---
168 | import parse from require "fir.generic.parser"
169 | --printArrow "Parsing comments"
170 | --
171 | parsed = {}
172 | for file in *files
173 | --printMini file
174 | parsed[file] = extracted[file] |> parse _, project.language
175 | --- dump? ---
176 | if args.dump
177 | import generate from "tableview"
178 | for file in *files
179 | print generate parsed[file]
180 | os.exit!
181 | --- emit ---
182 | unless project.format?
183 | printError "No format specified."
184 | printError "Please set a 'format' field (string) in #{FILE}"
185 | os.exit 1
186 | -- option to collect symbols
187 | symbols = []
188 | if project.format == 'markdown'
189 | import listAllSymbols from require "fir.generic.emit.markdown"
190 | for file, parsed in pairs parsed
191 | this_symbols = listAllSymbols parsed, {url_prefix: project.url_prefix}
192 | symbols = {...symbols, ...this_symbols}
193 | -- printMini "Symbols: #{(require 'inspect') symbols}"
194 | -- actual emitting
195 | import emit from require "fir.generic.emit.#{project.format}"
196 | for file in *files
197 | --printMini file
198 | emitted[file] = parsed[file] |> emit _, {...(project.emit or {}), :symbols}
199 | --- write ---
200 | unless project.output?
201 | printError "No output folder specified."
202 | printError "Please set an 'output' field (string) in #{FILE}"
203 | os.exit 1
204 | -- printArrow "Writing documentation..."
205 | -- make docs dir
206 | fs.mkdir project.output unless fs.isdir project.output
207 | -- write
208 | for oldfile, content in pairs emitted
209 | -- transform if exists
210 | file = oldfile
211 | if project.transform
212 | file = fspath project.output, project.transform oldfile
213 | --printMini file
214 | --
215 | mkdirFor file
216 | writeFile file, content
217 |
--------------------------------------------------------------------------------
/docs/tutorial/index.md:
--------------------------------------------------------------------------------
1 | # Tutorial
2 |
3 | This page explains how to document your code to work with the generic/default parser.
4 |
5 | ## Top header
6 |
7 | A first-level header (`#`) with an according description is inserted once with the following syntax (`#...#` [?](#languages)):
8 |
9 | === "Python"
10 | ```py
11 | ### Title ###
12 | # Description
13 | ```
14 |
15 | === "Lua/MoonScript"
16 | ```lua
17 | ---# Title #---
18 | -- Description
19 | ```
20 |
21 | The description can span as many lines as you wish as long as there is no empty line in the middle. If you want an empty line, just use an empty comment (this will also split the description into paragraphs).
22 |
23 | ### Hiding the document
24 |
25 | You can stop the document from being generated if you place `@internal` in the description as the first word. This will stop the page from being generated unless `all` is set to `true` in the [`emit` options](emit/markdown.md#emit).
26 |
27 | ## Section
28 |
29 | You can add a section (`##`) with the following syntax (`#...#` [?](#languages)):
30 |
31 | === "**Python**"
32 |
33 | ```py
34 | ## Title ##
35 | # Description
36 | ```
37 |
38 | === "**Lua/MoonScript**"
39 |
40 | ```lua
41 | --# Title #--
42 | -- Description
43 | ```
44 |
45 | The description can span as many lines as you wish as long as there is no empty line in the middle. If you want an empty line, just use an empty comment (this will also split the description into paragraphs).
46 |
47 | Every section generates with its own table of contents, separating between Types and Functions.
48 |
49 | ### Hiding a section
50 |
51 | You can stop a section from being generated if you place `@internal` in the description as the first word. This will stop the section from being emitted unless `all` is set to `true` in the [`emit` options](generic/emit/markdown.md#emit).
52 |
53 | ## Functions
54 |
55 | You can document a function with the following syntax:
56 |
57 | === "**Python**"
58 |
59 | ```py
60 | ## @function name alias1 alias2 :: type signature
61 | ## Summary
62 | # Description
63 | ```
64 |
65 | === "**Lua/MoonScript**"
66 |
67 | ```lua
68 | --- @function name alias1 alias2 :: type signature
69 | --- Summary
70 | -- Description
71 | ```
72 |
73 | The aliases must be separated by spaces and are not mandatory. The type signature is mandatory for a function and will be copied verbatim. The summary will be placed in its own paragraph and will be used for the table of contents as well.
74 |
75 | The description can span as many lines as you wish as long as there is no empty line in the middle. If you want an empty line, just use an empty comment (this will also split the description into paragraphs).
76 |
77 | ### Hiding a function
78 |
79 | You can stop a function from being generated if you place `@internal` in the description as the first word. This will stop the function from being emitted unless `all` is set to `true` in the [`emit` options](generic/emit/markdown.md#emit).
80 |
81 | ## Types
82 |
83 | You can document a type with the following syntax:
84 |
85 | === "**Python**"
86 |
87 | ```py
88 | ## @type name alias1 alias2
89 | ## Summary
90 | #:type Format
91 | # Describe your type here
92 | ```
93 |
94 | === "**Lua/MoonScript**"
95 |
96 | ```lua
97 | --- @type name alias1 alias2
98 | --- Summary
99 | --:type Format
100 | -- Describe your type here
101 | ```
102 |
103 | It is customary to describe your type within a [code block](#code-block). Everything else is mostly the same as functions.
104 |
105 | ## Constants
106 |
107 | You can document a constant (does not need to be a constant, but...) using the following syntax:
108 |
109 | !!! note
110 | You can also use `@const` instead of `@constant`.
111 |
112 | === "**Python**"
113 |
114 | ```py
115 | ## @constant name alias1 alias2 :: type
116 | ## Summary
117 | # Description
118 | ```
119 |
120 | === "**Lua/MoonScript**"
121 |
122 | ```lua
123 | --- @constant name alias1 alias2 :: type
124 | --- Summary
125 | -- Description
126 | ```
127 |
128 | The aliases must be separated by spaces and are not mandatory. The type is mandatory for a constant and will be copied verbatim. The summary will be placed in its own paragraph and will be used for the table of contents as well.
129 |
130 | The description can span as many lines as you wish as long as there is no empty line in the middle. If you want an empty line, just use an empty comment (this will also split the description into paragraphs).
131 |
132 | ## Classes
133 |
134 | You can document classes with the following syntax:
135 |
136 | === "**Python**"
137 |
138 | ```py
139 | ## @class name alias1 alias2
140 | ## Summary
141 | # Description
142 | ```
143 |
144 | === "**Lua/MoonScript**"
145 |
146 | ```lua
147 | --- @class name alias1 alias2
148 | --- Summary
149 | -- Description
150 | ```
151 |
152 | The aliases must be separated by spaces and are not mandatory. The summary will be placed in its own paragraph and will be used for the table of contents as well.
153 |
154 | The description can span as many lines as you wish as long as there is no empty line in the middle. If you want an empty line, just use an empty comment (this will also split the description into paragraphs).
155 |
156 | ## Descriptions
157 |
158 | There are several tricks to make your descriptions look better.
159 |
160 | ### Headers
161 |
162 | You can place headers within descriptions as such. They will be automatically indented properly.
163 |
164 | === "**Python**"
165 |
166 | ```py
167 | ## Header
168 | # Description follow-up
169 | ```
170 |
171 | === "**Lua/MoonScript**"
172 |
173 | ```lua
174 | --# Header
175 | -- Description follow-up
176 | ```
177 |
178 | ### Code blocks
179 |
180 | You can add a code block with the following syntax:
181 |
182 | === "**Python**"
183 |
184 | ```py
185 | #:language Title
186 | # code
187 | ```
188 |
189 | === "**Lua/MoonScript**"
190 |
191 | ```lua
192 | --:language Title
193 | -- code
194 | ```
195 |
196 | If you need to continue a description after a codeblock, place a comment containing only `:` (no spaces) to stop the codeblock, or alternatively start a different codeblock.
197 |
198 | ## Ignoring comments
199 |
200 | You can ignore a set of comments by placing `(single)///(single)` (like `#///#` or `--///--`) at both the start or the end of the region you want to ignore, like so:
201 |
202 | === "**Python**"
203 |
204 | ```py
205 | #///#
206 | # These comments will be ignored
207 | #///#
208 | ```
209 |
210 | === "**Lua/MoonScript**"
211 |
212 | ```lua
213 | --///--
214 | -- These comments will be ignored
215 | --///--
216 | ```
217 |
218 | These comments will not be extracted at all. You can use this to document within your functions without the results actually being in the output (in fact, this is recommended).
219 |
220 | ## Linking to symbols
221 |
222 | You can use `@@@symbol@@@` outside of inline code or a code block to automatically link to a symbol in the project. Symbols are collected by taking all the names and aliases of all exported documentation.
223 |
224 | If there is a name conflict, you can prefix it with its parent module, like `@@@parent.symbol@@@`.
225 |
226 | ## Languages
227 |
228 | The language table passed to `extract` and `parse` define the comments that the Fir generic pipeline will look for.
229 |
230 | ### Lead
231 |
232 | The `` character mentioned [here](#top-header) is the *last* character from the `single` comment field. So if your language looks like `{single = "--"}`, the lead will be `"-"`.
233 |
234 | ## Examples
235 |
236 | The Fir source code is documented using Fir! Read through the source code and compare to the documentation to see how it's documented.
237 |
--------------------------------------------------------------------------------
/fir/generic/emit/markdown.yue:
--------------------------------------------------------------------------------
1 | ---# fir.generic.emit.markdown #---
2 | -- An emitter that works with a @@@GenericAST@@@ and turns it into markdown.
3 | {:rep, :find, :match} = string
4 | Pcre = require "rex_pcre2"
5 |
6 | --# Utils #--
7 | -- @internal
8 | -- Utils to work internally.
9 |
10 | --- @function trim :: input:string -> trimmed:string, n:number
11 | --- Trims the whitespace at each end of a string.
12 | -- Taken from [Lua-users wiki's StringTrim (`trim11`)](http://lua-users.org/wiki/StringTrim).
13 | trim = (input) -> return if n := find input, "%S" then match input, ".*%S", n else ""
14 |
15 | --- @function bkpairs :: tbl:table -> Iterator
16 | --- Iterates over a table in key order.
17 | bkpairs = (t) ->
18 | a = {}
19 | table.insert a, n for n in pairs t
20 | table.sort a
21 | i = 0
22 | iter = ->
23 | i += 1
24 | if a[i] == nil
25 | return nil
26 | else
27 | return a[i], t[a[i]]
28 | return iter
29 |
30 | --# API #--
31 | -- This is the API provided to work with the generic markdown emitter.
32 |
33 | --- @function replaceSymbols :: str:string, symbols:{string:string} -> str:string
34 | --- Returns a string with the symbol syntax replaced with its documentation location
35 | --:md Symbol syntax
36 | -- @@@@@@
37 | --:
38 | export replaceSymbols = (str, symbols, no_backtick=false) -> Pcre.gsub str, "(`[^`]*?`)|@@@(.+?)@@@", (in_b, sym) ->
39 | in_b or (
40 | symbols[sym] and "[#{no_backtick and "" or "`"}#{sym}#{no_backtick and "" or "`"}](#{symbols[sym]})" or "#{no_backtick and "" or "`"}#{sym}#{no_backtick and "" or "`"}"
41 | )
42 |
43 | replaceNewlines = (str) -> str\gsub "\n", "
"
44 | newlinesIntoParagraphs = (str) -> str\gsub "\n", "\n\n"
45 |
46 | --- @function emitDescription :: desc:[@@@DescriptionLine@@@], options:table -> markdown:string
47 | --- Emits Markdown from the description of an element.
48 | --# Inherited options
49 | -- - `tabs:table`: Adds tabbed syntax to the final output.
50 | -- - `use:boolean|"docsify"|"pymdownx"` (`false`): Outputs tab syntax, either [docsify-tabs](https://jhildenbiddle.github.io/docsify-tabs/#/) or [pymdownx.tabbed](https://facelessuser.github.io/pymdown-extensions/extensions/tabbed/#syntax).
51 | -- - `docsify_header:string` (`"####"`): Headers to use for docsify-tabs.
52 | -- - `symbols:{string:string}`: Map of symbols to their documentation location
53 | -- - `symbols_in_code:boolean|'mkdocs'` (false): Whether to link symbols inside code. Only supports MkDocs.
54 | --# Recognized options
55 | -- - `headerOffset:number` (`1`): Offsets the header levels by n
56 | --///--
57 | export emitDescription = (desc, options={}) ->
58 | md = {}
59 | -- options
60 | options.headerOffset = 1 if options.headerOffset == nil
61 | -- emit
62 | for line in *desc
63 | switch line.type
64 | when "header"
65 | md[] = ""
66 | md[] = (rep "#", line.n + options.headerOffset) .. " " .. replaceSymbols line.content[1], options.symbols
67 | md[] = ""
68 | when "snippet"
69 | if options.tabs.use == 'docsify'
70 | if md[#md] == ""
71 | md[#md] = ""
72 | else
73 | md[] = ""
74 | md[] = ""
75 | md[] = "#{options.tabs.docsify_header} #{line.title}"
76 | md[] = ""
77 | elseif options.tabs.use == 'pymdownx'
78 | md[] = ""
79 | md[] = "=== \"#{line.title}\""
80 | md[] = ""
81 | md[] = "#{options.tabs.use == 'pymdownx' and ' ' or ''}```#{line.language}"
82 | for codeline in *line.content
83 | md[] = (options.tabs.use == 'pymdownx' and ' ' or '') .. codeline
84 | md[] = "#{options.tabs.use == 'pymdownx' and ' ' or ''}```"
85 | if options.tabs.use == 'docsify'
86 | md[] = ""
87 | md[] = ""
88 | elseif options.tabs.use == 'pymdownx'
89 | md[] = ""
90 | when "text"
91 | md[] = replaceSymbols line.content[1], options.symbols
92 | -- return
93 | return table.concat md, "\n"
94 | --///--
95 |
96 | --- @function emitSection :: section:@@@GenericSection@@@, options:table -> markdown:string
97 | --- Emits Markdown from a @@@GenericSection@@@
98 | -- This function takes the same options than [emit](#emit)
99 | --///--
100 | export emitSection = (section, options) ->
101 | md = {}
102 | -- failsafe option checking
103 | options.tabs ??= { use: false }
104 | options.tabs.docsify_header ??= "####"
105 | options.all ??= false
106 | options.columns ??= {}
107 | options.symbols ??= []
108 | options.inline_type ??= false
109 | options.types ??= {
110 | type: "Types"
111 | function: "Functions"
112 | constant: "Constants"
113 | class: "Classes"
114 | test: "Tests"
115 | table: "Tables"
116 | field: "Fields"
117 | variable: "Variables"
118 | }
119 | -- exit if internal
120 | if section.section.tags.internal and not options.all
121 | return table.concat md, "\n"
122 | -- emit title and description
123 | if section.section.name
124 | md[#md+1] = "## #{section.section.name}"
125 | md[#md+1] = ""
126 | if section.section.description
127 | md[#md+1] = emitDescription section.section.description, {headerOffset: 2, tabs: options.tabs, symbols: options.symbols}
128 | -- sort content
129 | byis = {}
130 | for k, v in pairs section.contents
131 | byis[v.is] or= {}
132 | byis[v.is][k] = v
133 | -- create TOC
134 | -- Header
135 | md[#md+1] = ""
136 | if options.columns[section.id]
137 | md[#md+1] = "| **#{options.columns[section.id][1]}** | #{options.columns[section.id][2]} |"
138 | elseif options.columns["*"]
139 | md[#md+1] = "| #{options.columns['*'][1]} | #{options.columns['*'][2]} |"
140 | else
141 | md[#md+1] = "| Element | Summary |"
142 | md[#md+1] = "|---------|---------|"
143 | -- elements
144 | for ty, elems in pairs byis
145 | -- subheaders
146 | md[#md+1] = "| **#{options.types[ty]}** | |"
147 | for elemk, elemv in bkpairs elems
148 | md[#md+1] = "| [#{elemk}](##{elemk}) | #{replaceNewlines replaceSymbols elemv.summary, options.symbols} |" unless elemv.tags?.internal and not options.all
149 | md[#md+1] = ""
150 | -- print types, functions, classes and constants
151 | for ty, elems in pairs byis
152 | for elemk, elemv in bkpairs elems
153 | -- continue if internal
154 | continue if elemv.tags?.internal and not options.all
155 | has_type = (ty == 'function') or (ty == 'constant') or (ty == 'field') or (ty == 'variable')
156 | -- print
157 | md[] = "" if options.inline_type
158 | md[] = "### #{options.inline_type and "#{elemk} " or elemk}"
159 | md[] = ":: #{replaceSymbols (trim elemv.type), options.symbols, true}" if has_type and options.inline_type
160 | md[] = "
" if options.inline_type
161 | md[] = ""
162 | md[] = "**Type:** `#{trim elemv.type}`{ .annotate } " if has_type and not options.inline_type
163 | md[] = "**Aliases:** `#{table.concat [n for n in *elemv.name[2,]], ', '}`" if #elemv.name > 1
164 | md[] = "" if (ty == "function") or (ty == "constant") or (ty == "field") or (ty == "variable") or (#elemv.name > 1)
165 | md[] = newlinesIntoParagraphs replaceSymbols elemv.summary, options.symbols
166 | md[] = ""
167 | md[] = emitDescription elemv.description, {headerOffset: 3, tabs: options.tabs, symbols: options.symbols}
168 | md[] = ""
169 | --
170 | return table.concat md, "\n"
171 | --///--
172 |
173 | --- @function listAllSymbols :: ast:@@@GenericAST@@@ -> {[symbol: string]: location:string}
174 | --- Locates all symbols in an AST
175 | --# Recognized options
176 | -- - `module:string`: Module string to use
177 | -- - `url_prefix:string`: Prefix of all documentation urls
178 | export listAllSymbols = (ast, options={}) ->
179 | base = options.module or ast.title
180 | prefix = (options.url_prefix or '/') .. base\gsub "%.", '/'
181 | prefix_parts = [part for part in base\gmatch "[^%.]+"]
182 | symbols = {}
183 | -- go into sections
184 | for i=1, #ast
185 | section = ast[i]
186 | for symbol_key, symbol_value in pairs section.contents
187 | for symbol_name in *symbol_value.name
188 | symbols[symbol_name] = "#{prefix}##{symbol_key}"
189 | symbols["#{prefix_parts[#prefix_parts]}.#{symbol_name}"] = "#{prefix}##{symbol_key}"
190 | return symbols
191 |
192 | --- @function emit :: ast:@@@GenericAST@@@, options:table -> markdown:string
193 | --- Emits Markdown from a @@@GenericAST@@@
194 | --# Recognized options
195 | -- - `tabs:table`: Adds tabbed syntax to the final output.
196 | -- - `use:boolean|"docsify"|"pymdownx"` (`false`): Outputs tab syntax, either [docsify-tabs](https://jhildenbiddle.github.io/docsify-tabs/#/) or [pymdownx.tabbed](https://facelessuser.github.io/pymdown-extensions/extensions/tabbed/#syntax).
197 | -- - `docsify_header:string` (`"####"`): **Unused**. Headers to use for docsify-tabs.
198 | -- - `all:boolean` (`false`): Also emits hidden elements.
199 | -- - `sections:table`: Settings for sections.
200 | -- - `columns:table`: Settings for columns.
201 | -- - `[n]:table`: (where `n` can be any number or `"*"`). Specifies the column names for section `n` (with fallback).
202 | -- - `[1]:string` (`"Element"`) - Left column
203 | -- - `[2]:string` (`"Summary"`) - Right column
204 | -- - `types:table`: Aliases for types.
205 | -- - `[type]:string`: (where `type` can be any of the types supported by [GenericAST](generic/parser.md#GenericAST)). Default values include `type=Types` and `function=Functions` among others.
206 | -- - `symbols:{string:string}`: Map of symbols to their documentation locations
207 | -- - `symbols_in_code:boolean|'mkdocs'` (false): Whether to link symbols inside code. Only supports MkDocs.
208 | --///-
209 | export emit = (ast, options={}) ->
210 | md = {}
211 | -- options
212 | options.tabs ??= { use: false }
213 | options.tabs.docsify_header ??= "####"
214 | options.all ??= false
215 | options.columns ??= {}
216 | options.symbols ??= {}
217 | options.symbols_in_code ??= false
218 | options.types ??= {
219 | type: "Types"
220 | function: "Functions"
221 | constant: "Constants"
222 | class: "Classes"
223 | test: "Tests"
224 | table: "Tables"
225 | field: "Fields"
226 | variable: "Variables"
227 | }
228 | -- exit if internal
229 | if ast.tags?.internal and not options.all
230 | return nil, "(internal)"
231 | -- emit title and description
232 | if ast.title
233 | md[#md+1] = "# #{ast.title}"
234 | md[#md+1] = ""
235 | if ast.description
236 | md[#md+1] = emitDescription ast.description, {headerOffset: 1, tabs: options.tabs, symbols: options.symbols}
237 | -- emit sections
238 | for i=1, #ast
239 | md[#md+1] = emitSection ast[i], options
240 | -- return
241 | return table.concat md, "\n"
242 | --///--
243 |
244 | --///--
245 | -- readFile = (f) ->
246 | -- local content
247 | -- with io.open f, "r"
248 | -- content = \read "*a"
249 | -- \close!
250 | -- return content
251 | -- import "fir.generic.backend" as {:extract}
252 | -- import "fir.generic.parser" as {:parse}
253 |
254 | -- const languageMoon = {single: "--"}
255 | -- (readFile "fir/generic/backend.mp")
256 | -- |> extract(_, languageMoon, {})
257 | -- |> parse( _, languageMoon)
258 | -- |> emit
259 | -- |> print
260 | --///--
261 |
--------------------------------------------------------------------------------
/fir/generic/parser.yue:
--------------------------------------------------------------------------------
1 | ---# fir.generic.parser #---
2 | -- A parser that works with the format provided by the [generic backend](/fir/generic/backend).
3 | --
4 | -- You can see an example of parser output [here](/fir/examples/generic-parser.html).
5 | {:match, :find, :gsub, :sub} = string
6 | import errorOut from 'fir.error'
7 |
8 | --# Utils #--
9 | -- @internal
10 | -- Several utils used internally.
11 |
12 | --- @function sanitize :: input:string -> escaped:string
13 | --- Takes any string and escapes it to work with patterns.
14 | -- @internal
15 | sanitize = (input) -> input\gsub "[%(%)%.%%%+%-%*%?%[%]%^%$]", "%%%0" if "string" == type input
16 |
17 | --- @function trim :: input:string -> trimmed:string, n:number
18 | --- Trims the whitespace at each end of a string.
19 | -- Taken from [Lua-users wiki's StringTrim (`trim11`)](http://lua-users.org/wiki/StringTrim).
20 | trim = (input) -> return if n := find input, "%S" then match input, ".*%S", n else ""
21 |
22 | --# Helpers #--
23 | -- Several functions that aid in the process of parsing.
24 |
25 | --- @type DescriptionLine
26 | --- A single element in a description returned by `parseDescription`
27 | --:moon Format
28 | -- DescriptionLine {
29 | -- type :: string (text|snippet|header)
30 | -- content :: [string]
31 | -- language? :: string -- only when type is snippet
32 | -- title? :: string -- only when type is snippet
33 | -- n? :: number -- only when type is header
34 | -- }
35 |
36 | --- @function parseDescription :: description:[string] -> description:[@@@DescriptionLine@@@], tags:[string]
37 | --- Parses codeblocks, tags, headers and normal text in descriptions.
38 | -- Returns an array of @@@DescriptionLine@@@s and an array of tags.
39 | --# Notes
40 | -- - In a codeblock, the first character of every line is removed (for a space).
41 | --# Supported tags
42 | -- - `@internal` - Adds an `internal` true flag to the element.
43 | --///--
44 | parseDescription = (desc) ->
45 | incode = false
46 | ndesc, code, tags = {}, {}, {}
47 | for line in *desc
48 | -- close snippet
49 | if line == ":"
50 | ndesc[] = code if incode
51 | incode = false
52 | -- add to snippet
53 | elseif incode
54 | code.content[] = sub line, 2
55 | -- tags
56 | elseif tag := match line, "^%s-@(%w+)(.-)"
57 | tag, value = match line, "^%s-@(%w+)(.-)"
58 | tags[tag] = (value == "") and true or value
59 | -- headers
60 | elseif header := match line, "^###%s+(.+)"
61 | ndesc[] = {content: {trim header}, type: "header", n: 3}
62 | elseif header := match line, "^##%s+(.+)"
63 | ndesc[] = {content: {trim header}, type: "header", n: 2}
64 | elseif header := match line, "^#%s+(.+)"
65 | ndesc[] = {content: {trim header}, type: "header", n: 1}
66 | elseif headern := match line, "^(%d)%s+(.+)"
67 | header, n = match line, "^(%d)%s+(.+)"
68 | ndesc[] = {content: {trim header}, type: "header", :n}
69 | -- tagged luassert tests (??x tag: .+)
70 | elseif match line, "%s-%?(%^?)([TtFfenu:=~])%s-(.-)%s-:%s-(.+)" -- tagged
71 | neg, mark, tag, line = match line, "%s-%?(%^?)([TtFfenu:=~])%s-(.-)%s-:%s-(.+)"
72 | ndesc[] = {content: {line}, :tag, :mark, negated: (neg != ""), type: "tagged-luassert-test"}
73 | -- luassert tests (??x .+)
74 | elseif match line, "%s-%?(%^?)([TtFfenu:=~])%s-(.+)" -- untagged
75 | neg, mark, line = match line, "%s-%?(%^?)([TtFfenu:=~])%s-(.+)"
76 | ndesc[] = {content: {line}, :mark, negated: (neg != ""), type: "luassert-test"}
77 | -- tagged tests (?? tag: .+)
78 | elseif taggedt := match line, "^%s-%?%?%s-(.-)%s-:%s-(.+)"
79 | tag, line = match line, "^%s-%?%?%s-(.-)%s-:%s-(.+)"
80 | ndesc[] = {content: {line}, :tag, type: "tagged-test"}
81 | -- test configuration (?$ .+)
82 | elseif match line, "%s-%?%$%s-(.+)"
83 | line = match line, "%s-%?%$%s-(.+)"
84 | ndesc[] = {content: {line}, type: "test-configuration"}
85 | -- tests (?? .+)
86 | elseif match line, "%s-%?%?%s-(.+)"
87 | line = match line, "%s-%?%?%s-(.+)"
88 | ndesc[] = {content: {line}, type: "test"}
89 | -- verbatim test lines (?! .+)
90 | elseif verbatimt := match line, "%s-%?!%s+(.+)"
91 | ndesc[] = {content: {verbatimt}, type: "verbatim-test"}
92 | -- verbatim test setup lines (!! .+)
93 | elseif verbatims := match line, "%s-!!%s+(.+)"
94 | ndesc[] = {content: {verbatims}, type: "verbatim-setup"}
95 | -- snippets
96 | elseif snippet := match line, "^:(%w+)%s+(.+)"
97 | -- if there was a previous snippet open, close it
98 | ndesc[] = code if incode
99 | -- init snippet
100 | language, title = match line, "^:(%w+)%s+(.+)"
101 | code = {:language, :title, content: {}, type: "snippet"}
102 | incode = true
103 | -- add line
104 | else
105 | ndesc[] = {type: "text", content: {line\match " ?(.+)"}}
106 | -- add last and return
107 | ndesc[] = code if incode
108 | return ndesc, tags
109 | --///--
110 |
111 | --- @function determineSummaryBoundary :: content:@@@GenericComment@@@.content, lead:string -> boundary:number
112 | --- Gets the boundary line where a summary ends and the description begins
113 | export determineSummaryBoundary = (content, lead) ->
114 | for i = 1, #content
115 | line = content[i]
116 | if line\match "^#{lead}%s+"
117 | continue
118 | else
119 | return i + 1
120 | return #content
121 |
122 | --# API #--
123 | -- This is the API provided to work with the generic parser.
124 |
125 | --- @type GenericAST GenericSection GenericSectionContent GenericElement
126 | --- The AST produced by `parse`.
127 | --:moon Format
128 | -- GenericAST {
129 | -- title :: string
130 | -- description :: [DescriptionLine]
131 | -- [n] :: GenericSection { -- where n is the ID of the section
132 | -- section :: GenericSectionDetails {
133 | -- id :: number
134 | -- name :: string
135 | -- description :: [DescriptionLine]
136 | -- }
137 | -- content :: GenericSectionContent {
138 | -- [name] :: GenericElement {
139 | -- is :: string (type|function|constant|class)
140 | -- description :: [DescriptionLine]
141 | -- name :: [string] -- an array, meant to also contain aliases
142 | -- summary :: string
143 | -- type :: string -- only when `is` is `"function"` or `"constant"`
144 | -- }
145 | -- }
146 | -- }
147 | -- }
148 |
149 | --- @function parse :: comments:[@@@GenericComment@@@], language:Language -> ast:@@@GenericAST@@@
150 | --- Parses a list of GenericComments into a GenericAST
151 | --///--
152 | export parse = (comments, language) ->
153 | ast = {}
154 | -- get character for special comments
155 | lead = sanitize sub language.single, -1
156 | -- keep track of variables
157 | tracking = {
158 | editing: false -- keep track of the part currently being edited, for fallbacks
159 | section: {id: 0, name: ""} -- keep track of the section being modified
160 | }
161 | context = { source: 'parser' }
162 | -- strip lead function
163 | stripLead = (str) -> match (str or ""), "^#{lead}+%s+(.+)"
164 | -- iterate comments
165 | for comment in *comments
166 | heading = comment.content[1]
167 | summary_boundary = determineSummaryBoundary comment.content, lead
168 | -- top header
169 | if title := match heading, "^#{lead}#(.+)##{lead}.+"
170 | tracking.editing = "title"
171 | ast.title = trim title
172 | ast.description, ast.tags = parseDescription [l for l in *comment.content[2,]]
173 | context = { ...context, tree: ast.title }
174 | -- section
175 | elseif section := match heading, "^#(.+)#.+"
176 | tracking.editing = "section"
177 | tracking.section = {
178 | id: tracking.section.id+1
179 | name: trim section
180 | }
181 | tracking.section.description, tracking.section.tags = parseDescription [l for l in *comment.content[2,]]
182 | ast[tracking.section.id] = {section: tracking.section, contents: {}}
183 | -- function
184 | elseif fn := match heading, "^#{lead}%s+@function%s+(.+)%s+::(.+)"
185 | fn, typ = match heading, "^#{lead}%s+@function%s+(.+)%s+::(.+)"
186 | names = [name for name in fn\gmatch "%S+"]
187 | main = names[1]
188 | if (not ast[tracking.section.id]) or (not ast[tracking.section.id].contents)
189 | errorOut "Functions must be contained within a section.",
190 | { ...context, tag_type: '@function', tag: main }
191 | ast[tracking.section.id].contents[main] = {
192 | is: "function"
193 | name: names
194 | type: typ
195 | summary: table.concat [stripLead comm for comm in *comment.content[2,summary_boundary]], '\n'
196 | }
197 | ast[tracking.section.id].contents[main].description, ast[tracking.section.id].contents[main].tags = parseDescription [l for l in *comment.content[summary_boundary,]]
198 | -- type
199 | elseif typ := match heading, "^#{lead}%s+@type%s+(.+)"
200 | names = [name for name in typ\gmatch "%S+"]
201 | main = names[1]
202 | if (not ast[tracking.section.id]) or (not ast[tracking.section.id].contents)
203 | errorOut "Types must be contained within a section.",
204 | { ...context, tag_type: '@type', tag: main }
205 | ast[tracking.section.id].contents[main] = {
206 | is: "type"
207 | name: names
208 | summary: table.concat [stripLead comm for comm in *comment.content[2,summary_boundary]], '\n'
209 | }
210 | ast[tracking.section.id].contents[main].description, ast[tracking.section.id].contents[main].tags = parseDescription [l for l in *comment.content[3,]]
211 | -- class
212 | elseif cls := match heading, "^#{lead}%s+@class%s+(.+)"
213 | names = [name for name in cls\gmatch "%S+"]
214 | main = names[1]
215 | if (not ast[tracking.section.id]) or (not ast[tracking.section.id].contents)
216 | errorOut "Classes must be contained within a section.",
217 | { ...context, tag_type: '@class', tag: main }
218 | ast[tracking.section.id].contents[main] = {
219 | is: "class"
220 | name: names
221 | summary: table.concat [stripLead comm for comm in *comment.content[2,summary_boundary]], '\n'
222 | }
223 | ast[tracking.section.id].contents[main].description, ast[tracking.section.id].contents[main].tags = parseDescription [l for l in *comment.content[3,]]
224 | -- const/constant
225 | elseif cst := match heading, "^#{lead}%s+@consta?n?t?%s+(.+)%s+::(.+)"
226 | cst, typ = match heading, "^#{lead}%s+@consta?n?t?%s+(.+)%s+::(.+)"
227 | names = [name for name in cst\gmatch "%S+"]
228 | main = names[1]
229 | if (not ast[tracking.section.id]) or (not ast[tracking.section.id].contents)
230 | errorOut "Constants must be contained within a section.",
231 | { ...context, tag_type: '@constant', tag: main }
232 | ast[tracking.section.id].contents[main] = {
233 | is: "constant"
234 | name: names
235 | type: typ
236 | summary: table.concat [stripLead comm for comm in *comment.content[2,summary_boundary]], '\n'
237 | }
238 | ast[tracking.section.id].contents[main].description, ast[tracking.section.id].contents[main].tags = parseDescription [l for l in *comment.content[3,]]
239 | -- test
240 | elseif test := match heading, "^#{lead}%s+@test%s+(.+)"
241 | names = [name for name in test\gmatch "%S+"]
242 | main = names[1]
243 | if (not ast[tracking.section.id]) or (not ast[tracking.section.id].contents)
244 | errorOut "Tests must be contained within a section.",
245 | { ...context, tag_type: '@test', tag: main }
246 | ast[tracking.section.id].contents[main] = {
247 | is: "test"
248 | name: names
249 | summary: table.concat [stripLead comm for comm in *comment.content[2,summary_boundary]], '\n'
250 | }
251 | ast[tracking.section.id].contents[main].description, ast[tracking.section.id].contents[main].tags = parseDescription [l for l in *comment.content[3,]]
252 | -- table
253 | elseif tbl := match heading, "^#{lead}%s+@table%s+(.+)"
254 | names = [name for name in tbl\gmatch "%S+"]
255 | main = names[1]
256 | if (not ast[tracking.section.id]) or (not ast[tracking.section.id].contents)
257 | errorOut "Tables must be contained within a section.",
258 | { ...context, tag_type: '@table', tag: main }
259 | ast[tracking.section.id].contents[main] = {
260 | is: "table"
261 | name: names
262 | summary: table.concat [stripLead comm for comm in *comment.content[2,summary_boundary]], '\n'
263 | }
264 | ast[tracking.section.id].contents[main].description, ast[tracking.section.id].contents[main].tags = parseDescription [l for l in *comment.content[3,]]
265 | -- field
266 | elseif field := match heading, "^#{lead}%s+@field%s+(.+)%s+::(.+)"
267 | fld, typ = match heading, "^#{lead}%s+@field%s+(.+)%s+::(.+)"
268 | names = [name for name in fld\gmatch "%S+"]
269 | main = names[1]
270 | if (not ast[tracking.section.id]) or (not ast[tracking.section.id].contents)
271 | errorOut "Fields must be contained within a section.",
272 | { ...context, tag_type: '@field', tag: main }
273 | ast[tracking.section.id].contents[main] = {
274 | is: "field"
275 | name: names
276 | type: typ
277 | summary: table.concat [stripLead comm for comm in *comment.content[2,summary_boundary]], '\n'
278 | }
279 | ast[tracking.section.id].contents[main].description, ast[tracking.section.id].contents[main].tags = parseDescription [l for l in *comment.content[3,]]
280 | -- var/variable
281 | elseif var := match heading, "^#{lead}%s+@vari?a?b?l?e?%s+(.+)%s+::(.+)"
282 | var, typ = match heading, "^#{lead}%s+@vari?a?b?l?e?%s+(.+)%s+::(.+)"
283 | names = [name for name in var\gmatch "%S+"]
284 | main = names[1]
285 | if (not ast[tracking.section.id]) or (not ast[tracking.section.id].contents)
286 | errorOut "Variables must be contained within a section.",
287 | { ...context, tag_type: '@variable', tag: main }
288 | ast[tracking.section.id].contents[main] = {
289 | is: "variable"
290 | name: names
291 | type: typ
292 | summary: table.concat [stripLead comm for comm in *comment.content[2,summary_boundary]], '\n'
293 | }
294 | ast[tracking.section.id].contents[main].description, ast[tracking.section.id].contents[main].tags = parseDescription [l for l in *comment.content[3,]]
295 | -- text
296 | else
297 | switch tracking.editing
298 | when "title"
299 | ast.description[#ast.description+1] = {type: "text", content: {""}}
300 | for l in *(parseDescription comment.content) do ast.description[#ast.description+1] = l
301 | when "section"
302 | tracking.section.description[#tracking.section.description+1] = {type: "text", content: {""}}
303 | for l in *(parseDescription comment.content) do tracking.section.description[#tracking.section.description+1] = l
304 | -- return
305 | return ast
306 | --///--
307 |
308 | --///--
309 | -- readFile = (f) ->
310 | -- local content
311 | -- with io.open f, "r"
312 | -- content = \read "*a"
313 | -- \close!
314 | -- return content
315 | -- import extract from require "fir.generic.backend"
316 | -- import generate from require "tableview"
317 | -- print generate parse (extract (readFile "fir/generic/backend.moon"), {single: "--"}, {}), {single: "--"}
318 | --///--
319 |
--------------------------------------------------------------------------------
/fir/generic/emit/tests.yue:
--------------------------------------------------------------------------------
1 | ---# fir.generic.emit.tests #---
2 | -- An emitter that works with a [GenericAST](/generic/parser.md#GenericAST) and turns it into runnable tests.
3 | {:rep, :find, :match} = string
4 | import errorOut, buildContext from 'fir.error'
5 |
6 | --# Utils #--
7 | -- @internal
8 | -- Utils to work internally.
9 |
10 | --- @function trim :: input:string -> trimmed:string, n:number
11 | --- Trims the whitespace at each end of a string.
12 | -- Taken from [Lua-users wiki's StringTrim (`trim11`)](http://lua-users.org/wiki/StringTrim).
13 | trim = (input) -> return if n := find input, "%S" then match input, ".*%S", n else ""
14 |
15 | --- @function fromMark :: mark:string, tag:string? -> func:string
16 | --- Returns a luassert function from a parser mark and maybe a tag
17 | fromMark = (mark, tag, context) -> switch mark
18 | when "T" then "is_true"
19 | when "t" then "truthy"
20 | when "F" then "is_false"
21 | when "f" then "falsy"
22 | when "E" then "has_no.errors"
23 | when "e" then "errors"
24 | when "n" then "near"
25 | when "u" then "unique"
26 | when ":" then "matches"
27 | when "=" then "equals"
28 | when "~" then "same"
29 | when "#"
30 | unless "string" == type tag
31 | errorOut "No tag specified while trying to use a luassert type assertion",
32 | buildContext context
33 | error "No tag specified while trying to use a luassert type assert" unless "string" == type tag
34 | return tag
35 |
36 | --- @constant KEYWORDS :: [string]
37 | --- Table of reserved keywords
38 | KEYWORDS = {
39 | "and", "break", "do", "else", "elseif", "end",
40 | "false", "for", "function", "goto", "if", "in",
41 | "local", "nil", "not", "or", "repeat", "return",
42 | "then", "true", "until", "while"
43 | }
44 |
45 | --- @function escapeName :: name:string -> escaped:string
46 | --- Escapes a variable name
47 | escapeName = (name) ->
48 | -- check that name is valid
49 | -- prepend underscore if starts with digit
50 | name = "_" .. name if name\match "^%d"
51 | -- avoid reserved keywords
52 | for keyword in *KEYWORDS do name = name\gsub keyword, "_%0"
53 | -- escape invalid characters
54 | escaped = name\gsub "[^a-zA-Z0-9_]", "_"
55 | return escaped
56 |
57 | --# API #--
58 | -- This is the API provided to work with the test emitter.
59 |
60 | --- @function emitString estring :: content:string
61 | --- Emits a string across languages
62 | -- @internal
63 | emitString = estring = (content) -> "'#{content}'"
64 |
65 | --- @function emitParentheses eparen :: content:string
66 | --- Emits an expression in parentheses across langauges
67 | -- @internal
68 | emitParentheses = eparen = (content) -> "(#{content})"
69 |
70 | --- @function emitLocalDeclaration elocal :: name:string, lhs:string, options:table
71 | --- Emits a local declaration across languages
72 | -- @internal
73 | emitLocalDeclaration = elocal = (name, lhs, options) ->
74 | switch options.language
75 | when 'moon'
76 | return "#{name} = #{lhs}"
77 | when 'lua'
78 | return "local #{name} = #{lhs}"
79 | else
80 | return "local #{name} = #{lhs}"
81 |
82 | --- @function emitTableIndex eindex :: table:string, index:string
83 | --- Emits a table index across languages
84 | emitTableIndex = eindex = (tbl, index) -> "#{tbl}[#{index}]"
85 |
86 | --- @function emitFunctionCall efn :: fn:string, args:[string], options:table
87 | --- Emits a function call across languages
88 | -- @internal
89 | emitFunctionCall = efn = (fn, args=[], options) ->
90 | switch options.language
91 | when 'moon', 'yue'
92 | return "#{fn}#{(#args > 0) and (' ' .. table.concat args, ', ') or '!'}"
93 | when 'lua'
94 | return "#{fn}(#{table.concat args, ', '})"
95 | else
96 | return "#{fn}(#{table.concat args, ', '})"
97 |
98 | --- @function emitInlineFunctionDefinition eidef :: args:[string], body:string, options:table
99 | --- Emits an inline function declaration across languages
100 | -- @internal
101 | emitInlineFunctionDefinition = eidef = (args, body, options) ->
102 | switch options.language
103 | when 'moon', 'yue'
104 | return "(#{table.concat args, ', '}) -> #{body}"
105 | when 'lua'
106 | return "function(#{table.concat args, ', '}) #{body} end"
107 | else
108 | return "function(#{table.concat args, ', '}) #{body} end"
109 |
110 | --- @function emitTableLiteral etable :: tbl:table, options:table
111 | --- Emits a table literal across languages
112 | -- @internal
113 | emitTableLiteral = etable = (tbl, options) ->
114 | switch options.language
115 | when 'moon', 'yue'
116 | return '{' .. (table.concat [ "[#{rhs}]: #{lhs}" for rhs, lhs in pairs tbl ], ', ') .. '}'
117 | when 'lua'
118 | return '{' .. (table.concat [ "[#{rhs}] = #{lhs}" for rhs, lhs in pairs tbl ], ', ') .. '}'
119 | else
120 | return '{' .. (table.concat [ "[#{rhs}] = #{lhs}" for rhs, lhs in pairs tbl ], ', ') .. '}'
121 |
122 | --- @function emitIfStatement eif :: condition:string, block:[string], options:table
123 | --- Emits an if statement across languages
124 | emitIfStatement = eif = (condition, block=[], options) ->
125 | switch options.language
126 | when 'moon', 'yue'
127 | return table.concat ["if #{condition}", ...[" " .. block_line for block_line in *block]], '\n'
128 | when 'lua'
129 | return table.concat ["if #{condition} then", ...[" " .. block_line for block_line in *block], 'end'], '\n'
130 | else
131 | return table.concat ["if #{condition} then", ...[" " .. block_line for block_line in *block], 'end'], '\n'
132 |
133 | --- @function emitPairsForStatement eforp :: k:string, v:string, iterator:string, body:string, options:string
134 | --- Emits a for statement across languages
135 | emitPairsForStatement = eforp = (k, v, iterator, body, options) ->
136 | switch options.language
137 | when 'moon', 'yue'
138 | return "for #{k}, #{v} in #{iterator} do #{body}"
139 | when 'lua'
140 | return "for #{k}, #{v} in #{iterator} do #{body} end"
141 | else
142 | return "for #{k}, #{v} in #{iterator} do #{body} end"
143 |
144 | --- @function emitTestHeader :: node:table, count:number, options:table, append:function, prepend:function, placement:string -> nil
145 | --- Emits test headers.
146 | -- @internal
147 | emitTestHeader = (node, count, options, append, prepend, placement) ->
148 | unless options.testHeaders then return count + 1
149 | append "--- @test #{placement}##{count}"
150 | append "--- Test for element #{placement} ##{count}"
151 | append "-- - **Type:** `#{node.type}`#{(node.type\match 'luassert') and (' ('..(fromMark node.mark)..')') or ''}"
152 | append "-- - **Tag:** `#{node.tag}`" if node.tag
153 | append "--:#{options.language ?? 'moon'} Test"
154 | append "-- #{node.content[1]}"
155 | return count + 1
156 |
157 | --- @function emitTestWrapper ewrap :: name:string, count:number, body:string, options:table -> code:string
158 | --- Wraps a test line in counters and error protectors
159 | emitTestWrapper = ewrap = (name, count, body, options) ->
160 | return table.concat {
161 | efn "xpcall",
162 | {
163 | eparen (eidef {}, (table.concat {
164 | '',
165 | ' ' .. (eif 'FIR_NEXT_PENDING', {
166 | ' ' .. efn 'FIR_PINCR', {}, options
167 | ' ' .. 'FIR_NEXT_PENDING = false'
168 | ' ' .. efn 'FIR_PEND', {(estring name), (estring tostring count)}, options
169 | ' ' .. 'return'
170 | }, options)
171 | ' ' .. body
172 | ' ' .. efn "FIR_SUCCEED", {(estring name), (estring tostring count)}, options
173 | ' ' .. efn "FIR_SINCR", {}, options
174 | '',
175 | }, options.newline),
176 | options
177 | ),
178 | eparen (eidef {'err'}, (table.concat {
179 | '',
180 | ' ' .. efn "FIR_FAIL", {(estring name), (estring tostring count), 'err'}, options
181 | ' ' .. efn "FIR_FINCR", {}, options
182 | '',
183 | }, options.newline),
184 | options
185 | ),
186 | },
187 | options
188 | }, options.newline
189 |
190 | --- @function emitInternal :: description:[DescriptionLine], options:table, append:function, prepend:function, placement:string -> nil
191 | --- Emits tests, as an internal function to be used in several parts.
192 | -- @internal
193 | emitInternal = (description, options, append, prepend, placement) ->
194 | ctrim = options.trim and trim or (x) -> x
195 | count = 1
196 | assert_fn = "assert#{options.luassert and '.truthy' or ''}"
197 | for node in *description
198 | switch node.type
199 | when "test-configuration"
200 | switch ctrim node.content[1]
201 | when "pending"
202 | append "FIR_NEXT_PENDING = true"
203 | when "test"
204 | count = emitTestHeader node, count, options, append, prepend, placement
205 | append ewrap "#{placement}##{count - 1}", count - 1, (efn assert_fn,
206 | { (ctrim node.content[1]) },
207 | options), options
208 | append ""
209 | when "tagged-test"
210 | count = emitTestHeader node, count, options, append, prepend, placement
211 | append eif "tags['#{ctrim node.tag}']",
212 | { ewrap "#{placement}##{count - 1}", count - 1, (efn assert_fn, { (ctrim node.content[1]) }, options), options },
213 | options
214 | append ""
215 | when "verbatim-test"
216 | count = emitTestHeader node, count, options, append, prepend, placement
217 | append ctrim node.content[1]
218 | append ""
219 | when "verbatim-setup"
220 | append ctrim node.content[1]
221 | when "luassert-test"
222 | count = emitTestHeader node, count, options, append, prepend, placement
223 | append ewrap "#{placement}##{count - 1}", count - 1, (efn "assert#{node.negated and '.not' or ''}.#{fromMark node.mark}",
224 | { ctrim node.content[1] },
225 | options), options
226 | append ""
227 | when "tagged-luassert-test"
228 | count = emitTestHeader node, count, options, append, prepend, placement
229 | if node.mark == "#" -- special type case
230 | append ewrap "#{placement}##{count - 1}", count - 1, (efn "assert#{node.negated and '.not' or ''}.#{fromMark node.mark, node.tag}",
231 | { ctrim node.content[1] },
232 | options), options
233 | else
234 | append eif (eindex 'tags', ctrim node.tag),
235 | {
236 | (ewrap "#{placement}##{count - 1}", count - 1,
237 | (efn "assert#{node.negated and '.not' or ''}.#{fromMark node.mark, node.tag}",
238 | { ctrim node.content[1] },
239 | options),
240 | options)
241 | }, options
242 | append ""
243 |
244 | --- @function emit :: ast:GenericAST, options:table -> code:string
245 | --- Emits Lua tests from a `GenericAST` using `test` and `tagged-test` nodes.
246 | --///--
247 | export emit = (ast, options={}) ->
248 | module = options.module or
249 | ast.title or
250 | errorOut "Could not generate tests. Module name not found. Please set %{green}`options.module`%{red}.",
251 | { source: 'emit.tests' }
252 | -- file and utils
253 | lua = {
254 | "---# Tests for #{module} #---"
255 | "-- This test suite was automatically generated from documentation comments,"
256 | "-- the tests are embedded in the code itself. Do not edit this file."
257 | "#{options.header or ''}"
258 | }
259 | clength = 4 -- where to start placing lines
260 | append = (x) ->
261 | return if (not options.docs) and x\match "^%-%-"
262 | lua[] = tostring x
263 | prepend = (x, offset=0) ->
264 | return if (not options.docs) and x\match "^%-%-"
265 | table.insert lua, clength+offset, tostring x
266 | clength += 1
267 | -- options
268 | options.trim ??= true
269 | options.newline ??= "\n"
270 | options.luassert ??= true
271 | options.print ??= 'pretty' -- | 'pretty-verbose' | 'plain' | 'plain-verbose' | 'tap' | false
272 | options.testHeaders ??= true
273 | options.docs ??= true
274 | options.language ??= 'moon'
275 | options.auto_unpack ??= false
276 | -- then require
277 | if options.luassert
278 | prepend elocal 'assert', (efn 'require', { estring 'luassert' }, options), options
279 | if (options.print == 'pretty') or (options.print == 'pretty-verbose')
280 | prepend elocal 'S', (efn 'require', { estring 'ansikit.style' }, options), options
281 | prepend elocal 'M', (efn 'require', { estring module }, options), options
282 | -- now unpack methods
283 | if options.auto_unpack
284 | prepend efn 'setmetatable', {'_ENV', (etable {[estring '__index']: 'M'}, options)}, options
285 | if options.unpack
286 | prepend ""
287 | prepend "--///--"
288 | prepend "-- unpack"
289 | for name in *options.unpack
290 | prepend elocal (escapeName name), (eindex 'M', estring name), options
291 | prepend "--///--"
292 | -- add snippet
293 | if options.snippet
294 | prepend ""
295 | prepend options.snippet
296 | -- parse arguments into a tag table
297 | prepend ""
298 | prepend "--///--"
299 | prepend "-- argument and tag initialization"
300 | prepend elocal "args, tags", "{...}, {}", options
301 | -- set metatable for options.all
302 | if options.all
303 | prepend efn 'setmetatable',
304 | {
305 | 'tags',
306 | (etable {
307 | [estring '__index']: (eidef {}, 'return true', options)
308 | }, options)
309 | },
310 | options
311 | prepend eforp '_', 'v', (efn 'ipairs', {'args'}, options), 'tags[v] = true', options
312 | prepend "--///--"
313 | -- preemptively count all tests
314 | pre_count = 0
315 | for node in *ast.description
316 | switch node.type
317 | when "test", "tagged-test", "verbatim-test", "luassert-test", "tagged-luassert-test"
318 | pre_count += 1
319 | for section in *ast
320 | for node in *section.section.description
321 | switch node.type
322 | when "test", "tagged-test", "verbatim-test", "luassert-test", "tagged-luassert-test"
323 | pre_count += 1
324 | for name, element in pairs section.contents
325 | for node in *element.description
326 | switch node.type
327 | when "test", "tagged-test", "verbatim-test", "luassert-test", "tagged-luassert-test"
328 | pre_count += 1
329 | -- if tap output, print header
330 | if options.print == 'tap'
331 | prepend efn 'print', {estring "TAP version 14"}, options
332 | prepend efn 'print', {estring "1..#{pre_count}"}, options
333 | -- setup counters and other metadata
334 | prepend "--///--"
335 | prepend "-- counters"
336 | prepend elocal "FIR_SUCCESSES, FIR_FAILURES, FIR_PENDINGS", "0, 0, 0", options
337 | prepend "-- incrementing functions"
338 | prepend elocal "FIR_SINCR, FIR_FINCR, FIR_PINCR", (
339 | table.concat {
340 | "(" .. (eidef {}, 'FIR_SUCCESSES = FIR_SUCCESSES + 1', options) .. ")",
341 | "(" .. (eidef {}, 'FIR_FAILURES = FIR_FAILURES + 1', options) .. ")",
342 | "(" .. (eidef {}, 'FIR_PENDINGS = FIR_PENDINGS + 1', options) .. ")"
343 | }, ", "
344 | ), options
345 | prepend "-- other functions"
346 | prepend elocal "FIR_SUCCEED", (eidef {'current', 'count'}, (
347 | switch options.print
348 | when false, 'plain', 'pretty'
349 | ''
350 | when 'tap'
351 | efn 'print', { "#{estring 'ok '} .. count .. ' - ' .. current" }, options
352 | when 'plain-verbose'
353 | efn 'print', {
354 | "#{estring "Success → "} .. current"
355 | }, options
356 | when 'pretty-verbose'
357 | efn 'print', {
358 | efn 'S.style', {
359 | "#{estring "%{green}Success%{reset} → "} .. current"
360 | }, options
361 | }, options
362 | ), options), options
363 | prepend elocal "FIR_FAIL", (eidef {'current', 'count', 'err'}, (
364 | switch options.print
365 | when false
366 | ''
367 | when 'tap'
368 | efn 'print', { "#{estring 'not ok '} .. count .. ' - ' .. current .. ': ' .. #{eparen (efn 'string.gsub', {'err', (estring '[\\r\\n]+'), (estring '')}, options)}" }, options
369 | when 'plain', 'plain-verbose'
370 | efn 'print', {
371 | "#{estring "Failure → "} .. current .. #{estring "\\n "} .. err"
372 | }, options
373 | when 'pretty', 'pretty-verbose'
374 | efn 'print', {
375 | efn 'S.style', {
376 | "#{estring "%{red}Failure%{reset} → "} .. current .. #{estring "\\n %{white}"} .. err"
377 | }, options
378 | }, options
379 | ), options), options
380 | prepend elocal "FIR_PEND", (eidef {'current', 'count'}, (
381 | switch options.print
382 | when false
383 | ''
384 | when 'tap'
385 | efn 'print', { "#{estring 'not ok '} .. count .. ' - ' .. current .. ' # SKIP'" }, options
386 | when 'plain', 'plain-verbose'
387 | efn 'print', {
388 | "#{estring "Pending → "} .. current"
389 | }, options
390 | when 'pretty', 'pretty-verbose'
391 | efn 'print', {
392 | efn 'S.style', {
393 | "#{estring "%{yellow}Pending%{reset} → "} .. current"
394 | }, options
395 | }, options
396 | ), options), options
397 | prepend elocal "FIR_RESULT", (eidef {'current'}, (
398 | switch options.print
399 | when false, 'tap'
400 | ''
401 | when 'plain', 'plain-verbose'
402 | table.concat {
403 | ''
404 | ' ' .. (elocal 'total_successes', (
405 | (efn 'string.rep', {(estring '+'), 'FIR_SUCCESSES'}, options)
406 | ), options)
407 | ' ' .. (elocal 'total_pendings', (
408 | (efn 'string.rep', {(estring '?'), 'FIR_PENDINGS'}, options)
409 | ), options)
410 | ' ' .. (elocal 'total_failures', (
411 | (efn 'string.rep', {(estring '-'), 'FIR_FAILURES'}, options)
412 | ), options)
413 | ' ' .. efn 'print', {}, options
414 | ' ' .. efn 'print', { "total_successes .. total_pendings .. total_failures" }, options
415 | ' ' .. efn 'print', { "#{estring '%{green}'} .. FIR_SUCCESSES .. #{estring '%{white} successes, %{yellow}'} .. FIR_PENDINGS .. #{estring '%{white} pending, %{red}'} .. FIR_FAILURES .. #{estring '%{white} errors.'}" }, options
416 | ''
417 | }, '\n'
418 | when 'pretty', 'pretty-verbose'
419 | table.concat {
420 | ''
421 | ' ' .. (elocal 'total_successes', (
422 | efn 'S.style', {
423 | "#{estring "%{green}"} .. " .. (efn 'string.rep', {(estring '+'), 'FIR_SUCCESSES'}, options)
424 | }, options
425 | ), options)
426 | ' ' .. (elocal 'total_pendings', (
427 | efn 'S.style', {
428 | "#{estring "%{yellow}"} .. " .. (efn 'string.rep', {(estring '?'), 'FIR_PENDINGS'}, options)
429 | }, options
430 | ), options)
431 | ' ' .. (elocal 'total_failures', (
432 | efn 'S.style', {
433 | "#{estring "%{red}"} .. " .. (efn 'string.rep', {(estring '-'), 'FIR_FAILURES'}, options)
434 | }, options
435 | ), options)
436 | ' ' .. efn 'print', {}, options
437 | ' ' .. efn 'print', { "total_successes .. total_pendings .. total_failures" }, options
438 | ' ' .. efn 'print', { efn 'S.style', {"#{estring '%{green}'} .. FIR_SUCCESSES .. #{estring '%{white} successes, %{yellow}'} .. FIR_PENDINGS .. #{estring '%{white} pending, %{red}'} .. FIR_FAILURES .. #{estring '%{white} errors.'}" }, options }, options
439 | ''
440 | }, '\n'
441 | ), options), options
442 | prepend "-- marker for pending tests"
443 | prepend elocal "FIR_NEXT_PENDING", "false", options
444 | prepend "--///--"
445 | -- emit tests or tagged tests in description
446 | if ast.description
447 | append "--# #{options.subheader or 'General'} #--"
448 | append "-- Tests for the whole file are placed here."
449 | -- append efn 'print', {(estring "#{options.subheader or 'General'} ->")}, options if options.print
450 | append ""
451 | emitInternal ast.description, options, append, prepend, ast.title
452 | -- iterate sections
453 | for section in *ast
454 | append "--# #{section.section.name} #--"
455 | append "-- Tests for #{section.section.name}."
456 | -- append efn 'print', {(estring " #{section.section.name} ->")}, options if options.print
457 | append ""
458 | -- emit tests in description
459 | emitInternal section.section.description, options, append, prepend, section.section.name
460 | -- emit tests in every element
461 | for name, element in pairs section.contents
462 | append "--# `#{name}`"
463 | append ""
464 | emitInternal element.description, options, append, prepend, name
465 | --
466 | append efn 'FIR_RESULT', {}, options
467 | return table.concat lua, "\n"
468 | --///--
469 |
--------------------------------------------------------------------------------
/docs/examples/generic-backend.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 | Table (11)
85 |
86 |
87 | table: 0x55a88bc94d00
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 | [
97 |
98 |
99 | 1
100 |
101 |
102 | ] =
103 |
104 |
105 |
106 | Table (3)
107 |
108 |
109 | table: 0x55a88bc686d0
110 |
111 |
112 |
113 |
114 |
115 |
116 | [
117 |
118 |
119 | "start"
120 |
121 |
122 | ] =
123 |
124 |
125 |
126 | 1
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 | [
137 |
138 |
139 | "content"
140 |
141 |
142 | ] =
143 |
144 |
145 |
146 | Table (2)
147 |
148 |
149 | table: 0x55a88bc77ff0
150 |
151 |
152 |
153 |
154 |
155 |
156 | [
157 |
158 |
159 | 1
160 |
161 |
162 | ] =
163 |
164 |
165 |
166 | "-# fir.generic.backend #---"
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 | [
175 |
176 |
177 | 2
178 |
179 |
180 | ] =
181 |
182 |
183 |
184 | " A generic implementation of a [backend](#/backend.md) for the Fir documentation generator."
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 | [
196 |
197 |
198 | "end"
199 |
200 |
201 | ] =
202 |
203 |
204 |
205 | 2
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 | [
219 |
220 |
221 | 2
222 |
223 |
224 | ] =
225 |
226 |
227 |
228 | Table (3)
229 |
230 |
231 | table: 0x55a88bc78040
232 |
233 |
234 |
235 |
236 |
237 |
238 | [
239 |
240 |
241 | "start"
242 |
243 |
244 | ] =
245 |
246 |
247 |
248 | 4
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 | [
259 |
260 |
261 | "content"
262 |
263 |
264 | ] =
265 |
266 |
267 |
268 | Table (2)
269 |
270 |
271 | table: 0x55a88bc772b0
272 |
273 |
274 |
275 |
276 |
277 |
278 | [
279 |
280 |
281 | 1
282 |
283 |
284 | ] =
285 |
286 |
287 |
288 | " This specific implementation uses a `language` module (defaults to `fir.generic.languages`) to"
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 | [
297 |
298 |
299 | 2
300 |
301 |
302 | ] =
303 |
304 |
305 |
306 | " parse comments from any file."
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 | [
318 |
319 |
320 | "end"
321 |
322 |
323 | ] =
324 |
325 |
326 |
327 | 5
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 | [
341 |
342 |
343 | 3
344 |
345 |
346 | ] =
347 |
348 |
349 |
350 | Table (3)
351 |
352 |
353 | table: 0x55a88bc77330
354 |
355 |
356 |
357 |
358 |
359 |
360 | [
361 |
362 |
363 | "start"
364 |
365 |
366 | ] =
367 |
368 |
369 |
370 | 7
371 |
372 |
373 |
374 |
375 |
376 |
377 |
378 |
379 |
380 | [
381 |
382 |
383 | "content"
384 |
385 |
386 | ] =
387 |
388 |
389 |
390 | Table (1)
391 |
392 |
393 | table: 0x55a88baeb510
394 |
395 |
396 |
397 |
398 |
399 |
400 | [
401 |
402 |
403 | 1
404 |
405 |
406 | ] =
407 |
408 |
409 |
410 | " This is yet another paragraph!"
411 |
412 |
413 |
414 |
415 |
416 |
417 |
418 |
419 |
420 |
421 | [
422 |
423 |
424 | "end"
425 |
426 |
427 | ] =
428 |
429 |
430 |
431 | 7
432 |
433 |
434 |
435 |
436 |
437 |
438 |
439 |
440 |
441 |
442 |
443 |
444 | [
445 |
446 |
447 | 4
448 |
449 |
450 | ] =
451 |
452 |
453 |
454 | Table (3)
455 |
456 |
457 | table: 0x55a88bb47f60
458 |
459 |
460 |
461 |
462 |
463 |
464 | [
465 |
466 |
467 | "start"
468 |
469 |
470 | ] =
471 |
472 |
473 |
474 | 11
475 |
476 |
477 |
478 |
479 |
480 |
481 |
482 |
483 |
484 | [
485 |
486 |
487 | "content"
488 |
489 |
490 | ] =
491 |
492 |
493 |
494 | Table (2)
495 |
496 |
497 | table: 0x55a88ba9d490
498 |
499 |
500 |
501 |
502 |
503 |
504 | [
505 |
506 |
507 | 1
508 |
509 |
510 | ] =
511 |
512 |
513 |
514 | "# @internal Utils #--"
515 |
516 |
517 |
518 |
519 |
520 |
521 |
522 | [
523 |
524 |
525 | 2
526 |
527 |
528 | ] =
529 |
530 |
531 |
532 | " Several utils used internally."
533 |
534 |
535 |
536 |
537 |
538 |
539 |
540 |
541 |
542 |
543 | [
544 |
545 |
546 | "end"
547 |
548 |
549 | ] =
550 |
551 |
552 |
553 | 12
554 |
555 |
556 |
557 |
558 |
559 |
560 |
561 |
562 |
563 |
564 |
565 |
566 | [
567 |
568 |
569 | 5
570 |
571 |
572 | ] =
573 |
574 |
575 |
576 | Table (3)
577 |
578 |
579 | table: 0x55a88bb66f20
580 |
581 |
582 |
583 |
584 |
585 |
586 | [
587 |
588 |
589 | "start"
590 |
591 |
592 | ] =
593 |
594 |
595 |
596 | 14
597 |
598 |
599 |
600 |
601 |
602 |
603 |
604 |
605 |
606 | [
607 |
608 |
609 | "content"
610 |
611 |
612 | ] =
613 |
614 |
615 |
616 | Table (2)
617 |
618 |
619 | table: 0x55a88bb448a0
620 |
621 |
622 |
623 |
624 |
625 |
626 | [
627 |
628 |
629 | 1
630 |
631 |
632 | ] =
633 |
634 |
635 |
636 | "- @function sanitize :: input:string -> escaped:string"
637 |
638 |
639 |
640 |
641 |
642 |
643 |
644 | [
645 |
646 |
647 | 2
648 |
649 |
650 | ] =
651 |
652 |
653 |
654 | "- Takes any string and escapes it to work with patterns."
655 |
656 |
657 |
658 |
659 |
660 |
661 |
662 |
663 |
664 |
665 | [
666 |
667 |
668 | "end"
669 |
670 |
671 | ] =
672 |
673 |
674 |
675 | 15
676 |
677 |
678 |
679 |
680 |
681 |
682 |
683 |
684 |
685 |
686 |
687 |
688 | [
689 |
690 |
691 | 6
692 |
693 |
694 | ] =
695 |
696 |
697 |
698 | Table (3)
699 |
700 |
701 | table: 0x55a88bb2e170
702 |
703 |
704 |
705 |
706 |
707 |
708 | [
709 |
710 |
711 | "start"
712 |
713 |
714 | ] =
715 |
716 |
717 |
718 | 18
719 |
720 |
721 |
722 |
723 |
724 |
725 |
726 |
727 |
728 | [
729 |
730 |
731 | "content"
732 |
733 |
734 | ] =
735 |
736 |
737 |
738 | Table (3)
739 |
740 |
741 | table: 0x55a88bba8560
742 |
743 |
744 |
745 |
746 |
747 |
748 | [
749 |
750 |
751 | 1
752 |
753 |
754 | ] =
755 |
756 |
757 |
758 | "- @function trim :: input:string -> trimmed:string, n:number"
759 |
760 |
761 |
762 |
763 |
764 |
765 |
766 | [
767 |
768 |
769 | 2
770 |
771 |
772 | ] =
773 |
774 |
775 |
776 | "- Trims the whitespace at each end of a string."
777 |
778 |
779 |
780 |
781 |
782 |
783 |
784 | [
785 |
786 |
787 | 3
788 |
789 |
790 | ] =
791 |
792 |
793 |
794 | " Taken from [Lua-users wiki's StringTrim (`trim11`)](http://lua-users.org/wiki/StringTrim)."
795 |
796 |
797 |
798 |
799 |
800 |
801 |
802 |
803 |
804 |
805 | [
806 |
807 |
808 | "end"
809 |
810 |
811 | ] =
812 |
813 |
814 |
815 | 20
816 |
817 |
818 |
819 |
820 |
821 |
822 |
823 |
824 |
825 |
826 |
827 |
828 | [
829 |
830 |
831 | 7
832 |
833 |
834 | ] =
835 |
836 |
837 |
838 | Table (3)
839 |
840 |
841 | table: 0x55a88bab1bf0
842 |
843 |
844 |
845 |
846 |
847 |
848 | [
849 |
850 |
851 | "start"
852 |
853 |
854 | ] =
855 |
856 |
857 |
858 | 23
859 |
860 |
861 |
862 |
863 |
864 |
865 |
866 |
867 |
868 | [
869 |
870 |
871 | "content"
872 |
873 |
874 | ] =
875 |
876 |
877 |
878 | Table (3)
879 |
880 |
881 | table: 0x55a88bab1cf0
882 |
883 |
884 |
885 |
886 |
887 |
888 | [
889 |
890 |
891 | 1
892 |
893 |
894 | ] =
895 |
896 |
897 |
898 | "- @function lines = input:string -> lines:table"
899 |
900 |
901 |
902 |
903 |
904 |
905 |
906 | [
907 |
908 |
909 | 2
910 |
911 |
912 | ] =
913 |
914 |
915 |
916 | "- Splits a table into lines."
917 |
918 |
919 |
920 |
921 |
922 |
923 |
924 | [
925 |
926 |
927 | 3
928 |
929 |
930 | ] =
931 |
932 |
933 |
934 | " Taken from [Penlight's `stringx` (`splitlines`)](https://stevedonovan.github.io/Penlight/api/libraries/pl.stringx.html#splitlines)."
935 |
936 |
937 |
938 |
939 |
940 |
941 |
942 |
943 |
944 |
945 | [
946 |
947 |
948 | "end"
949 |
950 |
951 | ] =
952 |
953 |
954 |
955 | 25
956 |
957 |
958 |
959 |
960 |
961 |
962 |
963 |
964 |
965 |
966 |
967 |
968 | [
969 |
970 |
971 | 8
972 |
973 |
974 | ] =
975 |
976 |
977 |
978 | Table (3)
979 |
980 |
981 | table: 0x55a88bc08520
982 |
983 |
984 |
985 |
986 |
987 |
988 | [
989 |
990 |
991 | "start"
992 |
993 |
994 | ] =
995 |
996 |
997 |
998 | 40
999 |
1000 |
1001 |
1002 |
1003 |
1004 |
1005 |
1006 |
1007 |
1008 | [
1009 |
1010 |
1011 | "content"
1012 |
1013 |
1014 | ] =
1015 |
1016 |
1017 |
1018 | Table (2)
1019 |
1020 |
1021 | table: 0x55a88bc08660
1022 |
1023 |
1024 |
1025 |
1026 |
1027 |
1028 | [
1029 |
1030 |
1031 | 1
1032 |
1033 |
1034 | ] =
1035 |
1036 |
1037 |
1038 | "- @function lconcat = la:[*], lb:[*] -> merged:[*]"
1039 |
1040 |
1041 |
1042 |
1043 |
1044 |
1045 |
1046 | [
1047 |
1048 |
1049 | 2
1050 |
1051 |
1052 | ] =
1053 |
1054 |
1055 |
1056 | "- Concatenates two lists"
1057 |
1058 |
1059 |
1060 |
1061 |
1062 |
1063 |
1064 |
1065 |
1066 |
1067 | [
1068 |
1069 |
1070 | "end"
1071 |
1072 |
1073 | ] =
1074 |
1075 |
1076 |
1077 | 41
1078 |
1079 |
1080 |
1081 |
1082 |
1083 |
1084 |
1085 |
1086 |
1087 |
1088 |
1089 |
1090 | [
1091 |
1092 |
1093 | 9
1094 |
1095 |
1096 | ] =
1097 |
1098 |
1099 |
1100 | Table (3)
1101 |
1102 |
1103 | table: 0x55a88bc70e40
1104 |
1105 |
1106 |
1107 |
1108 |
1109 |
1110 | [
1111 |
1112 |
1113 | "start"
1114 |
1115 |
1116 | ] =
1117 |
1118 |
1119 |
1120 | 48
1121 |
1122 |
1123 |
1124 |
1125 |
1126 |
1127 |
1128 |
1129 |
1130 | [
1131 |
1132 |
1133 | "content"
1134 |
1135 |
1136 | ] =
1137 |
1138 |
1139 |
1140 | Table (2)
1141 |
1142 |
1143 | table: 0x55a88bb599e0
1144 |
1145 |
1146 |
1147 |
1148 |
1149 |
1150 | [
1151 |
1152 |
1153 | 1
1154 |
1155 |
1156 | ] =
1157 |
1158 |
1159 |
1160 | "# API #--"
1161 |
1162 |
1163 |
1164 |
1165 |
1166 |
1167 |
1168 | [
1169 |
1170 |
1171 | 2
1172 |
1173 |
1174 | ] =
1175 |
1176 |
1177 |
1178 | " This is the API provided to work with the generic backend."
1179 |
1180 |
1181 |
1182 |
1183 |
1184 |
1185 |
1186 |
1187 |
1188 |
1189 | [
1190 |
1191 |
1192 | "end"
1193 |
1194 |
1195 | ] =
1196 |
1197 |
1198 |
1199 | 49
1200 |
1201 |
1202 |
1203 |
1204 |
1205 |
1206 |
1207 |
1208 |
1209 |
1210 |
1211 |
1212 | [
1213 |
1214 |
1215 | 10
1216 |
1217 |
1218 | ] =
1219 |
1220 |
1221 |
1222 | Table (3)
1223 |
1224 |
1225 | table: 0x55a88bc03760
1226 |
1227 |
1228 |
1229 |
1230 |
1231 |
1232 | [
1233 |
1234 |
1235 | "start"
1236 |
1237 |
1238 | ] =
1239 |
1240 |
1241 |
1242 | 51
1243 |
1244 |
1245 |
1246 |
1247 |
1248 |
1249 |
1250 |
1251 |
1252 | [
1253 |
1254 |
1255 | "content"
1256 |
1257 |
1258 | ] =
1259 |
1260 |
1261 |
1262 | Table (9)
1263 |
1264 |
1265 | table: 0x55a88bc037f0
1266 |
1267 |
1268 |
1269 |
1270 |
1271 |
1272 | [
1273 |
1274 |
1275 | 1
1276 |
1277 |
1278 | ] =
1279 |
1280 |
1281 |
1282 | "- @type Language"
1283 |
1284 |
1285 |
1286 |
1287 |
1288 |
1289 |
1290 | [
1291 |
1292 |
1293 | 2
1294 |
1295 |
1296 | ] =
1297 |
1298 |
1299 |
1300 | "- Language type accepted by [`extract`](#extract)."
1301 |
1302 |
1303 |
1304 |
1305 |
1306 |
1307 |
1308 | [
1309 |
1310 |
1311 | 3
1312 |
1313 |
1314 | ] =
1315 |
1316 |
1317 |
1318 | ":moon Format"
1319 |
1320 |
1321 |
1322 |
1323 |
1324 |
1325 |
1326 | [
1327 |
1328 |
1329 | 4
1330 |
1331 |
1332 | ] =
1333 |
1334 |
1335 |
1336 | " {"
1337 |
1338 |
1339 |
1340 |
1341 |
1342 |
1343 |
1344 | [
1345 |
1346 |
1347 | 5
1348 |
1349 |
1350 | ] =
1351 |
1352 |
1353 |
1354 | " single :: string"
1355 |
1356 |
1357 |
1358 |
1359 |
1360 |
1361 |
1362 | [
1363 |
1364 |
1365 | 6
1366 |
1367 |
1368 | ] =
1369 |
1370 |
1371 |
1372 | " multi :: [string]"
1373 |
1374 |
1375 |
1376 |
1377 |
1378 |
1379 |
1380 | [
1381 |
1382 |
1383 | 7
1384 |
1385 |
1386 | ] =
1387 |
1388 |
1389 |
1390 | " extensions :: [string]"
1391 |
1392 |
1393 |
1394 |
1395 |
1396 |
1397 |
1398 | [
1399 |
1400 |
1401 | 8
1402 |
1403 |
1404 | ] =
1405 |
1406 |
1407 |
1408 | " }"
1409 |
1410 |
1411 |
1412 |
1413 |
1414 |
1415 |
1416 | [
1417 |
1418 |
1419 | 9
1420 |
1421 |
1422 | ] =
1423 |
1424 |
1425 |
1426 | ":"
1427 |
1428 |
1429 |
1430 |
1431 |
1432 |
1433 |
1434 |
1435 |
1436 |
1437 | [
1438 |
1439 |
1440 | "end"
1441 |
1442 |
1443 | ] =
1444 |
1445 |
1446 |
1447 | 59
1448 |
1449 |
1450 |
1451 |
1452 |
1453 |
1454 |
1455 |
1456 |
1457 |
1458 |
1459 |
1460 | [
1461 |
1462 |
1463 | 11
1464 |
1465 |
1466 | ] =
1467 |
1468 |
1469 |
1470 | Table (3)
1471 |
1472 |
1473 | table: 0x55a88bb9a4f0
1474 |
1475 |
1476 |
1477 |
1478 |
1479 |
1480 | [
1481 |
1482 |
1483 | "start"
1484 |
1485 |
1486 | ] =
1487 |
1488 |
1489 |
1490 | 61
1491 |
1492 |
1493 |
1494 |
1495 |
1496 |
1497 |
1498 |
1499 |
1500 | [
1501 |
1502 |
1503 | "content"
1504 |
1505 |
1506 | ] =
1507 |
1508 |
1509 |
1510 | Table (7)
1511 |
1512 |
1513 | table: 0x55a88bb86a40
1514 |
1515 |
1516 |
1517 |
1518 |
1519 |
1520 | [
1521 |
1522 |
1523 | 1
1524 |
1525 |
1526 | ] =
1527 |
1528 |
1529 |
1530 | "- @function extract :: input:string, language?:Language, options?:table"
1531 |
1532 |
1533 |
1534 |
1535 |
1536 |
1537 |
1538 | [
1539 |
1540 |
1541 | 2
1542 |
1543 |
1544 | ] =
1545 |
1546 |
1547 |
1548 | "- Extracts comment from a string separated by newlines."
1549 |
1550 |
1551 |
1552 |
1553 |
1554 |
1555 |
1556 | [
1557 |
1558 |
1559 | 3
1560 |
1561 |
1562 | ] =
1563 |
1564 |
1565 |
1566 | "# Available options"
1567 |
1568 |
1569 |
1570 |
1571 |
1572 |
1573 |
1574 | [
1575 |
1576 |
1577 | 4
1578 |
1579 |
1580 | ] =
1581 |
1582 |
1583 |
1584 | " - `patterns:boolean` (`false`): Whether to use patterns for the language fields and ignore string or not."
1585 |
1586 |
1587 |
1588 |
1589 |
1590 |
1591 |
1592 | [
1593 |
1594 |
1595 | 5
1596 |
1597 |
1598 | ] =
1599 |
1600 |
1601 |
1602 | ' - `ignore:string` (`"///"`): String used to determine when to start or stop ignoring comments.'
1603 |
1604 |
1605 |
1606 |
1607 |
1608 |
1609 |
1610 | [
1611 |
1612 |
1613 | 6
1614 |
1615 |
1616 | ] =
1617 |
1618 |
1619 |
1620 | " - `merge:boolean` (`true`): Whether to merge adjacent single-line comments."
1621 |
1622 |
1623 |
1624 |
1625 |
1626 |
1627 |
1628 | [
1629 |
1630 |
1631 | 7
1632 |
1633 |
1634 | ] =
1635 |
1636 |
1637 |
1638 | ' - `paragraphs:boolean` (`true`): Whether to split multi-line comments by empty strings (`""`).'
1639 |
1640 |
1641 |
1642 |
1643 |
1644 |
1645 |
1646 |
1647 |
1648 |
1649 | [
1650 |
1651 |
1652 | "end"
1653 |
1654 |
1655 | ] =
1656 |
1657 |
1658 |
1659 | 67
1660 |
1661 |
1662 |
1663 |
1664 |
1665 |
1666 |
1667 |
1668 |
1669 |
1670 |
1671 |
1672 |
--------------------------------------------------------------------------------