Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add initial changelog revision #140

Merged
merged 6 commits into from
Sep 26, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
148 changes: 148 additions & 0 deletions internal/cli/bumpver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package cli

import (
"errors"
"strconv"
"strings"

"github.com/futurice/jalapeno/internal/cli/option"
"github.com/futurice/jalapeno/pkg/recipe"
"github.com/futurice/jalapeno/pkg/ui/changelog"
"github.com/spf13/cobra"
"golang.org/x/mod/semver"
)

type bumpVerOpts struct {
RecipeName string
RecipeVersion string
ChangelogMsg string
option.Common
option.WorkingDirectory
}

func NewBumpVerCmd() *cobra.Command {
var opts bumpVerOpts
var cmd = &cobra.Command{
Use: "bumpver",
Short: "Bump version number for recipe",
Long: "Bump version number for recipe. By default prompts user for update increment (patch/minor/major) and changelog messsage. These can also be specified directly with the -v and -m flags.",
Args: cobra.NoArgs,
PreRunE: func(cmd *cobra.Command, args []string) error {
return option.Parse(&opts)
},
RunE: func(cmd *cobra.Command, args []string) error {
err := runBumpVer(cmd, opts)
return errorHandler(cmd, err)
},
PostRun: func(cmd *cobra.Command, args []string) {
cmd.Root().SetContext(cmd.Context())
},
Example: `# Prompt version increment and changelog message
jalapeno bumpver
majori marked this conversation as resolved.
Show resolved Hide resolved

# Directly specify version and message
jalapeno bumpver -v v1.0.0 -m "Hello world"`,
}

cmd.Flags().StringVarP(&opts.RecipeName, "recipe", "r", "", "Name of the recipe to upgrade")
majori marked this conversation as resolved.
Show resolved Hide resolved
cmd.Flags().StringVarP(&opts.RecipeVersion, "version", "v", "", "New semver number for recipe")
cmd.Flags().StringVarP(&opts.ChangelogMsg, "message", "m", "", "Optional changelog message")

if err := option.ApplyFlags(&opts, cmd.Flags()); err != nil {
return nil
}

return cmd
}

func runBumpVer(cmd *cobra.Command, opts bumpVerOpts) error {
var increment string
newVer := opts.RecipeVersion
changelogMsg := opts.ChangelogMsg

if newVer != "" && !semver.IsValid(newVer) {
return errors.New("invalid version format, please enter valid Semantic Version")
}

re, err := recipe.LoadRecipe(opts.RecipeName)
if err != nil {
return err
}

if opts.RecipeVersion == "" {
cmd.Println()
prompt, err := changelog.RunChangelog()

if err != nil {
return err
}

increment, changelogMsg = prompt[0], prompt[1]
bumpedVer, err := BumpSemVer(re.Metadata.Version, increment)

if err != nil {
return err
}

newVer = bumpedVer
}

err = re.Metadata.Update(re, newVer, changelogMsg)
if err != nil {
return err
}

err = re.Save(opts.WorkingDirectory.Dir)
majori marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return err
}

cmd.Printf("bumped version %s => %s \n", re.Metadata.Version, newVer)
cmd.Printf("with changelog message %s \n", changelogMsg)

return nil
}

// BumpSemVer takes SemVer as string and an increment as string "patch", "minor", or "major"
// and bumps the version by the increment and returns the new version number
func BumpSemVer(ver string, by string) (string, error) {
trim := strings.TrimPrefix(ver, "v")
parsed := strings.Split(trim, ".")

intArr := make([]int, len(parsed))
resultArr := []string{"", "", ""}

for i, v := range parsed {
conv, err := strconv.Atoi(v)
if err != nil {
return "", err
}
intArr[i] = conv
}

switch by {
case "patch":
intArr[2]++
case "minor":
intArr[2] = 0
intArr[1]++
case "major":
intArr[2] = 0
intArr[1] = 0
intArr[0]++
}
majori marked this conversation as resolved.
Show resolved Hide resolved

for i, v := range intArr {
conv := strconv.Itoa(v)
resultArr[i] = conv
}

resultArr[0] = "v" + resultArr[0]
newVer := strings.Join(resultArr, ".")

if semver.IsValid(newVer) {
return newVer, nil
} else {
return "", errors.New("failed to create valid semantic version")
}
}
1 change: 1 addition & 0 deletions internal/cli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ func NewRootCmd() *cobra.Command {
NewUpgradeCmd(),
NewValidateCmd(),
NewWhyCmd(),
NewBumpVerCmd(),
)

