collections via markdown files
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Sebastian Frank 2019-03-22 17:22:03 +01:00
parent 4a9a3eec06
commit ff1da084af
Signed by: apairon
GPG Key ID: 7270D06DDA7FE8C3
46 changed files with 547 additions and 362 deletions

View File

@ -2,6 +2,7 @@ NEUERUNGEN:
- Cached Collection Webrequests
- recursive Collections
- Datei basierte Collections
- markdown-Filter `s=SYNTAX_HIGHLIGHT_SHEMA` Parameter
- image_process nutzt alle CPU-Kerne
- GZIP/Brotli Vor-Komprimierung der Inhalte und Assets

View File

@ -130,7 +130,7 @@ func ImageProcessFilter(in *pongo2.Value, param *pongo2.Value) (*pongo2.Value, *
}
} else {
// local file
imgSource = mark2web.ResolveInputPath(imgSource)
imgSource = mark2web.CurrentTreeNode.ResolveInputPath(imgSource)
if p.Filename == "" {
p.Filename = fmt.Sprintf(
"%s_%s",
@ -142,7 +142,7 @@ func ImageProcessFilter(in *pongo2.Value, param *pongo2.Value) (*pongo2.Value, *
var imgTarget string
if p.TargetDir != "" {
imgTarget = mark2web.ResolveOutputPath(
imgTarget = mark2web.CurrentTreeNode.ResolveOutputPath(
path.Clean(p.TargetDir) + "/" +
p.Filename,
)
@ -158,10 +158,10 @@ func ImageProcessFilter(in *pongo2.Value, param *pongo2.Value) (*pongo2.Value, *
}
}
p.Filename = mark2web.ResolveNavPath(p.TargetDir + "/" + p.Filename)
p.Filename = mark2web.CurrentTreeNode.ResolveNavPath(p.TargetDir + "/" + p.Filename)
} else {
imgTarget = mark2web.ResolveOutputPath(p.Filename)
imgTarget = mark2web.CurrentTreeNode.ResolveOutputPath(p.Filename)
}
if f, err := os.Stat(imgTarget); err == nil && !f.IsDir() {
@ -226,5 +226,5 @@ func ImageProcessFilter(in *pongo2.Value, param *pongo2.Value) (*pongo2.Value, *
helper.Log.Noticef("finished image: %s", imgTarget)
})
}
return pongo2.AsValue(mark2web.ResolveNavPath(p.Filename)), nil
return pongo2.AsValue(mark2web.CurrentTreeNode.ResolveNavPath(p.Filename)), nil
}

View File

@ -36,9 +36,6 @@ func TestImageProcessFilter(t *testing.T) {
},
},
}
mark2web.CurrentContext = &pongo2.Context{
"CurrentPath": "",
}
os.Remove("../../test/out/fit_300x300_q060_test.jpg")

View File

