@ -1,10 +1,12 @@
package zk
import (
"io/ioutil"
"path/filepath"
"github.com/mickael-menu/zk/util/errors"
"github.com/mickael-menu/zk/util/opt"
"github.com/mickael-menu/zk/util/paths"
toml "github.com/pelletier/go-toml"
)
@ -16,6 +18,37 @@ type Config struct {
Tool ToolConfig
Aliases map [ string ] string
Extra map [ string ] string
// Base directories for the relative template paths used in NoteConfig.
TemplatesDirs [ ] string
}
// NewDefaultConfig creates a new Config with the default settings.
func NewDefaultConfig ( ) Config {
return Config {
Note : NoteConfig {
FilenameTemplate : "{{id}}" ,
Extension : "md" ,
BodyTemplatePath : opt . NullString ,
Lang : "en" ,
DefaultTitle : "Untitled" ,
IDOptions : IDOptions {
Charset : CharsetAlphanum ,
Length : 4 ,
Case : CaseLower ,
} ,
} ,
Groups : map [ string ] GroupConfig { } ,
Format : FormatConfig {
Markdown : MarkdownConfig {
Hashtags : true ,
ColonTags : false ,
MultiwordTags : false ,
} ,
} ,
Aliases : map [ string ] string { } ,
Extra : map [ string ] string { } ,
TemplatesDirs : [ ] string { } ,
}
}
// RootGroupConfig returns the default GroupConfig for the root directory and its descendants.
@ -27,6 +60,31 @@ func (c Config) RootGroupConfig() GroupConfig {
}
}
// LocateTemplate returns the absolute path for the given template path, by
// looking for it in the templates directories registered in this Config.
func ( c Config ) LocateTemplate ( path string ) ( string , bool ) {
if path == "" {
return "" , false
}
exists := func ( path string ) bool {
exists , err := paths . Exists ( path )
return exists && err == nil
}
if filepath . IsAbs ( path ) {
return path , exists ( path )
}
for _ , dir := range c . TemplatesDirs {
if candidate := filepath . Join ( dir , path ) ; exists ( candidate ) {
return candidate , true
}
}
return path , false
}
// FormatConfig holds the configuration for document formats, such as Markdown.
type FormatConfig struct {
Markdown MarkdownConfig
@ -35,11 +93,11 @@ type FormatConfig struct {
// MarkdownConfig holds the configuration for Markdown documents.
type MarkdownConfig struct {
// Hashtags indicates whether #hashtags are supported.
Hashtags bool ` toml:"hashtags" default:"true" `
Hashtags bool
// ColonTags indicates whether :colon:tags: are supported.
ColonTags bool ` toml:"colon-tags" default:"false" `
ColonTags bool
// MultiwordTags indicates whether #multi-word tags# are supported.
MultiwordTags bool ` toml:"multiword-tags" default:"false" `
MultiwordTags bool
}
// ToolConfig holds the external tooling configuration.
@ -107,90 +165,110 @@ func (c *GroupConfig) Override(overrides ConfigOverrides) {
}
}
// OpenConfig creates a new Config instance from its TOML representation stored
// in the given file.
func OpenConfig ( path string , parentConfig Config ) ( Config , error ) {
content , err := ioutil . ReadFile ( path )
if err != nil {
return parentConfig , errors . Wrapf ( err , "failed to open config file at %s" , path )
}
return ParseConfig ( content , path , parentConfig )
}
// ParseConfig creates a new Config instance from its TOML representation.
// templatesDir is the base path for the relative templates.
func ParseConfig ( content [ ] byte , templatesDir string ) ( * Config , error ) {
// path is the config absolute path, from which will be derived the base path
// for templates.
//
// The parentConfig will be used to inherit default config settings.
func ParseConfig ( content [ ] byte , path string , parentConfig Config ) ( Config , error ) {
config := parentConfig
var tomlConf tomlConfig
err := toml . Unmarshal ( content , & tomlConf )
if err != nil {
return nil , errors . Wrap ( err , "failed to read config" )
}
root := GroupConfig {
Paths : [ ] string { } ,
Note : NoteConfig {
FilenameTemplate : "{{id}}" ,
Extension : "md" ,
BodyTemplatePath : opt . NullString ,
Lang : "en" ,
DefaultTitle : "Untitled" ,
IDOptions : IDOptions {
Charset : CharsetAlphanum ,
Length : 4 ,
Case : CaseLower ,
} ,
} ,
Extra : make ( map [ string ] string ) ,
return config , errors . Wrap ( err , "failed to read config" )
}
// Note
note := tomlConf . Note
if note . Filename != "" {
root . Note . FilenameTemplate = note . Filename
config . Note . FilenameTemplate = note . Filename
}
if note . Extension != "" {
root . Note . Extension = note . Extension
config . Note . Extension = note . Extension
}
if note . Template != "" {
root. Note . BodyTemplatePath = templatePathFrom String( note . Template , templatesDir )
config . Note . BodyTemplatePath = opt . NewNotEmptyString ( note . Template )
}
if note . IDLength != 0 {
root . Note . IDOptions . Length = note . IDLength
config . Note . IDOptions . Length = note . IDLength
}
if note . IDCharset != "" {
root . Note . IDOptions . Charset = charsetFromString ( note . IDCharset )
config . Note . IDOptions . Charset = charsetFromString ( note . IDCharset )
}
if note . IDCase != "" {
root . Note . IDOptions . Case = caseFromString ( note . IDCase )
config . Note . IDOptions . Case = caseFromString ( note . IDCase )
}
if note . Lang != "" {
root . Note . Lang = note . Lang
config . Note . Lang = note . Lang
}
if note . DefaultTitle != "" {
root . Note . DefaultTitle = note . DefaultTitle
config . Note . DefaultTitle = note . DefaultTitle
}
if tomlConf . Extra != nil {
for k , v := range tomlConf . Extra {
root . Extra [ k ] = v
config . Extra [ k ] = v
}
}
groups := make ( map [ string ] GroupConfig )
// Groups
for name , dirTOML := range tomlConf . Groups {
groups [ name ] = root . merge ( dirTOML , name , templatesDir )
parent , ok := config . Groups [ name ]
if ! ok {
parent = config . RootGroupConfig ( )
}
config . Groups [ name ] = parent . merge ( dirTOML , name )
}
aliases := make ( map [ string ] string )
// Format
markdown := tomlConf . Format . Markdown
if markdown . Hashtags != nil {
config . Format . Markdown . Hashtags = * markdown . Hashtags
}
if markdown . ColonTags != nil {
config . Format . Markdown . ColonTags = * markdown . ColonTags
}
if markdown . MultiwordTags != nil {
config . Format . Markdown . MultiwordTags = * markdown . MultiwordTags
}
// Tool
tool := tomlConf . Tool
if tool . Editor != nil {
config . Tool . Editor = opt . NewNotEmptyString ( * tool . Editor )
}
if tool . Pager != nil {
config . Tool . Pager = opt . NewStringWithPtr ( tool . Pager )
}
if tool . FzfPreview != nil {
config . Tool . FzfPreview = opt . NewStringWithPtr ( tool . FzfPreview )
}
// Aliases
if tomlConf . Aliases != nil {
for k , v := range tomlConf . Aliases {
aliases [ k ] = v
config. A liases[ k ] = v
}
}
return & Config {
Note : root . Note ,
Groups : groups ,
Format : tomlConf . Format ,
Tool : ToolConfig {
Editor : opt . NewNotEmptyString ( tomlConf . Tool . Editor ) ,
Pager : opt . NewStringWithPtr ( tomlConf . Tool . Pager ) ,
FzfPreview : opt . NewStringWithPtr ( tomlConf . Tool . FzfPreview ) ,
} ,
Aliases : aliases ,
Extra : root . Extra ,
} , nil
config . TemplatesDirs = append ( [ ] string { filepath . Join ( filepath . Dir ( path ) , "templates" ) } , config . TemplatesDirs ... )
return config , nil
}
func ( c GroupConfig ) merge ( tomlConf tomlGroupConfig , name string , templatesDir string ) GroupConfig {
func ( c GroupConfig ) merge ( tomlConf tomlGroupConfig , name string ) GroupConfig {
res := c . Clone ( )
if tomlConf . Paths != nil {
@ -211,7 +289,7 @@ func (c GroupConfig) merge(tomlConf tomlGroupConfig, name string, templatesDir s
res . Note . Extension = note . Extension
}
if note . Template != "" {
res . Note . BodyTemplatePath = templatePathFrom String( note . Template , templatesDir )
res . Note . BodyTemplatePath = opt . NewNotEmpty String( note . Template )
}
if note . IDLength != 0 {
res . Note . IDOptions . Length = note . IDLength
@ -241,7 +319,7 @@ func (c GroupConfig) merge(tomlConf tomlGroupConfig, name string, templatesDir s
type tomlConfig struct {
Note tomlNoteConfig
Groups map [ string ] tomlGroupConfig ` toml:"group" `
Format FormatConfig
Format toml FormatConfig
Tool tomlToolConfig
Extra map [ string ] string
Aliases map [ string ] string ` toml:"alias" `
@ -264,8 +342,18 @@ type tomlGroupConfig struct {
Extra map [ string ] string
}
type tomlFormatConfig struct {
Markdown tomlMarkdownConfig
}
type tomlMarkdownConfig struct {
Hashtags * bool ` toml:"hashtags" `
ColonTags * bool ` toml:"colon-tags" `
MultiwordTags * bool ` toml:"multiword-tags" `
}
type tomlToolConfig struct {
Editor string
Editor * string
Pager * string
FzfPreview * string ` toml:"fzf-preview" `
}
@ -297,13 +385,3 @@ func caseFromString(c string) Case {
return CaseLower
}
}
func templatePathFromString ( template string , templatesDir string ) opt . String {
if template == "" {
return opt . NullString
}
if ! filepath . IsAbs ( template ) {
template = filepath . Join ( templatesDir , template )
}
return opt . NewString ( template )
}