changeloguru/main.go

243 lines
5.4 KiB
Go
Raw Normal View History

package main
import (
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"strings"
"time"
2020-12-24 10:52:21 +00:00
"github.com/fatih/color"
"github.com/haunt98/changeloguru/pkg/changelog"
2020-11-09 08:46:42 +00:00
"github.com/haunt98/changeloguru/pkg/convention"
"github.com/haunt98/changeloguru/pkg/git"
"github.com/urfave/cli/v2"
"golang.org/x/mod/semver"
)
const (
2021-01-05 08:48:09 +00:00
name = "changeloguru"
currentDir = "."
2020-12-17 09:54:32 +00:00
markdownFiletype = "md"
defaultRepositry = currentDir
defaultOutput = currentDir
defaultFilename = "CHANGELOG"
defaultFiletype = markdownFiletype
fromFlag = "from"
toFlag = "to"
versionFlag = "version"
repositoryFlag = "repository"
outputFlag = "output"
filenameFlag = "filename"
filetypeFlag = "filetype"
debugFlag = "debug"
)
func main() {
a := &action{}
app := &cli.App{
2021-01-05 08:48:09 +00:00
Name: name,
Usage: "generate changelog from conventional commits",
Flags: []cli.Flag{
&cli.StringFlag{
Name: fromFlag,
2020-12-17 10:34:50 +00:00
Usage: "generate from `COMMIT`",
},
&cli.StringFlag{
Name: toFlag,
2021-01-05 08:48:09 +00:00
Usage: "generate to `COMMIT`",
},
&cli.StringFlag{
2021-01-05 10:11:31 +00:00
Name: versionFlag,
Usage: "`VERSION` to generate, follow Semantic Versioning",
},
&cli.StringFlag{
2020-12-18 09:21:59 +00:00
Name: repositoryFlag,
2021-01-05 09:06:50 +00:00
Usage: "`REPOSITORY` directory path",
2020-12-18 09:21:59 +00:00
DefaultText: defaultRepositry,
},
&cli.StringFlag{
Name: outputFlag,
2021-01-05 09:06:50 +00:00
Usage: "`OUTPUT` directory path",
DefaultText: defaultOutput,
},
2020-12-17 09:54:32 +00:00
&cli.StringFlag{
2020-12-18 09:21:59 +00:00
Name: filenameFlag,
Usage: "output `FILENAME`",
DefaultText: defaultFilename,
2020-12-17 09:54:32 +00:00
},
&cli.StringFlag{
2020-12-18 09:21:59 +00:00
Name: filetypeFlag,
2021-01-05 08:55:52 +00:00
Usage: "output `FILETYPE`",
2020-12-18 09:21:59 +00:00
DefaultText: defaultFiletype,
2020-12-17 09:54:32 +00:00
},
&cli.BoolFlag{
Name: debugFlag,
2021-01-05 09:04:52 +00:00
Aliases: []string{"d"},
2021-01-05 09:08:39 +00:00
Usage: "show debugging info",
},
},
Action: a.Run,
}
if err := app.Run(os.Args); err != nil {
2020-12-24 10:52:21 +00:00
// Highlight error
fmtErr := color.New(color.FgRed)
fmtErr.Printf("[%s error]: ", name)
fmt.Printf("%s\n", err.Error())
}
}
type action struct {
flags struct {
debug bool
from string
to string
version string
repository string
output string
filename string
filetype string
}
}
func (a *action) Run(c *cli.Context) error {
// Show help if there is nothing
2021-01-05 09:03:48 +00:00
if c.NArg() == 0 && c.NumFlags() == 0 {
return cli.ShowAppHelp(c)
}
// Set up
a.getFlags(c)
commits, err := a.getCommits()
if err != nil {
return err
}
a.logDebug("commits %+v", commits)
conventionalCommits := a.getConventionalCommits(commits)
a.logDebug("conventional commits %+v", conventionalCommits)
if err := a.generateChangelog(conventionalCommits); err != nil {
return err
}
return nil
}
func (a *action) getFlags(c *cli.Context) {
a.flags.debug = c.Bool(debugFlag)
a.flags.from = c.String(fromFlag)
a.flags.to = c.String(toFlag)
a.flags.version = c.String(versionFlag)
a.flags.repository = a.getFlagValue(c, repositoryFlag, defaultRepositry)
a.flags.output = a.getFlagValue(c, outputFlag, defaultOutput)
a.flags.filename = a.getFlagValue(c, filenameFlag, defaultFilename)
a.flags.filetype = a.getFlagValue(c, filetypeFlag, defaultFiletype)
}
func (a *action) getFlagValue(c *cli.Context, flag, fallback string) string {
value := c.String(flag)
if value == "" {
value = fallback
}
return value
}
func (a *action) getCommits() ([]git.Commit, error) {
r, err := git.NewRepository(a.flags.repository)
if err != nil {
return nil, err
}
return r.Log(a.flags.from, a.flags.to)
}
func (a *action) getConventionalCommits(commits []git.Commit) []convention.Commit {
2020-11-09 08:46:42 +00:00
conventionalCommits := make([]convention.Commit, 0, len(commits))
for _, commit := range commits {
conventionalCommit, err := convention.NewCommit(commit)
if err != nil {
a.logDebug("failed to new conventional commits %+v: %s", commit, err)
2020-11-09 08:46:42 +00:00
continue
}
conventionalCommits = append(conventionalCommits, conventionalCommit)
}
return conventionalCommits
}
func (a *action) generateChangelog(commits []convention.Commit) error {
realOutput := a.getRealOutput()
version, err := a.getVersion()
if err != nil {
return err
}
switch a.flags.filetype {
case markdownFiletype:
return a.generateMarkdownChangelog(realOutput, version, commits)
default:
return fmt.Errorf("unknown filetype %s", a.flags.filetype)
}
}
func (a *action) getRealOutput() string {
nameWithExt := a.flags.filename + "." + a.flags.filetype
realOutput := filepath.Join(a.flags.output, nameWithExt)
return realOutput
}
func (a *action) getVersion() (string, error) {
if a.flags.version == "" {
2021-01-05 10:11:31 +00:00
return "", fmt.Errorf("empty version")
}
if !strings.HasPrefix(a.flags.version, "v") {
a.flags.version = "v" + a.flags.version
}
if !semver.IsValid(a.flags.version) {
return "", fmt.Errorf("invalid semver %s", a.flags.version)
}
a.logDebug("version %s", a.flags.version)
2020-12-17 10:53:38 +00:00
return a.flags.version, nil
2020-12-17 09:54:32 +00:00
}
func (a *action) generateMarkdownChangelog(output, version string, commits []convention.Commit) error {
// If CHANGELOG file already exist
var oldData string
bytes, err := ioutil.ReadFile(output)
if err == nil {
oldData = string(bytes)
}
markdownGenerator := changelog.NewMarkdownGenerator(oldData, version, time.Now())
newData := markdownGenerator.Generate(commits)
if err := ioutil.WriteFile(output, []byte(newData), 0o644); err != nil {
return fmt.Errorf("failed to write file %s: %w", output, err)
}
return nil
}
func (a *action) logDebug(format string, v ...interface{}) {
if a.flags.debug {
log.Printf(format, v...)
}
}