Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
25 changes: 18 additions & 7 deletions internal/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,31 @@ func NewRootCmd() *cobra.Command {
// where folder would otherwise be interpreted as a subcommand and fail.
Args: cobra.ArbitraryArgs,
// When no subcommand is supplied, execute the push command
RunE: NewPushCmd(opts).RunE,
RunE: newPushCmd(opts).RunE,
}

// Add global flags
rootCmd.PersistentFlags().StringVarP(&opts.OutputFile, "output", "o", "", "Output file")
rootCmd.PersistentFlags().StringVar(&opts.IgnoreFile, "ignore", "", "Ignore file (default: .gitignore)")
rootCmd.PersistentFlags().StringVarP(&opts.IgnoreFile, "ignore", "i", "", "Ignore file (default: .gitignore)")
rootCmd.PersistentFlags().BoolVarP(&opts.KeepFile, "keep", "k", false, "Keep the generated file after pushing")

var showLineNumbers bool
rootCmd.PersistentFlags().BoolVarP(&showLineNumbers, "line-numbers", "n", false, "Show line numbers in output (overrides config setting)")
rootCmd.PersistentPreRunE = func(cmd *cobra.Command, _ []string) error {
// Set the pointer only if the flag was explicitly provided; otherwise
// leave it as nil to use the project settings.
if cmd.Flags().Changed("line-numbers") {
opts.ShowLineNumbers = &showLineNumbers
}
return nil
}

// Add commands
rootCmd.AddCommand(
NewGenerateCmd(opts),
NewPushCmd(opts),
NewPurgeCmd(),
NewSetupCmd(),
newGenerateCmd(opts),
newPushCmd(opts),
newPurgeCmd(),
newSetupCmd(),
newConfigCmd(),
)

return rootCmd
Expand Down
271 changes: 271 additions & 0 deletions internal/cli/cmd_config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
package cli

import (
"fmt"

"github.com/holonoms/sandworm/internal/config"
"github.com/spf13/cobra"
)

// ConfigOption represents a configuration option
type ConfigOption struct {
Key string
Description string
Default string
ValidValues []string // For enumerated values like true/false
Validator func(string) error
}

// Registry of all available configuration options
var configOptions = []ConfigOption{
{
Key: "claude.organization_id",
Description: "The organization ID to use for the Claude API",
Default: "",
},
{
Key: "claude.project_id",
Description: "The project ID to use for the Claude API",
Default: "",
},
{
Key: "claude.document_id",
Description: "The document ID to use for the Claude API",
Default: "",
},
{
Key: "processor.print_line_numbers",
Description: "Print line numbers in the output",
Default: "false",
ValidValues: []string{"true", "false"},
Validator: validateBoolOption,
},
}

// MARK: Sub-commands

// newConfigCmd creates the config command and its subcommands
func newConfigCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "config",
Short: "Manage project configuration",
}

// Add subcommands
cmd.AddCommand(
newConfigListCmd(),
newConfigGetCmd(),
newConfigSetCmd(),
newConfigUnsetCmd(),
)

return cmd
}

func newConfigListCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "list",
Short: "List all configuration values",
Args: cobra.NoArgs,
RunE: func(_ *cobra.Command, _ []string) error {
return runConfigList()
},
}

return cmd
}

func runConfigList() error {
cfg, err := config.New(".")
if err != nil {
return fmt.Errorf("unable to load config: %w", err)
}

fmt.Println("Available configuration options:")
fmt.Println()

for _, option := range configOptions {
fmt.Printf(" %s\n", option.Key)
fmt.Printf(" Description: %s\n", option.Description)
fmt.Printf(" Default: %s\n", option.Default)

if cfg.Has(option.Key) {
value := cfg.Get(option.Key)
fmt.Printf(" Current: %s\n", value)
} else {
fmt.Printf(" Current: %s (default)\n", option.Default)
}
fmt.Println()
}

return nil
}

func newConfigSetCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "set <key> <value>",
Short: "Set a configuration value",
Args: cobra.ExactArgs(2),
RunE: func(_ *cobra.Command, args []string) error {
return runConfigSet(args[0], args[1])
},
ValidArgsFunction: func(
_ *cobra.Command,
args []string,
_ string,
) ([]string, cobra.ShellCompDirective) {
if len(args) == 0 {
return configOptionsKeys(), cobra.ShellCompDirectiveNoFileComp
}
// For values, provide common completions based on the key
if len(args) == 1 {
option := findConfigOption(args[0])
if option != nil && len(option.ValidValues) > 0 {
return option.ValidValues, cobra.ShellCompDirectiveNoFileComp
}
}
return nil, cobra.ShellCompDirectiveNoFileComp
},
}

return cmd
}

func runConfigSet(key, value string) error {
// Find the configuration option
option := findConfigOption(key)
if option == nil {
return fmt.Errorf("unknown configuration option: %s\n\nRun 'sandworm config list' to see available options", key)
}

// Validate the value
if option.Validator != nil {
if err := option.Validator(value); err != nil {
return fmt.Errorf("invalid value for %s: %w", key, err)
}
}

cfg, err := config.New(".")
if err != nil {
return fmt.Errorf("unable to load config: %w", err)
}

if err := cfg.Set(key, value); err != nil {
return fmt.Errorf("unable to set config: %w", err)
}

fmt.Printf("Set %s = %s\n", key, value)
return nil
}

func newConfigGetCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "get <key>",
Short: "Get a configuration value",
Args: cobra.ExactArgs(1),
RunE: func(_ *cobra.Command, args []string) error {
return runConfigGet(args[0])
},
ValidArgsFunction: func(
_ *cobra.Command,
args []string,
_ string,
) ([]string, cobra.ShellCompDirective) {
if len(args) == 0 {
return configOptionsKeys(), cobra.ShellCompDirectiveNoFileComp
}
return nil, cobra.ShellCompDirectiveNoFileComp
},
}

return cmd
}

func runConfigGet(key string) error {
// Validate that the key is a known option
option := findConfigOption(key)
if option == nil {
return fmt.Errorf("unknown configuration option: %s\n\nRun 'sandworm config list' to see available options", key)
}

cfg, err := config.New(".")
if err != nil {
return fmt.Errorf("unable to load config: %w", err)
}

if !cfg.Has(key) {
fmt.Printf("%s = %s (default)\n", key, option.Default)
return nil
}

value := cfg.Get(key)
fmt.Printf("%s = %s\n", key, value)
return nil
}

func newConfigUnsetCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "unset <key>",
Short: "Unset a configuration value",
Args: cobra.ExactArgs(1),
RunE: func(_ *cobra.Command, args []string) error {
return runConfigUnset(args[0])
},
ValidArgsFunction: func(
_ *cobra.Command,
args []string,
_ string,
) ([]string, cobra.ShellCompDirective) {
if len(args) == 0 {
return configOptionsKeys(), cobra.ShellCompDirectiveNoFileComp
}
return nil, cobra.ShellCompDirectiveNoFileComp
},
}

return cmd
}

func runConfigUnset(key string) error {
cfg, err := config.New(".")
if err != nil {
return fmt.Errorf("unable to load config: %w", err)
}

if err := cfg.Delete(key); err != nil {
return fmt.Errorf("unable to unset config: %w", err)
}

fmt.Printf("Unset %s\n", key)
return nil
}

// MARK: Helpers

// findConfigOption finds a config option by key
func findConfigOption(key string) *ConfigOption {
for i := range configOptions {
if configOptions[i].Key == key {
return &configOptions[i]
}
}
return nil
}

func configOptionsKeys() []string {
keys := make([]string, len(configOptions))
for i, option := range configOptions {
keys[i] = option.Key
}
return keys
}

// MARK: Validators

// validateBoolOption validates that a value is either "true" or "false"
func validateBoolOption(value string) error {
if value != "true" && value != "false" {
return fmt.Errorf("value must be either 'true' or 'false', got: %s", value)
}
return nil
}
25 changes: 22 additions & 3 deletions internal/cli/cmd_generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ package cli
import (
"fmt"

"github.com/holonoms/sandworm/internal/config"
"github.com/holonoms/sandworm/internal/processor"
"github.com/holonoms/sandworm/internal/util"
"github.com/spf13/cobra"
)

// NewGenerateCmd creates the generate command
func NewGenerateCmd(opts *Options) *cobra.Command {
// newGenerateCmd creates the generate command
func newGenerateCmd(opts *Options) *cobra.Command {
cmd := &cobra.Command{
Use: "generate [directory]",
Short: "Generate concatenated file only",
Expand Down Expand Up @@ -38,7 +39,25 @@ func runGenerate(opts *Options) (int64, error) {
opts.Directory = "."
}

p, err := processor.New(opts.Directory, opts.OutputFile, opts.IgnoreFile)
printLineNumbers := false
if opts.ShowLineNumbers != nil {
printLineNumbers = *opts.ShowLineNumbers
} else {
// If line-numbers flag wasn't explicitly set, load & check the project's settings.
cfg, err := config.New(opts.Directory)
if err != nil {
return 0, fmt.Errorf("unable to load config: %w", err)
}

if cfg.Has("processor.print_line_numbers") {
value := cfg.Get("processor.print_line_numbers")
if value == "true" {
printLineNumbers = true
}
}
}

p, err := processor.New(opts.Directory, opts.OutputFile, opts.IgnoreFile, printLineNumbers)
if err != nil {
return 0, fmt.Errorf("unable to create processor: %w", err)
}
Expand Down
4 changes: 2 additions & 2 deletions internal/cli/cmd_purge.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import (
"github.com/spf13/cobra"
)

// NewPurgeCmd creates the purge command
func NewPurgeCmd() *cobra.Command {
// newPurgeCmd creates the purge command
func newPurgeCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "purge",
Short: "Remove all files from Claude project",
Expand Down
4 changes: 2 additions & 2 deletions internal/cli/cmd_push.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import (
"github.com/spf13/cobra"
)

// NewPushCmd creates the push command
func NewPushCmd(opts *Options) *cobra.Command {
// newPushCmd creates the push command
func newPushCmd(opts *Options) *cobra.Command {
cmd := &cobra.Command{
Use: "push [directory]",
Short: "Generate and push to Claude",
Expand Down
4 changes: 2 additions & 2 deletions internal/cli/cmd_setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import (
"github.com/spf13/cobra"
)

// NewSetupCmd creates the setup command
func NewSetupCmd() *cobra.Command {
// newSetupCmd creates the setup command
func newSetupCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "setup",
Short: "Configure Claude project",
Expand Down
4 changes: 4 additions & 0 deletions internal/cli/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ type Options struct {
// Directory specifies the root directory to process.
// If empty, defaults to the current directory (".").
Directory string

// ShowLineNumbers determines whether to show line numbers in the output.
// If nil, the value from config will be used. If set, it overrides the config.
ShowLineNumbers *bool
}

// SetDefaults sets default values for options based on the command context
Expand Down
Loading