From 8bea092e89ef718c565a3ffe929890a99d8ac892 Mon Sep 17 00:00:00 2001 From: Naveen Prashanth <78990165+gnpaone@users.noreply.github.com> Date: Mon, 2 Sep 2024 21:46:08 +0530 Subject: [PATCH] Bump version to v1.0.2 --- .github/workflows/{main.yml => tag.yml} | 0 CONTRIBUTING.md | 7 + README.md | 38 +-- action/go.mod | 3 +- dynreadme.go | 108 ++++---- examples/update.yml | 10 +- go.mod | 2 + helpers/table.go | 321 ------------------------ 8 files changed, 93 insertions(+), 396 deletions(-) rename .github/workflows/{main.yml => tag.yml} (100%) delete mode 100644 helpers/table.go diff --git a/.github/workflows/main.yml b/.github/workflows/tag.yml similarity index 100% rename from .github/workflows/main.yml rename to .github/workflows/tag.yml diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e69de29..bc3221c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -0,0 +1,7 @@ +Contributions are welcome! Please follow these steps: + +1. Fork the project. +2. Create a new branch (`git checkout -b feature-branch`). +3. Commit your changes (`git commit -m 'Add a feature'`). +4. Push to the branch (`git push origin feature-branch`). +5. Open a Pull Request. \ No newline at end of file diff --git a/README.md b/README.md index 38b23ba..0f26496 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ [![Issues](https://img.shields.io/github/issues/gnpaone/dynamic-update-readme?color=orange&style=flat-square)](https://github.com/gnpaone/dynamic-update-readme/issues) [![Go](https://img.shields.io/github/go-mod/go-version/gnpaone/dynamic-update-readme?color=maroon&style=flat-square)](https://github.com/gnpaone/dynamic-update-readme/blob/main/go.mod) [![Godoc](https://pkg.go.dev/badge/github.com/gnpaone/dynamic-update-readme.svg?utm_source=godoc)](https://godoc.org/github.com/gnpaone/dynamic-update-readme) +[![GitHub Marketplace](https://img.shields.io/badge/Marketplace-v1.0.2-undefined.svg?logo=github&logoColor=white&style=flat-square)](https://github.com/marketplace/actions/dynamic-update-readme) #### As a power user, automate the updating of markdown text with content obtained from other actions in the workflow, as well as a Go module that can update markdown text @@ -17,13 +18,13 @@ A Go module that updates markdown text and is integrated as a GitHub Action. ## Parameters -| Parameter | Workflow default | Description | Required | Possible values | -|:----------------:|:-------------------:|:-------------------------------------------|:-----------:|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| readme_path | ./README.md | Path of the markdown file | No | *File path relative to the root directory of the GitHub repo* | -| marker_text | *null* | Marker text to replace in markdown file | Yes | *Example markers to be added in the markdown:*
`` | -| markdown_text | *null* | Markdown text that needs to be updated | Yes | *Any markdown compatible text (For GitHub markdown please check [GitHub Flavored Markdown parser](https://github.github.com/gfm/))* | -| table | false | Markdown text is a table | No | true, false | -| table_options | *null* | Alignment for the table | No | align-*Alignment*, head-align-*Alignment*, text-align-*Alignment*, col-*Column*-align-*Alignment*, col-*Column*-w-*Min-width*, colH-*Column*-align-*Alignment*, colT-*Column*-align-*Alignment* | +| Parameter | Workflow default | Description | Required | Possible values | +|:----------------:|:-------------------:|:-------------------------------------------|:-----------:|:-------------------| +| readme_path | ./README.md | Path of the markdown file | No | *File path relative to the root directory of the GitHub repo* | +| marker_text | *null* | Marker text to replace in markdown file | Yes | *Example markers to be added in the markdown:*
`` | +| markdown_text | *null* | Markdown text that needs to be updated | Yes | *Any markdown compatible text* | +| table | false | Markdown text is a table | No | true, false | +| table_options | *null* | Alignment for the table | No | align-*Alignment*, col-*Column*-align-*Alignment*, col-*Column*-w-*Min-width* | _Alignment_: `left, right, center`
_Column_: Column number of the table (starts with `0`)
_Min-width_: Minimum width of a column (in other words, minimum number of hyphens (`-`) in the delimiter row)
@@ -31,26 +32,33 @@ A Go module that updates markdown text and is integrated as a GitHub Action. * Make sure to change the following in your GitHub repo settings: `Actions` > `General` > `Workflow permissions` > Choose `Read and write permissions` > Check `Allow GitHub Actions to create and approve pull requests` > `Save`. * Other parameters `commit_user`, `commit_email`, `commit_message` and `confirm_and_push` related to action workflow are optional. * If `confirm_and_push` is "false" committer detalis can be accessed via `outputs.commit_user`, `outputs.commit_email` & `outputs.commit_message` for further usage in the workflow. +* The syntax mostly revolves around [GitHub Flavored Markdown parser](https://github.github.com/gfm/). * The `table` parameter is optional as `markdown_text` supports table markdown syntax by default but this parameter can be used as a simple alternative or any special use case. If `table` is "true": + - It uses [github.com/willabides/mdtable](https://godoc.org/github.com/willabides/mdtable) module - `markdown_text` contents should follow these conditions: + Table row contents are seperated with ";" delimiter. First element will make up the table header. + For each element of table rows, table column contents are seperated with "," delimiter. - `table_options` can be used only if `table` is "true". + Description of options/values: 1. `align-`: This option aligns the whole table according to the alignment set. - 2. `head-`: This option aligns the header row of the table according to the alignment set. - 3. `text-`: This option aligns all the data rows of the table according to the alignment set. - 4. `col-`: This option sets properties of any particular column based on the column number. - 5. `colH-`: This option sets properties of header row of any particular column based on the column number. - 6. `colT-`: This option sets properties of data rows of any particular column based on the column number. - + Order of preference of the alignment options: - 1. Header rows: `colH-` > `col-` > `head-` > `align-` - 2. Data rows: `colT-` > `col-` > `text-` > `align-` + 2. `col-`: This option sets properties of any particular column based on the column number. + + Order of preference of the alignment options: `col-` > `align-` * The markdown file must contain start marker `` and end marker `` where "EXAMPLE_MARKER" is the input of `marker_text` parameter. Note that the `_START` and `_END` part is important. ## Usage Check the example workflow [here](https://github.com/gnpaone/dynamic-update-readme/blob/main/examples/update.yml). +## Contributing +See [CONTRIBUTING.md](https://github.com/gnpaone/dynamic-update-readme/blob/main/CONTRIBUTING.md) for more details. + +## Acknowledgements + +This project uses few of the open-source Go modules, without which it would not have been possible. I extend my gratitude to the developers and maintainers of these modules for their valuable contributions to the open-source community: + +* [github.com/willabides/mdtable](https://godoc.org/github.com/willabides/mdtable) - Generates markdown tables from string slices. +* [github.com/mattn/go-runewidth](https://godoc.org/github.com/mattn/go-runewidth) - Provides functions to get fixed width of the character or string. +* [github.com/rivo/uniseg](https://godoc.org/github.com/rivo/uniseg) - Implements Unicode Text Segmentation, Word Wrapping, and String Width Calculation. + ## License This project is licensed under GPL-3.0. Copyright © 2024, Naveen Prashanth. All Rights Reserved. diff --git a/action/go.mod b/action/go.mod index dcd9b07..b51c4a1 100644 --- a/action/go.mod +++ b/action/go.mod @@ -2,9 +2,10 @@ module github.com/gnpaone/dynamic-update-readme/update-readme-action go 1.22 -require github.com/gnpaone/dynamic-update-readme v1.0.1 +require github.com/gnpaone/dynamic-update-readme v1.0.2 require ( github.com/mattn/go-runewidth v0.0.16 // indirect github.com/rivo/uniseg v0.2.0 // indirect + github.com/willabides/mdtable v0.3.1 // indirect ) diff --git a/dynreadme.go b/dynreadme.go index b9b53a2..aa072f8 100644 --- a/dynreadme.go +++ b/dynreadme.go @@ -6,7 +6,7 @@ import ( "os" "strings" - table "github.com/gnpaone/dynamic-update-readme/helpers" + table "github.com/willabides/mdtable" ) // UpdateContent parses and updates content between markers @@ -69,57 +69,57 @@ func parseTableOptions(tableOptions string) []table.Option { } } - case strings.HasPrefix(part, "colH-"): - colHParts := strings.Split(part, "-") - if len(colHParts) >= 3 { - colHNum := parseColumnNumber(colHParts[1]) - switch colHParts[2] { - case "align": - if len(colHParts) == 4 { - alignment := parseAlignment(colHParts[3]) - options = append(options, table.ColumnHeaderAlignment(colHNum, alignment)) - } - } - } - - case strings.HasPrefix(part, "colT-"): - colTParts := strings.Split(part, "-") - if len(colTParts) >= 3 { - colTNum := parseColumnNumber(colTParts[1]) - switch colTParts[2] { - case "align": - if len(colTParts) == 4 { - alignment := parseAlignment(colTParts[3]) - options = append(options, table.ColumnTextAlignment(colTNum, alignment)) - } - } - } - - case strings.HasPrefix(part, "head-"): - headParts := strings.Split(part, "-") - if len(headParts) >= 2 { - switch headParts[1] { - case "align": - if len(headParts) == 3 { - alignment := parseAlignment(headParts[2]) - options = append(options, table.HeaderAlignment(alignment)) - } - } - - } - - case strings.HasPrefix(part, "text-"): - textParts := strings.Split(part, "-") - if len(textParts) >= 2 { - switch textParts[1] { - case "align": - if len(textParts) == 3 { - alignment := parseAlignment(textParts[2]) - options = append(options, table.TextAlignment(alignment)) - } - } - - } + // case strings.HasPrefix(part, "colH-"): + // colHParts := strings.Split(part, "-") + // if len(colHParts) >= 3 { + // colHNum := parseColumnNumber(colHParts[1]) + // switch colHParts[2] { + // case "align": + // if len(colHParts) == 4 { + // alignment := parseAlignment(colHParts[3]) + // options = append(options, table.ColumnHeaderAlignment(colHNum, alignment)) + // } + // } + // } + + // case strings.HasPrefix(part, "colT-"): + // colTParts := strings.Split(part, "-") + // if len(colTParts) >= 3 { + // colTNum := parseColumnNumber(colTParts[1]) + // switch colTParts[2] { + // case "align": + // if len(colTParts) == 4 { + // alignment := parseAlignment(colTParts[3]) + // options = append(options, table.ColumnTextAlignment(colTNum, alignment)) + // } + // } + // } + + // case strings.HasPrefix(part, "head-"): + // headParts := strings.Split(part, "-") + // if len(headParts) >= 2 { + // switch headParts[1] { + // case "align": + // if len(headParts) == 3 { + // alignment := parseAlignment(headParts[2]) + // options = append(options, table.HeaderAlignment(alignment)) + // } + // } + + // } + + // case strings.HasPrefix(part, "text-"): + // textParts := strings.Split(part, "-") + // if len(textParts) >= 2 { + // switch textParts[1] { + // case "align": + // if len(textParts) == 3 { + // alignment := parseAlignment(textParts[2]) + // options = append(options, table.TextAlignment(alignment)) + // } + // } + + // } } } return options @@ -142,7 +142,7 @@ func parseColumnNumber(num string) int { var colNum int if _, err := fmt.Sscanf(num, "%d", &colNum); err != nil { log.Fatalf("Error parsing column number: %s", err) - } + } return colNum } @@ -150,7 +150,7 @@ func parseColumnWidth(width string) int { var w int if _, err := fmt.Sscanf(width, "%d", &w); err != nil { log.Fatalf("Error parsing column width: %s", err) - } + } return w } diff --git a/examples/update.yml b/examples/update.yml index f8e0de3..27cdee2 100644 --- a/examples/update.yml +++ b/examples/update.yml @@ -12,7 +12,7 @@ jobs: # Marker as - name: Example 1 - multiple lines - uses: gnpaone/dynamic-update-readme@v1.0.1 + uses: gnpaone/dynamic-update-readme@v1.0.2 with: marker_text: "EXAMPLE" markdown_text: | @@ -51,7 +51,7 @@ jobs: ### Requirements - Node.js >= 18.0.0 - - npm >= 6.0.0 + - npm >= 9.0.0 ## Usage @@ -168,15 +168,15 @@ jobs: # Marker as - name: Example 2 - single line - uses: gnpaone/dynamic-update-readme@v1.0.1 + uses: gnpaone/dynamic-update-readme@v1.0.2 with: marker_text: "MARKER_TEXT" - markdown_text: "This is a sample text." + markdown_text: "#### This is a sample text." confirm_and_push: "false" # Marker as - name: Example 3 - add table - uses: gnpaone/dynamic-update-readme@v1.0.1 + uses: gnpaone/dynamic-update-readme@v1.0.2 with: marker_text: "TABLE" table: "true" diff --git a/go.mod b/go.mod index 4117397..017380e 100644 --- a/go.mod +++ b/go.mod @@ -4,4 +4,6 @@ go 1.22 require ( github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/rivo/uniseg v0.2.0 // indirect + github.com/willabides/mdtable v0.3.1 // indirect ) diff --git a/helpers/table.go b/helpers/table.go deleted file mode 100644 index 822a1e0..0000000 --- a/helpers/table.go +++ /dev/null @@ -1,321 +0,0 @@ -/* -Package table generates markdown tables from string slices with formatting options for alignment and column width. - -*/ -package table - -import ( - "bytes" - "strings" - - runewidth "github.com/mattn/go-runewidth" -) - -// Align is a value for markdown and text alignment -type Align uint8 - -// Align values -const ( - AlignDefault Align = iota // markdown: |--------| text: | foo | - AlignLeft // markdown: |:-------| text: | foo | - AlignRight // markdown: |-------:| text: | foo | - AlignCenter // markdown: |:------:| text: | foo | -) - -// defaultTextAlignment is the alignment used for text alignment on columns set to AlignDefault -const defaultTextAlignment = AlignLeft - -func (a Align) headerPrefix() string { - switch a { - case AlignLeft, AlignCenter: - return ":" - default: - return "-" - } -} - -func (a Align) headerSuffix() string { - switch a { - case AlignRight, AlignCenter: - return ":" - default: - return "-" - } -} - -func (a Align) fillCell(s string, width, padding int) string { - align := a - if align == AlignDefault { - align = defaultTextAlignment - } - pad := strings.Repeat(" ", padding) - var leftFill, rightFill int - switch align { - case AlignCenter: - filled := runewidth.FillLeft(s, width) - delta := runewidth.StringWidth(filled) - runewidth.StringWidth(s) - leftFill = delta / 2 - rightFill = delta/2 + delta%2 - s = strings.Repeat(" ", leftFill) + s + strings.Repeat(" ", rightFill) - case AlignRight: - s = runewidth.FillLeft(s, width) - case AlignLeft: - s = runewidth.FillRight(s, width) - } - return pad + s + pad -} - -// Generate generates a markdown table. -func Generate(data [][]string, options ...Option) []byte { - t := &table{ - data: data, - } - - for _, o := range options { - o(t) - } - - return t.generate() -} - -// Option is to control a table's formatting -type Option func(*table) - -// Start Options - -// HeaderAlignment sets the text alignment for headers -func HeaderAlignment(val Align) Option { - return func(t *table) { - t.headerAlignment = val - } -} - -// TextAlignment sets the default text alignment for non-header cells -func TextAlignment(val Align) Option { - return func(t *table) { - t.textAlignment = val - } -} - -// Alignment sets alignment for columns. -func Alignment(val Align) Option { - return func(t *table) { - t.mdAlignment = val - } -} - -// ColumnAlignment sets the markdown alignment for a column -func ColumnAlignment(column int, alignment Align) Option { - return func(t *table) { - t.mdAlignments = setColumnAlignment(t.mdAlignments, column, alignment) - } -} - -// ColumnHeaderAlignment sets the text alignment for a column header -func ColumnHeaderAlignment(column int, alignment Align) Option { - return func(t *table) { - t.headerAlignments = setColumnAlignment(t.headerAlignments, column, alignment) - } -} - -// ColumnTextAlignment sets the text alignment for a column -func ColumnTextAlignment(column int, alignment Align) Option { - return func(t *table) { - t.textAlignments = setColumnAlignment(t.textAlignments, column, alignment) - } -} - -// ColumnMinWidth sets the minimum width for a column -func ColumnMinWidth(column, width int) Option { - return func(t *table) { - if column < 0 { - return - } - delta := (column + 1) - len(t.minWidths) - if delta > 0 { - t.minWidths = append(t.minWidths, make([]int, delta)...) - } - t.minWidths[column] = width - } -} - -// End Options - -// table is a markdown table -type table struct { - data [][]string - mdAlignment Align - textAlignment Align - headerAlignment Align - mdAlignments []Align - textAlignments []Align - headerAlignments []Align - minWidths []int -} - -// columnMinWidth returns the minimum width that is set for a column. Returns 0 if non has been set. -func (t *table) columnMinWidth(column int) int { - if column < 0 { - return 0 - } - if len(t.minWidths) < column+1 { - return 0 - } - return t.minWidths[column] -} - -// columnAlignment returns the markdown alignment for a column -func (t *table) columnAlignment(column int) Align { - align := getColumnAlignment(t.mdAlignments, column) - if align != AlignDefault { - return align - } - return t.mdAlignment -} - -// columnTextAlignment returns text alignment for a column -// -// Order of preference: -// 1. value set with ColumnTextAlignment -// 2. value set with TextAlignment -// 3. columnAlignment(column) -// 4. defaultTextAlignment (which is AlignLeft) -func (t *table) columnTextAlignment(column int) Align { - align := getColumnAlignment(t.textAlignments, column) - if align != AlignDefault { - return align - } - align = t.textAlignment - if align != AlignDefault { - return align - } - align = t.columnAlignment(column) - if align != AlignDefault { - return align - } - return defaultTextAlignment -} - -// columnHeaderAlignment returns the text alignment for a column header -// -// Order or preference: -// 1. value set with ColumnHeaderAlignment -// 2. value set with HeaderAlignment -// 3. ColumnTextAlignment(column) -func (t *table) columnHeaderAlignment(column int) Align { - align := getColumnAlignment(t.headerAlignments, column) - if align != AlignDefault { - return align - } - align = t.headerAlignment - if align != AlignDefault { - return align - } - return t.columnTextAlignment(column) -} - -func getColumnAlignment(alignments []Align, column int) Align { - if column < 0 { - return AlignDefault - } - if len(alignments) < column+1 { - return AlignDefault - } - return alignments[column] -} - -func setColumnAlignment(alignments []Align, column int, align Align) []Align { - if column < 0 { - return alignments - } - delta := (column + 1) - len(alignments) - if delta > 0 { - alignments = append(alignments, make([]Align, delta)...) - } - alignments[column] = align - return alignments -} - -// generate returns the markdown representation of table -func (t *table) generate() []byte { - var buf bytes.Buffer - if len(t.data) == 0 { - return buf.Bytes() - } - row := 0 - buf.WriteString(t.renderRow(0, t.columnHeaderAlignment) + "\n") - buf.WriteString(t.renderHeaderRow()) - for row = 1; row < len(t.data); row++ { - buf.WriteString("\n" + t.renderRow(row, t.columnTextAlignment)) - } - return buf.Bytes() -} - -func (t *table) renderColumnHeader(column int) string { - width := t.columnWidth(column) - if width == 0 { - return "--" - } - align := t.columnAlignment(column) - return align.headerPrefix() + strings.Repeat("-", width) + align.headerSuffix() -} - -func cellValue(data [][]string, row, column int) string { - if row < 0 || column < 0 { - return "" - } - if len(data) < row+1 { - return "" - } - if len(data[row]) < column+1 { - return "" - } - return data[row][column] -} - -func (t *table) renderCell(row, column int, alignment Align) string { - s := cellValue(t.data, row, column) - width := t.columnWidth(column) - return alignment.fillCell(s, width, 1) -} - -func (t *table) renderRow(row int, alignmentFunc func(int) Align) string { - cells := make([]string, t.columnCount()) - for i := range cells { - cells[i] = t.renderCell(row, i, alignmentFunc(i)) - } - return "|" + strings.Join(cells, "|") + "|" -} - -func (t *table) renderHeaderRow() string { - headers := make([]string, t.columnCount()) - for i := range headers { - headers[i] = t.renderColumnHeader(i) - } - return "|" + strings.Join(headers, "|") + "|" -} - -// columnCount returns the number of columns in the table -func (t *table) columnCount() int { - count := 0 - for _, row := range t.data { - if len(row) > count { - count = len(row) - } - } - return count -} - -func (t *table) columnWidth(column int) int { - width := t.columnMinWidth(column) - for _, row := range t.data { - if len(row) < column+1 { - continue - } - strLen := len(row[column]) - if strLen > width { - width = strLen - } - } - return width -} \ No newline at end of file