Skip to content

Commit

Permalink
- Allow for indented function description (fix reconquest#50).
Browse files Browse the repository at this point in the history
- Add common code for multiline support for:
  description, example, stdout, stderr, stdin, set, see, and exitcode.
  • Loading branch information
landure committed Aug 19, 2023
1 parent a0fddce commit 67ce694
Show file tree
Hide file tree
Showing 7 changed files with 325 additions and 76 deletions.
192 changes: 132 additions & 60 deletions shdoc
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ BEGIN {
styles["github", "set", "from"] = "^(\\S+) (\\S+)"
styles["github", "set", "to"] = "**\\1** (\\2):"

styles["github", "li-preprocess", "from"] = "\n"
styles["github", "li-preprocess", "to"] = "\n "

styles["github", "li", "from"] = ".*"
styles["github", "li", "to"] = "* &"

Expand All @@ -50,7 +53,7 @@ BEGIN {
styles["github", "anchor", "from"] = ".*"
styles["github", "anchor", "to"] = "[&](#&)"

styles["github", "exitcode", "from"] = "([>!]?[0-9]{1,3}) (.*)"
styles["github", "exitcode", "from"] = "([>!]?[0-9]{1,3})[[:blank:]](.*)"
styles["github", "exitcode", "to"] = "**\\1**: \\2"

stderr_section_flag = 0
Expand Down Expand Up @@ -236,28 +239,30 @@ function reset() {
description = ""
}

function handle_description() {
function handle_description(text) {
debug("→ handle_description")

# Remove empty lines at the start of description.
sub(/^[[:space:]\n]*\n/, "", description)
sub(/^[[:space:]\n]*\n/, "", text)
# Remove empty lines at the end of description.
sub(/[[:space:]\n]*$/, "", description)
sub(/[[:space:]\n]*$/, "", text)

if (description == "") {
if (text == "") {
debug("→ → description: empty")
return;
}

description = text

if (section != "" && section_description == "") {
debug("→ → section description: added")
section_description = description
section_description = text
return;
}

if (file_description == "") {
debug("→ → file description: added")
file_description = description
file_description = text
return;
}
}
Expand Down Expand Up @@ -369,8 +374,8 @@ function render_docblock_list(docblock, docblock_name, title) {
for (i in docblock[docblock_name]) {
docblock[docblock_name][i]
# Ident additionnal lines to add them to the markdown list item.
gsub(/\n/, "\n ", docblock[docblock_name][i])
item = render("li", docblock[docblock_name][i])
item = render("li-preprocess", docblock[docblock_name][i])
item = render("li", item)
push(lines, item)
}

Expand Down Expand Up @@ -482,7 +487,9 @@ function render_docblock(func_name, description, docblock) {
if ("example" in docblock) {
push(lines, render("h4", "Example"))
push(lines, render("code", "bash"))
push(lines, unindent(docblock["example"]))
# Unindent should be done by the new code.
#push(lines, unindent(docblock["example"]))
push(lines, docblock["example"])
push(lines, render("/code"))
push(lines, "")
}
Expand Down Expand Up @@ -547,8 +554,8 @@ function render_docblock(func_name, description, docblock) {
if ("set" in docblock) {
push(lines, render("h4", "Variables set"))
for (i in docblock["set"]) {
item = docblock["set"][i]
item = render("set", item)
item = render("set", docblock["set"][i])
item = render("li-preprocess", item)
item = render("li", item)
push(lines, item)
}
Expand All @@ -560,7 +567,9 @@ function render_docblock(func_name, description, docblock) {
if ("exitcode" in docblock) {
push(lines, render("h4", "Exit codes"))
for (i in docblock["exitcode"]) {
item = render("li", render("exitcode", docblock["exitcode"][i]))
item = render("exitcode", docblock["exitcode"][i])
item = render("li-preprocess", item)
item = render("li", item)
push(lines, item)
}

Expand All @@ -583,7 +592,8 @@ function render_docblock(func_name, description, docblock) {
if ("see" in docblock) {
push(lines, render("h4", "See also"))
for (i in docblock["see"]) {
item = render("li", render_toc_link(docblock["see"][i]))
item = render("li-preprocess", render_toc_link(docblock["see"][i]))
item = render("li", item)
push(lines, item)
}

Expand All @@ -605,6 +615,92 @@ function debug(msg) {
debug("line: [" $0 "]")
}

# Previous line added a new docblock item.
# Check if current line has the needed indentation
# for it to be a multiple lines docblock item.
#
# This process must be done before any @ tag detection.
multiple_line_tag {
# Determine if the tag allow for next line without additionnal indentation.
# This should be only be true for @description and @example tags, for the moment.
no_indentation_match = ""
if (multiple_line_tag ~ /^(description|example)$/) {
no_indentation_match = sprintf("|%s[^@].*", after_hash_indentation)
}
multiple_line_identation_regex = sprintf( \
"^%s([[:blank:]]*|%s([[:blank:]]+[^[:blank:]]).*%s)$", \
hash_indentation, \
after_hash_indentation, \
no_indentation_match \
)

# Check if current line indentation does match the previous line docblock item.
if (match($0, multiple_line_identation_regex, contents)) {
debug("→ → @" multiple_line_tag " next line")
additional_line = contents[1]

# Detect text internal indentation.
if(trim(additional_line) != "" \
&& match(additional_line, /^([[:blank:]]*)([^[:blank:]].*)?$/, detected_indentation))
{
# Detect the minimal indentation of the text.
if(minimal_indentation == -1 \
|| length(minimal_indentation) > length(detected_indentation[1])) {
minimal_indentation = detected_indentation[1]
}
}

# Remove trailing spaces.
sub(/[[:space:]]+$/, "")

# Push matched message to corresponding docblock.
# docblock_append(multiple_line_docblock_name, "\n" $0)
text = concat(text, additional_line)

# Stop processing current line, and process next line.
next
} else {
# End of the multiple line tag.
debug("→ → END of @" multiple_line_tag)

# Remove minimal indentation from text.
if(minimal_indentation != -1) {
debug("→ → removing indentation from @ " multiple_line_tag " (length: " length(minimal_indentation) ")")
split(text, text_lines, "\n")
text = ""
for (i = 0; i < length(text_lines); i++) {
current_line = text_lines[i]
sub("^" minimal_indentation,"", current_line)
text = concat(text, current_line)
}
}

# Remove empty lines at the start of text.
sub(/^[[:space:]\n]*\n/, "", text)
# Remove empty lines at the end of text.
sub(/[[:space:]\n]*$/, "", text)

## Print final text on debug output.
debug("→ → Final text for @" multiple_line_tag " : [\n" text "\n]")

if(multiple_line_tag == "description") {
# If current tag is a description.
# Call handle_description with description set as the multiline text.
handle_description(text)
} else if (multiple_line_tag ~ /^(stdin|stdout|stderr|set|exitcode|see)$/) {
# If current tag is a multiple occurence tag.
# Push multi-line text as new item of the corresponding docblock.
docblock_push(multiple_line_tag, text)
} else {
docblock_set(multiple_line_tag, text)
}

# End previous line docblock item.
multiple_line_tag = ""
}
}


/^[[:space:]]*# @internal/ {
debug("→ @internal")
is_internal = 1
Expand All @@ -628,34 +724,33 @@ function debug(msg) {
next
}

/^[[:space:]]*# @description/ {
debug("→ @description")
in_description = 1
in_example = 0

handle_description()

reset()
}

in_description {
if (/^[^[[:space:]]*#]|^[[:space:]]*# @[^d]|^[[:space:]]*[^#]|^[[:space:]]*$/) {
debug("→ → in_description: leave")
# Process @description entries.
# Allow for multiple lines entries.
match($0, /^([[:blank:]]*#)([[:blank:]]+)@(description|example|stdin|stdout|stderr|set|exitcode|see)[[:blank:]]*(.*[^[:blank:]])?[[:blank:]]*$/, contents) {
# Fetch matched values.
hash_indentation = contents[1]
after_hash_indentation = contents[2]
tag_name = contents[3]
if(tag_name == "example") {
# For @example tag, the content of the tag line is ignored.
text = ""
} else {
text = trim(contents[4])
}
# minimal indentation is used to detect global indentation of the multiple line text.
# Line where the tag (e.g. @description) is is considered to have no indentation.
minimal_indentation = -1

in_description = 0
debug("→ @" tag_name)

handle_description()
} else {
debug("→ → in_description: concat")
sub(/^[[:space:]]*# @description[[:space:]]*/, "")
sub(/^[[:space:]]*#[[:space:]]*/, "")
sub(/^[[:space:]]*#$/, "")
# Signal the start of a multiple line tag.
multiple_line_tag = tag_name

description = concat(description, $0)
next
}
# Stop processing current line, and process next line.
next
}


/^[[:space:]]*# @section/ {
debug("→ @section")
sub(/^[[:space:]]*# @section /, "")
Expand All @@ -664,29 +759,6 @@ in_description {
next
}

/^[[:space:]]*# @example/ {
debug("→ @example")

in_example = 1


next
}

in_example {
if (! /^[[:space:]]*#[ ]{1,}/) {
debug("→ → in_example: leave")
in_example = 0
} else {
debug("→ → in_example: concat")
sub(/^[[:space:]]*#/, "")

docblock_concat("example", $0)
next
}

}

# Select @option lines with content.
/^[[:blank:]]*#[[:blank:]]+@option[[:blank:]]+[^[:blank:]]/ {
debug("→ @option")
Expand Down
57 changes: 57 additions & 0 deletions tests/testcases/@exitcode.test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#!/bin/bash

tests:put input <<EOF
# @name Project Name
# @brief Brief
# @description the file description
# with some more lines
# @description The func b description
# @exitcode 0 If everything is alright.
# @exitcode 1 If one of this errors occured :
#
# - Bad number of arguments:
# - Too many arguments.
# - Too few arguments.
# - Invalid argument format.
#
# @exitcode >1 If a issue occured during processing,
# with a long comment here on the issue.
b() {
}
EOF

tests:put expected <<EOF
# Project Name
Brief
## Overview
the file description
with some more lines
## Index
* [b](#b)
### b
The func b description
#### Exit codes
* **0**: If everything is alright.
* **1**: If one of this errors occured :
- Bad number of arguments:
- Too many arguments.
- Too few arguments.
- Invalid argument format.
* **>1**: If a issue occured during processing,
with a long comment here on the issue.
EOF

assert
6 changes: 4 additions & 2 deletions tests/testcases/@see.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ tests:put input <<EOF
# @see shdoc (https://github.com/reconquest/shdoc) and https://github.com/reconquest/import.bash
# @see [shdoc](https://github.com/reconquest/shdoc)
# @see Shell documation generator [shdoc](https://github.com/reconquest/shdoc).
# @see Shell documation generator [shdoc](https://github.com/reconquest/shdoc) (and https://github.com/reconquest/import.bash).
# @see Shell documation generator [shdoc](https://github.com/reconquest/shdoc)
# (and https://github.com/reconquest/import.bash).
test-working-see() {
}
Expand Down Expand Up @@ -78,7 +79,8 @@ test-see dummy function.
* shdoc ([https://github.com/reconquest/shdoc](https://github.com/reconquest/shdoc)) and [https://github.com/reconquest/import.bash](https://github.com/reconquest/import.bash)
* [shdoc](https://github.com/reconquest/shdoc)
* Shell documation generator [shdoc](https://github.com/reconquest/shdoc).
* Shell documation generator [shdoc](https://github.com/reconquest/shdoc) (and [https://github.com/reconquest/import.bash](https://github.com/reconquest/import.bash)).
* Shell documation generator [shdoc](https://github.com/reconquest/shdoc)
(and [https://github.com/reconquest/import.bash](https://github.com/reconquest/import.bash)).
EOF

assert
12 changes: 10 additions & 2 deletions tests/testcases/@set.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ tests:put input <<EOF
# @description a
# @description func b
# @set B int Error code if an erorr occured
# @set B int Error code if an error occured
# with some list to add details.
#
# - detail A
# - detail B
# @set C array Some array thing
b() {
}
Expand All @@ -32,7 +36,11 @@ func b
#### Variables set
* **B** (int): Error code if an erorr occured
* **B** (int): Error code if an error occured
with some list to add details.
- detail A
- detail B
* **C** (array): Some array thing
EOF

Expand Down
Loading

0 comments on commit 67ce694

Please sign in to comment.