Skip to content

Commit

Permalink
feat: support form themes (#155)
Browse files Browse the repository at this point in the history
* WIP: first pass on styles/themes

* comment package

* support themeing

* extract constants

* style updates and refactors

* update theme names

* add theme flag

* theme docs

* reorg styles + theme validation

* add validation step for settings

* rework scaffold rc validation

* fix failing tests
  • Loading branch information
hay-kot authored May 21, 2024
1 parent 705389d commit 5a7d87d
Show file tree
Hide file tree
Showing 13 changed files with 315 additions and 43 deletions.
3 changes: 2 additions & 1 deletion app/commands/cmd_new.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/hay-kot/scaffold/app/core/fsast"
"github.com/hay-kot/scaffold/app/scaffold"
"github.com/hay-kot/scaffold/app/scaffold/pkgs"
"github.com/hay-kot/scaffold/internal/styles"
"github.com/sahilm/fuzzy"
)

Expand Down Expand Up @@ -66,7 +67,7 @@ func (ctrl *Controller) New(args []string, flags FlagsNew) error {
default:
varfunc = func(p *scaffold.Project) (map[string]any, error) {
vars := scaffold.MergeMaps(argvars, ctrl.rc.Defaults)
vars, err = p.AskQuestions(vars, ctrl.engine)
vars, err = p.AskQuestions(vars, ctrl.engine, styles.Theme(ctrl.rc.Settings.Theme))
if err != nil {
return nil, err
}
Expand Down
5 changes: 3 additions & 2 deletions app/commands/prompts.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ package commands

import (
"github.com/charmbracelet/huh"
"github.com/hay-kot/scaffold/internal/styles"
)

func httpAuthPrompt() (username string, password string, err error) {
func httpAuthPrompt(theme styles.HuhTheme) (username string, password string, err error) {
form := huh.NewForm(
huh.NewGroup(
huh.NewInput().
Expand All @@ -17,7 +18,7 @@ func httpAuthPrompt() (username string, password string, err error) {
Value(&password).
Password(true),
),
)
).WithTheme(styles.Theme(theme))

err = form.Run()
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion app/commands/resolve.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func (ctrl *Controller) resolve(argPath string, noPrompt bool) (string, error) {
return "", err
}

username, password, err := httpAuthPrompt()
username, password, err := httpAuthPrompt(ctrl.rc.Settings.Theme)
if err != nil {
return "", err
}
Expand Down
24 changes: 9 additions & 15 deletions app/scaffold/askable.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import (
"strings"

"github.com/charmbracelet/huh"
"github.com/charmbracelet/lipgloss"
"github.com/hay-kot/scaffold/app/core/engine"
"github.com/hay-kot/scaffold/internal/styles"
)

type Askable struct {
Expand All @@ -24,19 +24,13 @@ func NewAskable(name string, key string, field huh.Field, fn func(vars engine.Va
}
}

var (
bold = lipgloss.NewStyle().Bold(true).Render
light = lipgloss.NewStyle().Foreground(lipgloss.Color("#FF83D5")).Render
base = lipgloss.NewStyle().Foreground(lipgloss.Color("#6C74FD")).Render
)

func (a *Askable) String() string {
bldr := strings.Builder{}

bldr.WriteString(" ")
bldr.WriteString(light("?"))
bldr.WriteString(styles.Light("?"))
bldr.WriteString(" ")
bldr.WriteString(bold(a.Name))
bldr.WriteString(styles.Bold(a.Name))
bldr.WriteString(" ")

val := a.Field.GetValue()
Expand All @@ -48,18 +42,18 @@ func (a *Askable) String() string {
}

if strings.Contains(v, "\n") {
bldr.WriteString(base("|"))
bldr.WriteString(styles.Base("|"))

for _, line := range strings.Split(v, "\n") {
bldr.WriteString("\n")
bldr.WriteString(" ")
bldr.WriteString(base(line))
bldr.WriteString(styles.Base(line))
}

break
}

bldr.WriteString(base(v))
bldr.WriteString(styles.Base(v))
case []string:
if len(v) == 0 {
return ""
Expand All @@ -68,13 +62,13 @@ func (a *Askable) String() string {
for _, v := range v {
bldr.WriteString("\n")
bldr.WriteString(" - ")
bldr.WriteString(base(v))
bldr.WriteString(styles.Base(v))
}
case bool:
if v {
bldr.WriteString(base("true"))
bldr.WriteString(styles.Base("true"))
} else {
bldr.WriteString(base("false"))
bldr.WriteString(styles.Base("false"))
}

default:
Expand Down
4 changes: 2 additions & 2 deletions app/scaffold/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ func (p *Project) validate() (str string, err error) {
return "", fmt.Errorf("{{ .Project }} directory does not exist")
}

func (p *Project) AskQuestions(def map[string]any, e *engine.Engine) (map[string]any, error) {
func (p *Project) AskQuestions(def map[string]any, e *engine.Engine, theme *huh.Theme) (map[string]any, error) {
projectMode := p.NameTemplate != "templates"

if projectMode {
Expand Down Expand Up @@ -188,7 +188,7 @@ func (p *Project) AskQuestions(def map[string]any, e *engine.Engine) (map[string
formgroups = append(formgroups, group)
}

form = huh.NewForm(formgroups...)
form = huh.NewForm(formgroups...).WithTheme(theme)

err := form.Run()
if err != nil {
Expand Down
43 changes: 36 additions & 7 deletions app/scaffold/rc.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,14 @@ import (

"github.com/go-git/go-git/v5/plumbing/transport"
githttp "github.com/go-git/go-git/v5/plumbing/transport/http"
"github.com/hay-kot/scaffold/internal/styles"
"gopkg.in/yaml.v3"
)

type ScaffoldRC struct {
// Settings define the settings for the scaffold application.
Settings Settings `yaml:"settings"`

// Defaults define a default value for a variable.
// name: myproject
// git_user: hay-kot
Expand Down Expand Up @@ -45,6 +49,10 @@ type ScaffoldRC struct {
Auth []AuthEntry `yaml:"auth"`
}

type Settings struct {
Theme styles.HuhTheme `yaml:"theme"`
}

type AuthEntry struct {
Match regexp.Regexp `yaml:"match"`
Basic BasicAuth `yaml:"basic"`
Expand All @@ -67,21 +75,35 @@ func (e RcValidationErrors) Error() string {
return "invalid scaffold rc"
}

func NewScaffoldRC(r io.Reader) (*ScaffoldRC, error) {
var out ScaffoldRC
// DefaultScaffoldRC returns a default scaffold rc file.
func DefaultScaffoldRC() *ScaffoldRC {
return &ScaffoldRC{
Settings: Settings{
Theme: styles.HuhThemeScaffold,
},
}
}

// NewScaffoldRC reads a scaffold rc file from the reader and returns a
// ScaffoldRC struct.
func NewScaffoldRC(r io.Reader) (*ScaffoldRC, error) {
out := DefaultScaffoldRC()
err := yaml.NewDecoder(r).Decode(&out)
if err != nil {
if errors.Is(err, io.EOF) {
// Assume empty file and return empty struct
return &out, nil
return out, nil
}
return nil, err
}

return out, nil
}

func (rc *ScaffoldRC) Validate() error {
errs := []RCValidationError{}

for k, v := range out.Shorts {
for k, v := range rc.Shorts {
_, err := url.ParseRequestURI(v)
if err != nil {
errs = append(errs, RCValidationError{
Expand All @@ -91,7 +113,7 @@ func NewScaffoldRC(r io.Reader) (*ScaffoldRC, error) {
}
}

for k, v := range out.Aliases {
for k, v := range rc.Aliases {
// Shorts must be absolute path or relative to ~ or a URL
_, err := url.ParseRequestURI(v)
if err != nil {
Expand All @@ -104,11 +126,18 @@ func NewScaffoldRC(r io.Reader) (*ScaffoldRC, error) {
}
}

if !rc.Settings.Theme.IsValid() {
errs = append(errs, RCValidationError{
Key: "settings.theme",
Cause: fmt.Errorf("invalid theme: %s", rc.Settings.Theme.String()),
})
}

if len(errs) > 0 {
return nil, RcValidationErrors(errs)
return RcValidationErrors(errs)
}

return &out, nil
return nil
}

func expandEnvVars(s string) string {
Expand Down
11 changes: 7 additions & 4 deletions app/scaffold/rc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,23 @@ import (
"github.com/stretchr/testify/require"
)

var badScaffoldRC = []byte(`---
func TestNewScaffoldRC(t *testing.T) {
badScaffoldRC := []byte(`---
shorts:
gh: github.com
gitea: gitea.com
aliases:
cli: app/cli
`)

var goodScaffoldRC = []byte(`---
goodScaffoldRC := []byte(`---
shorts:
gh: https://github.com
gitea: https://gitea.com
aliases:
cli: ~/app/cli
`)

func TestNewScaffoldRC(t *testing.T) {
tests := []struct {
name string
r io.Reader
Expand All @@ -46,7 +46,10 @@ func TestNewScaffoldRC(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := NewScaffoldRC(tt.r)
rc, err := NewScaffoldRC(tt.r)
require.NoError(t, err, "failed on setup")

err = rc.Validate()

switch {
case tt.wantErr:
Expand Down
23 changes: 22 additions & 1 deletion docs/docs/user-guide/scaffold-rc.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,27 @@ Scaffold RC is the runtime configuration file that can be used to define some de

Defaults to `~/.scaffold/scaffoldrc.yml`

## `settings`

The `settings` section allows you to define some global settings for the scaffolding process. These can be any key/value string pairs

**Example**

```yaml
settings:
theme: scaffold
```
### `theme`

The Theme settings allows the user to set the default theme for the scaffolding process. Options include

- `scaffold` (default)
- `charm`
- `dracula`
- `base16`
- `catppuccino`

## `defaults`

The `defaults` section allows you to set some default values for the scaffolding process. These can be any key/value string pairs
Expand Down Expand Up @@ -77,4 +98,4 @@ auth:

::: tip
the `match` key supports regular expressions giving you a lot of flexibility in defining your matchers.
:::
:::
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.22.0
require (
github.com/bmatcuk/doublestar/v4 v4.6.1
github.com/bradleyjkemp/cupaloy/v2 v2.8.0
github.com/catppuccin/go v0.2.0
github.com/charmbracelet/bubbles v0.17.2-0.20240108170749-ec883029c8e6
github.com/charmbracelet/bubbletea v0.25.0
github.com/charmbracelet/glamour v0.7.0
Expand All @@ -30,7 +31,6 @@ require (
github.com/atotto/clipboard v0.1.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/catppuccin/go v0.2.0 // indirect
github.com/cloudflare/circl v1.3.7 // indirect
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
Expand Down
12 changes: 12 additions & 0 deletions internal/styles/styles.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Package styles contains the shared styles for the terminal UI components.
package styles

import (
"github.com/charmbracelet/lipgloss"
)

var (
Bold = lipgloss.NewStyle().Bold(true).Render
Underline = lipgloss.NewStyle().Underline(true).Render
Base, Light = ThemeColorsScaffold.Compile()
)
Loading

0 comments on commit 5a7d87d

Please sign in to comment.