diff --git a/assets.go b/assets.go index 64675c3..9ae4235 100644 --- a/assets.go +++ b/assets.go @@ -1,24 +1,26 @@ package mark2web import ( + "log" + "path" + "regexp" "strings" - "gitbase.de/apairon/mark2web/config" "gitbase.de/apairon/mark2web/helper" cpy "github.com/otiai10/copy" ) // ProcessAssets copies the assets from input to output dir func ProcessAssets() { - switch config.Config.Assets.Action { + switch Config.Assets.Action { case "copy": - from := config.Config.Assets.FromPath - to := config.Config.Assets.ToPath + from := Config.Assets.FromPath + to := Config.Assets.ToPath if !strings.HasPrefix(from, "/") { - from = config.Config.Directories.Input + "/" + from + from = Config.Directories.Input + "/" + from } if !strings.HasPrefix(to, "/") { - to = config.Config.Directories.Output + "/" + to + to = Config.Directories.Output + "/" + to } helper.Log.Noticef("copying assets from '%s' to '%s'", from, to) err := cpy.Copy(from, to) @@ -27,3 +29,24 @@ func ProcessAssets() { } } } + +// fixAssetsPath replaces assets path based on current path +func fixAssetsPath(str, curNavPath string) string { + if find := Config.Assets.FixTemplate.Find; find != "" { + helper.Log.Debugf("fixing assets paths for path '%s'", curNavPath) + repl := Config.Assets.FixTemplate.Replace + toPath := Config.Assets.ToPath + + bToRoot := helper.BackToRoot(curNavPath) + regex, err := regexp.Compile(find) + if err != nil { + log.Panicf("could not compile regexp '%s' for assets path: %s", find, err) + } + repl = bToRoot + toPath + "/" + repl + repl = path.Clean(repl) + "/" + helper.Log.Debugf("new assets paths: %s", repl) + return regex.ReplaceAllString(str, repl) + } + + return str +} diff --git a/config/global.go b/config_global.go similarity index 87% rename from config/global.go rename to config_global.go index 982f5a1..5cb0d55 100644 --- a/config/global.go +++ b/config_global.go @@ -1,4 +1,4 @@ -package config +package mark2web import ( "io/ioutil" @@ -34,12 +34,12 @@ type GlobalConfig struct { var Config = new(GlobalConfig) -func ReadGlobalConfig(filename string) error { +func (c *GlobalConfig) ReadFromFile(filename string) error { data, err := ioutil.ReadFile(filename) if err != nil { return err } - err = yaml.Unmarshal(data, Config) + err = yaml.Unmarshal(data, c) if err != nil { return err } diff --git a/config/tree.go b/config_path.go similarity index 61% rename from config/tree.go rename to config_path.go index 02e577b..37a2527 100644 --- a/config/tree.go +++ b/config_path.go @@ -1,52 +1,4 @@ -package config - -import ( - "fmt" -) - -// MapString is a map[string]interface{} which always unmarsahls yaml to map[string]interface{} -type MapString map[string]interface{} - -// UnmarshalYAML handles all maps as map[string]interface{} for later JSON -// see https://github.com/elastic/beats/blob/6435194af9f42cbf778ca0a1a92276caf41a0da8/libbeat/common/mapstr.go -func (ms *MapString) UnmarshalYAML(unmarshal func(interface{}) error) error { - var result map[interface{}]interface{} - err := unmarshal(&result) - if err != nil { - return err - } - *ms = cleanUpInterfaceMap(result) - return nil -} - -func cleanUpInterfaceArray(in []interface{}) []interface{} { - result := make([]interface{}, len(in)) - for i, v := range in { - result[i] = cleanUpMapValue(v) - } - return result -} - -func cleanUpInterfaceMap(in map[interface{}]interface{}) MapString { - result := make(MapString) - for k, v := range in { - result[fmt.Sprintf("%v", k)] = cleanUpMapValue(v) - } - return result -} - -func cleanUpMapValue(v interface{}) interface{} { - switch v := v.(type) { - case []interface{}: - return cleanUpInterfaceArray(v) - case map[interface{}]interface{}: - return cleanUpInterfaceMap(v) - case string, bool, int, int8, int16, int32, int64, float32, float64: - return v - default: - return fmt.Sprintf("%v", v) - } -} +package mark2web // CollectionConfig describes a collection type CollectionConfig struct { @@ -131,18 +83,3 @@ type PathConfig struct { Data MapString `yaml:"Data"` } - -// PathConfigTree is complete config tree of content dir -type PathConfigTree struct { - InputPath string - OutputPath string - Hidden bool // for collections which are not part of the navigation - - ColMap MapString - - InputFiles []string - OtherFiles []string - - Config *PathConfig - Sub []*PathConfigTree -} diff --git a/content.go b/content.go index 36d97cf..b8addc8 100644 --- a/content.go +++ b/content.go @@ -7,8 +7,6 @@ import ( "regexp" "strings" - "gitbase.de/apairon/mark2web/config" - "gitbase.de/apairon/mark2web/context" "gitbase.de/apairon/mark2web/helper" "github.com/davecgh/go-spew/spew" "github.com/flosch/pongo2" @@ -17,63 +15,63 @@ import ( ) // ReadContentDir walks through content directory and builds the tree of configurations -func ReadContentDir(inBase string, outBase string, dir string, conf *config.PathConfig, tree *config.PathConfigTree) { - context.FillNodeConfig(tree, inBase, outBase, dir, conf) +func (node *TreeNode) ReadContentDir(inBase string, outBase string, dir string, conf *PathConfig) { + node.FillConfig(inBase, outBase, dir, conf) - files, err := ioutil.ReadDir(tree.InputPath) + files, err := ioutil.ReadDir(node.InputPath) if err != nil { helper.Log.Panic(err) } // first only files for _, f := range files { - p := tree.InputPath + "/" + f.Name() + p := node.InputPath + "/" + f.Name() if !f.IsDir() && f.Name() != "config.yml" { switch path.Ext(f.Name()) { case ".md": helper.Log.Debugf(".MD %s", p) - if tree.InputFiles == nil { - tree.InputFiles = make([]string, 0) + if node.InputFiles == nil { + node.InputFiles = make([]string, 0) } - tree.InputFiles = append(tree.InputFiles, f.Name()) + node.InputFiles = append(node.InputFiles, f.Name()) break default: helper.Log.Debugf("FIL %s", p) - if tree.OtherFiles == nil { - tree.OtherFiles = make([]string, 0) + if node.OtherFiles == nil { + node.OtherFiles = make([]string, 0) } - tree.OtherFiles = append(tree.OtherFiles, f.Name()) + node.OtherFiles = append(node.OtherFiles, f.Name()) } } } // only directorys, needed config before for _, f := range files { - p := tree.InputPath + "/" + f.Name() + p := node.InputPath + "/" + f.Name() if f.IsDir() { helper.Log.Debugf("DIR %s", p) - newTree := new(config.PathConfigTree) - if tree.Sub == nil { - tree.Sub = make([]*config.PathConfigTree, 0) + newTree := new(TreeNode) + if node.Sub == nil { + node.Sub = make([]*TreeNode, 0) } - tree.Sub = append(tree.Sub, newTree) - ReadContentDir(tree.InputPath, tree.OutputPath, f.Name(), tree.Config, newTree) + node.Sub = append(node.Sub, newTree) + newTree.ReadContentDir(node.InputPath, node.OutputPath, f.Name(), node.Config) } } } // ProcessContent walks recursivly through the input paths and processes all files for output -func ProcessContent(rootConf, conf *config.PathConfigTree) { - helper.CreateDirectory(conf.OutputPath) +func (node *TreeNode) ProcessContent(rootConf *TreeNode) { + helper.CreateDirectory(node.OutputPath) - curNavPath := strings.TrimPrefix(conf.OutputPath, config.Config.Directories.Output) + curNavPath := strings.TrimPrefix(node.OutputPath, Config.Directories.Output) curNavPath = strings.TrimPrefix(curNavPath, "/") curNavPath = path.Clean(curNavPath) if curNavPath == "." { curNavPath = "" } - goTo := conf.Config.This.GoTo + goTo := node.Config.This.GoTo if goTo != nil && *goTo != "" { goToFixed := *goTo if strings.HasPrefix(goToFixed, "/") { @@ -81,9 +79,9 @@ func ProcessContent(rootConf, conf *config.PathConfigTree) { } goToFixed = path.Clean(goToFixed) - switch config.Config.Webserver.Type { + switch Config.Webserver.Type { case "apache": - htaccessFile := conf.OutputPath + "/.htaccess" + htaccessFile := node.OutputPath + "/.htaccess" helper.Log.Noticef("writing '%s' with redirect to: %s", htaccessFile, goToFixed) err := ioutil.WriteFile(htaccessFile, []byte(`RewriteEngine on RewriteRule ^$ %{REQUEST_URI}`+goToFixed+`/ [R,L] @@ -95,12 +93,12 @@ RewriteRule ^$ %{REQUEST_URI}`+goToFixed+`/ [R,L] } } - for _, file := range conf.InputFiles { + for _, file := range node.InputFiles { var input []byte inFile := "InputString" if file != "" { - inFile = conf.InputPath + "/" + file + inFile = node.InputPath + "/" + file helper.Log.Debugf("reading file: %s", inFile) var err error @@ -112,7 +110,7 @@ RewriteRule ^$ %{REQUEST_URI}`+goToFixed+`/ [R,L] } else { // use input string if available and input filename == "" var inputString *string - if i := conf.Config.Index; i != nil { + if i := node.Config.Index; i != nil { inputString = i.InputString } if inputString != nil { @@ -121,7 +119,7 @@ RewriteRule ^$ %{REQUEST_URI}`+goToFixed+`/ [R,L] } } - newConfig := new(config.PathConfig) + newConfig := new(PathConfig) regex := regexp.MustCompile("(?s)^---(.*?)\\r?\\n\\r?---\\r?\\n\\r?") yamlData := regex.Find(input) @@ -137,14 +135,14 @@ RewriteRule ^$ %{REQUEST_URI}`+goToFixed+`/ [R,L] helper.Log.Debug("merging config with upper config") oldThis := newConfig.This - helper.Merge(newConfig, conf.Config) + helper.Merge(newConfig, node.Config) newConfig.This = oldThis helper.Log.Debug(spew.Sdump(newConfig)) input = regex.ReplaceAll(input, []byte("")) } else { - helper.Merge(newConfig, conf.Config) + helper.Merge(newConfig, node.Config) } // ignore ??? @@ -197,7 +195,7 @@ RewriteRule ^$ %{REQUEST_URI}`+goToFixed+`/ [R,L] } } - outFile := conf.OutputPath + "/" + outputFilename + outFile := node.OutputPath + "/" + outputFilename helper.Log.Debugf("using '%s' as output file", outFile) // use --- for splitting document in markdown parts @@ -228,7 +226,7 @@ RewriteRule ^$ %{REQUEST_URI}`+goToFixed+`/ [R,L] BuildNavigation(rootConf, &navMap, &navSlice, &navActive, curNavPath) // read yaml header as data for template - ctx := context.NewContext() + ctx := NewContext() ctx["This"] = newConfig.This ctx["Meta"] = newConfig.Meta ctx["Data"] = newConfig.Data @@ -247,9 +245,9 @@ RewriteRule ^$ %{REQUEST_URI}`+goToFixed+`/ [R,L] ctx["NavElement"] = &NavElement{ GoTo: helper.BackToRoot(curNavPath), Active: true, - ColMap: rootConf.ColMap, - Data: rootConf.Config.Data, - This: rootConf.Config.This, + ColMap: node.ColMap, + Data: node.Config.Data, + This: node.Config.This, SubMap: &navMap, SubSlice: &navSlice, } @@ -257,12 +255,12 @@ RewriteRule ^$ %{REQUEST_URI}`+goToFixed+`/ [R,L] helper.Log.Debugf("rendering template '%s' for '%s'", *newConfig.Template, outFile) templateFilename := *newConfig.Template - result, err := RenderTemplate(*newConfig.Template, conf, newConfig, &ctx) + result, err := renderTemplate(*newConfig.Template, node, newConfig, &ctx) if err != nil { helper.Log.Panicf("could not execute template '%s' for input file '%s': %s", templateFilename, inFile, err) } - result = FixAssetsPath(result, curNavPath) + result = fixAssetsPath(result, curNavPath) helper.Log.Noticef("writing to output file: %s", outFile) err = ioutil.WriteFile(outFile, []byte(result), 0644) @@ -275,11 +273,11 @@ RewriteRule ^$ %{REQUEST_URI}`+goToFixed+`/ [R,L] } // process other files, copy... - for _, file := range conf.OtherFiles { - switch config.Config.OtherFiles.Action { + for _, file := range node.OtherFiles { + switch Config.OtherFiles.Action { case "copy": - from := conf.InputPath + "/" + file - to := conf.OutputPath + "/" + file + from := node.InputPath + "/" + file + to := node.OutputPath + "/" + file helper.Log.Noticef("copying file from '%s' to '%s'", from, to) err := cpy.Copy(from, to) if err != nil { @@ -289,8 +287,9 @@ RewriteRule ^$ %{REQUEST_URI}`+goToFixed+`/ [R,L] } i := 0 - for i < len(conf.Sub) { - ProcessContent(rootConf, conf.Sub[i]) + // sub can dynamically increase, so no for range + for i < len(node.Sub) { + node.Sub[i].ProcessContent(rootConf) i++ } } diff --git a/context/context.go b/context.go similarity index 76% rename from context/context.go rename to context.go index d07d43e..274f20d 100644 --- a/context/context.go +++ b/context.go @@ -1,4 +1,4 @@ -package context +package mark2web import ( "io/ioutil" @@ -8,7 +8,6 @@ import ( "strings" "time" - "gitbase.de/apairon/mark2web/config" "gitbase.de/apairon/mark2web/helper" "github.com/davecgh/go-spew/spew" "github.com/extemporalgenome/slug" @@ -17,15 +16,15 @@ import ( ) var CurrentContext *pongo2.Context -var CurrentTreeNodeConfig *config.PathConfigTree -var CurrentPathConfig *config.PathConfig +var CurrentTreeNode *TreeNode +var CurrentPathConfig *PathConfig func NewContext() pongo2.Context { ctx := pongo2.Context{ "fnRequest": RequestFn, "fnRender": RenderFn, - "AssetsPath": config.Config.Assets.ToPath, + "AssetsPath": Config.Assets.ToPath, "Timestamp": time.Now().Unix, } CurrentContext = &ctx @@ -33,7 +32,128 @@ func NewContext() pongo2.Context { return ctx } -func FillNodeConfig(node *config.PathConfigTree, inBase, outBase, dir string, conf *config.PathConfig) { +func (node *TreeNode) handleCollections() { + for _, colConfig := range node.Config.This.Collections { + if colConfig != nil { + if colConfig.Name == nil || *colConfig.Name == "" { + helper.Log.Panicf("missing Name in collection config in '%s'", node.InputPath) + } + if colConfig.URL == nil || *colConfig.URL == "" { + helper.Log.Panicf("missing EntriesJSON in collection config in '%s'", node.InputPath) + } + } + + if node.ColMap == nil { + node.ColMap = make(MapString) + } + ctx := NewContext() + ctx["This"] = node.Config.This + ctx["Data"] = node.Config.Data + + url, err := pongo2.RenderTemplateString(*colConfig.URL, ctx) + if err != nil { + helper.Log.Panicf("invalid template string for Collection Element.URL in '%s': %s", node.InputPath, err) + } + + colData := helper.JSONWebRequest(url) + node.ColMap[*colConfig.Name] = colData + + if navT := colConfig.NavTemplate; navT != nil { + var entries []interface{} + var ok bool + if navT.EntriesAttribute != "" { + var colDataMap map[string]interface{} + if colDataMap, ok = colData.(map[string]interface{}); ok { + entries, ok = colDataMap[navT.EntriesAttribute].([]interface{}) + if !ok { + helper.Log.Debug(spew.Sdump(colDataMap)) + helper.Log.Panicf("invalid json data in [%s] from url '%s' for entries", navT.EntriesAttribute, url) + } + } + } else { + entries, ok = colData.([]interface{}) + } + if !ok { + helper.Log.Debug(spew.Sdump(colData)) + helper.Log.Panicf("invalid json data from url '%s', need array of objects for entries or object with configured NavTemplate.EntriesAttribute", url) + } + + // build navigation with detail sites + for idx, colEl := range entries { + ctxE := make(pongo2.Context) + err := helper.Merge(&ctxE, ctx) + if err != nil { + helper.Log.Panicf("could not merge context in '%s': %s", node.InputPath, err) + } + var jsonCtx map[string]interface{} + if jsonCtx, ok = colEl.(map[string]interface{}); !ok { + helper.Log.Debug(spew.Sdump(colEl)) + helper.Log.Panicf("no json object for entry index %d from url '%s'", idx, url) + } + err = helper.Merge(&ctxE, pongo2.Context(jsonCtx)) + if err != nil { + helper.Log.Panicf("could not merge context in '%s': %s", node.InputPath, err) + } + + tpl := "" + if navT.Template != "" { + tpl, err = pongo2.RenderTemplateString(navT.Template, ctxE) + if err != nil { + helper.Log.Panicf("invalid template string for NavTemplate.Template in '%s': %s", node.InputPath, err) + } + } + if tpl == "" { + tpl = *node.Config.Template + } + + dataKey := "" + if navT.DataKey != "" { + dataKey, err = pongo2.RenderTemplateString(navT.DataKey, ctxE) + if err != nil { + helper.Log.Panicf("invalid template string for NavTemplate.DataKey in '%s': %s", node.InputPath, err) + } + } + + goTo, err := pongo2.RenderTemplateString(navT.GoTo, ctxE) + if err != nil { + helper.Log.Panicf("invalid template string for NavTemplate.GoTo in '%s': %s", node.InputPath, err) + } + goTo = strings.Trim(goTo, "/") + goTo = path.Clean(goTo) + + if strings.Contains(goTo, "..") { + helper.Log.Panicf("going back via .. in NavTemplate.GoTo forbidden in collection config in '%s': %s", node.InputPath, goTo) + } + if goTo == "." { + helper.Log.Panicf("invalid config '.' for NavTemplate.GoTo in collection config in '%s'", node.InputPath) + } + if goTo == "" { + helper.Log.Panicf("missing NavTemplate.GoTo in collection config in '%s'", node.InputPath) + } + + navname := "" + if navT.Navname != "" { + navname, err = pongo2.RenderTemplateString(navT.Navname, ctxE) + if err != nil { + helper.Log.Panicf("invalid template string for NavTemplate.Navname in '%s': %s", node.InputPath, err) + } + } + body := "" + if navT.Body != "" { + body, err = pongo2.RenderTemplateString(navT.Body, ctxE) + if err != nil { + helper.Log.Panicf("invalid template string for NavTemplate.Body in '%s': %s", node.InputPath, err) + } + } + + Add2Nav(node, node.Config, tpl, goTo, navname, colEl, dataKey, body, navT.Hidden) + } + } + } + +} + +func (node *TreeNode) FillConfig(inBase, outBase, dir string, conf *PathConfig) { inPath := inBase if dir != "" { inPath += "/" + dir @@ -44,14 +164,14 @@ func FillNodeConfig(node *config.PathConfigTree, inBase, outBase, dir string, co node.InputPath = inPath // read config - newConfig := new(config.PathConfig) + newConfig := new(PathConfig) helper.Log.Debug("looking for config.yml ...") configFile := inPath + "/config.yml" if _, err := os.Stat(configFile); os.IsNotExist(err) { helper.Log.Debug("no config.yml found in this directory, using upper configs") helper.Merge(newConfig, conf) // remove this - newConfig.This = config.ThisPathConfig{} + newConfig.This = ThisPathConfig{} } else { helper.Log.Debug("reading config...") data, err := ioutil.ReadFile(configFile) @@ -100,167 +220,49 @@ func FillNodeConfig(node *config.PathConfigTree, inBase, outBase, dir string, co node.OutputPath = outPath // handle collections - for _, colConfig := range newConfig.This.Collections { - if colConfig != nil { - if colConfig.Name == nil || *colConfig.Name == "" { - helper.Log.Panicf("missing Name in collection config in '%s'", inPath) - } - if colConfig.URL == nil || *colConfig.URL == "" { - helper.Log.Panicf("missing EntriesJSON in collection config in '%s'", inPath) - } - } - - if node.ColMap == nil { - node.ColMap = make(config.MapString) - } - ctx := NewContext() - ctx["This"] = node.Config.This - ctx["Data"] = node.Config.Data - - url, err := pongo2.RenderTemplateString(*colConfig.URL, ctx) - if err != nil { - helper.Log.Panicf("invalid template string for Collection Element.URL in '%s': %s", inPath, err) - } - - colData := helper.JSONWebRequest(url) - node.ColMap[*colConfig.Name] = colData - - if navT := colConfig.NavTemplate; navT != nil { - var entries []interface{} - var ok bool - if navT.EntriesAttribute != "" { - var colDataMap map[string]interface{} - if colDataMap, ok = colData.(map[string]interface{}); ok { - entries, ok = colDataMap[navT.EntriesAttribute].([]interface{}) - if !ok { - helper.Log.Debug(spew.Sdump(colDataMap)) - helper.Log.Panicf("invalid json data in [%s] from url '%s' for entries", navT.EntriesAttribute, url) - } - } - } else { - entries, ok = colData.([]interface{}) - } - if !ok { - helper.Log.Debug(spew.Sdump(colData)) - helper.Log.Panicf("invalid json data from url '%s', need array of objects for entries or object with configured NavTemplate.EntriesAttribute", url) - } - - // build navigation with detail sites - for idx, colEl := range entries { - ctxE := make(pongo2.Context) - err := helper.Merge(&ctxE, ctx) - if err != nil { - helper.Log.Panicf("could not merge context in '%s': %s", inPath, err) - } - var jsonCtx map[string]interface{} - if jsonCtx, ok = colEl.(map[string]interface{}); !ok { - helper.Log.Debug(spew.Sdump(colEl)) - helper.Log.Panicf("no json object for entry index %d from url '%s'", idx, url) - } - err = helper.Merge(&ctxE, pongo2.Context(jsonCtx)) - if err != nil { - helper.Log.Panicf("could not merge context in '%s': %s", inPath, err) - } - - tpl := "" - if navT.Template != "" { - tpl, err = pongo2.RenderTemplateString(navT.Template, ctxE) - if err != nil { - helper.Log.Panicf("invalid template string for NavTemplate.Template in '%s': %s", inPath, err) - } - } - if tpl == "" { - tpl = *newConfig.Template - } - - dataKey := "" - if navT.DataKey != "" { - dataKey, err = pongo2.RenderTemplateString(navT.DataKey, ctxE) - if err != nil { - helper.Log.Panicf("invalid template string for NavTemplate.DataKey in '%s': %s", inPath, err) - } - } - - goTo, err := pongo2.RenderTemplateString(navT.GoTo, ctxE) - if err != nil { - helper.Log.Panicf("invalid template string for NavTemplate.GoTo in '%s': %s", inPath, err) - } - goTo = strings.Trim(goTo, "/") - goTo = path.Clean(goTo) - - if strings.Contains(goTo, "..") { - helper.Log.Panicf("going back via .. in NavTemplate.GoTo forbidden in collection config in '%s': %s", inPath, goTo) - } - if goTo == "." { - helper.Log.Panicf("invalid config '.' for NavTemplate.GoTo in collection config in '%s'", inPath) - } - if goTo == "" { - helper.Log.Panicf("missing NavTemplate.GoTo in collection config in '%s'", inPath) - } - - navname := "" - if navT.Navname != "" { - navname, err = pongo2.RenderTemplateString(navT.Navname, ctxE) - if err != nil { - helper.Log.Panicf("invalid template string for NavTemplate.Navname in '%s': %s", inPath, err) - } - } - body := "" - if navT.Body != "" { - body, err = pongo2.RenderTemplateString(navT.Body, ctxE) - if err != nil { - helper.Log.Panicf("invalid template string for NavTemplate.Body in '%s': %s", inPath, err) - } - } - - Add2Nav(node, node.Config, tpl, goTo, navname, colEl, dataKey, body, navT.Hidden) - } - } - - } + node.handleCollections() } -func Add2Nav(currentNode *config.PathConfigTree, pathConfig *config.PathConfig, tplFilename, outDir string, navname string, ctx interface{}, dataMapKey string, body string, hidden bool) { - newNodeConfig := new(config.PathConfigTree) - FillNodeConfig( - newNodeConfig, +func Add2Nav(currentNode *TreeNode, pathConfig *PathConfig, tplFilename, outDir string, navname string, ctx interface{}, dataMapKey string, body string, hidden bool) { + newNode := new(TreeNode) + newNode.FillConfig( currentNode.InputPath, currentNode.OutputPath, outDir, pathConfig, ) if navname != "" { - newNodeConfig.Config.This = config.ThisPathConfig{ + newNode.Config.This = ThisPathConfig{ Navname: &navname, } } if dataMapKey != "" { - if newNodeConfig.Config.Data == nil { - newNodeConfig.Config.Data = make(config.MapString) + if newNode.Config.Data == nil { + newNode.Config.Data = make(MapString) } // as submap in Data - newNodeConfig.Config.Data[dataMapKey] = ctx + newNode.Config.Data[dataMapKey] = ctx } else if m, ok := ctx.(map[string]interface{}); ok { // direct set data - newNodeConfig.Config.Data = m + newNode.Config.Data = m } // fake via normal file behavior - newNodeConfig.Config.Template = &tplFilename - newNodeConfig.InputFiles = []string{""} // empty file is special for use InputString + newNode.Config.Template = &tplFilename + newNode.InputFiles = []string{""} // empty file is special for use InputString indexInFile := "" indexOutFile := "index.html" - if idx := newNodeConfig.Config.Index; idx != nil { + if idx := newNode.Config.Index; idx != nil { if idx.OutputFile != nil && *idx.OutputFile != "" { indexOutFile = *idx.OutputFile } } - newNodeConfig.Config.Index = &config.IndexConfig{ + newNode.Config.Index = &IndexConfig{ InputFile: &indexInFile, OutputFile: &indexOutFile, InputString: &body, } - newNodeConfig.Hidden = hidden + newNode.Hidden = hidden - currentNode.Sub = append(currentNode.Sub, newNodeConfig) + currentNode.Sub = append(currentNode.Sub, newNode) } diff --git a/context/fn_render.go b/context/fn_render.go deleted file mode 100644 index 2994b82..0000000 --- a/context/fn_render.go +++ /dev/null @@ -1,24 +0,0 @@ -package context - -import ( - "github.com/flosch/pongo2" -) - -// RenderFn renders a pongo2 template with additional context -func RenderFn(templateFilename, outDir, ctx *pongo2.Value, param ...*pongo2.Value) *pongo2.Value { - dataMapKey := "" - body := "" - - for i, p := range param { - switch i { - case 0: - dataMapKey = p.String() - case 1: - body = p.String() - } - } - - Add2Nav(CurrentTreeNodeConfig, CurrentPathConfig, templateFilename.String(), outDir.String(), "", ctx.Interface(), dataMapKey, body, true) - - return pongo2.AsValue(nil) -} diff --git a/context/fn_request.go b/context/fn_request.go deleted file mode 100644 index a7d017a..0000000 --- a/context/fn_request.go +++ /dev/null @@ -1,12 +0,0 @@ -package context - -import ( - "gitbase.de/apairon/mark2web/helper" - "github.com/flosch/pongo2" -) - -// RequestFn will make a web request and returns map[string]interface form pongo2 -func RequestFn(url *pongo2.Value, args ...*pongo2.Value) *pongo2.Value { - u := url.String() - return pongo2.AsValue(helper.JSONWebRequest(u)) -} diff --git a/context_fn.go b/context_fn.go new file mode 100644 index 0000000..ef6210c --- /dev/null +++ b/context_fn.go @@ -0,0 +1,31 @@ +package mark2web + +import ( + "gitbase.de/apairon/mark2web/helper" + "github.com/flosch/pongo2" +) + +// RequestFn will make a web request and returns map[string]interface form pongo2 +func RequestFn(url *pongo2.Value, args ...*pongo2.Value) *pongo2.Value { + u := url.String() + return pongo2.AsValue(helper.JSONWebRequest(u)) +} + +// RenderFn renders a pongo2 template with additional context +func RenderFn(templateFilename, outDir, ctx *pongo2.Value, param ...*pongo2.Value) *pongo2.Value { + dataMapKey := "" + body := "" + + for i, p := range param { + switch i { + case 0: + dataMapKey = p.String() + case 1: + body = p.String() + } + } + + Add2Nav(CurrentTreeNode, CurrentPathConfig, templateFilename.String(), outDir.String(), "", ctx.Interface(), dataMapKey, body, true) + + return pongo2.AsValue(nil) +} diff --git a/filter/custom.go b/filter/custom.go index 3b69992..a1bd608 100644 --- a/filter/custom.go +++ b/filter/custom.go @@ -5,7 +5,7 @@ import ( "path" "strings" - "gitbase.de/apairon/mark2web/context" + "gitbase.de/apairon/mark2web" "gitbase.de/apairon/mark2web/helper" "github.com/ddliu/motto" "github.com/flosch/pongo2" @@ -44,8 +44,8 @@ func RegisterFilters(dir string) { fileBase, func(in, param *pongo2.Value) (out *pongo2.Value, erro *pongo2.Error) { thisObj, _ := vm.Object("({})") - if context.CurrentContext != nil { - thisObj.Set("context", *context.CurrentContext) + if mark2web.CurrentContext != nil { + thisObj.Set("context", *mark2web.CurrentContext) } if err != nil { diff --git a/filter/image_process.go b/filter/image_process.go index e06aecd..175e1a2 100644 --- a/filter/image_process.go +++ b/filter/image_process.go @@ -12,17 +12,16 @@ import ( "strconv" "strings" - "gitbase.de/apairon/mark2web/config" - "gitbase.de/apairon/mark2web/context" + "gitbase.de/apairon/mark2web" "gitbase.de/apairon/mark2web/helper" "github.com/disintegration/imaging" "github.com/flosch/pongo2" ) -func parseImageParams(str string) (*config.ImagingConfig, error) { - p := config.ImagingConfig{} +func parseImageParams(str string) (*mark2web.ImagingConfig, error) { + p := mark2web.ImagingConfig{} if str == "" { - helper.Merge(&p, context.CurrentPathConfig.Imaging) + helper.Merge(&p, mark2web.CurrentPathConfig.Imaging) // Filename and Format are only valid for current image p.Filename = "" p.Format = "" @@ -131,7 +130,7 @@ func ImageProcessFilter(in *pongo2.Value, param *pongo2.Value) (*pongo2.Value, * } } else { // local file - imgSource = context.ResolveInputPath(imgSource) + imgSource = mark2web.ResolveInputPath(imgSource) if p.Filename == "" { p.Filename = fmt.Sprintf( "%s_%s", @@ -143,7 +142,7 @@ func ImageProcessFilter(in *pongo2.Value, param *pongo2.Value) (*pongo2.Value, * var imgTarget string if p.TargetDir != "" { - imgTarget = context.ResolveOutputPath( + imgTarget = mark2web.ResolveOutputPath( path.Clean(p.TargetDir) + "/" + p.Filename, ) @@ -159,10 +158,10 @@ func ImageProcessFilter(in *pongo2.Value, param *pongo2.Value) (*pongo2.Value, * } } - p.Filename = context.ResolveNavPath(p.TargetDir + "/" + p.Filename) + p.Filename = mark2web.ResolveNavPath(p.TargetDir + "/" + p.Filename) } else { - imgTarget = context.ResolveOutputPath(p.Filename) + imgTarget = mark2web.ResolveOutputPath(p.Filename) } if f, err := os.Stat(imgTarget); err == nil && !f.IsDir() { @@ -236,5 +235,5 @@ func ImageProcessFilter(in *pongo2.Value, param *pongo2.Value) (*pongo2.Value, * } } } - return pongo2.AsValue(context.ResolveNavPath(p.Filename)), nil + return pongo2.AsValue(mark2web.ResolveNavPath(p.Filename)), nil } diff --git a/filter/markdown.go b/filter/markdown.go index 5a5fe13..86b1237 100644 --- a/filter/markdown.go +++ b/filter/markdown.go @@ -1,7 +1,7 @@ package filter import ( - "gitbase.de/apairon/mark2web/context" + "gitbase.de/apairon/mark2web" "gitbase.de/apairon/mark2web/helper" "github.com/flosch/pongo2" ) @@ -10,7 +10,7 @@ import ( func MarkdownFilter(in *pongo2.Value, param *pongo2.Value) (*pongo2.Value, *pongo2.Error) { chromaRenderer := false chromaStyle := "monokai" - if m := context.CurrentPathConfig.Markdown; m != nil { + if m := mark2web.CurrentPathConfig.Markdown; m != nil { if m.ChromaRenderer != nil && *m.ChromaRenderer { chromaRenderer = true } diff --git a/filter/relative_path.go b/filter/relative_path.go index 132048e..81b8392 100644 --- a/filter/relative_path.go +++ b/filter/relative_path.go @@ -1,14 +1,14 @@ package filter import ( - "gitbase.de/apairon/mark2web/context" + "gitbase.de/apairon/mark2web" "github.com/flosch/pongo2" ) // RelativePathFilter returns the relative path to navpoint based on current nav func RelativePathFilter(in, param *pongo2.Value) (*pongo2.Value, *pongo2.Error) { return pongo2.AsValue( - context.ResolveNavPath( + mark2web.ResolveNavPath( in.String(), ), ), nil diff --git a/map_string.go b/map_string.go new file mode 100644 index 0000000..54bbb55 --- /dev/null +++ b/map_string.go @@ -0,0 +1,47 @@ +package mark2web + +import "fmt" + +// MapString is a map[string]interface{} which always unmarsahls yaml to map[string]interface{} +type MapString map[string]interface{} + +// UnmarshalYAML handles all maps as map[string]interface{} for later JSON +// see https://github.com/elastic/beats/blob/6435194af9f42cbf778ca0a1a92276caf41a0da8/libbeat/common/mapstr.go +func (ms *MapString) UnmarshalYAML(unmarshal func(interface{}) error) error { + var result map[interface{}]interface{} + err := unmarshal(&result) + if err != nil { + return err + } + *ms = cleanUpInterfaceMap(result) + return nil +} + +func cleanUpInterfaceArray(in []interface{}) []interface{} { + result := make([]interface{}, len(in)) + for i, v := range in { + result[i] = cleanUpMapValue(v) + } + return result +} + +func cleanUpInterfaceMap(in map[interface{}]interface{}) MapString { + result := make(MapString) + for k, v := range in { + result[fmt.Sprintf("%v", k)] = cleanUpMapValue(v) + } + return result +} + +func cleanUpMapValue(v interface{}) interface{} { + switch v := v.(type) { + case []interface{}: + return cleanUpInterfaceArray(v) + case map[interface{}]interface{}: + return cleanUpInterfaceMap(v) + case string, bool, int, int8, int16, int32, int64, float32, float64: + return v + default: + return fmt.Sprintf("%v", v) + } +} diff --git a/mark2web/main.go b/mark2web/main.go index 9c25594..2205b1e 100644 --- a/mark2web/main.go +++ b/mark2web/main.go @@ -7,7 +7,6 @@ import ( "path" "gitbase.de/apairon/mark2web" - "gitbase.de/apairon/mark2web/config" "gitbase.de/apairon/mark2web/filter" "gitbase.de/apairon/mark2web/helper" ) @@ -23,7 +22,7 @@ var ( var log = helper.Log -var contentConfig = new(config.PathConfigTree) +var tree = new(mark2web.TreeNode) func main() { inDir := flag.String("in", "./", "input directory") @@ -88,12 +87,12 @@ func main() { log.Debug("reading global config...") configFilename := *inDir + "/config.yml" - err := config.ReadGlobalConfig(configFilename) + err := mark2web.Config.ReadFromFile(configFilename) if err != nil { log.Panicf("could not read file '%s': %s", configFilename, err) } - config.Config.Directories.Input = *inDir - config.Config.Directories.Output = *outDir + mark2web.Config.Directories.Input = *inDir + mark2web.Config.Directories.Output = *outDir log.Debugf("reading input directory %s", *inDir) @@ -106,29 +105,29 @@ func main() { defaultFilenameIgnore := "^_" defaultFilenameOutputExtension := "html" - defaultPathConfig := new(config.PathConfig) + defaultPathConfig := new(mark2web.PathConfig) defaultPathConfig.Template = &defaultTemplate - defaultPathConfig.Index = &config.IndexConfig{ + defaultPathConfig.Index = &mark2web.IndexConfig{ InputFile: &defaultInputFile, OutputFile: &defaultOutputFile, } - defaultPathConfig.Path = &config.DirnameConfig{ + defaultPathConfig.Path = &mark2web.DirnameConfig{ Strip: &defaultPathStrip, IgnoreForNav: &defaultPathIgnoreForNav, } - defaultPathConfig.Filename = &config.FilenameConfig{ + defaultPathConfig.Filename = &mark2web.FilenameConfig{ Strip: &defaultFilenameStrip, Ignore: &defaultFilenameIgnore, OutputExtension: &defaultFilenameOutputExtension, } - defaultPathConfig.Imaging = &config.ImagingConfig{ + defaultPathConfig.Imaging = &mark2web.ImagingConfig{ Width: 1920, Height: 1920, Process: "fit", Quality: 75, } - mark2web.ReadContentDir(*inDir+"/content", *outDir, "", defaultPathConfig, contentConfig) + tree.ReadContentDir(*inDir+"/content", *outDir, "", defaultPathConfig) //spew.Dump(contentConfig) //spew.Dump(navMap) @@ -139,7 +138,8 @@ func main() { if _, err := os.Stat(filtersDir); !os.IsNotExist(err) { filter.RegisterFilters(filtersDir) } - mark2web.ProcessContent(contentConfig, contentConfig) + tree.ProcessContent(tree) mark2web.ProcessAssets() + } diff --git a/navigation.go b/navigation.go index 679b69d..27659d0 100644 --- a/navigation.go +++ b/navigation.go @@ -5,7 +5,6 @@ import ( "regexp" "strings" - "gitbase.de/apairon/mark2web/config" "gitbase.de/apairon/mark2web/helper" ) @@ -15,19 +14,19 @@ type NavElement struct { GoTo string Active bool - ColMap config.MapString + ColMap MapString Data interface{} - This config.ThisPathConfig + This ThisPathConfig SubMap *map[string]*NavElement SubSlice *[]*NavElement } // BuildNavigation builds the navigation trees for use in templates -func BuildNavigation(conf *config.PathConfigTree, curNavMap *map[string]*NavElement, curNavSlice *[]*NavElement, navActive *[]*NavElement, activeNav string) { - for _, el := range conf.Sub { +func BuildNavigation(node *TreeNode, curNavMap *map[string]*NavElement, curNavSlice *[]*NavElement, navActive *[]*NavElement, activeNav string) { + for _, el := range node.Sub { if el.Hidden { continue // ignore hidden nav points from collections } @@ -47,7 +46,7 @@ func BuildNavigation(conf *config.PathConfigTree, curNavMap *map[string]*NavElem } } - elPath := strings.TrimPrefix(el.OutputPath, config.Config.Directories.Output+"/") + elPath := strings.TrimPrefix(el.OutputPath, Config.Directories.Output+"/") subMap := make(map[string]*NavElement) subSlice := make([]*NavElement, 0) diff --git a/context/path.go b/path.go similarity index 73% rename from context/path.go rename to path.go index eb19c84..9806e6f 100644 --- a/context/path.go +++ b/path.go @@ -1,10 +1,9 @@ -package context +package mark2web import ( "path" "strings" - "gitbase.de/apairon/mark2web/config" "gitbase.de/apairon/mark2web/helper" ) @@ -21,9 +20,9 @@ func ResolveNavPath(target string) string { // ResolveOutputPath fixes output directory relative to current navigation path func ResolveOutputPath(target string) string { if strings.HasPrefix(target, "/") { - target = config.Config.Directories.Output + "/" + target + target = Config.Directories.Output + "/" + target } else { - target = CurrentTreeNodeConfig.OutputPath + "/" + target + target = CurrentTreeNode.OutputPath + "/" + target } return path.Clean(target) } @@ -31,9 +30,9 @@ func ResolveOutputPath(target string) string { // ResolveInputPath fixes input directory relative to current navigation path func ResolveInputPath(target string) string { if strings.HasPrefix(target, "/") { - target = config.Config.Directories.Input + "/" + target + target = Config.Directories.Input + "/" + target } else { - target = CurrentTreeNodeConfig.InputPath + "/" + target + target = CurrentTreeNode.InputPath + "/" + target } return path.Clean(target) } diff --git a/render.go b/render.go index aa4c900..0034fe1 100644 --- a/render.go +++ b/render.go @@ -2,12 +2,7 @@ package mark2web import ( "log" - "path" - "regexp" - "gitbase.de/apairon/mark2web/config" - "gitbase.de/apairon/mark2web/context" - "gitbase.de/apairon/mark2web/helper" "github.com/flosch/pongo2" ) @@ -19,11 +14,11 @@ func SetTemplateDir(dir string) { templateDir = dir } -// RenderTemplate renders a pongo2 template with context -func RenderTemplate(filename string, treeNodeConfig *config.PathConfigTree, pathConfig *config.PathConfig, ctx *pongo2.Context) (string, error) { - context.CurrentContext = ctx - context.CurrentTreeNodeConfig = treeNodeConfig - context.CurrentPathConfig = pathConfig +// renderTemplate renders a pongo2 template with context +func renderTemplate(filename string, node *TreeNode, pathConfig *PathConfig, ctx *pongo2.Context) (string, error) { + CurrentContext = ctx + CurrentTreeNode = node + CurrentPathConfig = pathConfig templateFile := templateDir + "/" + filename template := templateCache[templateFile] if template == nil { @@ -37,24 +32,3 @@ func RenderTemplate(filename string, treeNodeConfig *config.PathConfigTree, path return template.Execute(*ctx) } - -// FixAssetsPath replaces assets path based on current path -func FixAssetsPath(str, curNavPath string) string { - if find := config.Config.Assets.FixTemplate.Find; find != "" { - helper.Log.Debugf("fixing assets paths for path '%s'", curNavPath) - repl := config.Config.Assets.FixTemplate.Replace - toPath := config.Config.Assets.ToPath - - bToRoot := helper.BackToRoot(curNavPath) - regex, err := regexp.Compile(find) - if err != nil { - log.Panicf("could not compile regexp '%s' for assets path: %s", find, err) - } - repl = bToRoot + toPath + "/" + repl - repl = path.Clean(repl) + "/" - helper.Log.Debugf("new assets paths: %s", repl) - return regex.ReplaceAllString(str, repl) - } - - return str -} diff --git a/tree.go b/tree.go new file mode 100644 index 0000000..69d5f12 --- /dev/null +++ b/tree.go @@ -0,0 +1,16 @@ +package mark2web + +// TreeNode is complete config tree of content dir +type TreeNode struct { + InputPath string + OutputPath string + Hidden bool // for collections which are not part of the navigation + + ColMap MapString + + InputFiles []string + OtherFiles []string + + Config *PathConfig + Sub []*TreeNode +}