package mark2web import ( "io/ioutil" "os" "path" "regexp" "strings" "time" "gitbase.de/apairon/mark2web/helper" "github.com/davecgh/go-spew/spew" "github.com/extemporalgenome/slug" "github.com/flosch/pongo2" "gopkg.in/yaml.v2" ) var CurrentContext *pongo2.Context var CurrentTreeNode *TreeNode var CurrentPathConfig *PathConfig func NewContext() pongo2.Context { ctx := pongo2.Context{ "fnRequest": RequestFn, "fnRender": RenderFn, "AssetsPath": Config.Assets.ToPath, "Timestamp": time.Now().Unix, } CurrentContext = &ctx return ctx } 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 } helper.Log.Infof("reading input directory: %s", inPath) node.InputPath = inPath // read config 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 = ThisPathConfig{} } else { helper.Log.Debug("reading config...") data, err := ioutil.ReadFile(configFile) if err != nil { helper.Log.Panicf("could not read file '%s': %s", configFile, err) } err = yaml.Unmarshal(data, newConfig) if err != nil { helper.Log.Panicf("could not parse YAML file '%s': %s", configFile, err) } helper.Log.Debug("merging config with upper config") oldThis := newConfig.This helper.Merge(newConfig, conf) newConfig.This = oldThis helper.Log.Debug(spew.Sdump(newConfig)) } node.Config = newConfig // calc outDir stripedDir := dir var regexStr *string if newConfig.Path != nil { regexStr = newConfig.Path.Strip } if regexStr != nil && *regexStr != "" { if regex, err := regexp.Compile(*regexStr); err != nil { helper.Log.Panicf("error compiling path.strip regex '%s' from '%s': %s", *regexStr, inBase+"/"+dir, err) } else { stripedDir = regex.ReplaceAllString(stripedDir, "$1") } } if node.Config.This.Navname == nil { navname := strings.Replace(stripedDir, "_", " ", -1) node.Config.This.Navname = &navname } stripedDir = slug.Slug(stripedDir) outPath := outBase + "/" + stripedDir outPath = path.Clean(outPath) helper.Log.Infof("calculated output directory: %s", outPath) node.OutputPath = outPath // handle collections node.handleCollections() } func Add2Nav(currentNode *TreeNode, pathConfig *PathConfig, tplFilename, outDir string, navname string, ctx interface{}, dataMapKey string, body string, hidden bool) { newNode := new(TreeNode) newNode.root = currentNode.root newNode.fillConfig( currentNode.InputPath, currentNode.OutputPath, outDir, pathConfig, ) if navname != "" { newNode.Config.This = ThisPathConfig{ Navname: &navname, } } if dataMapKey != "" { if newNode.Config.Data == nil { newNode.Config.Data = make(MapString) } // as submap in Data newNode.Config.Data[dataMapKey] = ctx } else if m, ok := ctx.(map[string]interface{}); ok { // direct set data newNode.Config.Data = m } // fake via normal file behavior newNode.Config.Template = &tplFilename newNode.InputFiles = []string{""} // empty file is special for use InputString indexInFile := "" indexOutFile := "index.html" if idx := newNode.Config.Index; idx != nil { if idx.OutputFile != nil && *idx.OutputFile != "" { indexOutFile = *idx.OutputFile } } newNode.Config.Index = &IndexConfig{ InputFile: &indexInFile, OutputFile: &indexOutFile, InputString: &body, } newNode.Hidden = hidden currentNode.Sub = append(currentNode.Sub, newNode) }