-
+shdoc has no args and expects a shell script with comments on stdin and will produce markdown as stdout.
+
+```bash
+$ shdoc < your-shell-script.sh > doc.md
+```
+
+## Example
Generate documentation with the following command:
+
~~~bash
$ shdoc < lib.sh > doc.md
~~~
-_Source_ [examples/readme-example.sh](examples/readme-example.sh)
+The table below shows the result for the following files:
+_Source_: [examples/readme-example.sh](examples/readme-example.sh)
_Output_: [examples/readme-example.md](examples/readme-example.md)
+
+
+
+ |
+
~~~bash
#!/bin/bash
# @file libexample
@@ -69,8 +79,24 @@ say-hello() {
echo "Hello $1"
}
-~~~
+# @section Sub-section
+# @description Some grouped functions.
+# Sections allow a sub-set of other annotations and will ignore unsupported ones.
+# @see README
+# @example
+# # @section example
+# # @see [some-link](./README.md)
+# # @example ...
+
+# @description This is nested
+deeper-level() { echo; }
+
+# @endsection
+
+# @description Back up again
+up-again() { echo; }
+~~~
|
@@ -91,19 +117,22 @@ The project solves lots of problems:
## Index
* [say-hello](#say-hello)
+* [Sub-section](#sub-section)
+ * [deeper-level](#deeper-level)
+* [up-again](#up-again)
-### say-hello
+## say-hello
My super function.
Not thread-safe.
-#### Example
+### Example
```bash
echo "test: $(say-hello World)"
```
-#### Options
+### Options
* **-h** | **--help**
@@ -113,30 +142,55 @@ echo "test: $(say-hello World)"
Set a value.
-#### Arguments
+### Arguments
* **$1** (string): A value to print
-#### Exit codes
+### Exit codes
* **0**: If successful.
* **1**: If an empty string passed.
-#### Output on stdout
+### Output on stdout
* Output 'Hello $1'.
It hopes you say Hello back.
-#### Output on stderr
+### Output on stderr
* Output 'Oups !' on error.
It did it again.
-#### See also
+### See also
* [validate()](#validate)
* [shdoc](https://github.com/reconquest/shdoc).
+## Sub-section
+
+Some grouped functions.
+Sections allow a sub-set of other annotations and will ignore unsupported ones.
+
+### Example
+
+```bash
+# @section example
+# @see [some-link](./README.md)
+# @example ...
+```
+
+### See also
+
+* [README](#readme)
+
+### deeper-level
+
+This is nested
+
+## up-again
+
+Back up again
+
~~~
|
@@ -150,6 +204,11 @@ echo "test: $(say-hello World)"
A name of the project, used as a title of the doc. Can be specified once in the beginning of the
file.
+**Note:**
+A comment block linked to `@name` or `@file` must be followed by at least one none-comment line
+after its `@description` and other annotations, or it will not be recognized correctly.
+correctly.
+
**Example**
```bash
@@ -193,17 +252,63 @@ function super() {
### `@section`
-The name of a section of the file. Can be used to group functions.
+The name of a section of the file. Can be used to group functions. Creates a paragraph titled with the section name.
+All functions following this annotation will be placed as sub-paragraphs of the given section.
+Use again to start the next section or `@endsection` to break out of the current section.
+`@section` blocks need to be followed by at least one none-comment none-EOF line to be printed.
+
+**fork-specific**
+- The sections will also appear in the TOC.
+- `@section` influences the header level used for function description titles.
+ 1. When a section is active, function headers will use `###`, the section header itself uses `##`.
+ 2. After `@endsection` has been encountered, or before and `@section` annotation, functions will use `##`.
+ 3. The nesting will also be reflected in the TOC.
**Example**
```bash
# @section My utilities functions
# @description The following functions can be used to solve problems.
+
+# @description sub-function
+func-in-section() { ... }
+```
+
+### `@endsection`
+
+**This annotation is fork-specific**
+When `@section` is used, shdoc can not by itself detect the end of a section. It would assume the remaining script to be on a deeper nested level and only ever insert subsequent section titles in-between.
+This annotation can be used to disable the grouping again for the following functions. It has no effect on its own. Captions for functions contained in sections are level 3 (`###`),
+ungrouped functions are level 2 (`##`).
+
+**original:**
+In the original, there is no termination marker for `@section` and no adjustment of header levels depending on section nesting. There, all functions are always level 3, sections are level 2 and section descriptions
+are inserted in-between. So there is no visible distinction between function descriptions contained in a section and others not contained. In fact, once a `@section` was specified, everything following it
+appears as if it is contained in that.
+
+**Example**
+```bash
+# @section First group
+# @description Logical group
+
+# @description
+some-group() { ... }
+
+# @endsection
+
+# @description something not grouped
+important-top-level() { ... }
+
+# @section
+# @description another sub-group
```
### `@example`
-A multiline example of the function usage. Can be specified only alongside the function definition.
+A multiline example of the function usage. Can be specified only alongside the function and section definitions.
+Must be followed by an empty line if other annotations shall be used after it in the same block. Without the empty line,
+the annotation would instead be put into the example.
+
+**fork-specific**: Also works for `@section` and `@file`.
**Example**
```bash
@@ -225,8 +330,8 @@ If an option argument is expected, it must be specified between `<` and `>`
```bash
# @description Says hi to a given person.
# @option -h A short option.
-# @arg --value= A long option with argument.
-# @arg -v | --value A long option with short option alternative.
+# @option --value= A long option with argument.
+# @option -v | --value A long option with short option alternative.
say-hello() {
...
}
@@ -265,7 +370,9 @@ say-hello-world() {
### `@set`
A description of a global variable that is set while calling the function.
-Can be specified multiple times to describe any number of variables
+Can be specified multiple times to describe any number of variables.
+
+**fork-specific**: Also works for `@section` and `@file`.
**Example**
@@ -339,6 +446,8 @@ say-hello-world() {
Create a link on the given function in the "See Also" section.
+**fork-specific**: Also works for `@section` and `@file`.
+
**Example**
```bash
@@ -351,10 +460,11 @@ say-hello-world() {
### `@internal`
-When you want to skip documentation generation for a particular function, you can specify this
-`@internal` tag.
-It allows you to have the same style of doc comments across the script and keep internal
-functions hidden from users.
+When you want to skip documentation generation for a particular function, you can specify this `@internal` tag.
+It allows you to have the same style of doc comments across the script and keep internal functions hidden from users.
+
+**fork-specific:**
+When used in `@section` blocks, all functions until the next `@endsection` or `@section` will be considered internal.
**Example**
@@ -365,40 +475,140 @@ show-msg() {
}
```
-## Usage
+## Advanced features
-shdoc has no args and expects a shell script with comments on stdin and will produce markdown as stdout.
+**fork-specific**
+
+This section describes advanced features and capabilities which are not necessarily needed for simple function documentation.
+
+### Literal MD source lines
+
+By default, `shdoc` performs whitespace trimming at the start of a comment line while it builds description blocks.
+This behavior disables the possibility to use formatting options like code blocks by indentation (4 spaces), or use
+pre-formatted (and indented) text within fenced code blocks (`~~~` or ` ``` `).
+
+It is possible to disable the trimming behavior by using `#|` as comment prefix, instead of just `# ` (as for annotations).
+`shdoc` will only consume the beginning of the line matching the regex `[ ]*#|` and leave the rest untouched.
+**Note:** This behaviour has implications for lines where tables are declared.
+
+**Examples**
+- Simple
+
+
+ | Input |
+ Result |
+
+
+ |
+
```bash
-$ shdoc < your-shell-script.sh > doc.md
+# @description Simple
+#| This is kept literally...
+#| ... this too
+# me too... not.
+```
+
+ |
+
+
+```md
+Simple
+ This is kept literally...
+ ... this too
+me too... not.
```
+ |
+
+
+
+- Tables
+**Note:** In MD itself, there is little reason to indenting a table. Instead, indenting it too far would convert the table into a code block instead,
+where the table source code is not processed as MD.
+The literal md marker does not perform any extra checks to find out wether the pipe contained in `#|` is part of the table or not. The pipe character
+connected to the comment hash will be stripped. As a consequence, writing tables requires a space between `#` and the left beginning `|` for regular
+tables. Alternatively, if the table code should really be indented, 2 pipes must be used, with spaces in between.
+
+
+
+ | Input |
+ Result |
+
+
+ |
+
+```bash
+# @description Table
+#| co1 | co2 |
+# | abc | def |
+#| | 123 | 456 |
+#| | 123 | 456 |
+```
+
+ |
+
+
+```md
+Table
+ co1 | co2 |
+| abc | def |
+ | 123 | 456 |
+ | 123 | 456 |
+```
+
+ |
+
+
+
+
+Takeaway:
+1. If (for optical reasons only) you want to indent a table, don't use `#|`.
+2. For regular tables, keep at least one space between `#` and `|` at the beginning of the line
+3. Indenting table source requires `#|`, followed by the desired amount of spaces, then `|` to start the table row.
+
+### Parameter support
+
+`shdoc` can perform simple parameter replacement. It will recognize replacement tags in the form `%%%%` and
+try to retrieve the value of an equally-named environment variable ``. If any value is found, the entire tag
+is replaced. The tag is left untouched otherwise.
+
+This allows the documentation source comments to work like a simple template
+
+**Rules:**
+- Only tags in comment blocks are recognized (lines starting with `[[:space:]]*#`).
+- Tag replacement happens first, before any other processing is done. This allows to place annotations in tags.
+- Multiple tags can be replaced per line (identical and different ones), but they will not be resolved recursively.
+ **Exception:** Tags are looped over in the order of definition. When the content of the first tag resolves to a
+ subsequent tag name, then this subsequent tag will be replaced in a later iteration.
+- `` must be a valid name for a shell environment variable, consisting of `[a-Z0-9-_]+`.
+- `shdoc` does not perform any parameter expansion.
+- There is no way to 'mask' or 'quote' a tag, if it matches the form above.
+- Example blocks or sections fenced in backticks or quotes are neither recognized, not treated specially.
+
## Installation
### Arch Linux
Arch Linux users can install shdoc using package in AUR: [shdoc-git](https://aur.archlinux.org/packages/shdoc-git)
+**Note:**
+The package in AUR installs the original version [reconquest/shdoc](https://github.com/reconquest/shdoc). This fork does not have a package.
+However, shdoc itself is a single script file, it can just be downloaded directly from the repository.
+
### Using Git
NOTE: shdoc requires gawk: `apt-get install gawk`
```bash
-git clone --recursive https://github.com/reconquest/shdoc
+git clone --recursive https://github.com/GB609/shdoc
cd shdoc
sudo make install
```
### Others
-Unfortunately, there are no packages of shdoc for other distros, but we're looking for contributions.
-
-## Examples
-
-See example documentation on:
-
-* [tests.sh](https://github.com/reconquest/tests.sh/blob/master/REFERENCE.md)
-* [coproc.bash](https://github.com/reconquest/coproc.bash/blob/master/REFERENCE.md)
+There are no packages of shdoc for other distros.
# LICENSE
diff --git a/examples/readme-example.md b/examples/readme-example.md
index 3f41e0a..742663a 100644
--- a/examples/readme-example.md
+++ b/examples/readme-example.md
@@ -13,19 +13,22 @@ The project solves lots of problems:
## Index
* [say-hello](#say-hello)
+* [Sub-section](#sub-section)
+ * [deeper-level](#deeper-level)
+* [up-again](#up-again)
-### say-hello
+## say-hello
My super function.
Not thread-safe.
-#### Example
+### Example
```bash
echo "test: $(say-hello World)"
```
-#### Options
+### Options
* **-h** | **--help**
@@ -35,27 +38,52 @@ echo "test: $(say-hello World)"
Set a value.
-#### Arguments
+### Arguments
* **$1** (string): A value to print
-#### Exit codes
+### Exit codes
* **0**: If successful.
* **1**: If an empty string passed.
-#### Output on stdout
+### Output on stdout
* Output 'Hello $1'.
It hopes you say Hello back.
-#### Output on stderr
+### Output on stderr
* Output 'Oups !' on error.
It did it again.
-#### See also
+### See also
* [validate()](#validate)
* [shdoc](https://github.com/reconquest/shdoc).
+## Sub-section
+
+Some grouped functions.
+Sections allow a sub-set of other annotations and will ignore unsupported ones.
+
+### Example
+
+```bash
+# @section example
+# @see [some-link](./README.md)
+# @example ...
+```
+
+### See also
+
+* [README](#readme)
+
+### deeper-level
+
+This is nested
+
+## up-again
+
+Back up again
+
diff --git a/examples/readme-example.sh b/examples/readme-example.sh
index f5a3bab..b064167 100644
--- a/examples/readme-example.sh
+++ b/examples/readme-example.sh
@@ -38,3 +38,20 @@ say-hello() {
echo "Hello $1"
}
+
+# @section Sub-section
+# @description Some grouped functions.
+# Sections allow a sub-set of other annotations and will ignore unsupported ones.
+# @see README
+# @example
+# # @section example
+# # @see [some-link](./README.md)
+# # @example ...
+
+# @description This is nested
+deeper-level() { echo; }
+
+# @endsection
+
+# @description Back up again
+up-again() { echo; }
\ No newline at end of file
diff --git a/shdoc b/shdoc
index 5fbdad3..f013e6c 100755
--- a/shdoc
+++ b/shdoc
@@ -61,6 +61,8 @@ BEGIN {
debug_fd = 2
}
debug_file = "/dev/fd/" debug_fd
+
+ function_nesting = 2
}
# @description Display the given error message with its line number on stderr.
@@ -108,6 +110,11 @@ function process_function(text) {
}
debug("→ function")
+ if (internal_section){
+ debug("→ → function: contained in internal section, skip")
+ return
+ }
+
if (is_internal) {
debug("→ → function: it is internal, skip")
is_internal = 0
@@ -124,9 +131,9 @@ function process_function(text) {
)
# Add function documentation to output.
- doc = concat(doc, render_docblock(func_name, description, docblock))
+ doc = concat(doc, render_docblock(func_name, description, docblock, function_nesting))
# Add function link to table of contents.
- toc = concat(toc, render_toc_item(func_name))
+ toc = concat(toc, render_toc_item(func_name, function_nesting))
}
# Function document has been added to output.
@@ -178,15 +185,16 @@ function render_toc_link(text) {
# @see https://github.com/jch/html-pipeline/blob/master/lib/html/pipeline/toc_filter.rb#L44-L45
url = tolower(url)
gsub(/[^[:alnum:] _-]/, "", url)
- gsub(/_/, "", url)
gsub(/ /, "-", url)
}
return "[" text "](#" url ")"
}
-function render_toc_item(title) {
- return "* " render_toc_link(title)
+function render_toc_item(title, nesting_level) {
+ nesting_depth = (nesting_level - 2) * 2 + 2
+ print_format = "%" nesting_depth "s"
+ return sprintf(print_format, "* ") render_toc_link(title)
}
function unindent(text) {
@@ -232,10 +240,23 @@ function unindent(text) {
function reset() {
debug("→ reset()")
- delete docblock
+ # Make sure the variable exists before deleting.
+ # Without this, shdoc will fail for fully unannotated scripts.
+ # This might happen when batch-handling a list of files
+ if (typeof(docblock) != "unassigned"){
+ delete docblock
+ }
description = ""
}
+function reset_section() {
+ debug("→ reset_section()")
+
+ delete docblock_filter
+ section = ""
+ section_description = ""
+}
+
function handle_description() {
debug("→ handle_description")
@@ -250,14 +271,16 @@ function handle_description() {
}
if (section != "" && section_description == "") {
- debug("→ → section description: added")
- section_description = description
- return;
+ debug("→ → section description: added")
+ section_description = description
+ description = ""
+ return;
}
- if (file_description == "") {
+ if (file_intro_enabled != 0 && file_description == "" && in_file_block == 0) {
debug("→ → file description: added")
- file_description = description
+ file_description = description
+ description = ""
return;
}
}
@@ -317,12 +340,10 @@ function docblock_concat(key, value) {
function docblock_push(key, value) {
new_item_index = length(docblock[key])
# Reinitialize docblock key value if it is empty to allow for array storage.
- if(new_item_index == 0)
- {
+ if(new_item_index == 0) {
delete docblock[key]
}
- if(isarray(value))
- {
+ if(isarray(value)) {
# Value is an array. Add its contents key by key to the docblock.
# Note that is only allow for single dimension value array.
@@ -361,8 +382,8 @@ function docblock_append(docblock_name, text) {
# @param title Title of the rendered section.
#
# @stdout A unordered list of the dockblock entries.
-function render_docblock_list(docblock, docblock_name, title) {
- push(lines, render("h4", title))
+function render_docblock_list(docblock, docblock_name, title, nesting) {
+ push(lines, render(nesting, title))
# Initialize list item.
item = ""
# For each dockblock line.
@@ -451,26 +472,66 @@ function process_at_option(text) {
}
}
-function render_docblock(func_name, description, docblock) {
+function process_section() {
+ debug("→ process_section")
+ if (!section_active || section == "") {
+ debug("→ → no valid section name - skip")
+ return;
+ }
+ if (is_internal){
+ debug("→ → section marked as internal - skip")
+ internal_section = 1
+ is_internal = 0
+ reset_section();
+ return;
+ }
+
+ debug("→ → section: [" section "]")
+ debug("→ → section_description: [" section_description "]")
+
+ # support a subset of additional annotations for sections themselves.
+ docblock_filter["example"] = 1
+ docblock_filter["see"] = 1
+ docblock_filter["set"] = 1
+
+ toc = concat(toc, render_toc_item(section, 2))
+
+ result = render_docblock(section, section_description, docblock, 2)
+ doc = concat(doc, result)
+
+ reset_section();
+}
+
+function docblock_allows(key) {
+ debug("→ → check docblock filter for: [" key "]")
+ if (!isarray(docblock_filter) || length(docblock_filter) == 0) {
+ debug("→ → → no filters defined")
+ return 1
+ }
+ if (key in docblock_filter) {
+ debug("→ → → filter is: [" docblock_filter[key] "]")
+ return docblock_filter[key]
+ } else {
+ debug("→ → → not allowed/defined: [" key "]")
+ return 0
+ }
+}
+
+function render_docblock(func_name, description, docblock, nesting) {
debug("→ render_docblock")
- debug("→ → func_name: [" func_name "]")
+ debug("→ → block_name: [" func_name "]")
debug("→ → description: [" description "]")
+ debug("→ → header_depth: [" nesting "]")
# Reset lines variable to allow for a new array creation.
delete lines
- if (section != "") {
- lines[0] = render("h2", section)
- if (section_description != "") {
- push(lines, section_description)
- # Add empty line to signal end of description.
- push(lines, "")
- }
- section = ""
- section_description = ""
- push(lines, render("h3", func_name))
- } else {
- lines[0] = render("h3", func_name)
+ nesting_top = "h" nesting;
+ nesting_one = "h" (nesting + 1);
+ nesting_two = "h" (nesting + 2);
+
+ if (func_name) {
+ lines[0] = render(nesting_top, func_name)
}
if (description != "") {
@@ -478,9 +539,11 @@ function render_docblock(func_name, description, docblock) {
# Add empty line to signal end of description.
push(lines, "")
}
+
+ filter_active = isarray(docblock_filter)
- if ("example" in docblock) {
- push(lines, render("h4", "Example"))
+ if (docblock_allows("example") && "example" in docblock) {
+ push(lines, render(nesting_one, "Example"))
push(lines, render("code", "bash"))
push(lines, unindent(docblock["example"]))
push(lines, render("/code"))
@@ -488,9 +551,9 @@ function render_docblock(func_name, description, docblock) {
}
if ("option" in docblock || "option-bad" in docblock) {
- push(lines, render("h4", "Options"))
+ push(lines, render(nesting_one, "Options"))
- if ("option" in docblock) {
+ if (docblock_allows("option") && "option" in docblock) {
for (i in docblock["option"]) {
# Add strong around options, but exclude pipes.
term = render("strong", docblock["option"][i]["term"])
@@ -507,7 +570,7 @@ function render_docblock(func_name, description, docblock) {
}
}
- if ("option-bad" in docblock) {
+ if (docblock_allows("option-bad") && "option-bad" in docblock) {
for (i in docblock["option-bad"]) {
item = render("li", docblock["option-bad"][i])
push(lines, item)
@@ -518,8 +581,8 @@ function render_docblock(func_name, description, docblock) {
}
}
- if ("arg" in docblock) {
- push(lines, render("h4", "Arguments"))
+ if (docblock_allows("arg") && "arg" in docblock) {
+ push(lines, render(nesting_one, "Arguments"))
# Sort args by indexes (i.e. by argument number.)
asorti(docblock["arg"], sorted_indexes)
@@ -537,15 +600,15 @@ function render_docblock(func_name, description, docblock) {
push(lines, "")
}
- if ("noargs" in docblock) {
+ if (docblock_allows("noargs") && "noargs" in docblock) {
push(lines, render("i", "Function has no arguments."))
# Add empty line to signal end of list in markdown.
push(lines, "")
}
- if ("set" in docblock) {
- push(lines, render("h4", "Variables set"))
+ if (docblock_allows("set") && "set" in docblock) {
+ push(lines, render(nesting_one, "Variables set"))
for (i in docblock["set"]) {
item = docblock["set"][i]
item = render("set", item)
@@ -557,8 +620,8 @@ function render_docblock(func_name, description, docblock) {
push(lines, "")
}
- if ("exitcode" in docblock) {
- push(lines, render("h4", "Exit codes"))
+ if (docblock_allows("exitcode") && "exitcode" in docblock) {
+ push(lines, render(nesting_one, "Exit codes"))
for (i in docblock["exitcode"]) {
item = render("li", render("exitcode", docblock["exitcode"][i]))
push(lines, item)
@@ -568,20 +631,20 @@ function render_docblock(func_name, description, docblock) {
push(lines, "")
}
- if ("stdin" in docblock) {
- render_docblock_list(docblock, "stdin", "Input on stdin")
+ if (docblock_allows("stdin") && "stdin" in docblock) {
+ render_docblock_list(docblock, "stdin", "Input on stdin", nesting_one)
}
- if ("stdout" in docblock) {
- render_docblock_list(docblock, "stdout", "Output on stdout")
+ if (docblock_allows("stdout") && "stdout" in docblock) {
+ render_docblock_list(docblock, "stdout", "Output on stdout", nesting_one)
}
- if ("stderr" in docblock) {
- render_docblock_list(docblock, "stderr", "Output on stderr")
+ if (docblock_allows("stderr") && "stderr" in docblock) {
+ render_docblock_list(docblock, "stderr", "Output on stderr", nesting_one)
}
- if ("see" in docblock) {
- push(lines, render("h4", "See also"))
+ if (docblock_allows("see") && "see" in docblock) {
+ push(lines, render(nesting_one, "See also"))
for (i in docblock["see"]) {
item = render("li", render_toc_link(docblock["see"][i]))
push(lines, item)
@@ -605,17 +668,39 @@ function debug(msg) {
debug("line: [" $0 "]")
}
+/^[[:space:]]*#.*?%%[a-zA-Z0-9_\-]+%%.*/ {
+ debug("→ found placeholders")
+ splitted[0] = ""
+ patsplit($0, splitted, /%%([a-zA-Z0-9_\-]+?)%%/)
+
+ for(i in splitted) {
+ env_name = splitted[i]
+ gsub(/%%/, "", env_name)
+ if (ENVIRON[env_name] != ""){
+ debug("→ → replace [" splitted[i] "] with " ENVIRON[env_name])
+ gsub(splitted[i], ENVIRON[env_name])
+ }
+ }
+}
+
/^[[:space:]]*# @internal/ {
debug("→ @internal")
- is_internal = 1
+ # ignore the flag while we are in an internal section
+ # to prevent dangling values not reset by function blocks
+ # because internal_section is checked first in process_function
+ if (!internal_section){
+ is_internal = 1
+ }
next
}
/^[[:space:]]*# @(name|file)/ {
debug("→ @name|@file")
- sub(/^[[:space:]]*# @(name|file) /, "")
+ sub(/^[[:space:]]*# @(name|file)[[:space:]]*/, "")
+ file_intro_enabled = 1
file_title = $0
+ in_file_block = 1
next
}
@@ -645,31 +730,50 @@ in_description {
in_description = 0
handle_description()
+ } else if (match($0, /^[[:space:]]*#\|/)) {
+ debug("→ → in_description: literal [" $0 "] (keep spaces)")
+ sub(/^[[:space:]]*#\|/, "")
+ description = concat(description, $0)
+ next
} else {
- debug("→ → in_description: concat")
sub(/^[[:space:]]*# @description[[:space:]]*/, "")
sub(/^[[:space:]]*#[[:space:]]*/, "")
sub(/^[[:space:]]*#$/, "")
-
+ debug("→ → in_description: concat [" $0 "]")
description = concat(description, $0)
next
}
}
+/^[[:space:]]*# @endsection/ {
+ debug("→ @endsection")
+ process_section()
+ section = ""
+ section_description = ""
+ section_active = 0
+ function_nesting = 2
+ internal_section = 0
+
+ next
+}
+
/^[[:space:]]*# @section/ {
debug("→ @section")
sub(/^[[:space:]]*# @section /, "")
+ if (internal_section){
+ internal_section = 0
+ }
section = $0
+ section_active = 1
+ function_nesting = 3
next
}
/^[[:space:]]*# @example/ {
debug("→ @example")
-
in_example = 1
-
next
}
@@ -719,10 +823,10 @@ in_example {
# Test if @arg is a numbered item (or $@).
if(match(arg_text, /^\$([0-9]+|@)[[:space:]]/, contents)) {
- debug(" → → found arg $" arg_number)
-
# Fetch matched values.
arg_number = contents[1]
+
+ debug(" → → found arg $" arg_number)
# Zero pad argument number for sorting.
if(arg_number ~ /[0-9]+/){
@@ -835,14 +939,15 @@ match($0, /^([[:blank:]]*#[[:blank:]]+)@(stdin|stdout|stderr)[[:blank:]]+(.*[^[:
# - `function function_name () {`
# - `function_name () {`
# - `function_name {`
-/^[[:blank:]]*(function[[:blank:]]+)?([a-zA-Z0-9_\-:-\\.]+)[[:blank:]]*(\([[:blank:]]*\))?[[:blank:]]*\{/ \
+/^[[:blank:]]*(function[[:blank:]]+)?([a-zA-Z0-9_\-:-\\.]+)[[:blank:]]*(\([[:blank:]]*\))?[[:blank:]]*[({]/ \
{
process_function($0)
+ function_declaration_complete = 1
}
# If line look like a function declaration but is missing opening bracket,
/^[[:blank:]]*(function[[:blank:]]+)?([a-zA-Z0-9_\-:-\\.]+)[[:blank:]]*(\([[:blank:]]*\))?/ \
-{
+ && !function_declaration_complete {
# store it for future use
debug("→ look like a function declaration, store line")
function_declaration = $0
@@ -850,7 +955,7 @@ match($0, /^([[:blank:]]*#[[:blank:]]+)@(stdin|stdout|stderr)[[:blank:]]+(.*[^[:
}
# Handle lone opening bracket if previous line is a function declaration.
-/^[[:blank:]]*\{/ \
+/^[[:blank:]]*[({]/ \
&& function_declaration != "" {
debug("→ multi-line function declaration.")
# Process function declaration.
@@ -868,13 +973,26 @@ match($0, /^([[:blank:]]*#[[:blank:]]+)@(stdin|stdout|stderr)[[:blank:]]+(.*[^[:
# Handle non comment lines.
/^[^#]*$/ {
debug("→ break")
+
+ if(file_intro_enabled && in_file_block && isarray(docblock) && length(docblock)){
+ # support a subset of additional annotations for @file sections.
+ debug("handle file docblock")
+ docblock_filter["example"] = 1
+ docblock_filter["see"] = 1
+ docblock_filter["set"] = 1
+ file_annoblock = render_docblock("", "", docblock, 1)
+ delete docblock_filter
+ }
# Line is not an opening bracket,
# this is not a function declaration.
function_declaration = ""
+ function_declaration_complete = 0
+ in_file_block = 0
# Add current (section) description to output.
handle_description();
+ process_section();
# Reset docblock.
reset()
@@ -895,8 +1013,10 @@ END {
debug("→ → file_description: [" file_description "]")
debug("→ END }")
- if (file_title != "") {
- print render("h1", file_title)
+ if (file_intro_enabled) {
+ if (file_title != "") {
+ print render("h1", file_title)
+ }
if (file_brief != "") {
print file_brief "\n"
@@ -904,7 +1024,11 @@ END {
if (file_description != "") {
print render("h2", "Overview")
- print file_description "\n"
+ print file_description "\n"
+ }
+
+ if (file_annoblock != "") {
+ print file_annoblock
}
}
diff --git a/tests/testcases/@endsection.test.sh b/tests/testcases/@endsection.test.sh
new file mode 100644
index 0000000..9286680
--- /dev/null
+++ b/tests/testcases/@endsection.test.sh
@@ -0,0 +1,64 @@
+
+tests:put input <0**: On failure
* **5**: On some error.
-#### Input on stdin
+### Input on stdin
* Path to something.
-#### Output on stdout
+### Output on stdout
* Path to something.
-#### Output on stderr
+### Output on stderr
* Stderr description.
-#### See also
+### See also
* [some:other:func()](#someotherfunc)
* Shell documation generator [shdoc](https://github.com/reconquest/shdoc).
diff --git a/tests/testcases/issue-12-no-space-in-function-declaration.test.sh b/tests/testcases/issue-12-no-space-in-function-declaration.test.sh
index ab44dfb..7709d9b 100644
--- a/tests/testcases/issue-12-no-space-in-function-declaration.test.sh
+++ b/tests/testcases/issue-12-no-space-in-function-declaration.test.sh
@@ -20,11 +20,11 @@ tests:put expected < |