Skip to content

Commit

Permalink
feat: add command to create a markdown changelog file
Browse files Browse the repository at this point in the history
  • Loading branch information
zbindenren committed Dec 31, 2020
1 parent 6a27211 commit b874c81
Show file tree
Hide file tree
Showing 21 changed files with 2,144 additions and 4 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@

# Dependency directories (remove the comment below to include it)
# vendor/
dist/
2 changes: 1 addition & 1 deletion .golangci.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
run:
tests: true
# tests: false
skip-dirs:
- .github
- build
Expand Down
14 changes: 14 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package main

import (
"log"

"github.com/zbindenren/cc/internal/cmd"
)

func main() {
command := cmd.New()
if err := command.Run(); err != nil {
log.Fatal(err)
}
}
189 changes: 189 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
// Package config configures the changelog generation.
package config

import (
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"sort"

"gopkg.in/yaml.v3"
)

const (
// FileName is the configuration file name.
FileName = ".cc.yml"
)

// common errors
var (
ErrNotFound = errors.New("not found")
ErrEmpty = errors.New("empty config")
)

// Changelog configures the changelog.
type Changelog struct {
Sections []Section `yaml:"sections"`
Flavor string `yaml:"flavor"`
}

// Section is a section config.
type Section struct {
Type string `yaml:"type"`
Title string `yaml:"title"`
Hidden bool `yaml:"hidden"`
}

// Title creates the title from the header type.
func (c Changelog) Title(headerType string) (title string, ok bool) {
for _, s := range c.Sections {
if s.Type == headerType {
return s.Title, true
}
}

return "", false
}

// IsHidden returns true if section should be hidden in changelog.
func (c Changelog) IsHidden(headerType string) bool {
for _, s := range c.Sections {
if s.Type == headerType {
return s.Hidden
}
}

return true
}

// List returns not hidden section titles.
func (c Changelog) List() []string {
l := make([]string, 0, len(c.Sections))

for _, s := range c.Sections {
if !s.Hidden {
l = append(l, s.Title)
}
}

sort.Strings(l)

l = append([]string{"Breaking Changes"}, l...)

return l
}

// Validate validates configuration.
func (c Changelog) Validate() error {
for _, s := range c.Sections {
if err := s.validate(); err != nil {
return err
}
}

if !(c.Flavor == "gitlab" || c.Flavor == "github") {
return errors.New("flavor has to be either 'gitlab' or github")
}

return nil
}

// Load is looking for a configuration file named '.cc.yml' in dir. If found
// it tries to unmarshal it into Changelog.
func Load(dir string) (*Changelog, error) {
configPath := filepath.Join(dir, FileName)
if _, err := os.Stat(configPath); os.IsNotExist(err) {
return nil, ErrNotFound
}

b, err := ioutil.ReadFile(configPath) // nolint: gosec
if err != nil {
return nil, fmt.Errorf("failed to read %s: %w", configPath, err)
}

c := Changelog{
Flavor: "gitlab",
}

if err := yaml.Unmarshal(b, &c); err != nil {
return nil, fmt.Errorf("failed to parse %s: %w", configPath, err)
}

if len(c.Sections) == 0 {
return nil, ErrEmpty
}

return &c, nil
}

// Write writes section configration to <dir>/.cc.yml
func Write(dir string, c Changelog) error {
p := filepath.Join(dir, FileName)

b, err := yaml.Marshal(c)
if err != nil {
return err
}

if err := ioutil.WriteFile(p, b, 0600); err != nil {
return fmt.Errorf("failed to write file %s: %w", p, err)
}

return nil
}

// Default represents the default configuration.
var Default = Changelog{
Flavor: "gitlab",
Sections: []Section{
{
Type: "build",
Title: "Build System",
Hidden: true,
},
{
Type: "docs",
Title: "Documentation",
Hidden: true,
},
{
Type: "feat",
Title: "New Features",
Hidden: false,
},
{
Type: "fix",
Title: "Bug Fixes",
Hidden: false,
},
{
Type: "refactor",
Title: "Code Refactoring",
Hidden: true,
},
{
Type: "test",
Title: "Test",
Hidden: true,
},
{
Type: "chore",
Title: "Tasks",
Hidden: true,
},
},
}

func (s Section) validate() error {
if s.Title == "" {
return errors.New("title cannot be empty")
}

if s.Type == "" {
return errors.New("type cannot be empty")
}

return nil
}
42 changes: 42 additions & 0 deletions config/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package config

import (
"io/ioutil"
"os"
"testing"

"github.com/stretchr/testify/require"
"github.com/tj/assert"
)

func TestWriteLoadConfig(t *testing.T) {
tmpDir, err := ioutil.TempDir("", "writeloadtest")
require.NoError(t, err)

defer os.RemoveAll(tmpDir)

err = Write(tmpDir, Default)
require.NoError(t, err)

c, err := Load(tmpDir)
require.NoError(t, err)

require.Equal(t, Default, *c)

_, err = Load(".")
require.Equal(t, ErrNotFound, err)
}

func TestIsHidden(t *testing.T) {
assert.True(t, Default.IsHidden("chore"))
assert.False(t, Default.IsHidden("fix"))
}

func TestTitle(t *testing.T) {
title, ok := Default.Title("feat")
assert.True(t, ok)
assert.Equal(t, "New Features", title)

_, ok = Default.Title("not-exist")
assert.False(t, ok)
}
4 changes: 4 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@ module github.com/zbindenren/cc
go 1.15

require (
github.com/Masterminds/semver v1.5.0
github.com/bbuck/go-lexer v1.0.0
github.com/postfinance/flash v0.1.0
github.com/stretchr/testify v1.6.1
github.com/tj/assert v0.0.3
go.uber.org/zap v1.16.0
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776
gotest.tools v2.2.0+incompatible
)
Loading

0 comments on commit b874c81

Please sign in to comment.