diff --git a/.gitignore b/.gitignore index 824187c..70762f3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ .env .env.* !.env.example +env.* dist/ bin/ diff --git a/README.md b/README.md index f0942fc..9586c3e 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,35 @@ * Filtering by key/prefix/groups when printing keys. * Literal (what you see is what you get) or interpolated (shell-like interpolation of variables) modes. +## Example + +```shell +# Crate a new env file +touch env.test + +# Create a key/pair value +dottie --file env.test set my_key value + +# Create another key/pair value with two comments (one is validation that the value must be a number) +dottie --file env.test set --comment 'first line' --comment '@dottie/validate number' my_int 123 + +# Check validation (success) +dottie --file env.test validate + +# Print the file +dottie --file env.test print + +# Print the file (but pretty) +dottie --file env.test print --pretty + +# Change the "my_int" key to a non-number +# NOTE: the comments are kept even if they are omitted here +dottie --file env.test set my_int test + +# Test validation again +dottie --file env.test validate +``` + ## Install ### homebrew tap diff --git a/cmd/cmd_print.go b/cmd/cmd_print.go index 2777a8f..ba2b955 100644 --- a/cmd/cmd_print.go +++ b/cmd/cmd_print.go @@ -4,7 +4,6 @@ import ( "context" "os" - "github.com/davecgh/go-spew/spew" "github.com/jippi/dottie/pkg/tui" "github.com/urfave/cli/v3" ) @@ -14,9 +13,6 @@ var printCommand = &cli.Command{ Usage: "Print environment variables", Before: setup, Action: func(_ context.Context, _ *cli.Command) error { - spew.Config.DisablePointerMethods = true - spew.Config.DisableMethods = true - settings.Interpolate = true tui.Theme.Dark.Printer(tui.Renderer(os.Stdout)).Println(env.Render(*settings)) diff --git a/cmd/cmd_set.go b/cmd/cmd_set.go index 86d4bbe..f0a3d5e 100644 --- a/cmd/cmd_set.go +++ b/cmd/cmd_set.go @@ -58,7 +58,7 @@ var setCommand = &cli.Command{ Comments: cmd.StringSlice("comment"), } - assignment := ast.Assignment{ + assignment := &ast.Assignment{ Name: key, Literal: cmd.Args().Get(1), Active: !cmd.Bool("commented"), @@ -66,7 +66,7 @@ var setCommand = &cli.Command{ assignment.SetQuote(cmd.String("quote-style")) - _, err := env.Set(&assignment, options) + _, err := env.Set(assignment, options) if err != nil { return err } diff --git a/cmd/main.go b/cmd/main.go index 4fa6bab..69ef8a0 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -27,9 +27,6 @@ var ( func main() { __load() - // spew.Config.DisableMethods = true - // spew.Config.DisablePointerMethods = true - origHelpPrinterCustom := cli.HelpPrinterCustom defer func() { cli.HelpPrinterCustom = origHelpPrinterCustom @@ -66,5 +63,8 @@ func main() { } func __load() { + spew.Config.DisablePointerMethods = true + spew.Config.DisableMethods = true + spew.Dump() } diff --git a/pkg/ast/document.go b/pkg/ast/document.go index c752fa5..5525acb 100644 --- a/pkg/ast/document.go +++ b/pkg/ast/document.go @@ -2,6 +2,7 @@ package ast import ( + "bytes" "fmt" "os" "reflect" @@ -112,22 +113,16 @@ func (doc *Document) Set(input *Assignment, options SetOptions) (bool, error) { return false, fmt.Errorf("Key [%s] does not exists", input.Name) } - group = doc.GetGroup(RenderSettings{FilterGroup: options.Group}) - if group == nil { - group = &Group{ - Name: input.Group.Name, - } - - doc.Groups = append(doc.Groups, group) - } + group = doc.EnsureGroup(options.Group) existing = &Assignment{ - Name: input.Name, - Group: group, + Name: input.Name, + Literal: input.Literal, + Active: input.Active, + Group: group, } - switch { - case len(options.Before) > 0: + if len(options.Before) > 0 { before := options.Before var res []Statement @@ -148,9 +143,20 @@ func (doc *Document) Set(input *Assignment, options SetOptions) (bool, error) { } group.Statements = res + } - default: + if group != nil { group.Statements = append(group.Statements, existing) + } else { + idx := len(doc.Statements) - 1 + + // if laste statement is a newline, replace it with the new assignment + if idx > 1 && doc.Statements[idx].Is(&Newline{}) { + doc.Statements[idx] = existing + } else { + // otherwise append it + doc.Statements = append(doc.Statements, existing) + } } } @@ -169,6 +175,22 @@ func (doc *Document) Set(input *Assignment, options SetOptions) (bool, error) { return true, nil } +func (doc *Document) EnsureGroup(name string) *Group { + if len(name) == 0 { + return nil + } + + group := doc.GetGroup(RenderSettings{FilterGroup: name}) + + if group == nil && len(name) > 0 { + group = &Group{ + Name: "# " + name, + } + } + + return group +} + func (d *Document) GetConfig(name string) (string, error) { for _, comment := range d.Annotations { if comment.Annotation == nil { @@ -198,14 +220,35 @@ func (d *Document) GetPosition(name string) (int, *Assignment) { func (d *Document) RenderFull() string { return d.Render(RenderSettings{ IncludeCommented: true, + Interpolate: false, ShowBlankLines: true, ShowColors: false, ShowComments: true, ShowGroups: true, - Interpolate: false, }) } func (d *Document) Render(config RenderSettings) string { - return renderStatements(d.Statements, config) + var buf bytes.Buffer + + root := renderStatements(d.Statements, config) + if len(root) > 0 { + buf.WriteString(root) + } + + hasOutput := config.WithGroups() && len(root) > 0 + + for _, group := range d.Groups { + output := group.Render(config) + + if hasOutput && len(output) > 0 { + buf.WriteString("\n") + } + + hasOutput = config.WithGroups() && len(output) > 0 + + buf.WriteString(output) + } + + return buf.String() } diff --git a/pkg/ast/group.go b/pkg/ast/group.go index 09ae0f9..f4b1498 100644 --- a/pkg/ast/group.go +++ b/pkg/ast/group.go @@ -41,9 +41,9 @@ func (g *Group) Render(config RenderSettings) string { var buf bytes.Buffer - res := renderStatements(g.Statements, config) + rendered := renderStatements(g.Statements, config) - if config.WithGroups() && len(res) > 0 { + if config.WithGroups() && len(rendered) > 0 { if config.WithColors() { out := tui.Theme.Info.Printer(tui.RendererWithTTY(&buf)) out.Println("################################################################################") @@ -62,7 +62,7 @@ func (g *Group) Render(config RenderSettings) string { } // Render the statements attached to the group - buf.WriteString(res) + buf.WriteString(rendered) return buf.String() } diff --git a/pkg/ast/new_line.go b/pkg/ast/new_line.go index edc05fe..7985b39 100644 --- a/pkg/ast/new_line.go +++ b/pkg/ast/new_line.go @@ -1,6 +1,8 @@ package ast -import "reflect" +import ( + "reflect" +) type Newline struct { Blank bool `json:"blank"` diff --git a/pkg/ast/render_settings.go b/pkg/ast/render_settings.go index ebf92df..f545560 100644 --- a/pkg/ast/render_settings.go +++ b/pkg/ast/render_settings.go @@ -9,11 +9,11 @@ type RenderSettings struct { FilterGroup string IncludeCommented bool - ShowPretty bool - ShowComments bool - ShowGroups bool ShowBlankLines bool ShowColors bool + ShowComments bool + ShowGroups bool + ShowPretty bool Interpolate bool } diff --git a/pkg/ast/shared.go b/pkg/ast/shared.go index ea973d8..08ed781 100644 --- a/pkg/ast/shared.go +++ b/pkg/ast/shared.go @@ -30,25 +30,13 @@ func renderStatements(statements []Statement, config RenderSettings) string { var ( buf bytes.Buffer prev Statement - line *Newline printed bool ) for _, stmt := range statements { switch val := stmt.(type) { case *Group: - output := val.Render(config) - if len(output) == 0 { - continue - } - - if config.WithBlankLines() && !prev.Is(line) { - buf.WriteString("\n") - } - - buf.WriteString(output) - - printed = true + panic("group should never happen in renderStatements") case *Comment: printed = true @@ -65,12 +53,9 @@ func renderStatements(statements []Statement, config RenderSettings) string { // which mean they are too close in the document, so we will // attempt to inject some new-lines to give them some space if config.WithBlankLines() && val.Is(prev) { - switch { // only allow cuddling of assignments if they both have no comments - case val.HasComments() || assignmentHasComments(prev): + if val.HasComments() || assignmentHasComments(prev) { buf.WriteString("\n") - - default: } } diff --git a/pkg/parser/parser.go b/pkg/parser/parser.go index 42bb31e..74f4532 100644 --- a/pkg/parser/parser.go +++ b/pkg/parser/parser.go @@ -62,9 +62,6 @@ func (p *Parser) Parse() (*ast.Document, error) { // Append the group global.Groups = append(global.Groups, group) - // Append it to the statements list - global.Statements = append(global.Statements, val) - case *ast.Assignment: val.Position.File = p.filename