diff --git a/.gitignore b/.gitignore index 50e1def..701fb69 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,4 @@ release-notes.txt # End of https://www.gitignore.io/api/go +.idea/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 29f2348..9a6fa5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +### Added +- Add `--json` flag to `fmt` and `show` commands. + ## [0.7.0] - 2020-07-03 ### Changed - Install git and openssh on docker image diff --git a/README.md b/README.md index c50eae6..df51aa8 100644 --- a/README.md +++ b/README.md @@ -122,6 +122,8 @@ Show the change log for a specific version: changelog show 1.2.3 ``` +The `show` command accepts a `--json` option which formats the version as JSON. + ### release Create a new release: @@ -143,6 +145,8 @@ Currently, the following transformations are applied: - Version links are put at the bottom of the file - List bullet is always `-` +The `fmt` command accepts a `--json` option which formats the changelog as JSON. + ## Contributing Feel free to fork and submit a PR. You can also take a look, at the [Issues][] tab to see some ideas. diff --git a/chg/change.go b/chg/change.go index e9107c0..1459b2c 100644 --- a/chg/change.go +++ b/chg/change.go @@ -3,6 +3,7 @@ package chg //go:generate stringer -type=ChangeType import ( + "encoding/json" "fmt" "io" "strings" @@ -16,6 +17,16 @@ type ChangeList struct { Items []*Item } +func (cl *ChangeList) MarshalJSON() ([]byte, error) { + return json.Marshal(&struct { + Type string `json:"type"` + Items []*Item `json:"items"` + }{ + Type: ChangeStringFromType(cl.Type), + Items: cl.Items, + }) +} + // ChangeType is the type of the changes type ChangeType int @@ -30,6 +41,26 @@ const ( Security ) +// ChangeTypeFromString creates a type based on its string name +func ChangeStringFromType(ct ChangeType) string { + switch ct { + case Added: + return "added" + case Changed: + return "changed" + case Deprecated: + return "deprecated" + case Fixed: + return "fixed" + case Removed: + return "removed" + case Security: + return "security" + default: + return "unknown" + } +} + // ChangeTypeFromString creates a type based on its string name func ChangeTypeFromString(ct string) ChangeType { switch strings.ToLower(ct) { diff --git a/chg/changelog.go b/chg/changelog.go index 8f0952f..48aa035 100644 --- a/chg/changelog.go +++ b/chg/changelog.go @@ -11,8 +11,8 @@ import ( // Changelog is the main struct that holds all the data // in a format specific to the spec type Changelog struct { - Preamble string - Versions []*Version + Preamble string `json:"preamble"` + Versions []*Version `json:"versions"` } // NewChangelog creates the Changelog struct diff --git a/chg/changelog_test.go b/chg/changelog_test.go index aaca33f..946872c 100644 --- a/chg/changelog_test.go +++ b/chg/changelog_test.go @@ -2,6 +2,7 @@ package chg import ( "bytes" + "encoding/json" "testing" "github.com/stretchr/testify/assert" @@ -115,6 +116,83 @@ func TestChangelogRelease(t *testing.T) { }) } +func TestChangelogEncodeJson(t *testing.T) { + c := Changelog{ + Preamble: "This is the preamble", + Versions: []*Version{ + { + Name: "Unreleased", + Link: "http://example.com/1.0.0..HEAD", + Changes: []*ChangeList{ + { + Type: Added, + Items: []*Item{ + {Description: "New feature"}, + }, + }, + }, + }, + { + Name: "1.0.0", + Link: "http://example.com/abcdef..1.0.0", + }, + { + Name: "0.2.0", + Link: "http://example.com/abcdef..0.2.0", + }, + }, + } + + t.Run("RenderJson", func(t *testing.T) { + buf := &bytes.Buffer{} + enc := json.NewEncoder(buf) + enc.SetIndent("", " ") + err := enc.Encode(c) + + assert.Nil(t, err) + + result := string(buf.Bytes()) + + expectedJson := `{ + "preamble": "This is the preamble", + "versions": [ + { + "name": "Unreleased", + "date": "", + "link": "http://example.com/1.0.0..HEAD", + "yanked": false, + "changes": [ + { + "type": "added", + "items": [ + { + "description": "New feature" + } + ] + } + ] + }, + { + "name": "1.0.0", + "date": "", + "link": "http://example.com/abcdef..1.0.0", + "yanked": false, + "changes": null + }, + { + "name": "0.2.0", + "date": "", + "link": "http://example.com/abcdef..0.2.0", + "yanked": false, + "changes": null + } + ] +} +` + assert.Equal(t, expectedJson, result) + }) +} + func TestChangelogReleaseMinimal(t *testing.T) { c := Changelog{ Versions: []*Version{ diff --git a/chg/item.go b/chg/item.go index a6a9726..b7402e5 100644 --- a/chg/item.go +++ b/chg/item.go @@ -7,10 +7,10 @@ import ( // Item holds the change itself type Item struct { - Description string + Description string `json:"description"` } -// Render rendes the change as a list item +// Render renders the change as a list item func (i *Item) Render(w io.Writer) { io.WriteString(w, fmt.Sprintf("- %s\n", i.Description)) } diff --git a/chg/version.go b/chg/version.go index 28aa2a2..35f6de2 100644 --- a/chg/version.go +++ b/chg/version.go @@ -8,11 +8,11 @@ import ( // Version stores information about the version being defined and // its sections type Version struct { - Name string - Date string // Date in the format YYYY-MM-DD - Link string - Yanked bool // True if the release was yanked/removed - Changes []*ChangeList + Name string `json:"name"` + Date string `json:"date"` // Date in the format YYYY-MM-DD + Link string `json:"link"` + Yanked bool `json:"yanked"` // True if the release was yanked/removed + Changes []*ChangeList `json:"changes"` } // Change returns the Change with name diff --git a/cmd/fmt.go b/cmd/fmt.go index 41d6b82..760e580 100644 --- a/cmd/fmt.go +++ b/cmd/fmt.go @@ -1,18 +1,31 @@ package cmd import ( + "encoding/json" "github.com/rcmachado/changelog/parser" "github.com/spf13/cobra" ) func newFmtCmd(iostreams *IOStreams) *cobra.Command { - return &cobra.Command{ + var jsonFlag bool + + command := &cobra.Command{ Use: "fmt", Short: "Reformat the change log file", Long: "Reformats changelog input following keepachangelog.com spec", - Run: func(cmd *cobra.Command, args []string) { + RunE: func(cmd *cobra.Command, args []string) error { changelog := parser.Parse(iostreams.In) - changelog.Render(iostreams.Out) + if jsonFlag { + enc := json.NewEncoder(iostreams.Out) + enc.SetIndent("", " ") + return enc.Encode(changelog) + } else { + changelog.Render(iostreams.Out) + return nil + } }, } + command.Flags().BoolVar(&jsonFlag, "json", false, "output JSON") + + return command } diff --git a/cmd/fmt_test.go b/cmd/fmt_test.go index 525a399..66a0bc5 100644 --- a/cmd/fmt_test.go +++ b/cmd/fmt_test.go @@ -72,3 +72,83 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. assert.Nil(t, err) assert.Equal(t, expected, string(out.Bytes())) } + +func TestFmtCmdJson(t *testing.T) { + changelog := `# Changelog +Hello +## [Unreleased] +### Changed +- Out of order entries + +### Added +- Something else + +## [0.1.0] - 2018-06-17 + +### Added + +- Command A + +[Unreleased]: https://github.com/rcmachado/changelog/compare/0.2.0...HEAD +[0.1.0]: https://github.com/rcmachado/changelog/compare/ae761ff...0.1.0` + + expected := `{ + "preamble": "Hello", + "versions": [ + { + "name": "Unreleased", + "date": "", + "link": "https://github.com/rcmachado/changelog/compare/0.2.0...HEAD", + "yanked": false, + "changes": [ + { + "type": "changed", + "items": [ + { + "description": "Out of order entries" + } + ] + }, + { + "type": "added", + "items": [ + { + "description": "Something else" + } + ] + } + ] + }, + { + "name": "0.1.0", + "date": "2018-06-17", + "link": "https://github.com/rcmachado/changelog/compare/ae761ff...0.1.0", + "yanked": false, + "changes": [ + { + "type": "added", + "items": [ + { + "description": "Command A" + } + ] + } + ] + } + ] +} +` + + out := new(bytes.Buffer) + iostreams := &IOStreams{ + In: strings.NewReader(changelog), + Out: out, + } + + fmt := newFmtCmd(iostreams) + fmt.SetArgs([]string{"--json"}) + _, err := fmt.ExecuteC() + + assert.Nil(t, err) + assert.Equal(t, expected, string(out.Bytes())) +} diff --git a/cmd/show.go b/cmd/show.go index 79565bc..5d16ddb 100644 --- a/cmd/show.go +++ b/cmd/show.go @@ -1,6 +1,7 @@ package cmd import ( + "encoding/json" "fmt" "github.com/rcmachado/changelog/parser" @@ -8,8 +9,10 @@ import ( ) func newShowCmd(iostreams *IOStreams) *cobra.Command { - return &cobra.Command{ - Use: "show [version]", + var jsonFlag bool + + command := &cobra.Command{ + Use: "command [version]", Short: "Show changelog for [version]", Long: `Show changelog section and entries for version [version]`, Args: cobra.ExactArgs(1), @@ -22,9 +25,16 @@ func newShowCmd(iostreams *IOStreams) *cobra.Command { cmd.SilenceUsage = true return fmt.Errorf("Unknown version: '%s'\n", version) } - - v.RenderChanges(iostreams.Out) - return nil + if jsonFlag { + enc := json.NewEncoder(iostreams.Out) + enc.SetIndent("", " ") + return enc.Encode(v) + } else { + v.RenderChanges(iostreams.Out) + return nil + } }, } + command.Flags().BoolVar(&jsonFlag, "json", false, "output JSON") + return command }