Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 14 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,20 +60,20 @@ Add the `Inputs` and/or `Outputs` and/or `Secrets` (only supported by reusable w

<!-- AUTO-DOC-INPUT:START - Do not remove or modify this section -->

| INPUT | TYPE | REQUIRED | DEFAULT | DESCRIPTION |
|-------------------------|--------|----------|----------------|----------------------------------------------------------------------------------------------------|
| bin\_path | string | false | | Path to the auto-doc binary |
| col\_max\_width | string | false | `"1000"` | Max width of a column |
| col\_max\_words | string | false | `"5"` | Max number of words per line<br>in a column |
| filename | string | false | `"action.yml"` | Path to the yaml file |
| input\_columns | string | false | | List of action.yml **input** columns names<br>to display, default (display all columns) |
| output | string | false | `"README.md"` | Path to the output file |
| output\_columns | string | false | | List of action.yml **output** column names<br>to display, default (display all columns) |
| reusable | string | false | | Boolean Indicating whether the file is<br>a reusable workflow |
| reusable\_input\_columns | string | false | | List of reusable workflow **input** column<br> names to display, default (display all<br>columns) |
| reusable\_output\_columns | string | false | | List of reusable workflow **output** column<br> names to display, default (display all<br>columns) |
| reusable\_secret\_columns | string | false | | List of reusable workflow **secret** column<br> names to display, default (display all<br>columns) |
| version | string | false | | The version number to run |
| INPUT | TYPE | REQUIRED | DEFAULT | DESCRIPTION |
|-------------------------|--------|----------|----------------|------------------------------------------------------------------------------------------------|
| bin\_path | string | false | | Path to the auto-doc binary |
| col\_max\_width | string | false | `"1000"` | Max width of a column |
| col\_max\_words | string | false | `"5"` | Max number of words per line<br>in a column |
| filename | string | false | `"action.yml"` | Path to the yaml file |
| input\_columns | string | false | | List of action.yml **input** columns names<br>to display, default (display all columns) |
| output | string | false | `"README.md"` | Path to the output file |
| output\_columns | string | false | | List of action.yml **output** column names<br>to display, default (display all columns) |
| reusable | string | false | | Boolean Indicating whether the file is<br>a reusable workflow |
| reusable\_input\_columns | string | false | | List of reusable workflow **input** column<br>names to display, default (display all columns) |
| reusable\_output\_columns | string | false | | List of reusable workflow **output** column<br>names to display, default (display all columns) |
| reusable\_secret\_columns | string | false | | List of reusable workflow **secret** column<br>names to display, default (display all columns) |
| version | string | false | | The version number to run |

<!-- AUTO-DOC-INPUT:END -->

Expand Down
12 changes: 4 additions & 8 deletions internal/types/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ func (a *Action) WriteDocumentation(inputTable, outputTable *strings.Builder) er

var output []byte

hasInputsData, inputStartIndexes, inputEndIndexes := utils.HasBytesInBetween(
hasInputsData, Indices := utils.HasBytesInBetween(
input,
[]byte(internal.InputAutoDocStart),
[]byte(internal.InputAutoDocEnd),
Expand All @@ -90,9 +90,7 @@ func (a *Action) WriteDocumentation(inputTable, outputTable *strings.Builder) er
inputsStr := strings.TrimSpace(inputTable.String())

if hasInputsData {
for i := 0; i < len(inputStartIndexes); i++ {
output = utils.ReplaceBytesInBetween(output, inputStartIndexes[i], inputEndIndexes[i], []byte(inputsStr))
}
output = utils.ReplaceBytesInBetween(output, Indices, []byte(inputsStr))
} else {
re := regexp.MustCompile(fmt.Sprintf("(?m)^%s", internal.InputsHeader))
output = re.ReplaceAllFunc(input, func(match []byte) []byte {
Expand All @@ -103,7 +101,7 @@ func (a *Action) WriteDocumentation(inputTable, outputTable *strings.Builder) er
})
}

hasOutputsData, outputStartIndexes, outputEndIndexes := utils.HasBytesInBetween(
hasOutputsData, Indices := utils.HasBytesInBetween(
output,
[]byte(internal.OutputAutoDocStart),
[]byte(internal.OutputAutoDocEnd),
Expand All @@ -112,9 +110,7 @@ func (a *Action) WriteDocumentation(inputTable, outputTable *strings.Builder) er
outputsStr := strings.TrimSpace(outputTable.String())

if hasOutputsData {
for i := 0; i < len(outputStartIndexes); i++ {
output = utils.ReplaceBytesInBetween(output, outputStartIndexes[i], outputEndIndexes[i], []byte(outputsStr))
}
output = utils.ReplaceBytesInBetween(output, Indices, []byte(outputsStr))
} else {
re := regexp.MustCompile(fmt.Sprintf("(?m)^%s", internal.OutputsHeader))
output = re.ReplaceAllFunc(output, func(match []byte) []byte {
Expand Down
21 changes: 8 additions & 13 deletions internal/types/reusable.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,10 @@ import (

"github.com/olekukonko/tablewriter"
"github.com/spf13/cobra"
"gopkg.in/yaml.v3"

"github.com/tj-actions/auto-doc/v2/internal"
"github.com/tj-actions/auto-doc/v2/internal/utils"
"gopkg.in/yaml.v3"
)

// ReusableInput represents the input of the reusable workflow
Expand Down Expand Up @@ -92,7 +93,7 @@ func (r *Reusable) WriteDocumentation(inputTable, outputTable, secretsTable *str

var output []byte

hasInputsData, inputStartIndexes, inputEndIndexes := utils.HasBytesInBetween(
hasInputsData, Indices := utils.HasBytesInBetween(
input,
[]byte(internal.InputAutoDocStart),
[]byte(internal.InputAutoDocEnd),
Expand All @@ -102,9 +103,7 @@ func (r *Reusable) WriteDocumentation(inputTable, outputTable, secretsTable *str
inputsStr := strings.TrimSpace(inputTable.String())

if hasInputsData {
for i := 0; i < len(inputStartIndexes); i++ {
output = utils.ReplaceBytesInBetween(output, inputStartIndexes[i], inputEndIndexes[i], []byte(inputsStr))
}
output = utils.ReplaceBytesInBetween(output, Indices, []byte(inputsStr))
} else {
re := regexp.MustCompile(fmt.Sprintf("(?m)^%s", internal.InputsHeader))
output = re.ReplaceAllFunc(input, func(match []byte) []byte {
Expand All @@ -115,7 +114,7 @@ func (r *Reusable) WriteDocumentation(inputTable, outputTable, secretsTable *str
})
}

hasOutputsData, outputStartIndexes, outputEndIndexes := utils.HasBytesInBetween(
hasOutputsData, Indices := utils.HasBytesInBetween(
output,
[]byte(internal.OutputAutoDocStart),
[]byte(internal.OutputAutoDocEnd),
Expand All @@ -124,9 +123,7 @@ func (r *Reusable) WriteDocumentation(inputTable, outputTable, secretsTable *str
outputsStr := strings.TrimSpace(outputTable.String())

if hasOutputsData {
for i := 0; i < len(outputStartIndexes); i++ {
output = utils.ReplaceBytesInBetween(output, outputStartIndexes[i], outputEndIndexes[i], []byte(outputsStr))
}
output = utils.ReplaceBytesInBetween(output, Indices, []byte(outputsStr))
} else {
re := regexp.MustCompile(fmt.Sprintf("(?m)^%s", internal.OutputsHeader))
output = re.ReplaceAllFunc(output, func(match []byte) []byte {
Expand All @@ -137,7 +134,7 @@ func (r *Reusable) WriteDocumentation(inputTable, outputTable, secretsTable *str
})
}

hasSecretsData, secretsStartIndexes, secretsEndIndexes := utils.HasBytesInBetween(
hasSecretsData, Indices := utils.HasBytesInBetween(
output,
[]byte(internal.SecretsAutoDocStart),
[]byte(internal.SecretsAutoDocEnd),
Expand All @@ -146,9 +143,7 @@ func (r *Reusable) WriteDocumentation(inputTable, outputTable, secretsTable *str
secretsStr := strings.TrimSpace(secretsTable.String())

if hasSecretsData {
for i := 0; i < len(secretsStartIndexes); i++ {
output = utils.ReplaceBytesInBetween(output, secretsStartIndexes[i], secretsEndIndexes[i], []byte(secretsStr))
}
output = utils.ReplaceBytesInBetween(output, Indices, []byte(secretsStr))
} else {
re := regexp.MustCompile(fmt.Sprintf("(?m)^%s", internal.SecretsHeader))
output = re.ReplaceAllFunc(output, func(match []byte) []byte {
Expand Down
63 changes: 20 additions & 43 deletions internal/utils/bytes_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,57 +17,34 @@ limitations under the License.
package utils

import (
"fmt"
"regexp"
)

// HasBytesInBetween checks if a byte array has a start and end byte array and returns true if and all occurrences of start and end
func HasBytesInBetween(value, start, end []byte) (found bool, startIndexes []int, endIndexes []int) {
startRegexp := regexp.MustCompile("(?m)^" + string(start))
endRegexp := regexp.MustCompile("(?m)^" + string(end))
func HasBytesInBetween(value, start, end []byte) (found bool, Indices [][]int) {
// Multiline regex
findRegex := regexp.MustCompile(fmt.Sprintf(`(?s)%s(.*?)%s`, regexp.QuoteMeta(string(start)), regexp.QuoteMeta(string(end))))
Indices = findRegex.FindAllIndex(value, -1)

// Find all start and end indexes
for i := 0; i < len(value); i++ {
startLoc := startRegexp.FindIndex(value[i:])
endLoc := endRegexp.FindIndex(value[i:])
if len(startLoc) > 0 && len(endLoc) > 0 {
startIndex := startLoc[0] + i
endIndex := endLoc[1] + i

if startIndex < endIndex {
// Check if there is a closer end index between the current start and the previously found end index
for j := len(endIndexes) - 1; j >= 0; j-- {
if endIndex < endIndexes[j] && endIndexes[j] < startIndex {
// Use the closer end index instead
endIndex = endIndexes[j]
}
}

startIndexes = append(startIndexes, startIndex)
endIndexes = append(endIndexes, endIndex)

// Move the index to the end of the found end index
i = endIndex - 1
} else {
// Move the index to the end of the found start index
i = startIndex - 1
}
}
if len(Indices) == 0 {
return false, Indices
}

if len(startIndexes) == 0 || len(endIndexes) == 0 {
return false, nil, nil
}

return true, startIndexes, endIndexes
return true, Indices
}

// ReplaceBytesInBetween replaces a byte array between a start and end index with a new byte array
func ReplaceBytesInBetween(value []byte, startIndex int, endIndex int, new []byte) []byte {
t := make([]byte, len(value)+len(new))
w := 0
// ReplaceBytesInBetween replaces a byte array between an array of start and end Indices with a new byte array
func ReplaceBytesInBetween(value []byte, indices [][]int, new []byte) []byte {
t := make([]byte, 0, len(value)+len(new)*len(indices))
prevIndex := 0

for _, v := range indices {
t = append(t, value[prevIndex:v[0]]...)
t = append(t, new...)
prevIndex = v[1]
}

w += copy(t[:startIndex], value[:startIndex])
w += copy(t[w:w+len(new)], new)
w += copy(t[w:], value[endIndex:])
return t[0:w]
t = append(t, value[prevIndex:]...)
return t
}
94 changes: 64 additions & 30 deletions internal/utils/word_wrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,54 +19,88 @@ package utils
import (
"regexp"
"strings"
"unicode"
)

// WordWrap wraps lengthy words with the br html tag except code blocks, links, and code
// WordWrap wraps text at the specified number of columns
func WordWrap(s string, limit int) string {
if strings.TrimSpace(s) == "" {
return s
}
// compile regular expressions for Markdown links and code blocks and code
linkRegex := regexp.MustCompile(`\[.*]\(.*\)`)
codeBlockRegex := regexp.MustCompile(`\` + "```" + `.*` + "```" + `\s*`)

// convert string to slice
strSlice := strings.Fields(s)
currentLimit := limit
var (
linkIndices [][]int = getAllLinksIndex(s)
codeBlockIndices [][]int = getAllCodeBlocksIndex(s)
codeIndices [][]int = getAllCodeIndex(s)
parenthesisIndices [][]int = getAllParenthesisIndex(s)
italicIndices [][]int = getAllItalicIndex(s)
start int = 0
)

var result string
// split the string into words that aren't between any of the links, code blocks, code and parenthesis
strSlice := strings.FieldsFunc(s, func(r rune) bool {
shouldExclude := isWithin(start, linkIndices) || isWithin(start, codeBlockIndices) || isWithin(start, codeIndices) || isWithin(start, parenthesisIndices) || isWithin(start, italicIndices)
start++
return !shouldExclude && unicode.IsSpace(r)
})

var result = ""

for len(strSlice) >= 1 {
// convert slice/array back to string
// but insert <br> at specified limit
// unless the current slice contains a Markdown link or code block or code
hasMore := len(strSlice) > currentLimit

if hasMore && len(result) > 0 {
result += " "
// but insert \r\n at specified limit
if len(strSlice) < limit {
limit = len(strSlice)
}

if len(strSlice) < currentLimit {
currentLimit = len(strSlice)
result = result + strings.Join(strSlice[:currentLimit], " ")
} else if currentLimit == limit && !linkRegex.MatchString(strings.Join(strSlice[:currentLimit], " ")) && !codeBlockRegex.MatchString(strings.Join(strSlice[:currentLimit], " ")) {
result = result + strings.Join(strSlice[:currentLimit], " ") + "<br>"
} else {
result = result + strings.Join(strSlice[:currentLimit], " ")
}
result = result + strings.Join(strSlice[:limit], " ") + "<br>"

// discard the elements that were copied over to result
strSlice = strSlice[currentLimit:]
strSlice = strSlice[limit:]
}

// Trim the last <br> tag
result = strings.TrimSuffix(result, "<br>")

// change the limit
// to cater for the last few words in the line
if len(strSlice) < currentLimit {
currentLimit = len(strSlice)
return result
}

func isWithin(index int, ranges [][]int) bool {
for _, r := range ranges {
if index >= r[0] && index < r[1] {
return true
}
}

// Remove trailing <br> if any
result = strings.TrimSuffix(result, "<br>")
return false
}

func getAllLinksIndex(s string) [][]int {
linkRegex := regexp.MustCompile(`\[(.*?)\]\((.*?)\)`)

return linkRegex.FindAllStringIndex(s, -1)
}

func getAllCodeBlocksIndex(s string) [][]int {
codeBlockRegex := regexp.MustCompile("```[^`]*```")

return codeBlockRegex.FindAllStringIndex(s, -1)
}

func getAllCodeIndex(s string) [][]int {
codeRegex := regexp.MustCompile("`[^`]*`")

return codeRegex.FindAllStringIndex(s, -1)
}

func getAllParenthesisIndex(s string) [][]int {
parenthesisRegex := regexp.MustCompile(`\((.*?)\)`)

return parenthesisRegex.FindAllStringIndex(s, -1)
}

func getAllItalicIndex(s string) [][]int {
italicRegex := regexp.MustCompile(`\*(.*?)\*`)

return strings.TrimSpace(result)
return italicRegex.FindAllStringIndex(s, -1)
}
Loading