Skip to content

Commit

Permalink
Add support for default init sources in user config
Browse files Browse the repository at this point in the history
Allow users to specify default sources for new environments in
~/.hermit.hcl using the init-sources configuration option. These
sources will be used when initializing a new environment unless
overridden by command-line arguments.
  • Loading branch information
lox committed Feb 18, 2025
1 parent 483aef3 commit 88b6f97
Show file tree
Hide file tree
Showing 7 changed files with 162 additions and 13 deletions.
3 changes: 2 additions & 1 deletion app/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import (

// GlobalState configurable by user to be passed through to Hermit.
type GlobalState struct {
Env envars.Envars `help:"Extra environment variables to apply to environments."`
Env envars.Envars `help:"Extra environment variables to apply to environments."`
UserConfig string `help:"Path to Hermit user configuration file." default:"~/.hermit.hcl" env:"HERMIT_USER_CONFIG"`
}

type cliInterface interface {
Expand Down
14 changes: 12 additions & 2 deletions app/init_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,23 @@ type initCmd struct {
Dir string `arg:"" help:"Directory to create environment in (${default})." default:"${env}" predictor:"dir"`
}

func (i *initCmd) Run(w *ui.UI, config Config) error {
func (i *initCmd) Run(w *ui.UI, config Config, userConfig UserConfig) error {
_, sum, err := GenInstaller(config)
if err != nil {
return errors.WithStack(err)
}

w.Tracef("Command line sources: %v", i.Sources)
w.Tracef("User config: %+v", userConfig)

sources := i.Sources
if len(sources) == 0 {
w.Tracef("Using init-sources from user config: %v", userConfig.InitSources)
sources = userConfig.InitSources
}

return hermit.Init(w, i.Dir, config.BaseDistURL, hermit.UserStateDir, hermit.Config{
Sources: i.Sources,
Sources: sources,
ManageGit: !i.NoGit,
AddIJPlugin: i.Idea,
}, sum)
Expand Down
12 changes: 10 additions & 2 deletions app/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,12 @@ func Main(config Config) {
if err != nil {
log.Fatalf("couldn't get working directory: %s", err) // nolint: gocritic
}
common := cliBase{Plugins: config.KongPlugins}
common := cliBase{
Plugins: config.KongPlugins,
GlobalState: GlobalState{
UserConfig: "~/.hermit.hcl",
},
}

// But we activate any environment we find
if envDir, err := hermit.FindEnvDir(os.Args[0]); err == nil {
Expand All @@ -167,10 +172,13 @@ func Main(config Config) {
cli = &unactivated{cliBase: common}
}

userConfig, err := LoadUserConfig()
userConfigPath := cli.getGlobalState().UserConfig
p.Tracef("Loading user config from: %s", userConfigPath)
userConfig, err := LoadUserConfig(userConfigPath)
if err != nil {
log.Printf("%s: %s", userConfigPath, err)
}
p.Tracef("Loaded user config: %+v", userConfig)

githubToken := os.Getenv("HERMIT_GITHUB_TOKEN")
if githubToken == "" {
Expand Down
18 changes: 10 additions & 8 deletions app/user_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ import (
"github.com/cashapp/hermit/errors"
)

const userConfigPath = "~/.hermit.hcl"

var userConfigSchema = func() string {
schema, err := hcl.Schema(&UserConfig{})
if err != nil {
Expand All @@ -25,18 +23,19 @@ var userConfigSchema = func() string {

// UserConfig is stored in ~/.hermit.hcl
type UserConfig struct {
Prompt string `hcl:"prompt,optional" default:"env" enum:"env,short,none" help:"Modify prompt to include hermit environment (env), just an icon (short) or nothing (none)"`
ShortPrompt bool `hcl:"short-prompt,optional" help:"If true use a short prompt when an environment is activated."`
NoGit bool `hcl:"no-git,optional" help:"If true Hermit will never add/remove files from Git automatically."`
Idea bool `hcl:"idea,optional" help:"If true Hermit will try to add the IntelliJ IDEA plugin automatically."`
Prompt string `hcl:"prompt,optional" default:"env" enum:"env,short,none" help:"Modify prompt to include hermit environment (env), just an icon (short) or nothing (none)"`
ShortPrompt bool `hcl:"short-prompt,optional" help:"If true use a short prompt when an environment is activated."`
NoGit bool `hcl:"no-git,optional" help:"If true Hermit will never add/remove files from Git automatically."`
Idea bool `hcl:"idea,optional" help:"If true Hermit will try to add the IntelliJ IDEA plugin automatically."`
InitSources []string `hcl:"init-sources,optional" help:"Default sources to use when initialising a new Hermit environment."`
}

// LoadUserConfig from disk.
func LoadUserConfig() (UserConfig, error) {
func LoadUserConfig(configPath string) (UserConfig, error) {
config := UserConfig{}
// always return a valid config on error, with defaults set.
_ = hcl.Unmarshal([]byte{}, &config)
data, err := os.ReadFile(kong.ExpandPath(userConfigPath))
data, err := os.ReadFile(kong.ExpandPath(configPath))
if os.IsNotExist(err) {
return config, nil
} else if err != nil {
Expand Down Expand Up @@ -71,6 +70,9 @@ func (u *userConfigResolver) Resolve(context *kong.Context, parent *kong.Path, f
case "idea":
return u.config.Idea, nil

case "init-sources":
return u.config.InitSources, nil

default:
return nil, nil
}
Expand Down
91 changes: 91 additions & 0 deletions app/user_config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package app

import (
"os"
"path/filepath"
"testing"

"github.com/alecthomas/assert/v2"
)

func TestLoadUserConfig(t *testing.T) {
// Create a temporary directory for our test files
tmpDir := t.TempDir()

tests := []struct {
name string
configContents string
expected UserConfig
expectError bool
}{
{
name: "basic config with init-sources",
configContents: `
init-sources = [
"source1",
"source2"
]`,
expected: UserConfig{
Prompt: "env", // Default value
InitSources: []string{"source1", "source2"},
},
},
{
name: "full config",
configContents: `
prompt = "short"
short-prompt = true
no-git = true
idea = true
init-sources = [
"source1",
"source2"
]`,
expected: UserConfig{
Prompt: "short",
ShortPrompt: true,
NoGit: true,
Idea: true,
InitSources: []string{"source1", "source2"},
},
},
{
name: "empty config",
configContents: "",
expected: UserConfig{
Prompt: "env", // Default value
},
},
{
name: "invalid HCL",
configContents: `
init-sources = [
"unclosed array
`,
expectError: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Create a temporary config file
configPath := filepath.Join(tmpDir, "config.hcl")
err := os.WriteFile(configPath, []byte(tt.configContents), 0644)
assert.NoError(t, err)

// Load the config
config, err := LoadUserConfig(configPath)
if tt.expectError {
assert.Error(t, err)
return
}

assert.NoError(t, err)
assert.Equal(t, tt.expected.InitSources, config.InitSources)
assert.Equal(t, tt.expected.Prompt, config.Prompt)
assert.Equal(t, tt.expected.ShortPrompt, config.ShortPrompt)
assert.Equal(t, tt.expected.NoGit, config.NoGit)
assert.Equal(t, tt.expected.Idea, config.Idea)
})
}
}
2 changes: 2 additions & 0 deletions docs/docs/usage/user-config-schema.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ short-prompt = boolean # (optional)
no-git = boolean # (optional)
# If true Hermit will try to add the IntelliJ IDEA plugin automatically.
idea = boolean # (optional)
# Default sources to use when initialising a new Hermit environment.
init-sources = [string] # (optional)
35 changes: 35 additions & 0 deletions integration/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,41 @@ func TestIntegration(t *testing.T) {
"bin/hermit", ".idea/externalDependencies.xml",
"bin/activate-hermit", "bin/hermit.hcl"),
outputContains("Creating new Hermit environment")}},
{name: "InitWithUserConfigSources",
script: `
cat > hermit.hcl <<EOF
init-sources = ["source1", "source2"]
EOF
hermit init --user-config hermit.hcl .
`,
expectations: exp{
filesExist("bin/hermit.hcl"),
fileContains("bin/hermit.hcl", `source1`),
fileContains("bin/hermit.hcl", `source2`)}},
{name: "InitWithCommandLineSources",
script: `
cat > hermit.hcl <<EOF
init-sources = ["source1", "source2"]
EOF
hermit init --user-config hermit.hcl --sources source3,source4 .
`,
expectations: exp{
filesExist("bin/hermit.hcl"),
fileContains("bin/hermit.hcl", `source3`),
fileContains("bin/hermit.hcl", `source4`)}},
{name: "InitSourcesCommandLineOverridesUserConfig",
script: `
cat > hermit.hcl <<EOF
init-sources = ["source1", "source2"]
EOF
hermit init --user-config hermit.hcl --sources source3,source4 .
assert test "$(grep -c source1 bin/hermit.hcl)" = "0"
assert test "$(grep -c source2 bin/hermit.hcl)" = "0"
`,
expectations: exp{
filesExist("bin/hermit.hcl"),
fileContains("bin/hermit.hcl", `source3`),
fileContains("bin/hermit.hcl", `source4`)}},
{name: "HermitEnvarIsSet",
script: `
hermit init .
Expand Down

0 comments on commit 88b6f97

Please sign in to comment.