diff --git a/core/zk/config.go b/core/zk/config.go index d4208c5..c0b0d7b 100644 --- a/core/zk/config.go +++ b/core/zk/config.go @@ -30,6 +30,29 @@ type ConfigOverrides struct { Extra map[string]string } +// Clone creates a copy of the DirConfig receiver. +func (c DirConfig) Clone() DirConfig { + clone := c + clone.Extra = make(map[string]string) + for k, v := range c.Extra { + clone.Extra[k] = v + } + return clone +} + +// Override modifies the DirConfig receiver by updating the properties +// overriden in ConfigOverrides. +func (c *DirConfig) Override(overrides ConfigOverrides) { + if !overrides.BodyTemplatePath.IsNull() { + c.BodyTemplatePath = overrides.BodyTemplatePath + } + if overrides.Extra != nil { + for k, v := range overrides.Extra { + c.Extra[k] = v + } + } +} + // ParseConfig creates a new Config instance from its HCL representation. // templatesDir is the base path for the relative templates. func ParseConfig(content []byte, templatesDir string) (*Config, error) { diff --git a/core/zk/config_test.go b/core/zk/config_test.go index ec8a8cf..c251460 100644 --- a/core/zk/config_test.go +++ b/core/zk/config_test.go @@ -239,3 +239,98 @@ func TestParseResolvesTemplatePaths(t *testing.T) { test("template.tpl", "/test/.zk/templates/template.tpl") test("/abs/template.tpl", "/abs/template.tpl") } + +func TestDirConfigClone(t *testing.T) { + original := DirConfig{ + FilenameTemplate: "{{id}}.note", + BodyTemplatePath: opt.NewString("default.note"), + IDOptions: IDOptions{ + Length: 4, + Charset: CharsetAlphanum, + Case: CaseLower, + }, + Extra: map[string]string{ + "hello": "world", + }, + } + + clone := original.Clone() + // Check that the clone is equivalent + assert.Equal(t, clone, original) + + clone.FilenameTemplate = "modified" + clone.BodyTemplatePath = opt.NewString("modified") + clone.IDOptions.Length = 41 + clone.IDOptions.Charset = CharsetNumbers + clone.IDOptions.Case = CaseUpper + clone.Extra["test"] = "modified" + + // Check that we didn't modify the original + assert.Equal(t, original, DirConfig{ + FilenameTemplate: "{{id}}.note", + BodyTemplatePath: opt.NewString("default.note"), + IDOptions: IDOptions{ + Length: 4, + Charset: CharsetAlphanum, + Case: CaseLower, + }, + Extra: map[string]string{ + "hello": "world", + }, + }) +} + +func TestDirConfigOverride(t *testing.T) { + sut := DirConfig{ + FilenameTemplate: "filename", + BodyTemplatePath: opt.NewString("body.tpl"), + IDOptions: IDOptions{ + Length: 4, + Charset: CharsetLetters, + Case: CaseUpper, + }, + Extra: map[string]string{ + "hello": "world", + "salut": "le monde", + }, + } + + // Empty overrides + sut.Override(ConfigOverrides{}) + assert.Equal(t, sut, DirConfig{ + FilenameTemplate: "filename", + BodyTemplatePath: opt.NewString("body.tpl"), + IDOptions: IDOptions{ + Length: 4, + Charset: CharsetLetters, + Case: CaseUpper, + }, + Extra: map[string]string{ + "hello": "world", + "salut": "le monde", + }, + }) + + // Some overrides + sut.Override(ConfigOverrides{ + BodyTemplatePath: opt.NewString("overriden-template"), + Extra: map[string]string{ + "hello": "overriden", + "additional": "value", + }, + }) + assert.Equal(t, sut, DirConfig{ + FilenameTemplate: "filename", + BodyTemplatePath: opt.NewString("overriden-template"), + IDOptions: IDOptions{ + Length: 4, + Charset: CharsetLetters, + Case: CaseUpper, + }, + Extra: map[string]string{ + "hello": "overriden", + "salut": "le monde", + "additional": "value", + }, + }) +} diff --git a/core/zk/zk.go b/core/zk/zk.go index 7d23b31..7a9783e 100644 --- a/core/zk/zk.go +++ b/core/zk/zk.go @@ -121,8 +121,8 @@ func locateRoot(path string) (string, error) { return locate(path) } -// DirAt creates a Dir representation of the slip box directory at the given path. -func (zk *Zk) DirAt(path string) (*Dir, error) { +// DirAt returns a Dir representation of the slip box directory at the given path. +func (zk *Zk) DirAt(path string, overrides ...ConfigOverrides) (*Dir, error) { wrap := errors.Wrapperf("%v: not a valid slip box directory", path) path, err := filepath.Abs(path) @@ -140,6 +140,11 @@ func (zk *Zk) DirAt(path string) (*Dir, error) { // Fallback on root config. config = zk.Config.DirConfig } + config = config.Clone() + + for _, v := range overrides { + config.Override(v) + } return &Dir{ Name: name, @@ -150,8 +155,8 @@ func (zk *Zk) DirAt(path string) (*Dir, error) { // RequiredDirAt is the same as DirAt, but checks that the directory exists // before returning the Dir. -func (zk *Zk) RequireDirAt(path string) (*Dir, error) { - dir, err := zk.DirAt(path) +func (zk *Zk) RequireDirAt(path string, overrides ...ConfigOverrides) (*Dir, error) { + dir, err := zk.DirAt(path, overrides...) if err != nil { return nil, err } diff --git a/core/zk/zk_test.go b/core/zk/zk_test.go index 745281b..ebc2c92 100644 --- a/core/zk/zk_test.go +++ b/core/zk/zk_test.go @@ -26,7 +26,8 @@ func TestDirAtGivenPath(t *testing.T) { } { actual, err := zk.DirAt(path) assert.Nil(t, err) - assert.Equal(t, actual, &Dir{Name: name, Path: filepath.Join(wd, name)}) + assert.Equal(t, actual.Name, name) + assert.Equal(t, actual.Path, filepath.Join(wd, name)) } } @@ -70,3 +71,113 @@ func TestDirAtRoot(t *testing.T) { }, }) } + +// Modifying the DirConfig of the returned Dir should not modify the global config. +func TestDirAtReturnsClonedConfig(t *testing.T) { + zk := Zk{ + Path: "/test", + Config: Config{ + DirConfig: DirConfig{ + FilenameTemplate: "{{id}}.note", + BodyTemplatePath: opt.NewString("default.note"), + IDOptions: IDOptions{ + Length: 4, + Charset: CharsetAlphanum, + Case: CaseLower, + }, + Extra: map[string]string{ + "hello": "world", + }, + }, + }, + } + + dir, err := zk.DirAt(".") + assert.Nil(t, err) + + dir.Config.FilenameTemplate = "modified" + dir.Config.BodyTemplatePath = opt.NewString("modified") + dir.Config.IDOptions.Length = 41 + dir.Config.IDOptions.Charset = CharsetNumbers + dir.Config.IDOptions.Case = CaseUpper + dir.Config.Extra["test"] = "modified" + + assert.Equal(t, zk.Config.DirConfig, DirConfig{ + FilenameTemplate: "{{id}}.note", + BodyTemplatePath: opt.NewString("default.note"), + IDOptions: IDOptions{ + Length: 4, + Charset: CharsetAlphanum, + Case: CaseLower, + }, + Extra: map[string]string{ + "hello": "world", + }, + }) +} + +func TestDirAtWithOverrides(t *testing.T) { + zk := Zk{ + Path: "/test", + Config: Config{ + DirConfig: DirConfig{ + FilenameTemplate: "{{id}}.note", + BodyTemplatePath: opt.NewString("default.note"), + IDOptions: IDOptions{ + Length: 4, + Charset: CharsetLetters, + Case: CaseUpper, + }, + Extra: map[string]string{ + "hello": "world", + }, + }, + }, + } + + dir, err := zk.DirAt(".", + ConfigOverrides{ + BodyTemplatePath: opt.NewString("overriden-template"), + Extra: map[string]string{ + "hello": "overriden", + "additional": "value", + }, + }, + ConfigOverrides{ + Extra: map[string]string{ + "additional": "value2", + "additional2": "value3", + }, + }, + ) + + assert.Nil(t, err) + assert.Equal(t, dir.Config, DirConfig{ + FilenameTemplate: "{{id}}.note", + BodyTemplatePath: opt.NewString("overriden-template"), + IDOptions: IDOptions{ + Length: 4, + Charset: CharsetLetters, + Case: CaseUpper, + }, + Extra: map[string]string{ + "hello": "overriden", + "additional": "value2", + "additional2": "value3", + }, + }) + + // Check that the original config was not modified. + assert.Equal(t, zk.Config.DirConfig, DirConfig{ + FilenameTemplate: "{{id}}.note", + BodyTemplatePath: opt.NewString("default.note"), + IDOptions: IDOptions{ + Length: 4, + Charset: CharsetLetters, + Case: CaseUpper, + }, + Extra: map[string]string{ + "hello": "world", + }, + }) +}