package helper import ( "bytes" "io/ioutil" "os" "path" "regexp" "strings" "gitbase.de/apairon/mark2web/config" "github.com/Depado/bfchroma" "github.com/davecgh/go-spew/spew" "github.com/extemporalgenome/slug" "github.com/flosch/pongo2" cpy "github.com/otiai10/copy" "gopkg.in/russross/blackfriday.v2" "gopkg.in/yaml.v2" ) func fillNodeConfig(node *config.PathConfigTree, inBase, outBase, dir string, conf *config.PathConfig) { inPath := inBase if dir != "" { inPath += "/" + dir } Log.Infof("reading input directory: %s", inPath) node.InputPath = inPath // read config newConfig := new(config.PathConfig) Log.Debug("looking for config.yml ...") configFile := inPath + "/config.yml" if _, err := os.Stat(configFile); os.IsNotExist(err) { Log.Debug("no config.yml found in this directory, using upper configs") config.Merge(newConfig, conf) // remove this newConfig.This = config.ThisPathConfig{} } else { Log.Debug("reading config...") data, err := ioutil.ReadFile(configFile) if err != nil { Log.Panicf("could not read file '%s': %s", configFile, err) } err = yaml.Unmarshal(data, newConfig) if err != nil { Log.Panicf("could not parse YAML file '%s': %s", configFile, err) } Log.Debug("merging config with upper config") oldThis := newConfig.This config.Merge(newConfig, conf) newConfig.This = oldThis 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 { 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) Log.Infof("calculated output directory: %s", outPath) node.OutputPath = outPath } // 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) { fillNodeConfig(tree, inBase, outBase, dir, conf) files, err := ioutil.ReadDir(tree.InputPath) if err != nil { Log.Panic(err) } // first only files for _, f := range files { p := tree.InputPath + "/" + f.Name() if !f.IsDir() && f.Name() != "config.yml" { switch path.Ext(f.Name()) { case ".md": Log.Debugf(".MD %s", p) if tree.InputFiles == nil { tree.InputFiles = make([]string, 0) } tree.InputFiles = append(tree.InputFiles, f.Name()) break default: Log.Debugf("FIL %s", p) if tree.OtherFiles == nil { tree.OtherFiles = make([]string, 0) } tree.OtherFiles = append(tree.OtherFiles, f.Name()) } } } // only directorys, needed config before for _, f := range files { p := tree.InputPath + "/" + f.Name() if f.IsDir() { Log.Debugf("DIR %s", p) newTree := new(config.PathConfigTree) if tree.Sub == nil { tree.Sub = make([]*config.PathConfigTree, 0) } tree.Sub = append(tree.Sub, newTree) ReadContentDir(tree.InputPath, tree.OutputPath, f.Name(), tree.Config, newTree) } } } // ProcessContent walks recursivly through the input paths and processes all files for output func ProcessContent(rootConf, conf *config.PathConfigTree) { CreateDirectory(conf.OutputPath) curNavPath := strings.TrimPrefix(conf.OutputPath, config.Config.Directories.Output) curNavPath = strings.TrimPrefix(curNavPath, "/") curNavPath = path.Clean(curNavPath) if curNavPath == "." { curNavPath = "" } goTo := conf.Config.This.GoTo if goTo != nil && *goTo != "" { goToFixed := *goTo if strings.HasPrefix(goToFixed, "/") { goToFixed = BackToRoot(curNavPath) + goToFixed } goToFixed = path.Clean(goToFixed) switch config.Config.Webserver.Type { case "apache": htaccessFile := conf.OutputPath + "/.htaccess" Log.Noticef("writing '%s' with redirect to: %s", htaccessFile, goToFixed) err := ioutil.WriteFile(htaccessFile, []byte(`RewriteEngine on RewriteRule ^$ %{REQUEST_URI}`+goToFixed+`/ [R,L] `), 0644) if err != nil { Log.Panicf("could not write '%s': %s", htaccessFile, err) } break } } for _, file := range conf.InputFiles { var input []byte inFile := "InputString" if file != "" { inFile = conf.InputPath + "/" + file Log.Debugf("reading file: %s", inFile) var err error input, err = ioutil.ReadFile(inFile) if err != nil { Log.Panicf("could not read '%s':%s", inFile, err) } Log.Infof("processing input file '%s'", inFile) } else { // use input string if available and input filename == "" var inputString *string if i := conf.Config.Index; i != nil { inputString = i.InputString } if inputString != nil { Log.Debugf("using input string instead of file") input = []byte(*inputString) } } newConfig := new(config.PathConfig) regex := regexp.MustCompile("(?s)^---(.*?)\\r?\\n\\r?---\\r?\\n\\r?") yamlData := regex.Find(input) if string(yamlData) != "" { // replace tabs yamlData = bytes.Replace(yamlData, []byte("\t"), []byte(" "), -1) Log.Debugf("found yaml header in '%s', merging config", inFile) err := yaml.Unmarshal(yamlData, newConfig) if err != nil { Log.Panicf("could not parse YAML header from '%s': %s", inFile, err) } Log.Debug("merging config with upper config") oldThis := newConfig.This config.Merge(newConfig, conf.Config) newConfig.This = oldThis Log.Debug(spew.Sdump(newConfig)) input = regex.ReplaceAll(input, []byte("")) } else { config.Merge(newConfig, conf.Config) } // ignore ??? ignoreFile := false var ignoreRegex *string var stripRegex *string var outputExt *string if f := newConfig.Filename; f != nil { ignoreRegex = f.Ignore stripRegex = f.Strip outputExt = f.OutputExtension } if ignoreRegex != nil && *ignoreRegex != "" { regex, err := regexp.Compile(*ignoreRegex) if err != nil { Log.Panicf("could not compile filename.ignore regexp '%s' for file '%s': %s", *ignoreRegex, inFile, err) } ignoreFile = regex.MatchString(file) } if ignoreFile { Log.Infof("ignoring file '%s', because of filename.ignore", inFile) } else { // build output filename outputFilename := file var indexInputFile *string var indexOutputFile *string if i := newConfig.Index; i != nil { indexInputFile = i.InputFile indexOutputFile = i.OutputFile } if indexInputFile != nil && *indexInputFile == file && indexOutputFile != nil && *indexOutputFile != "" { outputFilename = *indexOutputFile } else { if stripRegex != nil && *stripRegex != "" { regex, err := regexp.Compile(*stripRegex) if err != nil { Log.Panicf("could not compile filename.strip regexp '%s' for file '%s': %s", *stripRegex, inFile, err) } outputFilename = regex.ReplaceAllString(outputFilename, "$1") } if outputExt != nil && *outputExt != "" { outputFilename += "." + *outputExt } } outFile := conf.OutputPath + "/" + outputFilename Log.Debugf("using '%s' as output file", outFile) // use --- for splitting document in markdown parts regex := regexp.MustCompile("\\r?\\n\\r?---\\r?\\n\\r?") inputParts := regex.Split(string(input), -1) htmlParts := make([]*pongo2.Value, 0) for _, iPart := range inputParts { htmlParts = append(htmlParts, pongo2.AsSafeValue( string(renderMarkdown([]byte(iPart), newConfig.Markdown)))) } // build navigation navMap := make(map[string]*NavElement) navSlice := make([]*NavElement, 0) navActive := make([]*NavElement, 0) BuildNavigation(rootConf, &navMap, &navSlice, &navActive, curNavPath) // read yaml header as data for template ctx := make(map[string]interface{}) ctx["This"] = newConfig.This ctx["Meta"] = newConfig.Meta ctx["Data"] = newConfig.Data ctx["NavMap"] = navMap ctx["NavSlice"] = navSlice ctx["NavActive"] = navActive ctx["Body"] = pongo2.AsSafeValue(string(renderMarkdown(input, newConfig.Markdown))) ctx["BodyParts"] = htmlParts ctx["AssetsPath"] = config.Config.Assets.ToPath ctx["CurrentPath"] = curNavPath // register functions ctx["fnRequest"] = RequestFn ctx["fnRender"] = RenderFn Log.Debugf("rendering template '%s' for '%s'", *newConfig.Template, outFile) templateFilename := *newConfig.Template result, err := RenderTemplate(*newConfig.Template, conf, newConfig, &ctx) if err != nil { Log.Panicf("could not execute template '%s' for input file '%s': %s", templateFilename, inFile, err) } result = FixAssetsPath(result, curNavPath) Log.Noticef("writing to output file: %s", outFile) err = ioutil.WriteFile(outFile, []byte(result), 0644) if err != nil { Log.Panicf("could not write to output file '%s': %s", outFile, err) } //fmt.Println(string(html)) } } // process other files, copy... for _, file := range conf.OtherFiles { switch config.Config.OtherFiles.Action { case "copy": from := conf.InputPath + "/" + file to := conf.OutputPath + "/" + file Log.Noticef("copying file from '%s' to '%s'", from, to) err := cpy.Copy(from, to) if err != nil { Log.Panicf("could not copy file from '%s' to '%s': %s", from, to, err) } } } i := 0 for i < len(conf.Sub) { ProcessContent(rootConf, conf.Sub[i]) i++ } } func renderMarkdown(input []byte, markdownConf *config.MarkdownConfig) []byte { var options []blackfriday.Option var chromaRenderer *bool var chromaStyle *string if m := markdownConf; m != nil { chromaRenderer = m.ChromaRenderer chromaStyle = m.ChromaStyle } if chromaStyle == nil { style := "monokai" chromaStyle = &style } if chromaRenderer != nil && *chromaRenderer { options = []blackfriday.Option{ blackfriday.WithRenderer( bfchroma.NewRenderer( bfchroma.Style(*chromaStyle), ), ), } } // fix \r from markdown for blackfriday input = bytes.Replace(input, []byte("\r"), []byte(""), -1) return blackfriday.Run(input, options...) }