Replace dir configs with more flexible group config overrides

pull/6/head
Mickaël Menu 3 years ago
parent 1f89978f84
commit 7a1ba21a64
No known key found for this signature in database
GPG Key ID: 53D73664CD359895

@ -19,7 +19,7 @@ func TestCreate(t *testing.T) {
Dir: zk.Dir{
Name: "log",
Path: "/test/log",
Config: zk.DirConfig{
Config: zk.GroupConfig{
Note: zk.NoteConfig{
Extension: "md",
},
@ -83,7 +83,7 @@ func TestCreateTriesUntilValidPath(t *testing.T) {
Dir: zk.Dir{
Name: "log",
Path: "/test/log",
Config: zk.DirConfig{
Config: zk.GroupConfig{
Note: zk.NoteConfig{
Extension: "md",
},
@ -137,7 +137,7 @@ func TestCreateErrorWhenNoValidPaths(t *testing.T) {
Dir: zk.Dir{
Name: "log",
Path: "/test/log",
Config: zk.DirConfig{
Config: zk.GroupConfig{
Note: zk.NoteConfig{
Extension: "md",
},

@ -11,15 +11,16 @@ import (
// Config holds the global user configuration.
type Config struct {
Note NoteConfig
Dirs map[string]DirConfig
Groups map[string]GroupConfig
Tool ToolConfig
Aliases map[string]string
Extra map[string]string
}
// RootDirConfig returns the default DirConfig for the root directory and its descendants.
func (c Config) RootDirConfig() DirConfig {
return DirConfig{
// RootGroupConfig returns the default GroupConfig for the root directory and its descendants.
func (c Config) RootGroupConfig() GroupConfig {
return GroupConfig{
Paths: []string{},
Note: c.Note,
Extra: c.Extra,
}
@ -48,8 +49,9 @@ type NoteConfig struct {
IDOptions IDOptions
}
// DirConfig holds the user configuration for a given directory.
type DirConfig struct {
// GroupConfig holds the user configuration for a given group of notes.
type GroupConfig struct {
Paths []string
Note NoteConfig
Extra map[string]string
}
@ -61,9 +63,13 @@ type ConfigOverrides struct {
Extra map[string]string
}
// Clone creates a copy of the DirConfig receiver.
func (c DirConfig) Clone() DirConfig {
// Clone creates a copy of the GroupConfig receiver.
func (c GroupConfig) Clone() GroupConfig {
clone := c
clone.Paths = make([]string, len(c.Paths))
copy(clone.Paths, c.Paths)
clone.Extra = make(map[string]string)
for k, v := range c.Extra {
clone.Extra[k] = v
@ -71,9 +77,9 @@ func (c DirConfig) Clone() DirConfig {
return clone
}
// Override modifies the DirConfig receiver by updating the properties
// Override modifies the GroupConfig receiver by updating the properties
// overriden in ConfigOverrides.
func (c *DirConfig) Override(overrides ConfigOverrides) {
func (c *GroupConfig) Override(overrides ConfigOverrides) {
if !overrides.BodyTemplatePath.IsNull() {
c.Note.BodyTemplatePath = overrides.BodyTemplatePath
}
@ -93,7 +99,8 @@ func ParseConfig(content []byte, templatesDir string) (*Config, error) {
return nil, errors.Wrap(err, "failed to read config")
}
root := DirConfig{
root := GroupConfig{
Paths: []string{},
Note: NoteConfig{
FilenameTemplate: "{{id}}",
Extension: "md",
@ -140,9 +147,9 @@ func ParseConfig(content []byte, templatesDir string) (*Config, error) {
}
}
dirs := make(map[string]DirConfig)
for name, dirTOML := range tomlConf.Dirs {
dirs[name] = root.merge(dirTOML, templatesDir)
groups := make(map[string]GroupConfig)
for name, dirTOML := range tomlConf.Groups {
groups[name] = root.merge(dirTOML, name, templatesDir)
}
aliases := make(map[string]string)
@ -153,8 +160,8 @@ func ParseConfig(content []byte, templatesDir string) (*Config, error) {
}
return &Config{
Note: root.Note,
Dirs: dirs,
Note: root.Note,
Groups: groups,
Tool: ToolConfig{
Editor: opt.NewNotEmptyString(tomlConf.Tool.Editor),
Pager: opt.NewStringWithPtr(tomlConf.Tool.Pager),
@ -165,9 +172,19 @@ func ParseConfig(content []byte, templatesDir string) (*Config, error) {
}, nil
}
func (c DirConfig) merge(tomlConf tomlDirConfig, templatesDir string) DirConfig {
func (c GroupConfig) merge(tomlConf tomlGroupConfig, name string, templatesDir string) GroupConfig {
res := c.Clone()
if tomlConf.Paths != nil {
for _, p := range tomlConf.Paths {
res.Paths = append(res.Paths, p)
}
} else {
// If no `paths` config property was given for this group, we assume
// that its name will be used as the path.
res.Paths = append(res.Paths, name)
}
note := tomlConf.Note
if note.Filename != "" {
res.Note.FilenameTemplate = note.Filename
@ -205,7 +222,7 @@ func (c DirConfig) merge(tomlConf tomlDirConfig, templatesDir string) DirConfig
// tomlConfig holds the TOML representation of Config
type tomlConfig struct {
Note tomlNoteConfig
Dirs map[string]tomlDirConfig `toml:"dir"`
Groups map[string]tomlGroupConfig `toml:"group"`
Tool tomlToolConfig
Extra map[string]string
Aliases map[string]string `toml:"alias"`
@ -222,7 +239,8 @@ type tomlNoteConfig struct {
IDCase string `toml:"id-case"`
}
type tomlDirConfig struct {
type tomlGroupConfig struct {
Paths []string
Note tomlNoteConfig
Extra map[string]string
}

@ -26,7 +26,7 @@ func TestParseDefaultConfig(t *testing.T) {
DefaultTitle: "Untitled",
Lang: "en",
},
Dirs: make(map[string]DirConfig),
Groups: make(map[string]GroupConfig),
Tool: ToolConfig{
Editor: opt.NullString,
Pager: opt.NullString,
@ -71,7 +71,10 @@ func TestParseComplete(t *testing.T) {
ls = "zk list $@"
ed = "zk edit $@"
[dir.log.note]
[group.log]
paths = ["journal/daily", "journal/weekly"]
[group.log.note]
filename = "{{date}}.md"
extension = "note"
template = "log.md"
@ -81,11 +84,14 @@ func TestParseComplete(t *testing.T) {
id-length = 8
id-case = "mixed"
[dir.log.extra]
[group.log.extra]
log-ext = "value"
[dir.ref.note]
[group.ref.note]
filename = "{{slug title}}.md"
[group."without path"]
paths = []
`), "")
assert.Nil(t, err)
@ -102,8 +108,9 @@ func TestParseComplete(t *testing.T) {
Lang: "fr",
DefaultTitle: "Sans titre",
},
Dirs: map[string]DirConfig{
Groups: map[string]GroupConfig{
"log": {
Paths: []string{"journal/daily", "journal/weekly"},
Note: NoteConfig{
FilenameTemplate: "{{date}}.md",
Extension: "note",
@ -123,6 +130,7 @@ func TestParseComplete(t *testing.T) {
},
},
"ref": {
Paths: []string{"ref"},
Note: NoteConfig{
FilenameTemplate: "{{slug title}}.md",
Extension: "txt",
@ -140,6 +148,25 @@ func TestParseComplete(t *testing.T) {
"salut": "le monde",
},
},
"without path": {
Paths: []string{},
Note: NoteConfig{
FilenameTemplate: "{{id}}.note",
Extension: "txt",
BodyTemplatePath: opt.NewString("default.note"),
IDOptions: IDOptions{
Length: 4,
Charset: CharsetAlphanum,
Case: CaseLower,
},
Lang: "fr",
DefaultTitle: "Sans titre",
},
Extra: map[string]string{
"hello": "world",
"salut": "le monde",
},
},
},
Tool: ToolConfig{
Editor: opt.NewString("vim"),
@ -157,7 +184,7 @@ func TestParseComplete(t *testing.T) {
})
}
func TestParseMergesDirConfig(t *testing.T) {
func TestParseMergesGroupConfig(t *testing.T) {
conf, err := ParseConfig([]byte(`
[note]
filename = "root-filename"
@ -173,18 +200,18 @@ func TestParseMergesDirConfig(t *testing.T) {
hello = "world"
salut = "le monde"
[dir.log.note]
[group.log.note]
filename = "log-filename"
template = "log-template"
id-charset = "numbers"
id-length = 8
id-case = "mixed"
[dir.log.extra]
[group.log.extra]
hello = "override"
log-ext = "value"
[dir.inherited]
[group.inherited]
`), "")
assert.Nil(t, err)
@ -201,8 +228,9 @@ func TestParseMergesDirConfig(t *testing.T) {
Lang: "fr",
DefaultTitle: "Sans titre",
},
Dirs: map[string]DirConfig{
Groups: map[string]GroupConfig{
"log": {
Paths: []string{"log"},
Note: NoteConfig{
FilenameTemplate: "log-filename",
Extension: "txt",
@ -222,6 +250,7 @@ func TestParseMergesDirConfig(t *testing.T) {
},
},
"inherited": {
Paths: []string{"inherited"},
Note: NoteConfig{
FilenameTemplate: "root-filename",
Extension: "txt",
@ -321,8 +350,9 @@ func TestParseResolvesTemplatePaths(t *testing.T) {
test("/abs/template.tpl", "/abs/template.tpl")
}
func TestDirConfigClone(t *testing.T) {
original := DirConfig{
func TestGroupConfigClone(t *testing.T) {
original := GroupConfig{
Paths: []string{"original"},
Note: NoteConfig{
FilenameTemplate: "{{id}}.note",
Extension: "md",
@ -344,6 +374,7 @@ func TestDirConfigClone(t *testing.T) {
// Check that the clone is equivalent
assert.Equal(t, clone, original)
clone.Paths = []string{"cloned"}
clone.Note.FilenameTemplate = "modified"
clone.Note.Extension = "txt"
clone.Note.BodyTemplatePath = opt.NewString("modified")
@ -355,7 +386,8 @@ func TestDirConfigClone(t *testing.T) {
clone.Extra["test"] = "modified"
// Check that we didn't modify the original
assert.Equal(t, original, DirConfig{
assert.Equal(t, original, GroupConfig{
Paths: []string{"original"},
Note: NoteConfig{
FilenameTemplate: "{{id}}.note",
Extension: "md",
@ -374,8 +406,9 @@ func TestDirConfigClone(t *testing.T) {
})
}
func TestDirConfigOverride(t *testing.T) {
sut := DirConfig{
func TestGroupConfigOverride(t *testing.T) {
sut := GroupConfig{
Paths: []string{"path"},
Note: NoteConfig{
FilenameTemplate: "filename",
BodyTemplatePath: opt.NewString("body.tpl"),
@ -393,7 +426,8 @@ func TestDirConfigOverride(t *testing.T) {
// Empty overrides
sut.Override(ConfigOverrides{})
assert.Equal(t, sut, DirConfig{
assert.Equal(t, sut, GroupConfig{
Paths: []string{"path"},
Note: NoteConfig{
FilenameTemplate: "filename",
BodyTemplatePath: opt.NewString("body.tpl"),
@ -417,7 +451,8 @@ func TestDirConfigOverride(t *testing.T) {
"additional": "value",
},
})
assert.Equal(t, sut, DirConfig{
assert.Equal(t, sut, GroupConfig{
Paths: []string{"path"},
Note: NoteConfig{
FilenameTemplate: "filename",
BodyTemplatePath: opt.NewString("overriden-template"),

@ -62,15 +62,23 @@ const defaultConfig = `# zk configuration file
#key = "value"
# DIRECTORY OVERRIDES
# GROUP OVERRIDES
#
# You can override global settings from [note] and [extra] for a particular
# directory by declaring a [dir."<PATH>"] section.
# <PATH> can contain any character, including slashes for subdirectories.
# group of notes by declaring a [group."<name>"] section.
#
# Specify the list of directories which will automatically belong to the group
# with the optional ` + "`" + `paths` + "`" + ` property.
#
# Omiting ` + "`" + `paths` + "`" + ` is equivalent to providing a single path equal to the name of
# the group. This can be useful to quickly declare a group by the name of the
# directory it applies to.
#[dir."<PATH>".note]
#[dir."<NAME>"]
#paths = ["<DIR1>", "<DIR2>"]
#[dir."<NAME>".note]
#filename = "{{date now}}"
#[dir."<PATH>".extra]
#[dir."<NAME>".extra]
#key = "value"
@ -150,7 +158,7 @@ type Dir struct {
// Absolute path to the directory.
Path string
// User configuration for this directory.
Config DirConfig
Config GroupConfig
}
// Open locates a notebook at the given path and parses its configuration.
@ -268,7 +276,7 @@ func (zk *Zk) RootDir() Dir {
return Dir{
Name: "",
Path: zk.Path,
Config: zk.Config.RootDirConfig(),
Config: zk.Config.RootGroupConfig(),
}
}
@ -284,13 +292,7 @@ func (zk *Zk) DirAt(path string, overrides ...ConfigOverrides) (*Dir, error) {
return nil, err
}
config, ok := zk.Config.Dirs[name]
if !ok {
// Fallback on root config.
config = zk.Config.RootDirConfig()
}
config = config.Clone()
config := zk.findConfigForDirNamed(name).Clone()
for _, v := range overrides {
config.Override(v)
}
@ -302,6 +304,18 @@ func (zk *Zk) DirAt(path string, overrides ...ConfigOverrides) (*Dir, error) {
}, nil
}
func (zk *Zk) findConfigForDirNamed(name string) GroupConfig {
for _, group := range zk.Config.Groups {
for _, path := range group.Paths {
if path == name {
return group
}
}
}
// Fallback on root config.
return zk.Config.RootGroupConfig()
}
// RequiredDirAt is the same as DirAt, but checks that the directory exists
// before returning the Dir.
func (zk *Zk) RequireDirAt(path string, overrides ...ConfigOverrides) (*Dir, error) {

@ -23,7 +23,7 @@ func TestRootDir(t *testing.T) {
assert.Equal(t, zk.RootDir(), Dir{
Name: "",
Path: wd,
Config: zk.Config.RootDirConfig(),
Config: zk.Config.RootGroupConfig(),
})
}
@ -84,7 +84,7 @@ func TestDirAtRoot(t *testing.T) {
Case: CaseLower,
},
},
Dirs: map[string]DirConfig{
Groups: map[string]GroupConfig{
"log": {
Note: NoteConfig{
FilenameTemplate: "{{date}}.md",
@ -101,7 +101,8 @@ func TestDirAtRoot(t *testing.T) {
assert.Nil(t, err)
assert.Equal(t, dir.Name, "")
assert.Equal(t, dir.Path, wd)
assert.Equal(t, dir.Config, DirConfig{
assert.Equal(t, dir.Config, GroupConfig{
Paths: []string{},
Note: NoteConfig{
FilenameTemplate: "{{id}}.note",
BodyTemplatePath: opt.NewString("default.note"),
@ -117,7 +118,54 @@ func TestDirAtRoot(t *testing.T) {
})
}
// Modifying the DirConfig of the returned Dir should not modify the global config.
// When requesting a directory, the matching GroupConfig will be returned.
func TestDirAtFindsGroup(t *testing.T) {
wd, _ := os.Getwd()
zk := Zk{
Path: wd,
Config: Config{
Groups: map[string]GroupConfig{
"ref": {
Paths: []string{"ref"},
Note: NoteConfig{
BodyTemplatePath: opt.NewString("citation.note"),
},
Extra: make(map[string]string),
},
"log": {
Paths: []string{"journal/daily", "journal/weekly"},
Note: NoteConfig{
BodyTemplatePath: opt.NewString("logging.note"),
},
Extra: make(map[string]string),
},
},
},
}
dir, err := zk.DirAt("ref")
assert.Nil(t, err)
assert.Equal(t, dir.Config, GroupConfig{
Paths: []string{"ref"},
Note: NoteConfig{
BodyTemplatePath: opt.NewString("citation.note"),
},
Extra: make(map[string]string),
})
dir, err = zk.DirAt("journal/weekly")
assert.Nil(t, err)
assert.Equal(t, dir.Config, GroupConfig{
Paths: []string{"journal/daily", "journal/weekly"},
Note: NoteConfig{
BodyTemplatePath: opt.NewString("logging.note"),
},
Extra: make(map[string]string),
})
}
// Modifying the GroupConfig of the returned Dir should not modify the global config.
func TestDirAtReturnsClonedConfig(t *testing.T) {
zk := Zk{
Path: "/test",
@ -147,7 +195,8 @@ func TestDirAtReturnsClonedConfig(t *testing.T) {
dir.Config.Note.IDOptions.Case = CaseUpper
dir.Config.Extra["test"] = "modified"
assert.Equal(t, zk.Config.RootDirConfig(), DirConfig{
assert.Equal(t, zk.Config.RootGroupConfig(), GroupConfig{
Paths: []string{},
Note: NoteConfig{
FilenameTemplate: "{{id}}.note",
BodyTemplatePath: opt.NewString("default.note"),
@ -199,7 +248,8 @@ func TestDirAtWithOverrides(t *testing.T) {
)
assert.Nil(t, err)
assert.Equal(t, dir.Config, DirConfig{
assert.Equal(t, dir.Config, GroupConfig{
Paths: []string{},
Note: NoteConfig{
FilenameTemplate: "{{id}}.note",
BodyTemplatePath: opt.NewString("overriden-template"),
@ -217,7 +267,8 @@ func TestDirAtWithOverrides(t *testing.T) {
})
// Check that the original config was not modified.
assert.Equal(t, zk.Config.RootDirConfig(), DirConfig{
assert.Equal(t, zk.Config.RootGroupConfig(), GroupConfig{
Paths: []string{},
Note: NoteConfig{
FilenameTemplate: "{{id}}.note",
BodyTemplatePath: opt.NewString("default.note"),

Loading…
Cancel
Save