@ -8,7 +8,7 @@ import (
// RelativePathFilter returns the relative path to navpoint based on current nav
func RelativePathFilter(in, param *pongo2.Value) (*pongo2.Value, *pongo2.Error) {
return pongo2.AsValue(
mark2web.ResolveNavPath(
mark2web.CurrentTreeNode.ResolveNavPath(
in.String(),
),
), nil

View File

@ -17,10 +17,23 @@ func TestRelativePathFilter(t *testing.T) {
"testabs": "/abs",
"testsub": "../sub/rel",
}
mark2web.Config.Directories.Output = "../../test/out"
mark2web.CurrentTreeNode = &mark2web.TreeNode{
InputPath: "../../test/in/content",
OutputPath: "../../test/out/sub",
Config: &mark2web.PathConfig{
Imaging: &mark2web.ImagingConfig{
Quality: 60,
Height: 300,
Width: 300,
Process: "fit",
},
},
}
Convey("parse template", func() {
mark2web.CurrentContext = &pongo2.Context{
"CurrentPath": "sub",
}
output, err := pongo2.RenderTemplateString("{{ testrel|relative_path }}", ctx)
So(err, ShouldBeNil)

View File

@ -2,7 +2,6 @@ package helper
import (
"os"
"strings"
)
// CreateDirectory creates direcory with all missing parents and panic if error
@ -25,14 +24,3 @@ func CreateDirectory(dir string) {
Log.Panicf("unknown error for output directory '%s': %s", dir, err)
}
}
// BackToRoot builds ../../ string
func BackToRoot(curNavPath string) string {
tmpPath := ""
if curNavPath != "" {
for i := strings.Count(curNavPath, "/") + 1; i > 0; i-- {
tmpPath += "../"
}
}
return tmpPath
}

18
pkg/helper/regexp.go Normal file
View File

@ -0,0 +1,18 @@
package helper
import "regexp"
// GetRegexpParams gets a map of named regexp group matches
// use pe. (?P<Year>\d{4})-(?P<Month>\d{2})-(?P<Day>\d{2}) as regexp
func GetRegexpParams(regEx *regexp.Regexp, str string) (paramsMap map[string]string) {
match := regEx.FindStringSubmatch(str)
paramsMap = make(map[string]string)
for i, name := range regEx.SubexpNames() {
if i > 0 && i <= len(match) {
paramsMap[name] = match[i]
}
}
return
}

View File

@ -35,13 +35,13 @@ func ProcessAssets() {
}
// fixAssetsPath replaces assets path based on current path
func fixAssetsPath(str, curNavPath string) string {
func (node *TreeNode) fixAssetsPath(str string) string {
if find := Config.Assets.FixTemplate.Find; find != "" {
helper.Log.Debugf("fixing assets paths for path '%s'", curNavPath)
helper.Log.Debugf("fixing assets paths for path '%s'", node.CurrentNavPath())
repl := Config.Assets.FixTemplate.Replace
toPath := Config.Assets.ToPath
bToRoot := helper.BackToRoot(curNavPath)
bToRoot := node.BackToRootPath()
regex, err := regexp.Compile(find)
if err != nil {
log.Panicf("could not compile regexp '%s' for assets path: %s", find, err)

View File

@ -1,7 +1,9 @@
package mark2web
import (
"io/ioutil"
"path"
"regexp"
"strings"
"gitbase.de/apairon/mark2web/pkg/helper"
@ -20,13 +22,12 @@ var colCache = make(map[string]*colCacheEntry)
func (node *TreeNode) handleCollections() {
collections := append(node.Config.Collections, node.Config.This.Collections...)
for _, colConfig := range 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 colConfig.Name == nil || *colConfig.Name == "" {
helper.Log.Panicf("missing Name in collection config in '%s'", node.InputPath)
}
if (colConfig.URL == nil || *colConfig.URL == "") &&
(colConfig.Directory == nil) {
helper.Log.Panicf("missing URL and Directory in collection config in '%s'", node.InputPath)
}
if node.ColMap == nil {
@ -36,21 +37,73 @@ func (node *TreeNode) handleCollections() {
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)
}
var colData interface{}
if cacheEntry, ok := colCache[url]; ok {
colData = cacheEntry.data
cacheEntry.hit++
} else {
colData = helper.JSONWebRequest(url)
colCache[url] = &colCacheEntry{
data: colData,
navnames: make([]string, 0),
errSrcText := ""
cacheKey := ""
if colConfig.URL != nil {
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)
}
errSrcText = "URL " + url
cacheKey = url
if cacheEntry, ok := colCache[url]; ok {
colData = cacheEntry.data
cacheEntry.hit++
} else {
helper.Log.Noticef("reading collection from: %s", errSrcText)
colData = helper.JSONWebRequest(url)
colCache[url] = &colCacheEntry{
data: colData,
navnames: make([]string, 0),
}
}
} else {
path := node.ResolveInputPath(colConfig.Directory.Path)
errSrcText = "DIR " + path
helper.Log.Noticef("reading collection from: %s", errSrcText)
d, err := ioutil.ReadDir(path)
if err != nil {
helper.Log.Panicf("could not read directory '%s': %s", path, err)
}
mStr := "."
if colConfig.Directory.MatchFilename != "" {
mStr = colConfig.Directory.MatchFilename
}
matcher, err := regexp.Compile(mStr)
if err != nil {
helper.Log.Panicf("could not compile regex for MatchFilename '%s' in '%s': %s", mStr, path, err)
}
if colConfig.Directory.ReverseOrder {
for i := len(d)/2 - 1; i >= 0; i-- {
opp := len(d) - 1 - i
d[i], d[opp] = d[opp], d[i]
}
}
fcolData := make([]pongo2.Context, 0)
for _, fh := range d {
if !fh.IsDir() && matcher.MatchString(fh.Name()) {
inFile := path + "/" + fh.Name()
md, err := ioutil.ReadFile(inFile)
if err != nil {
helper.Log.Panicf("could not read file '%s': %s", inFile, err)
}
_, ctx := node.processMarkdownWithHeader(md, inFile)
(*ctx)["FilenameMatch"] = helper.GetRegexpParams(matcher, fh.Name())
fcolData = append(fcolData, *ctx)
}
}
colData = fcolData
}
node.ColMap[*colConfig.Name] = colData
@ -64,7 +117,7 @@ func (node *TreeNode) handleCollections() {
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)
helper.Log.Panicf("invalid json data in [%s] from '%s' for entries", navT.EntriesAttribute, errSrcText)
}
}
} else {
@ -72,7 +125,7 @@ func (node *TreeNode) handleCollections() {
}
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)
helper.Log.Panicf("invalid json data from '%s', need array of objects for entries or object with configured NavTemplate.EntriesAttribute", errSrcText)
}
// build navigation with detail sites
@ -85,7 +138,7 @@ func (node *TreeNode) handleCollections() {
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)
helper.Log.Panicf("no json object for entry index %d from '%s'", idx, errSrcText)
}
err = helper.Merge(&ctxE, pongo2.Context(jsonCtx))
if err != nil {
@ -143,14 +196,14 @@ func (node *TreeNode) handleCollections() {
}
}
if l := len(colCache[url].navnames); colCache[url].hit > 1 &&
if l := len(colCache[cacheKey].navnames); colCache[cacheKey].hit > 1 &&
l > 0 &&
navname == colCache[url].navnames[l-1] {
navname == colCache[cacheKey].navnames[l-1] {
// navname before used same url, so recursion loop
helper.Log.Panicf("collection request loop detected for in '%s' for url: %s", node.InputPath, url)
helper.Log.Panicf("collection request loop detected for in '%s' for : %s", node.InputPath, errSrcText)
}
colCache[url].navnames = append(colCache[url].navnames, navname)
colCache[cacheKey].navnames = append(colCache[cacheKey].navnames, navname)
node.addSubNode(tpl, goTo, navname, colEl, dataKey, body, navT.Hidden)
}

View File

@ -2,10 +2,18 @@ package mark2web
import "gitbase.de/apairon/mark2web/pkg/helper"
// CollectionDirectoryConfig specifies how to handle a directory of markdown files as a collection
type CollectionDirectoryConfig struct {
Path string `yaml:"Path"`
MatchFilename string `yaml:"MatchFilename"`
ReverseOrder bool `yaml:"ReverseOrder"`
}
// CollectionConfig describes a collection
type CollectionConfig struct {
Name *string `yaml:"Name"`
URL *string `yaml:"URL"`
Name *string `yaml:"Name"`
URL *string `yaml:"URL"`
Directory *CollectionDirectoryConfig `yaml:"Directory"`
NavTemplate *struct {
EntriesAttribute string `yaml:"EntriesAttribute"`
GoTo string `yaml:"GoTo"`

View File

@ -65,24 +65,104 @@ func (node *TreeNode) ReadContentDir(inBase string, outBase string, dir string,
}
}
func (node *TreeNode) processMarkdownWithHeader(md []byte, errorRef string) (*PathConfig, *pongo2.Context) {
newConfig := new(PathConfig)
headerRegex := regexp.MustCompile("(?s)^---(.*?)\\r?\\n\\r?---\\r?\\n\\r?")
yamlData := headerRegex.Find(md)
if string(yamlData) != "" {
// replace tabs
yamlData = bytes.Replace(yamlData, []byte("\t"), []byte(" "), -1)
helper.Log.Debugf("found yaml header in '%s', merging config", errorRef)
err := yaml.Unmarshal(yamlData, newConfig)
if err != nil {
helper.Log.Panicf("could not parse YAML header from '%s': %s", errorRef, err)
}
helper.Log.Debug("merging config with upper config")
oldThis := newConfig.This
helper.Merge(newConfig, node.Config)
newConfig.This = oldThis
helper.Log.Debug(spew.Sdump(newConfig))
md = headerRegex.ReplaceAll(md, []byte(""))
} else {
helper.Merge(newConfig, node.Config)
}
// use --- for splitting document in markdown parts
regex := regexp.MustCompile("\\r?\\n\\r?---\\r?\\n\\r?")
inputParts := regex.Split(string(md), -1)
htmlParts := make([]*pongo2.Value, 0)
chromaRenderer := false
chromaStyle := "monokai"
if m := newConfig.Markdown; m != nil {
if m.ChromaRenderer != nil && *m.ChromaRenderer {
chromaRenderer = true
}
if m.ChromaStyle != nil && *m.ChromaStyle != "" {
chromaStyle = *m.ChromaStyle
}
}
for _, iPart := range inputParts {
htmlParts = append(htmlParts,
pongo2.AsSafeValue(
string(helper.RenderMarkdown([]byte(iPart), chromaRenderer, chromaStyle))))
}
// build navigation
navMap := make(map[string]*NavElement)
navSlice := make([]*NavElement, 0)
navActive := make([]*NavElement, 0)
node.buildNavigation(&navMap, &navSlice, &navActive)
// read yaml header as data for template
ctx := NewContext()
ctx["This"] = newConfig.This
ctx["Meta"] = newConfig.Meta
ctx["Markdown"] = newConfig.Markdown
ctx["Data"] = newConfig.Data
ctx["ColMap"] = node.root.ColMap // root as NavMap and NavSlice, for sub go to NavElement.ColMap
ctx["NavMap"] = navMap
ctx["NavSlice"] = navSlice
ctx["NavActive"] = navActive
ctx["Body"] = pongo2.AsSafeValue(string(helper.RenderMarkdown(md, chromaRenderer, chromaStyle)))
ctx["BodyParts"] = htmlParts
ctx["CurrentPath"] = node.CurrentNavPath()
// set active nav element
if len(navActive) > 0 {
ctx["NavElement"] = navActive[len(navActive)-1]
} else {
// if no active path to content, we are in root dir
ctx["NavElement"] = &NavElement{
GoTo: node.BackToRootPath(),
Active: true,
ColMap: node.ColMap,
Data: node.Config.Data,
This: node.Config.This,
SubMap: &navMap,
SubSlice: &navSlice,
}
}
return newConfig, &ctx
}
// ProcessContent walks recursivly through the input paths and processes all files for output
func (node *TreeNode) ProcessContent() {
helper.CreateDirectory(node.OutputPath)
curNavPath := strings.TrimPrefix(node.OutputPath, Config.Directories.Output)
curNavPath = strings.TrimPrefix(curNavPath, "/")
curNavPath = path.Clean(curNavPath)
if curNavPath == "." {
curNavPath = ""
}
if node.root != node {
// write htaccess for rewrites, root will be written in WriteWebserverConfig()
goTo := node.Config.This.GoTo
if goTo != nil && *goTo != "" {
goToFixed := *goTo
if strings.HasPrefix(goToFixed, "/") {
goToFixed = helper.BackToRoot(curNavPath) + goToFixed
goToFixed = node.BackToRootPath() + goToFixed
}
goToFixed = path.Clean(goToFixed)
@ -91,82 +171,63 @@ func (node *TreeNode) ProcessContent() {
}
for _, file := range node.InputFiles {
var input []byte
inFile := "InputString"
if file != "" {
inFile = node.InputPath + "/" + file
helper.Log.Debugf("reading file: %s", inFile)
var err error
input, err = ioutil.ReadFile(inFile)
if err != nil {
helper.Log.Panicf("could not read '%s':%s", inFile, err)
}
helper.Log.Infof("processing input file '%s'", inFile)
} else {
// use input string if available and input filename == ""
var inputString *string
if i := node.Config.Index; i != nil {
inputString = i.InputString
}
if inputString != nil {
helper.Log.Debugf("using input string instead of file")
input = []byte(*inputString)
}
}
newConfig := new(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)
helper.Log.Debugf("found yaml header in '%s', merging config", inFile)
err := yaml.Unmarshal(yamlData, newConfig)
if err != nil {
helper.Log.Panicf("could not parse YAML header from '%s': %s", inFile, err)
}
helper.Log.Debug("merging config with upper config")
oldThis := newConfig.This
helper.Merge(newConfig, node.Config)
newConfig.This = oldThis
helper.Log.Debug(spew.Sdump(newConfig))
input = regex.ReplaceAll(input, []byte(""))
} else {
helper.Merge(newConfig, node.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 {
helper.Log.Panicf("could not compile filename.ignore regexp '%s' for file '%s': %s", *ignoreRegex, inFile, err)
if file != "" {
inFile = node.InputPath + "/" + file
var ignoreRegex *string
if f := node.Config.Filename; f != nil {
ignoreRegex = f.Ignore
}
if ignoreRegex != nil && *ignoreRegex != "" {
regex, err := regexp.Compile(*ignoreRegex)
if err != nil {
helper.Log.Panicf("could not compile filename.ignore regexp '%s' for file '%s': %s", *ignoreRegex, inFile, err)
}
ignoreFile = regex.MatchString(file)
}
ignoreFile = regex.MatchString(file)
}
if ignoreFile {
helper.Log.Infof("ignoring file '%s', because of filename.ignore", inFile)
} else {
var input []byte
if file != "" {
helper.Log.Debugf("reading file: %s", inFile)
var err error
input, err = ioutil.ReadFile(inFile)
if err != nil {
helper.Log.Panicf("could not read '%s':%s", inFile, err)
}
helper.Log.Infof("processing input file '%s'", inFile)
} else {
// use input string if available and input filename == ""
var inputString *string
if i := node.Config.Index; i != nil {
inputString = i.InputString
}
if inputString != nil {
helper.Log.Debugf("using input string instead of file")
input = []byte(*inputString)
}
}
newConfig, ctx := node.processMarkdownWithHeader(input, inFile)
// build output filename
outputFilename := file
var stripRegex *string
var outputExt *string
if f := newConfig.Filename; f != nil {
stripRegex = f.Strip
outputExt = f.OutputExtension
}
var indexInputFile *string
var indexOutputFile *string
if i := newConfig.Index; i != nil {
@ -194,71 +255,14 @@ func (node *TreeNode) ProcessContent() {
outFile := node.OutputPath + "/" + outputFilename
helper.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)
chromaRenderer := false
chromaStyle := "monokai"
if m := newConfig.Markdown; m != nil {
if m.ChromaRenderer != nil && *m.ChromaRenderer {
chromaRenderer = true
}
if m.ChromaStyle != nil && *m.ChromaStyle != "" {
chromaStyle = *m.ChromaStyle
}
}
for _, iPart := range inputParts {
htmlParts = append(htmlParts,
pongo2.AsSafeValue(
string(helper.RenderMarkdown([]byte(iPart), chromaRenderer, chromaStyle))))
}
// build navigation
navMap := make(map[string]*NavElement)
navSlice := make([]*NavElement, 0)
navActive := make([]*NavElement, 0)
buildNavigation(node.root, &navMap, &navSlice, &navActive, curNavPath)
// read yaml header as data for template
ctx := NewContext()
ctx["This"] = newConfig.This
ctx["Meta"] = newConfig.Meta
ctx["Markdown"] = newConfig.Markdown
ctx["Data"] = newConfig.Data
ctx["ColMap"] = node.root.ColMap // root as NavMap and NavSlice, for sub go to NavElement.ColMap
ctx["NavMap"] = navMap
ctx["NavSlice"] = navSlice
ctx["NavActive"] = navActive
ctx["Body"] = pongo2.AsSafeValue(string(helper.RenderMarkdown(input, chromaRenderer, chromaStyle)))
ctx["BodyParts"] = htmlParts
ctx["CurrentPath"] = curNavPath
// set active nav element
if len(navActive) > 0 {
ctx["NavElement"] = navActive[len(navActive)-1]
} else {
// if no active path to content, we are in root dir
ctx["NavElement"] = &NavElement{
GoTo: helper.BackToRoot(curNavPath),
Active: true,
ColMap: node.ColMap,
Data: node.Config.Data,
This: node.Config.This,
SubMap: &navMap,
SubSlice: &navSlice,
}
}
helper.Log.Debugf("rendering template '%s' for '%s'", *newConfig.Template, outFile)
templateFilename := *newConfig.Template
result, err := renderTemplate(*newConfig.Template, node, 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 = node.fixAssetsPath(result)
helper.Log.Noticef("writing to output file: %s", outFile)
err = ioutil.WriteFile(outFile, []byte(result), 0644)

View File

@ -25,7 +25,11 @@ type NavElement struct {
}
// buildNavigation builds the navigation trees for use in templates
func buildNavigation(tree *TreeNode, curNavMap *map[string]*NavElement, curNavSlice *[]*NavElement, navActive *[]*NavElement, activeNav string) {
func (node *TreeNode) buildNavigation(curNavMap *map[string]*NavElement, curNavSlice *[]*NavElement, navActive *[]*NavElement) {
buildNavigationRecursive(node.root, curNavMap, curNavSlice, navActive, node.CurrentNavPath(), node.BackToRootPath())
}
func buildNavigationRecursive(tree *TreeNode, curNavMap *map[string]*NavElement, curNavSlice *[]*NavElement, navActive *[]*NavElement, activeNav string, backToRoot string) {
for _, el := range tree.Sub {
if el.Hidden {
continue // ignore hidden nav points from collections
@ -88,8 +92,7 @@ func buildNavigation(tree *TreeNode, curNavMap *map[string]*NavElement, curNavSl
if activeNav != "" && activeNav != "/" {
// calculate relative path
bToRoot := helper.BackToRoot(activeNav)
navEl.GoTo = bToRoot + navEl.GoTo
navEl.GoTo = backToRoot + navEl.GoTo
navEl.GoTo = path.Clean(navEl.GoTo)
}
@ -98,6 +101,6 @@ func buildNavigation(tree *TreeNode, curNavMap *map[string]*NavElement, curNavSl
*curNavSlice = append(*curNavSlice, &navEl)
}
buildNavigation(el, &subMap, &subSlice, navActive, activeNav)
buildNavigationRecursive(el, &subMap, &subSlice, navActive, activeNav, backToRoot)
}
}

View File

@ -3,36 +3,58 @@ package mark2web
import (
"path"
"strings"
"gitbase.de/apairon/mark2web/pkg/helper"
)
// ResolveNavPath fixes nav target relative to current navigation path
func ResolveNavPath(target string) string {
curNavPath := (*CurrentContext)["CurrentPath"].(string)
func (node *TreeNode) ResolveNavPath(target string) string {
if strings.HasPrefix(target, "/") {
target = helper.BackToRoot(curNavPath) + target
target = node.BackToRootPath() + target
}
target = path.Clean(target)
return target
}
// ResolveOutputPath fixes output directory relative to current navigation path
func ResolveOutputPath(target string) string {
func (node *TreeNode) ResolveOutputPath(target string) string {
if strings.HasPrefix(target, "/") {
target = Config.Directories.Output + "/" + target
} else {
target = CurrentTreeNode.OutputPath + "/" + target
target = node.OutputPath + "/" + target
}
return path.Clean(target)
}
// ResolveInputPath fixes input directory relative to current navigation path
func ResolveInputPath(target string) string {
func (node *TreeNode) ResolveInputPath(target string) string {
if strings.HasPrefix(target, "/") {
target = Config.Directories.Input + "/" + target
} else {
target = CurrentTreeNode.InputPath + "/" + target
target = node.InputPath + "/" + target
}
return path.Clean(target)
}
// CurrentNavPath is current navigation path for this node
func (node *TreeNode) CurrentNavPath() string {
curNavPath := strings.TrimPrefix(node.OutputPath, Config.Directories.Output)
curNavPath = strings.TrimPrefix(curNavPath, "/")
curNavPath = path.Clean(curNavPath)
if curNavPath == "." {
curNavPath = ""
}
return curNavPath
}
// BackToRootPath builds ../../ string
func (node *TreeNode) BackToRootPath() string {
curNavPath := node.CurrentNavPath()
tmpPath := ""
if curNavPath != "" {
for i := strings.Count(curNavPath, "/") + 1; i > 0; i-- {
tmpPath += "../"
}
}
return tmpPath
}

View File

@ -10,23 +10,16 @@ Data:
---
# Installation
Damit die korrekten Versionsinformationen dynamisch in das finale mark2web-Binary eingefügt wurde, ist eine manuelle Installation aus dem Git-Repository sinnvoll.
Da die benötigten Pakete über die Go "vendor"-Funktionalität eingebunden sind ist ein `git submodule --init --recursive` nötig, wie im folgenden Abschnitt zu sehen ist:
```sh
mkdir -p $GOPATH/src/gitbase.de/apairon
git clone https://gitbase.de/apairon/mark2web.git $GOPATH/src/gitbase.de/apairon/mark2web
go get -v gitbase.de/apairon/mark2web/cmd/mark2web
cd $GOPATH/src/gitbase.de/apairon/mark2web
git submodule update --init --recursive
./build.sh
# setze Versioninformationen ins Binary
pkg=$GOPATH/src/gitbase.de/apairon/mark2web
go install -v -ldflags "-X main.Version=`cat $pkg/build/VERSION` -X main.GitHash=`git --git-dir $pkg/.git rev-parse HEAD` -X main.BuildTime=`date -u '+%Y-%m-%d_%I:%M:%S%p'`" gitbase.de/apairon/mark2web/cmd/mark2web
```
---
Eine Installation über `go install gitbase.de/apairon/mark2web` wird derzeit noch nicht unterstützt, da dabei die Informationen für `mark2web -version` nicht generiert werden.
## Releases
Vorkompilierte Binaries finden Sie auf der [Releases-Seite auf gitbase.de](https://gitbase.de/apairon/mark2web/releases).

Binary file not shown.

Before

Width:  |  Height:  |  Size: 790 KiB

View File

@ -1,150 +0,0 @@
---
Data:
background: /img/wire.jpg
slider:
- img: /img/wire.jpg
alt:
opacity: 1
---
# Konfiguration
Die Konfigurationsdatein sind im YAML-Format gehalten (siehe: [Wikipedia](https://de.wikipedia.org/wiki/YAML)).
## globale Einstellungen
Die obersten Verzeichnis sich befindende Datei `config.yml` kann z.B. folgenden Inhalt haben:
```yaml
Webserver:
Type: "apache"
Assets:
FromPath: "assets"
ToPath: "assets"
Action: "copy"
FixTemplate:
Find: "\\.\\./assets/"
Replace: ""
OtherFiles:
Action: "copy"
```
### Sektion `Webserver:`
#### `Type:`
Derzeit wird hier nur der Wert `apache` unterstützt. Ist dieser Wert gesetzt werden automatische .htaccess-Dateien mit mod_rewrite-Anweisungen generiert, die eine saubere Weiterleitung bei entsprechenden Konfig-Anweisungen im `content`-Verzeichnis ermöglichen.
### Sektion `Assets:`
`Assets:` steuert, wie mit Bild/JS/CSS Dateien umgegangen werden soll.
#### `FromPath:`
Lage des Asset-Verzeichnis unterhalb des `content`-Verzeichnis
#### `ToPath:`
Zielverzeichnis im Ausgabe-Verzeichnis der fertig generierten Website
#### `Action:`
Derzeit nur `copy`, also das Kopieren der Dateien und Unterordner ins Zielverzeichnis
#### `FixTemplate:`
Wenn hier `Find:` (regulärer Ausdruck) und `Replace:` (Ersetzung) angeben sind, werden die gefundenden Pfadangaben in der generierten HTML-Dateien durch den korrekten relativen Pfad zum Asset-Verzeichnis ersetzt.
### Sektion `OtherFiles:`
`OtherFiles:` definiert, wie mit anderen Dateien innerhalb des `content`-Verzeichnis umgegangen werden soll.
#### `Action:`
Derzeit nur `copy`, also das Kopieren der Dateien in das entsprechende Unterverzeichnis im Ausgabe-Verzeichnis
---
## Konfiguration im `content`-Verzeichnis
Im `content`-Verzeichnis, sowie in jedem Unterverzeichnis unterhalb von `content` kann sich eine `config.yml`-Datei befinden, wie aus folgendem Beispiel:
```yaml
This:
GoTo: "/de/service/impressum/"
Navname: "Impressumsverweis"
```
oder
```yaml
This:
Navname: "FAQ's"
Data:
slogan: "Wer nicht fragt, bekommt keine Antwort."
Template: "base.html"
Index:
InputFile: "README.md"
OutputFile: "index.html"
Meta:
Title: "Fragen und Antworten"
Description: "Dies ist die Fragen und Antworten Unterseite."
Keywords: "FAQ, Fragen, Antworten"
Data:
background: "bg.jpg"
slider:
- img: "assets/img/slider1.jpg"
alt: "Alternativtext 1"
- img: "assets/img/slider2.jpg"
alt: "Alternativtext 2"
- img: "assets/img/slider3.jpg"
alt: "Alternativtext 3"
```
### `This:` Sektion
Sämtlich Werte unterhalb dieser Sektion gelten nur für den Inhalt, bzw. Navigationspunkt in dessen Ordner sich die `config.yml` befindet. Die Werte werden nicht an Unterordner wertervererbt.
#### `GoTo:`
Falls der Navigationspunkt selbst keinen Inhalt darstellen soll, sondern nur weiterleiten soll, so wird hier das Weiterleitungsziel eingegeben.
Das Ziel ist der absolute (startend mit `/`) oder relative Pfad zum Zielnavigationspunkt.
Die Schreibweise des Pfades ist so zu verwenden, wie der Pfad nach Umschreibung und Säuberung des Pfades im Zielverzeichnis dargestellt wird.
Aus `de/mainnav/03_Fragen und Antworten` wird also z.B. `de/mainnav/fragen-und-antworten`.
#### `Navname:`
Dieser Wert überschreibt den aus dem Ordnernamen automatisch abgeleiteten Navigationspunkt-Namen. Dies ist zum Beispiel dann nützlich, wenn Sonderzeichen im Verzeichnisnamen nicht vorkommen sollen, aber im Namen des Navigationspunkts gebraucht werden.
#### `Data:`
Unterhalb von `Data:` können beliebige Datenstrukturen erfasst werden. Da diese Struktur unterhalb von `This:` angeordnet ist, werden auch die Daten nicht weiter an Unterordner vererbt.
Hier können z.B. Informationen zum Navigationspunkt abgelegt werden, die im Template Zusatzinformationen darstellen (z.B. ein Slogan zu einem Navigationspunkt).
### `Meta:` Sektion
Unter `Title:`, `Description:` und `Keywords:` werden die typischen Metaangaben abgelegt, die im
```html
<head>
...
</head>
```
übllicherweise Verwendung finden. Die entsprechenden Platzhalter stehen im Template zur Verfügung.
`Meta:` vererbt seine individuellen Informationen an die Unterordner weiter, sofern diese dort nicht selbst in einer `config.yml` oder im Kopf der Markdown-Datei definiert sind.
### `Data:` Sektion
`Data:` an dieser Stelle kann, wie auch `Data:` unterhalb von `This:`, beliebige Daten aufnehmen. Die Daten hier allerdings werden an Unterordner weitervererbt, sofern diese nicht dort oder in der Markdown-Datei selbst festegelegt überschrieben wurden.

View File

@ -1,3 +0,0 @@
This:
Data:
teaser: Globale Konfiguration und individuelle Content-Einstellungen

View File

@ -0,0 +1,16 @@
---
Template: base_doc.html
Data:
background: /img/wire.jpg
slider:
- img: /img/wire.jpg
alt:
opacity: 1
---
# Konfiguration
Die Konfigurationsdatein sind im YAML-Format gehalten (siehe: [Wikipedia](https://de.wikipedia.org/wiki/YAML)).

View File

@ -0,0 +1,17 @@
Die obersten Verzeichnis sich befindende Datei `config.yml` kann z.B. folgenden Inhalt haben:
```yaml
Webserver:
Type: "apache"
Assets:
FromPath: "assets"
ToPath: "assets"
Action: "copy"
FixTemplate:
Find: "\\.\\./assets/"
Replace: ""
OtherFiles:
Action: "copy"
```

View File

@ -0,0 +1,5 @@
---
Data:
Version: "ab v1.0"
---

View File

@ -0,0 +1,7 @@
---
Data:
Version: "ab v1.0"
---
Derzeit wird hier nur der Wert `apache` unterstützt. Ist dieser Wert gesetzt werden automatische .htaccess-Dateien mit mod_rewrite-Anweisungen generiert, die eine saubere Weiterleitung bei entsprechenden Konfig-Anweisungen im `content`-Verzeichnis ermöglichen.

View File

@ -0,0 +1,7 @@
---
Data:
Version: "ab v1.0"
---
`Assets:` steuert, wie mit Bild/JS/CSS Dateien umgegangen werden soll.

View File

@ -0,0 +1,7 @@
---
Data:
Version: "ab v1.0"
---
Lage des Asset-Verzeichnis unterhalb des `content`-Verzeichnis

View File

@ -0,0 +1,7 @@
---
Data:
Version: "ab v1.0"
---
Zielverzeichnis im Ausgabe-Verzeichnis der fertig generierten Website

View File

@ -0,0 +1,7 @@
---
Data:
Version: "ab v1.0"
---
Derzeit nur `copy`, also das Kopieren der Dateien und Unterordner ins Zielverzeichnis

View File

@ -0,0 +1,7 @@
---
Data:
Version: "ab v1.0"
---
Wenn hier `Find:` (regulärer Ausdruck) und `Replace:` (Ersetzung) angeben sind, werden die gefundenden Pfadangaben in der generierten HTML-Dateien durch den korrekten relativen Pfad zum Asset-Verzeichnis ersetzt.

View File

@ -0,0 +1,7 @@
---
Data:
Version: "ab v1.0"
---
`OtherFiles:` definiert, wie mit anderen Dateien innerhalb des `content`-Verzeichnis umgegangen werden soll.

View File

@ -0,0 +1,7 @@
---
Data:
Version: "ab v1.0"
---
Derzeit nur `copy`, also das Kopieren der Dateien in das entsprechende Unterverzeichnis im Ausgabe-Verzeichnis

View File

@ -0,0 +1,45 @@
---
Data:
Version: "ab v1.0"
---
Im `content`-Verzeichnis, sowie in jedem Unterverzeichnis unterhalb von `content` kann sich eine `config.yml`-Datei befinden, wie aus folgendem Beispiel:
```yaml
This:
GoTo: "/de/service/impressum/"
Navname: "Impressumsverweis"
```
oder
```yaml
This:
Navname: "FAQ's"
Data:
slogan: "Wer nicht fragt, bekommt keine Antwort."
Template: "base.html"
Index:
InputFile: "README.md"
OutputFile: "index.html"
Meta:
Title: "Fragen und Antworten"
Description: "Dies ist die Fragen und Antworten Unterseite."
Keywords: "FAQ, Fragen, Antworten"
Data:
background: "bg.jpg"
slider:
- img: "assets/img/slider1.jpg"
alt: "Alternativtext 1"
- img: "assets/img/slider2.jpg"
alt: "Alternativtext 2"
- img: "assets/img/slider3.jpg"
alt: "Alternativtext 3"
```

View File

@ -0,0 +1,7 @@
---
Data:
Version: "ab v1.0"
---
Sämtlich Werte unterhalb dieser Sektion gelten nur für den Inhalt, bzw. Navigationspunkt in dessen Ordner sich die `config.yml` befindet. Die Werte werden nicht an Unterordner wertervererbt.

View File

@ -0,0 +1,10 @@
---
Data:
Version: "ab v1.0"
---
Falls der Navigationspunkt selbst keinen Inhalt darstellen soll, sondern nur weiterleiten soll, so wird hier das Weiterleitungsziel eingegeben.
Das Ziel ist der absolute (startend mit `/`) oder relative Pfad zum Zielnavigationspunkt.
Die Schreibweise des Pfades ist so zu verwenden, wie der Pfad nach Umschreibung und Säuberung des Pfades im Zielverzeichnis dargestellt wird.
Aus `de/mainnav/03_Fragen und Antworten` wird also z.B. `de/mainnav/fragen-und-antworten`.

View File

@ -0,0 +1,7 @@
---
Data:
Version: "ab v1.0"
---
Dieser Wert überschreibt den aus dem Ordnernamen automatisch abgeleiteten Navigationspunkt-Namen. Dies ist zum Beispiel dann nützlich, wenn Sonderzeichen im Verzeichnisnamen nicht vorkommen sollen, aber im Namen des Navigationspunkts gebraucht werden.

View File

@ -0,0 +1,8 @@
---
Data:
Version: "ab v1.0"
---
Unterhalb von `Data:` können beliebige Datenstrukturen erfasst werden. Da diese Struktur unterhalb von `This:` angeordnet ist, werden auch die Daten nicht weiter an Unterordner vererbt.
Hier können z.B. Informationen zum Navigationspunkt abgelegt werden, die im Template Zusatzinformationen darstellen (z.B. ein Slogan zu einem Navigationspunkt).

View File

@ -0,0 +1,17 @@
---
Data:
Version: "ab v1.0"
---
Unter `Title:`, `Description:` und `Keywords:` werden die typischen Metaangaben abgelegt, die im
```html
<head>
...
</head>
```
übllicherweise Verwendung finden. Die entsprechenden Platzhalter stehen im Template zur Verfügung.
`Meta:` vererbt seine individuellen Informationen an die Unterordner weiter, sofern diese dort nicht selbst in einer `config.yml` oder im Kopf der Markdown-Datei definiert sind.

View File

@ -0,0 +1,7 @@
---
Data:
Version: "ab v1.0"
---
`Data:` an dieser Stelle kann, wie auch `Data:` unterhalb von `This:`, beliebige Daten aufnehmen. Die Daten hier allerdings werden an Unterordner weitervererbt, sofern diese nicht dort oder in der Markdown-Datei selbst festegelegt überschrieben wurden.

View File

@ -0,0 +1,9 @@
This:
Data:
teaser: Globale Konfiguration und individuelle Content-Einstellungen
Collections:
- Name: doccoll
Directory:
Path: "."
MatchFilename: "^_\\d+(?P<lowdash>_*)(?P<title>.+)\\.md"
ReverseOrder: False

View File

@ -292,4 +292,14 @@ code.language-mermaid svg {
border-radius: 5px;
display:inline-block;
color: #fff;
}
.versionBadge {
text-align: right;
color: #444;
font-size: 10px;
padding: 3px;
background: #ddd;
border: #444 solid 1px;
border-radius: 5px;
}

View File

@ -0,0 +1,19 @@
{% extends 'base.html' %}
{% block part0 %}
{{ Body }}
{% endblock part0 %}
{% block part1 %}
{% for e in NavElement.ColMap.doccoll %}
{% with e.FilenameMatch.lowdash|count + 1 as h %}
<h{{ h }}>
{{ e.FilenameMatch.title }}
{% if e.Data.Version %}
<span class="versionBadge">{{ e.Data.Version }}</span>
{% endif %}
</h{{ h }}>
{% endwith %}
{{ e.Body }}
{% endfor %}
{% endblock part1 %}

View File

@ -0,0 +1,5 @@
function count(el, param) {
return el.length
}
module.exports = count;