return cmd
Expand Down
14 changes: 14 additions & 0 deletions pkg/recipe/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ type Metadata struct {
// Description of what the recipe does
Description string `yaml:"description"`

// Changelog of recipe versions
Changelog map[string]string `yaml:"changelog,omitempty"`

// URL to source code for this recipe
Source string `yaml:"source,omitempty"`

Expand Down Expand Up @@ -72,3 +75,14 @@ func (m *Metadata) Validate() error {

return nil
}

func (m *Metadata) Update(re *Recipe, newVer, msg string) error {
majori marked this conversation as resolved.
Show resolved Hide resolved
majori marked this conversation as resolved.
Show resolved Hide resolved
if re.Changelog == nil {
re.Changelog = map[string]string{re.Metadata.Version: "Init version"}
}

re.Changelog[newVer] = msg
re.Version = newVer

return nil
}
58 changes: 58 additions & 0 deletions pkg/ui/changelog/changelog.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package changelog

import (
"errors"

tea "github.com/charmbracelet/bubbletea"
changelog "github.com/futurice/jalapeno/pkg/ui/changelog/prompt"
)

func RunChangelog() ([]string, error) {
majori marked this conversation as resolved.
Show resolved Hide resolved
var changelog []string
verInc, err := RunSelectPrompt()

if err != nil {
return []string{}, errors.New("failed to get version type")
}

logmsg, err := RunTextAreaPrompt()

if err != nil {
return []string{}, errors.New("failed to get log message")
}

changelog = append(changelog, verInc)
changelog = append(changelog, logmsg)

return changelog, nil
}

func RunSelectPrompt() (string, error) {
majori marked this conversation as resolved.
Show resolved Hide resolved
options := []string{"patch", "minor", "major"}

p := tea.NewProgram(changelog.NewSelectModel(options))

if m, err := p.Run(); err != nil {
return "", err
} else {
sel, ok := m.(changelog.SelectModel)
if !ok {
return "", errors.New("internal error: unexpected model type")
}
return sel.Value(), nil
}
}

func RunTextAreaPrompt() (string, error) {
p := tea.NewProgram(changelog.NewStringModel())

if m, err := p.Run(); err != nil {
return "", err
} else {
txt, ok := m.(changelog.StringModel)
if !ok {
return "", errors.New("internal error: unexpected model type")
}
return txt.Value(), nil
}
}
135 changes: 135 additions & 0 deletions pkg/ui/changelog/prompt/select.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package changelog

import (
"fmt"
"io"
"strings"

"github.com/charmbracelet/bubbles/list"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/muesli/reflow/wordwrap"
)

var (
itemStyle = lipgloss.NewStyle().PaddingLeft(2)
selectedItemStyle = lipgloss.NewStyle().PaddingLeft(0).Foreground(lipgloss.Color("170"))
)

type SelectModel struct {
list list.Model
value string
showDescription bool
submitted bool
width int
}

var _ tea.Model = SelectModel{}

type selectItem string

var _ list.Item = selectItem("")

func (i selectItem) FilterValue() string { return "" }

type selectItemDelegate struct{}

var _ list.ItemDelegate = selectItemDelegate{}

func (d selectItemDelegate) Height() int { return 1 }
func (d selectItemDelegate) Spacing() int { return 0 }
func (d selectItemDelegate) Update(_ tea.Msg, _ *list.Model) tea.Cmd { return nil }
func (d selectItemDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) {
i, ok := listItem.(selectItem)
if !ok {
return
}

fn := itemStyle.Render
if index == m.Index() {
fn = func(s ...string) string {
return selectedItemStyle.Render("> " + strings.Join(s, " "))
}
}

fmt.Fprint(w, fn(string(i)))
}

func NewSelectModel(options []string) SelectModel {
items := make([]list.Item, len(options))
for i := range options {
items[i] = selectItem(options[i])
}

const (
defaultWidth = 20
defaultHeight = 14
)

l := list.New(items, selectItemDelegate{}, defaultWidth, defaultHeight)
l.SetShowStatusBar(false)
l.SetFilteringEnabled(false)
l.SetShowHelp(false)
l.SetShowTitle(false)

return SelectModel{
list: l,
}
}

func (m SelectModel) Init() tea.Cmd {
return nil
}

func (m SelectModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.Type {
case tea.KeyEnter:
m.submitted = true
m.value = string(m.list.SelectedItem().(selectItem))
return m, tea.Quit
case tea.KeyRunes:
switch string(msg.Runes) {
case "?":
if !m.showDescription {
m.showDescription = true
return m, nil
}
}
}

case tea.WindowSizeMsg:
m.width = msg.Width
m.list.SetWidth(msg.Width)
}

var cmd tea.Cmd
m.list, cmd = m.list.Update(msg)
return m, cmd
}

func (m SelectModel) View() string {
var s strings.Builder
if m.submitted {
s.WriteString(fmt.Sprintf(": %s", m.value))
return s.String()
}

s.WriteRune('\n')
if m.showDescription {
s.WriteString(wordwrap.String("Select version update type", m.width))
s.WriteRune('\n')
}

s.WriteString(m.list.View())
return s.String()
}

func (m SelectModel) Value() string {
return m.value
}

func (m SelectModel) IsSubmitted() bool {
return m.submitted
}
Loading