Compare commits
51 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6b34509d9a
|
||
|
|
8210e16305
|
||
|
|
2f114885ac
|
||
|
|
9f45010228
|
||
|
|
5f8e267bbf
|
||
|
|
6ad0bb6ed9
|
||
|
|
7bc7d50c0c
|
||
|
|
de7931acda
|
||
|
|
4041868a9f
|
||
|
|
6c3f985d1f
|
||
|
|
2f2454ee54
|
||
|
|
a2eaa3f4b4
|
||
|
|
9c0d959181
|
||
|
|
567cd1646e
|
||
|
|
38820e6baf
|
||
|
|
e3dfbcb591
|
||
|
|
7f9910244b
|
||
|
|
7fa0c67f6f
|
||
|
|
a8ad0f0b59
|
||
|
|
09c6176ea7
|
||
|
|
3f4ab3b9c8
|
||
|
|
6c59e6684f
|
||
|
|
aa6ade5657
|
||
|
|
c4e6a2f409
|
||
|
|
3a467134b3
|
||
|
|
15af8e487c
|
||
|
|
cc5870dcec
|
||
|
|
6e3688d713
|
||
|
|
8e84901465
|
||
|
|
079534ab71
|
||
|
|
9e2f16dde9
|
||
|
|
3726be5b58
|
||
|
|
cb55dcd42b
|
||
|
|
5acc4083aa
|
||
|
|
e943271561
|
||
|
|
d78ecf4682
|
||
|
|
39f1932cc3
|
||
|
|
650bdc2fd6
|
||
|
|
8f1345d4aa
|
||
|
|
4c2a13d6b5
|
||
|
|
4e12a6e6e3
|
||
|
|
e99b4326a8
|
||
|
|
9f499ea1de
|
||
|
|
0aefb5c758
|
||
|
|
bd3d04b061
|
||
|
|
a36b0e21c9
|
||
|
|
1f71e0467f
|
||
|
|
9d815a2a9b
|
||
|
|
8d0f8b40bf
|
||
|
|
232902ea63
|
||
|
|
e79a527ac6
|
18
.drone.yml
@@ -16,12 +16,16 @@ steps:
|
||||
- git submodule update --init --recursive
|
||||
- git fetch --tags
|
||||
- ./build.sh
|
||||
when:
|
||||
event: [ push, tag ]
|
||||
|
||||
- name: test with example content
|
||||
image: alpine
|
||||
commands:
|
||||
- ./dist/mark2web-`cat VERSION`-linux-amd64 -version
|
||||
- ./dist/mark2web-`cat VERSION`-linux-amd64 -in example -out example_out -create -logLevel debug
|
||||
when:
|
||||
event: [ push, tag ]
|
||||
|
||||
- name: build for freebsd
|
||||
image: golang:latest
|
||||
@@ -72,12 +76,22 @@ steps:
|
||||
- name: deploy website
|
||||
image: apairon/mark2web:latest
|
||||
pull: never
|
||||
environment:
|
||||
RSYNC_PASS:
|
||||
from_secret: rsync_pass
|
||||
commands:
|
||||
- /mark2web -version
|
||||
- /mark2web -in website -out html -create -logLevel debug
|
||||
- /mark2web -in website -out html -create -logLevel info
|
||||
- '
|
||||
rsync -rlcgD -i -u -v --stats
|
||||
--delete
|
||||
-e "sshpass -p $${RSYNC_PASS} ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -p 22222"
|
||||
html/
|
||||
basiskonfiguration_mark2web_rsync@deploy.bc1.basehosts.de:./'
|
||||
when:
|
||||
branch: [ master ]
|
||||
event: [ push ]
|
||||
event: [ promote, push ]
|
||||
target: [ "", website ]
|
||||
|
||||
- name: prepare release
|
||||
image: apairon/mark2web:latest
|
||||
|
||||
39
.gitmodules
vendored
@@ -25,9 +25,6 @@
|
||||
[submodule "vendor/github.com/davecgh/go-spew"]
|
||||
path = vendor/github.com/davecgh/go-spew
|
||||
url = https://github.com/davecgh/go-spew
|
||||
[submodule "vendor/github.com/aymerick/raymond"]
|
||||
path = vendor/github.com/aymerick/raymond
|
||||
url = https://github.com/aymerick/raymond
|
||||
[submodule "vendor/github.com/imdario/mergo"]
|
||||
path = vendor/github.com/imdario/mergo
|
||||
url = https://github.com/imdario/mergo
|
||||
@@ -40,9 +37,33 @@
|
||||
[submodule "vendor/github.com/juju/errors"]
|
||||
path = vendor/github.com/juju/errors
|
||||
url = https://github.com/juju/errors
|
||||
[submodule "vendor/github.com/gosimple/slug"]
|
||||
path = vendor/github.com/gosimple/slug
|
||||
url = https://github.com/gosimple/slug
|
||||
[submodule "vendor/github.com/rainycape/unidecode"]
|
||||
path = vendor/github.com/rainycape/unidecode
|
||||
url = https://github.com/rainycape/unidecode
|
||||
[submodule "vendor/github.com/flosch/pongo2-addons"]
|
||||
path = vendor/github.com/flosch/pongo2-addons
|
||||
url = https://github.com/flosch/pongo2-addons
|
||||
[submodule "vendor/github.com/extemporalgenome/slug"]
|
||||
path = vendor/github.com/extemporalgenome/slug
|
||||
url = https://github.com/extemporalgenome/slug
|
||||
[submodule "vendor/golang.org/x/text"]
|
||||
path = vendor/golang.org/x/text
|
||||
url = https://go.googlesource.com/text
|
||||
[submodule "vendor/github.com/flosch/go-humanize"]
|
||||
path = vendor/github.com/flosch/go-humanize
|
||||
url = https://github.com/flosch/go-humanize
|
||||
[submodule "vendor/github.com/russross/blackfriday"]
|
||||
path = vendor/github.com/russross/blackfriday
|
||||
url = https://github.com/russross/blackfriday
|
||||
[submodule "vendor/github.com/robertkrimen/otto"]
|
||||
path = vendor/github.com/robertkrimen/otto
|
||||
url = https://github.com/robertkrimen/otto
|
||||
[submodule "vendor/gopkg.in/sourcemap.v1"]
|
||||
path = vendor/gopkg.in/sourcemap.v1
|
||||
url = https://gopkg.in/sourcemap.v1
|
||||
[submodule "vendor/github.com/ddliu/motto"]
|
||||
path = vendor/github.com/ddliu/motto
|
||||
url = https://github.com/ddliu/motto
|
||||
[submodule "vendor/github.com/disintegration/imaging"]
|
||||
path = vendor/github.com/disintegration/imaging
|
||||
url = https://github.com/disintegration/imaging
|
||||
[submodule "vendor/golang.org/x/image"]
|
||||
path = vendor/golang.org/x/image
|
||||
url = https://go.googlesource.com/image
|
||||
|
||||
9
.vscode/settings.json
vendored
@@ -1,5 +1,14 @@
|
||||
{
|
||||
"files.associations": {
|
||||
"**/templates/*.html": "django-html"
|
||||
},
|
||||
"saveAndRun": {
|
||||
"commands": [
|
||||
{
|
||||
"match": "website/.*",
|
||||
"cmd": "time mark2web -in ${workspaceRoot}/website -out ${workspaceRoot}/html -create",
|
||||
"silent": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM alpine
|
||||
RUN apk update && apk add openssh-client sshpass rsync gzip && rm -r /var/cache/
|
||||
RUN apk update && apk add ca-certificates openssh-client sshpass rsync gzip && rm -r /var/cache/
|
||||
ADD mark2web /
|
||||
CMD ["/mark2web"]
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
Dies ist das erste Release von *mark2web*.
|
||||
In diesem Release werden folgende Features unterstützt:
|
||||
NEUERUNGEN:
|
||||
|
||||
- Verabeitung von Markdown-Dateien für Website-Inhalte
|
||||
- Umwandlung der Ordnerstruktur des `content`-Verzeichnis in Navigationsbäume
|
||||
- Verarbeitung von Pongo2-Templates mit dem Inhalt zur finalen Website
|
||||
- Kopieren der Assets ins Zielverzeichnis
|
||||
- Anpassung des Asset-Pfads in den HTML-Dateien
|
||||
- `t=ZIEL_VERZEICHNIS` Parameter im `image_process` Filter
|
||||
|
||||
47
config/global.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// GlobalConfig is config which is used only once in root dir
|
||||
type GlobalConfig struct {
|
||||
Webserver struct {
|
||||
Type string `yaml:"Type"`
|
||||
} `yaml:"Webserver"`
|
||||
|
||||
Assets struct {
|
||||
FromPath string `yaml:"FromPath"`
|
||||
ToPath string `yaml:"ToPath"`
|
||||
Action string `yaml:"Action"`
|
||||
FixTemplate struct {
|
||||
Find string `yaml:"Find"`
|
||||
Replace string `yaml:"Replace"`
|
||||
} `yaml:"FixTemplate"`
|
||||
} `yaml:"Assets"`
|
||||
|
||||
OtherFiles struct {
|
||||
Action string `yaml:"Action"`
|
||||
} `yaml:"OtherFiles"`
|
||||
|
||||
Directories struct {
|
||||
Input string
|
||||
Output string
|
||||
}
|
||||
}
|
||||
|
||||
var Config = new(GlobalConfig)
|
||||
|
||||
func ReadGlobalConfig(filename string) error {
|
||||
data, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = yaml.Unmarshal(data, Config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
172
config/tree.go
Normal file
@@ -0,0 +1,172 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/imdario/mergo"
|
||||
)
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
// CollectionConfig describes a collection
|
||||
type CollectionConfig struct {
|
||||
Name *string `yaml:"Name"`
|
||||
URL *string `yaml:"URL"`
|
||||
NavTemplate *struct {
|
||||
EntriesAttribute string `yaml:"EntriesAttribute"`
|
||||
GoTo string `yaml:"GoTo"`
|
||||
Navname string `yaml:"Navname"`
|
||||
Body string `yaml:"Body"`
|
||||
DataKey string `yaml:"DataKey"`
|
||||
Hidden bool `yaml:"Hidden"`
|
||||
Template string `yaml:"Template"`
|
||||
//Recursive bool `yaml:"Recursive"`
|
||||
} `yaml:"NavTemplate"`
|
||||
}
|
||||
|
||||
// ThisPathConfig is struct for This in paths yaml
|
||||
type ThisPathConfig struct {
|
||||
Navname *string `yaml:"Navname"`
|
||||
GoTo *string `yaml:"GoTo"`
|
||||
Collections []*CollectionConfig `yaml:"Collections"`
|
||||
Data MapString `yaml:"Data"`
|
||||
}
|
||||
|
||||
// IndexConfig describes index input and output file
|
||||
type IndexConfig struct {
|
||||
InputFile *string `yaml:"InputFile"`
|
||||
InputString *string `yaml:"InputString"`
|
||||
OutputFile *string `yaml:"OutputFile"`
|
||||
}
|
||||
|
||||
// MetaData describes meta data for current site/tree node
|
||||
type MetaData struct {
|
||||
Title *string `yaml:"Title"`
|
||||
Description *string `yaml:"Description"`
|
||||
Keywords *string `yaml:"Keywords"`
|
||||
}
|
||||
|
||||
// DirnameConfig describes how to handle directory names
|
||||
type DirnameConfig struct {
|
||||
Strip *string `yaml:"Strip"`
|
||||
IgnoreForNav *string `yaml:"IgnoreForNav"`
|
||||
}
|
||||
|
||||
// FilenameConfig describes how to handle filenames
|
||||
type FilenameConfig struct {
|
||||
Strip *string `yaml:"Strip"`
|
||||
Ignore *string `yaml:"Ignore"`
|
||||
OutputExtension *string `yaml:"OutputExtension"`
|
||||
}
|
||||
|
||||
// MarkdownConfig describes markdown handling
|
||||
type MarkdownConfig struct {
|
||||
ChromaRenderer *bool `yaml:"ChromaRenderer"`
|
||||
ChromaStyle *string `yaml:"ChromaStyle"`
|
||||
}
|
||||
|
||||
// ImagingConfig defines parameter for imaging processing
|
||||
type ImagingConfig struct {
|
||||
Width int `yaml:"Width"`
|
||||
Height int `yaml:"Height"`
|
||||
Process string `yaml:"Process"`
|
||||
Anchor string `yaml:"Anchor"`
|
||||
Quality int `yaml:"Quality"`
|
||||
|
||||
TargetDir string `yaml:"-"`
|
||||
Filename string `yaml:"-"`
|
||||
Format string `yaml:"-"`
|
||||
}
|
||||
|
||||
// PathConfig of subdir
|
||||
type PathConfig struct {
|
||||
This ThisPathConfig `yaml:"This"`
|
||||
Template *string `yaml:"Template"`
|
||||
Index *IndexConfig `yaml:"Index"`
|
||||
Meta *MetaData `yaml:"Meta"`
|
||||
Path *DirnameConfig `yaml:"Path"`
|
||||
Filename *FilenameConfig `yaml:"Filename"`
|
||||
Markdown *MarkdownConfig `yaml:"Markdown"`
|
||||
Imaging *ImagingConfig `yaml:"Imaging"`
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
type ptrTransformer struct{}
|
||||
|
||||
func (t ptrTransformer) Transformer(typ reflect.Type) func(dst, src reflect.Value) error {
|
||||
if typ.Kind() == reflect.Ptr {
|
||||
return func(dst, src reflect.Value) error {
|
||||
if dst.CanSet() {
|
||||
if dst.IsNil() {
|
||||
dst.Set(src)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Merge merges 2 objects or maps
|
||||
func Merge(dst, src interface{}) error {
|
||||
return mergo.Merge(dst, src, mergo.WithTransformers(ptrTransformer{}))
|
||||
}
|
||||
28
helper/assets.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package helper
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"gitbase.de/apairon/mark2web/config"
|
||||
cpy "github.com/otiai10/copy"
|
||||
)
|
||||
|
||||
// ProcessAssets copies the assets from input to output dir
|
||||
func ProcessAssets() {
|
||||
switch config.Config.Assets.Action {
|
||||
case "copy":
|
||||
from := config.Config.Assets.FromPath
|
||||
to := config.Config.Assets.ToPath
|
||||
if !strings.HasPrefix(from, "/") {
|
||||
from = config.Config.Directories.Input + "/" + from
|
||||
}
|
||||
if !strings.HasPrefix(to, "/") {
|
||||
to = config.Config.Directories.Output + "/" + to
|
||||
}
|
||||
Log.Noticef("copying assets from '%s' to '%s'", from, to)
|
||||
err := cpy.Copy(from, to)
|
||||
if err != nil {
|
||||
Log.Panicf("could not copy assets from '%s' to '%s': %s", from, to, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
514
helper/content.go
Normal file
@@ -0,0 +1,514 @@
|
||||
package helper
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"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 newContext() pongo2.Context {
|
||||
return pongo2.Context{
|
||||
"fnRequest": RequestFn,
|
||||
"fnRender": RenderFn,
|
||||
|
||||
"AssetsPath": config.Config.Assets.ToPath,
|
||||
|
||||
"Timestamp": time.Now().Unix,
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
// handle collections
|
||||
for _, colConfig := range newConfig.This.Collections {
|
||||
if colConfig != nil {
|
||||
if colConfig.Name == nil || *colConfig.Name == "" {
|
||||
Log.Panicf("missing Name in collection config in '%s'", inPath)
|
||||
}
|
||||
if colConfig.URL == nil || *colConfig.URL == "" {
|
||||
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 {
|
||||
Log.Panicf("invalid template string for Collection Element.URL in '%s': %s", inPath, err)
|
||||
}
|
||||
|
||||
colData := 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 {
|
||||
Log.Debug(spew.Sdump(colDataMap))
|
||||
Log.Panicf("invalid json data in [%s] from url '%s' for entries", navT.EntriesAttribute, url)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
entries, ok = colData.([]interface{})
|
||||
}
|
||||
if !ok {
|
||||
Log.Debug(spew.Sdump(colData))
|
||||
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 := config.Merge(&ctxE, ctx)
|
||||
if err != nil {
|
||||
Log.Panicf("could not merge context in '%s': %s", inPath, err)
|
||||
}
|
||||
var jsonCtx map[string]interface{}
|
||||
if jsonCtx, ok = colEl.(map[string]interface{}); !ok {
|
||||
Log.Debug(spew.Sdump(colEl))
|
||||
Log.Panicf("no json object for entry index %d from url '%s'", idx, url)
|
||||
}
|
||||
err = config.Merge(&ctxE, pongo2.Context(jsonCtx))
|
||||
if err != nil {
|
||||
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 {
|
||||
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 {
|
||||
Log.Panicf("invalid template string for NavTemplate.DataKey in '%s': %s", inPath, err)
|
||||
}
|
||||
}
|
||||
|
||||
goTo, err := pongo2.RenderTemplateString(navT.GoTo, ctxE)
|
||||
if err != nil {
|
||||
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, "..") {
|
||||
Log.Panicf("going back via .. in NavTemplate.GoTo forbidden in collection config in '%s': %s", inPath, goTo)
|
||||
}
|
||||
if goTo == "." {
|
||||
Log.Panicf("invalid config '.' for NavTemplate.GoTo in collection config in '%s'", inPath)
|
||||
}
|
||||
if goTo == "" {
|
||||
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 {
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// 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 := newContext()
|
||||
ctx["This"] = newConfig.This
|
||||
ctx["Meta"] = newConfig.Meta
|
||||
ctx["Data"] = newConfig.Data
|
||||
ctx["ColMap"] = rootConf.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(renderMarkdown(input, newConfig.Markdown)))
|
||||
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: BackToRoot(curNavPath),
|
||||
Active: true,
|
||||
ColMap: rootConf.ColMap,
|
||||
Data: rootConf.Config.Data,
|
||||
This: rootConf.Config.This,
|
||||
SubMap: &navMap,
|
||||
SubSlice: &navSlice,
|
||||
}
|
||||
}
|
||||
|
||||
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...)
|
||||
}
|
||||
24
helper/dir.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package helper
|
||||
|
||||
import "os"
|
||||
|
||||
// CreateDirectory creates direcory with all missing parents and panic if error
|
||||
func CreateDirectory(dir string) {
|
||||
Log.Debugf("trying to create output directory: %s", dir)
|
||||
|
||||
if dirH, err := os.Stat(dir); os.IsNotExist(err) {
|
||||
err := os.MkdirAll(dir, 0755)
|
||||
if err != nil {
|
||||
Log.Panicf("could not create output directory '%s': %s", dir, err)
|
||||
}
|
||||
Log.Noticef("created output directory: %s", dir)
|
||||
} else if dirH != nil {
|
||||
if dirH.IsDir() {
|
||||
Log.Noticef("output directory '%s' already exists", dir)
|
||||
} else {
|
||||
Log.Panicf("output directory '%s' is no directory", dir)
|
||||
}
|
||||
} else {
|
||||
Log.Panicf("unknown error for output directory '%s': %s", dir, err)
|
||||
}
|
||||
}
|
||||
52
helper/logger.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package helper
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/op/go-logging"
|
||||
)
|
||||
|
||||
// Log is global logger
|
||||
var Log = logging.MustGetLogger("myLogger")
|
||||
|
||||
// ConfigureLogger sets logger backend and level
|
||||
func ConfigureLogger(level string) {
|
||||
logBackend := logging.NewLogBackend(os.Stderr, "", 0)
|
||||
logBackendFormatter := logging.NewBackendFormatter(logBackend, logging.MustStringFormatter(
|
||||
`%{color}%{time:15:04:05.000} %{shortfunc} ▶ %{level:.4s} %{id:03x}%{color:reset} %{message}`,
|
||||
))
|
||||
logBackendLeveled := logging.AddModuleLevel(logBackendFormatter)
|
||||
logBackendLevel := logging.INFO
|
||||
switch level {
|
||||
case "debug":
|
||||
logBackendLevel = logging.DEBUG
|
||||
break
|
||||
|
||||
case "info":
|
||||
logBackendLevel = logging.INFO
|
||||
break
|
||||
|
||||
case "notice":
|
||||
logBackendLevel = logging.NOTICE
|
||||
break
|
||||
|
||||
case "warning":
|
||||
logBackendLevel = logging.WARNING
|
||||
break
|
||||
|
||||
case "error":
|
||||
logBackendLevel = logging.ERROR
|
||||
break
|
||||
|
||||
}
|
||||
logBackendLeveled.SetLevel(logBackendLevel, "")
|
||||
logging.SetBackend(logBackendLeveled)
|
||||
}
|
||||
|
||||
func init() {
|
||||
spew.Config.DisablePointerAddresses = true
|
||||
spew.Config.DisableCapacities = true
|
||||
spew.Config.DisableMethods = true
|
||||
spew.Config.DisablePointerMethods = true
|
||||
}
|
||||
103
helper/navigation.go
Normal file
@@ -0,0 +1,103 @@
|
||||
package helper
|
||||
|
||||
import (
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"gitbase.de/apairon/mark2web/config"
|
||||
)
|
||||
|
||||
// NavElement is one element with ist attributes and subs
|
||||
type NavElement struct {
|
||||
Navname string
|
||||
GoTo string
|
||||
Active bool
|
||||
|
||||
ColMap config.MapString
|
||||
|
||||
Data interface{}
|
||||
|
||||
This config.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 {
|
||||
if el.Hidden {
|
||||
continue // ignore hidden nav points from collections
|
||||
}
|
||||
|
||||
var ignNav *string
|
||||
if p := el.Config.Path; p != nil {
|
||||
ignNav = p.IgnoreForNav
|
||||
}
|
||||
if ignNav != nil && *ignNav != "" {
|
||||
regex, err := regexp.Compile(*ignNav)
|
||||
if err != nil {
|
||||
Log.Panicf("could not compile IngoreForNav regexp '%s' in '%s': %s", *ignNav, el.InputPath, err)
|
||||
}
|
||||
if regex.MatchString(path.Base(el.InputPath)) {
|
||||
Log.Debugf("ignoring input directory '%s' in navigation", el.InputPath)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
elPath := strings.TrimPrefix(el.OutputPath, config.Config.Directories.Output+"/")
|
||||
|
||||
subMap := make(map[string]*NavElement)
|
||||
subSlice := make([]*NavElement, 0)
|
||||
navEl := NavElement{
|
||||
Active: strings.HasPrefix(activeNav, elPath),
|
||||
Data: el.Config.Data,
|
||||
ColMap: el.ColMap,
|
||||
SubMap: &subMap,
|
||||
SubSlice: &subSlice,
|
||||
}
|
||||
|
||||
navEl.This = el.Config.This
|
||||
|
||||
if navEl.Active {
|
||||
// add to navActive level navigation
|
||||
currentLevel := strings.Count(activeNav, "/")
|
||||
if len(*navActive) <= currentLevel {
|
||||
// not registered
|
||||
*navActive = append(*navActive, &navEl)
|
||||
}
|
||||
}
|
||||
|
||||
n := el.Config.This.Navname
|
||||
if n != nil {
|
||||
navEl.Navname = *n
|
||||
}
|
||||
g := el.Config.This.GoTo
|
||||
if g != nil {
|
||||
if strings.HasPrefix(*g, "/") {
|
||||
// abslute
|
||||
navEl.GoTo = *g
|
||||
} else {
|
||||
// relative
|
||||
navEl.GoTo = elPath + "/" + *g
|
||||
}
|
||||
} else {
|
||||
navEl.GoTo = elPath + "/"
|
||||
}
|
||||
|
||||
if activeNav != "" && activeNav != "/" {
|
||||
// calculate relative path
|
||||
bToRoot := BackToRoot(activeNav)
|
||||
navEl.GoTo = bToRoot + navEl.GoTo
|
||||
navEl.GoTo = path.Clean(navEl.GoTo)
|
||||
}
|
||||
|
||||
(*curNavMap)[path.Base(el.OutputPath)] = &navEl
|
||||
if curNavSlice != nil {
|
||||
*curNavSlice = append(*curNavSlice, &navEl)
|
||||
}
|
||||
|
||||
BuildNavigation(el, &subMap, &subSlice, navActive, activeNav)
|
||||
}
|
||||
}
|
||||
103
helper/render.go
Normal file
@@ -0,0 +1,103 @@
|
||||
package helper
|
||||
|
||||
import (
|
||||
"log"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"gitbase.de/apairon/mark2web/config"
|
||||
"github.com/flosch/pongo2"
|
||||
)
|
||||
|
||||
var templateCache = make(map[string]*pongo2.Template)
|
||||
var currentContext *pongo2.Context
|
||||
var currentTreeNodeConfig *config.PathConfigTree
|
||||
var currentPathConfig *config.PathConfig
|
||||
var templateDir string
|
||||
|
||||
// BackToRoot builds ../../ string
|
||||
func BackToRoot(curNavPath string) string {
|
||||
tmpPath := ""
|
||||
if curNavPath != "" {
|
||||
for i := strings.Count(curNavPath, "/") + 1; i > 0; i-- {
|
||||
tmpPath += "../"
|
||||
}
|
||||
}
|
||||
return tmpPath
|
||||
}
|
||||
|
||||
// ResolveNavPath fixes nav target relative to current navigation path
|
||||
func ResolveNavPath(target string) string {
|
||||
curNavPath := (*currentContext)["CurrentPath"].(string)
|
||||
if strings.HasPrefix(target, "/") {
|
||||
target = BackToRoot(curNavPath) + target
|
||||
}
|
||||
target = path.Clean(target)
|
||||
return target
|
||||
}
|
||||
|
||||
// ResolveOutputPath fixes output directory relative to current navigation path
|
||||
func ResolveOutputPath(target string) string {
|
||||
if strings.HasPrefix(target, "/") {
|
||||
target = config.Config.Directories.Output + "/" + target
|
||||
} else {
|
||||
target = currentTreeNodeConfig.OutputPath + "/" + target
|
||||
}
|
||||
return path.Clean(target)
|
||||
}
|
||||
|
||||
// ResolveInputPath fixes input directory relative to current navigation path
|
||||
func ResolveInputPath(target string) string {
|
||||
if strings.HasPrefix(target, "/") {
|
||||
target = config.Config.Directories.Input + "/" + target
|
||||
} else {
|
||||
target = currentTreeNodeConfig.InputPath + "/" + target
|
||||
}
|
||||
return path.Clean(target)
|
||||
}
|
||||
|
||||
// SetTemplateDir sets base directory for searching template files
|
||||
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) {
|
||||
currentContext = ctx
|
||||
currentTreeNodeConfig = treeNodeConfig
|
||||
currentPathConfig = pathConfig
|
||||
templateFile := templateDir + "/" + filename
|
||||
template := templateCache[templateFile]
|
||||
if template == nil {
|
||||
var err error
|
||||
if template, err = pongo2.FromFile(templateFile); err != nil {
|
||||
log.Panicf("could not parse template '%s': %s", templateFile, err)
|
||||
} else {
|
||||
templateCache[templateFile] = template
|
||||
}
|
||||
}
|
||||
|
||||
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 != "" {
|
||||
Log.Debugf("fixing assets paths for path '%s'", curNavPath)
|
||||
repl := config.Config.Assets.FixTemplate.Replace
|
||||
toPath := config.Config.Assets.ToPath
|
||||
|
||||
bToRoot := 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) + "/"
|
||||
Log.Debugf("new assets paths: %s", repl)
|
||||
return regex.ReplaceAllString(str, repl)
|
||||
}
|
||||
|
||||
return str
|
||||
}
|
||||
377
helper/template_filters.go
Normal file
@@ -0,0 +1,377 @@
|
||||
package helper
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"gitbase.de/apairon/mark2web/config"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/ddliu/motto"
|
||||
"github.com/disintegration/imaging"
|
||||
"github.com/flosch/pongo2"
|
||||
_ "github.com/flosch/pongo2-addons"
|
||||
_ "github.com/robertkrimen/otto/underscore"
|
||||
)
|
||||
|
||||
func init() {
|
||||
err := pongo2.ReplaceFilter("markdown", MarkdownFilter)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
newFilters := map[string]pongo2.FilterFunction{
|
||||
"image_process": ImageProcessFilter,
|
||||
"relative_path": RelativePathFilter,
|
||||
"json": JSONFilter,
|
||||
"dump": DumpFilter,
|
||||
}
|
||||
for name, fn := range newFilters {
|
||||
err := pongo2.RegisterFilter(name, fn)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DumpFilter is a pongo2 filter, which returns a spew.Dump of the input
|
||||
func DumpFilter(in *pongo2.Value, param *pongo2.Value) (*pongo2.Value, *pongo2.Error) {
|
||||
dumpString := spew.Sdump(in.Interface())
|
||||
return pongo2.AsValue(string(dumpString)), nil
|
||||
}
|
||||
|
||||
// JSONFilter is a pongo2 filter, which returns a json string of the input
|
||||
func JSONFilter(in *pongo2.Value, param *pongo2.Value) (*pongo2.Value, *pongo2.Error) {
|
||||
pretty := false
|
||||
for _, s := range strings.Split(param.String(), ",") {
|
||||
switch s {
|
||||
case "pretty":
|
||||
pretty = true
|
||||
}
|
||||
}
|
||||
var err error
|
||||
var jsonBytes []byte
|
||||
if pretty {
|
||||
jsonBytes, err = json.MarshalIndent(in.Interface(), "", " ")
|
||||
|
||||
} else {
|
||||
jsonBytes, err = json.Marshal(in.Interface())
|
||||
}
|
||||
if err != nil {
|
||||
return nil, &pongo2.Error{
|
||||
Sender: "filter:json",
|
||||
OrigError: err,
|
||||
}
|
||||
}
|
||||
|
||||
return pongo2.AsSafeValue(string(jsonBytes)), nil
|
||||
}
|
||||
|
||||
// MarkdownFilter is a pongo2 filter, which converts markdown to html
|
||||
func MarkdownFilter(in *pongo2.Value, param *pongo2.Value) (*pongo2.Value, *pongo2.Error) {
|
||||
return pongo2.AsSafeValue(
|
||||
string(
|
||||
renderMarkdown(
|
||||
[]byte(in.String()),
|
||||
currentPathConfig.Markdown,
|
||||
))),
|
||||
nil
|
||||
}
|
||||
|
||||
func parseImageParams(str string) (*config.ImagingConfig, error) {
|
||||
p := config.ImagingConfig{}
|
||||
if str == "" {
|
||||
config.Merge(&p, currentPathConfig.Imaging)
|
||||
// Filename and Format are only valid for current image
|
||||
p.Filename = ""
|
||||
p.Format = ""
|
||||
return &p, nil
|
||||
}
|
||||
for _, s := range strings.Split(str, ",") {
|
||||
e := strings.Split(s, "=")
|
||||
if len(e) < 2 {
|
||||
return nil, fmt.Errorf("invalid image parameter: %s", s)
|
||||
}
|
||||
var err error
|
||||
switch e[0] {
|
||||
case "w":
|
||||
p.Width, err = strconv.Atoi(e[1])
|
||||
case "h":
|
||||
p.Height, err = strconv.Atoi(e[1])
|
||||
case "f":
|
||||
p.Filename = e[1]
|
||||
case "t":
|
||||
p.TargetDir = e[1]
|
||||
case "p":
|
||||
p.Process = e[1]
|
||||
case "a":
|
||||
p.Anchor = e[1]
|
||||
case "q":
|
||||
p.Quality, err = strconv.Atoi(e[1])
|
||||
if p.Quality < 0 || p.Quality > 100 {
|
||||
err = errors.New("q= must be between 1 and 100")
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid image parameter: %s", s)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not convert image parameter to correct value type for '%s': %s", s, err)
|
||||
}
|
||||
}
|
||||
return &p, nil
|
||||
}
|
||||
|
||||
func getImageFromURL(url string) (image.Image, string, error) {
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("could not get url '%s': %s", url, err)
|
||||
}
|
||||
|
||||
img, format, err := image.Decode(resp.Body)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("could read body from url '%s': %s", url, err)
|
||||
}
|
||||
|
||||
return img, format, nil
|
||||
}
|
||||
|
||||
// ImageProcessFilter read the image url and process parameters and saves the resized/processed image
|
||||
// param: w=WITDH,h=HEIGHT
|
||||
func ImageProcessFilter(in *pongo2.Value, param *pongo2.Value) (*pongo2.Value, *pongo2.Error) {
|
||||
imgSource := in.String()
|
||||
p, err := parseImageParams(param.String())
|
||||
if err != nil {
|
||||
return nil, &pongo2.Error{
|
||||
Sender: "filter:image_resize",
|
||||
OrigError: err,
|
||||
}
|
||||
}
|
||||
if p == nil {
|
||||
return nil, &pongo2.Error{
|
||||
Sender: "filter:image_resize",
|
||||
OrigError: errors.New("no imaging config defined"),
|
||||
}
|
||||
}
|
||||
|
||||
var img image.Image
|
||||
if p.Process == "" {
|
||||
p.Process = "resize"
|
||||
}
|
||||
filePrefix := fmt.Sprintf(
|
||||
"%s_%dx%d_q%03d",
|
||||
p.Process,
|
||||
p.Width,
|
||||
p.Height,
|
||||
p.Quality,
|
||||
)
|
||||
if strings.HasPrefix(imgSource, "http://") || strings.HasPrefix(imgSource, "https://") {
|
||||
// remote file
|
||||
img, p.Format, err = getImageFromURL(imgSource)
|
||||
if err != nil {
|
||||
return nil, &pongo2.Error{
|
||||
Sender: "filter:image_resize",
|
||||
OrigError: fmt.Errorf("could not open image '%s': %s", imgSource, err),
|
||||
}
|
||||
}
|
||||
// build filename
|
||||
if p.Filename == "" {
|
||||
var fBase string
|
||||
if u, _ := url.Parse(imgSource); u != nil {
|
||||
fBase = strings.Split(path.Base(u.Path), ".")[0]
|
||||
}
|
||||
|
||||
p.Filename = fmt.Sprintf(
|
||||
"%s_%x_%s.%s",
|
||||
filePrefix,
|
||||
md5.Sum([]byte(imgSource)),
|
||||
fBase,
|
||||
p.Format,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// local file
|
||||
imgSource = ResolveInputPath(imgSource)
|
||||
if p.Filename == "" {
|
||||
p.Filename = fmt.Sprintf(
|
||||
"%s_%s",
|
||||
filePrefix,
|
||||
path.Base(imgSource),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
var imgTarget string
|
||||
if p.TargetDir != "" {
|
||||
imgTarget = ResolveOutputPath(
|
||||
path.Clean(p.TargetDir) + "/" +
|
||||
p.Filename,
|
||||
)
|
||||
|
||||
pt := path.Dir(imgTarget)
|
||||
if _, err := os.Stat(pt); os.IsNotExist(err) {
|
||||
Log.Infof("create image target dir: %s", pt)
|
||||
if err := os.MkdirAll(pt, 0755); err != nil {
|
||||
return nil, &pongo2.Error{
|
||||
Sender: "filter:image_resize",
|
||||
OrigError: fmt.Errorf("could not create image target dir '%s': %s", pt, err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
p.Filename = ResolveNavPath(p.TargetDir + "/" + p.Filename)
|
||||
|
||||
} else {
|
||||
imgTarget = ResolveOutputPath(p.Filename)
|
||||
}
|
||||
|
||||
if f, err := os.Stat(imgTarget); err == nil && !f.IsDir() {
|
||||
Log.Noticef("skipped processing image from %s to %s, file already exists", imgSource, imgTarget)
|
||||
} else {
|
||||
Log.Noticef("processing image from %s to %s", imgSource, imgTarget)
|
||||
if strings.HasPrefix(imgSource, "http://") || strings.HasPrefix(imgSource, "https://") {
|
||||
// webrequest before finding target filename, because of file format in filename
|
||||
} else {
|
||||
img, err = imaging.Open(imgSource, imaging.AutoOrientation(true))
|
||||
if err != nil {
|
||||
return nil, &pongo2.Error{
|
||||
Sender: "filter:image_resize",
|
||||
OrigError: fmt.Errorf("could not open image '%s': %s", imgSource, err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch p.Process {
|
||||
case "resize":
|
||||
img = imaging.Resize(img, p.Width, p.Height, imaging.Lanczos)
|
||||
case "fit":
|
||||
img = imaging.Fit(img, p.Width, p.Height, imaging.Lanczos)
|
||||
case "fill":
|
||||
var anchor imaging.Anchor
|
||||
switch strings.ToLower(p.Anchor) {
|
||||
case "":
|
||||
fallthrough
|
||||
case "center":
|
||||
anchor = imaging.Center
|
||||
case "topleft":
|
||||
anchor = imaging.TopLeft
|
||||
case "top":
|
||||
anchor = imaging.Top
|
||||
case "topright":
|
||||
anchor = imaging.TopRight
|
||||
case "left":
|
||||
anchor = imaging.Left
|
||||
case "right":
|
||||
anchor = imaging.Right
|
||||
case "bottomleft":
|
||||
anchor = imaging.BottomLeft
|
||||
case "bottom":
|
||||
anchor = imaging.Bottom
|
||||
case "bottomright":
|
||||
anchor = imaging.BottomRight
|
||||
default:
|
||||
return nil, &pongo2.Error{
|
||||
Sender: "filter:image_resize",
|
||||
OrigError: fmt.Errorf("unknown anchor a=%s definition", p.Anchor),
|
||||
}
|
||||
}
|
||||
img = imaging.Fill(img, p.Width, p.Height, anchor, imaging.Lanczos)
|
||||
default:
|
||||
return nil, &pongo2.Error{
|
||||
Sender: "filter:image_resize",
|
||||
OrigError: fmt.Errorf("invalid p parameter '%s'", p.Process),
|
||||
}
|
||||
}
|
||||
|
||||
var encodeOptions = make([]imaging.EncodeOption, 0)
|
||||
if p.Quality > 0 {
|
||||
encodeOptions = append(encodeOptions, imaging.JPEGQuality(p.Quality))
|
||||
}
|
||||
|
||||
err = imaging.Save(img, imgTarget, encodeOptions...)
|
||||
if err != nil {
|
||||
return nil, &pongo2.Error{
|
||||
Sender: "filter:image_resize",
|
||||
OrigError: fmt.Errorf("could save image '%s': %s", imgTarget, err),
|
||||
}
|
||||
}
|
||||
}
|
||||
return pongo2.AsValue(ResolveNavPath(p.Filename)), nil
|
||||
}
|
||||
|
||||
// RelativePathFilter returns the relative path to navpoint based on current nav
|
||||
func RelativePathFilter(in, param *pongo2.Value) (*pongo2.Value, *pongo2.Error) {
|
||||
return pongo2.AsValue(
|
||||
ResolveNavPath(
|
||||
in.String(),
|
||||
),
|
||||
), nil
|
||||
}
|
||||
|
||||
// RegisterFilters reads a directory and register filters from files within it
|
||||
func RegisterFilters(dir string) {
|
||||
files, err := ioutil.ReadDir(dir)
|
||||
if err != nil {
|
||||
Log.Panicf("could not read from template filters dir '%s': %s", dir, err)
|
||||
}
|
||||
for _, f := range files {
|
||||
if !f.IsDir() {
|
||||
switch path.Ext(f.Name()) {
|
||||
case ".js":
|
||||
fileBase := strings.TrimSuffix(f.Name(), ".js")
|
||||
jsFile := dir + "/" + f.Name()
|
||||
Log.Debugf("trying to register filter from: %s", jsFile)
|
||||
/*
|
||||
jsStr, err := ioutil.ReadFile(jsFile)
|
||||
if err != nil {
|
||||
Log.Panicf("could not read '%s': %s", jsFile, err)
|
||||
}
|
||||
*/
|
||||
vm := motto.New()
|
||||
fn, err := vm.Run(jsFile)
|
||||
if err != nil {
|
||||
Log.Panicf("error in javascript vm for '%s': %s", jsFile, err)
|
||||
}
|
||||
if !fn.IsFunction() {
|
||||
Log.Panicf("%s does not contain a function code", jsFile)
|
||||
}
|
||||
|
||||
err = pongo2.RegisterFilter(
|
||||
fileBase,
|
||||
func(in, param *pongo2.Value) (out *pongo2.Value, erro *pongo2.Error) {
|
||||
thisObj, _ := vm.Object("({})")
|
||||
if currentContext != nil {
|
||||
thisObj.Set("context", *currentContext)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
Log.Panicf("could not set context as in '%s': %s", jsFile, err)
|
||||
}
|
||||
ret, err := fn.Call(thisObj.Value(), in.Interface(), param.Interface())
|
||||
if err != nil {
|
||||
Log.Panicf("error in javascript file '%s' while calling returned function: %s", jsFile, err)
|
||||
}
|
||||
retGo, err := ret.Export()
|
||||
if err != nil {
|
||||
Log.Panicf("export error for '%s': %s", jsFile, err)
|
||||
}
|
||||
return pongo2.AsValue(retGo), nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
Log.Panicf("could not register filter from '%s': %s", jsFile, err)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
125
helper/template_functions.go
Normal file
@@ -0,0 +1,125 @@
|
||||
package helper
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"gitbase.de/apairon/mark2web/config"
|
||||
"github.com/flosch/pongo2"
|
||||
)
|
||||
|
||||
func jsonWebRequest(url string) interface{} {
|
||||
Log.Noticef("requesting url via GET %s", url)
|
||||
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
Log.Panicf("could not get url '%s': %s", url, err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
Log.Panicf("could not read body from url '%s': %s", url, err)
|
||||
}
|
||||
|
||||
Log.Debugf("output from url '%s':\n%s", url, string(body))
|
||||
|
||||
if resp.StatusCode >= 400 {
|
||||
Log.Panicf("bad status '%d - %s' from url '%s'", resp.StatusCode, resp.Status, url)
|
||||
}
|
||||
|
||||
contentType := resp.Header.Get("Content-Type")
|
||||
|
||||
if strings.Contains(contentType, "json") {
|
||||
|
||||
} else {
|
||||
Log.Panicf("is not json '%s' from url '%s'", contentType, url)
|
||||
}
|
||||
|
||||
jsonMap := make(map[string]interface{})
|
||||
err = json.Unmarshal(body, &jsonMap)
|
||||
if err == nil {
|
||||
return jsonMap
|
||||
}
|
||||
|
||||
jsonArrayMap := make([]map[string]interface{}, 0)
|
||||
err = json.Unmarshal(body, &jsonArrayMap)
|
||||
if err == nil {
|
||||
return jsonArrayMap
|
||||
}
|
||||
|
||||
Log.Panicf("could not read json from '%s': invalid type", url)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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(jsonWebRequest(u))
|
||||
}
|
||||
|
||||
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,
|
||||
currentNode.InputPath,
|
||||
currentNode.OutputPath,
|
||||
outDir,
|
||||
pathConfig,
|
||||
)
|
||||
if navname != "" {
|
||||
newNodeConfig.Config.This = config.ThisPathConfig{
|
||||
Navname: &navname,
|
||||
}
|
||||
}
|
||||
if dataMapKey != "" {
|
||||
if newNodeConfig.Config.Data == nil {
|
||||
newNodeConfig.Config.Data = make(config.MapString)
|
||||
}
|
||||
// as submap in Data
|
||||
newNodeConfig.Config.Data[dataMapKey] = ctx
|
||||
} else if m, ok := ctx.(map[string]interface{}); ok {
|
||||
// direct set data
|
||||
newNodeConfig.Config.Data = m
|
||||
}
|
||||
|
||||
// fake via normal file behavior
|
||||
newNodeConfig.Config.Template = &tplFilename
|
||||
newNodeConfig.InputFiles = []string{""} // empty file is special for use InputString
|
||||
indexInFile := ""
|
||||
indexOutFile := "index.html"
|
||||
if idx := newNodeConfig.Config.Index; idx != nil {
|
||||
if idx.OutputFile != nil && *idx.OutputFile != "" {
|
||||
indexOutFile = *idx.OutputFile
|
||||
}
|
||||
}
|
||||
newNodeConfig.Config.Index = &config.IndexConfig{
|
||||
InputFile: &indexInFile,
|
||||
OutputFile: &indexOutFile,
|
||||
InputString: &body,
|
||||
}
|
||||
newNodeConfig.Hidden = hidden
|
||||
|
||||
currentNode.Sub = append(currentNode.Sub, newNodeConfig)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
678
main.go
@@ -3,23 +3,11 @@ package main
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/imdario/mergo"
|
||||
|
||||
"github.com/Depado/bfchroma"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/flosch/pongo2"
|
||||
"github.com/gosimple/slug"
|
||||
"github.com/op/go-logging"
|
||||
cpy "github.com/otiai10/copy"
|
||||
"gopkg.in/russross/blackfriday.v2"
|
||||
"gopkg.in/yaml.v2"
|
||||
"gitbase.de/apairon/mark2web/config"
|
||||
"gitbase.de/apairon/mark2web/helper"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -31,587 +19,13 @@ var (
|
||||
BuildTime = "UNKNOWN"
|
||||
)
|
||||
|
||||
var log = logging.MustGetLogger("myLogger")
|
||||
var log = helper.Log
|
||||
|
||||
var inDir *string
|
||||
var outDir *string
|
||||
|
||||
var templateCache = make(map[string]*pongo2.Template)
|
||||
|
||||
// GlobalConfig is config which is used only once in root dir
|
||||
type GlobalConfig struct {
|
||||
Webserver struct {
|
||||
Type string `yaml:"Type"`
|
||||
} `yaml:"Webserver"`
|
||||
|
||||
Assets struct {
|
||||
FromPath string `yaml:"FromPath"`
|
||||
ToPath string `yaml:"ToPath"`
|
||||
Action string `yaml:"Action"`
|
||||
FixTemplate struct {
|
||||
Find string `yaml:"Find"`
|
||||
Replace string `yaml:"Replace"`
|
||||
} `yaml:"FixTemplate"`
|
||||
} `yaml:"Assets"`
|
||||
|
||||
OtherFiles struct {
|
||||
Action string `yaml:"Action"`
|
||||
} `yaml:"OtherFiles"`
|
||||
}
|
||||
|
||||
var globalConfig = new(GlobalConfig)
|
||||
|
||||
// ThisPathConfig is struct for This in paths yaml
|
||||
type ThisPathConfig struct {
|
||||
Navname *string `yaml:"Navname"`
|
||||
GoTo *string `yaml:"GoTo"`
|
||||
Data interface{} `yaml:"Data"`
|
||||
}
|
||||
|
||||
type indexStruct struct {
|
||||
InputFile *string `yaml:"InputFile"`
|
||||
OutputFile *string `yaml:"OutputFile"`
|
||||
}
|
||||
|
||||
type metaStruct struct {
|
||||
Title *string `yaml:"Title"`
|
||||
Description *string `yaml:"Description"`
|
||||
Keywords *string `yaml:"Keywords"`
|
||||
}
|
||||
|
||||
type pathStruct struct {
|
||||
Strip *string `yaml:"Strip"`
|
||||
IgnoreForNav *string `yaml:"IgnoreForNav"`
|
||||
}
|
||||
|
||||
type filenameStruct struct {
|
||||
Strip *string `yaml:"Strip"`
|
||||
Ignore *string `yaml:"Ignore"`
|
||||
OutputExtension *string `yaml:"OutputExtension"`
|
||||
}
|
||||
|
||||
type markdownStruct struct {
|
||||
ChromaRenderer *bool `yaml:"ChromaRenderer"`
|
||||
ChromaStyle *string `yaml:"ChromaStyle"`
|
||||
}
|
||||
|
||||
// PathConfig of subdir
|
||||
type PathConfig struct {
|
||||
This ThisPathConfig `yaml:"This"`
|
||||
Template *string `yaml:"Template"`
|
||||
Index *indexStruct `yaml:"Index"`
|
||||
Meta *metaStruct `yaml:"Meta"`
|
||||
Path *pathStruct `yaml:"Path"`
|
||||
Filename *filenameStruct `yaml:"Filename"`
|
||||
Markdown *markdownStruct `yaml:"Markdown"`
|
||||
|
||||
Data interface{} `yaml:"Data"`
|
||||
}
|
||||
|
||||
// PathConfigTree is complete config tree of content dir
|
||||
type PathConfigTree struct {
|
||||
InputPath string
|
||||
OutputPath string
|
||||
|
||||
InputFiles []string
|
||||
OtherFiles []string
|
||||
|
||||
Config *PathConfig
|
||||
Sub []*PathConfigTree
|
||||
}
|
||||
|
||||
var contentConfig = new(PathConfigTree)
|
||||
|
||||
type ptrTransformer struct{}
|
||||
|
||||
func (t ptrTransformer) Transformer(typ reflect.Type) func(dst, src reflect.Value) error {
|
||||
if typ.Kind() == reflect.Ptr {
|
||||
return func(dst, src reflect.Value) error {
|
||||
if dst.CanSet() {
|
||||
if dst.IsNil() {
|
||||
dst.Set(src)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func merge(dst, src interface{}) error {
|
||||
return mergo.Merge(dst, src, mergo.WithTransformers(ptrTransformer{}))
|
||||
}
|
||||
|
||||
func backToRoot(curNavPath string) string {
|
||||
tmpPath := ""
|
||||
if curNavPath != "" {
|
||||
for i := strings.Count(curNavPath, "/") + 1; i > 0; i-- {
|
||||
tmpPath += "../"
|
||||
}
|
||||
}
|
||||
return tmpPath
|
||||
}
|
||||
|
||||
func readContentDir(inBase string, outBase string, dir string, conf *PathConfig, tree *PathConfigTree) {
|
||||
inPath := inBase
|
||||
if dir != "" {
|
||||
inPath += "/" + dir
|
||||
}
|
||||
|
||||
log.Infof("reading input directory: %s", inPath)
|
||||
|
||||
files, err := ioutil.ReadDir(inPath)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
|
||||
tree.InputPath = inPath
|
||||
|
||||
// read config
|
||||
newConfig := new(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")
|
||||
merge(newConfig, conf)
|
||||
// remove this
|
||||
newConfig.This = 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
|
||||
merge(newConfig, conf)
|
||||
newConfig.This = oldThis
|
||||
|
||||
log.Debug(spew.Sdump(newConfig))
|
||||
}
|
||||
|
||||
tree.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 tree.Config.This.Navname == nil {
|
||||
navname := strings.Replace(stripedDir, "_", " ", -1)
|
||||
tree.Config.This.Navname = &navname
|
||||
}
|
||||
|
||||
stripedDir = slug.Make(stripedDir)
|
||||
outPath := outBase + "/" + stripedDir
|
||||
outPath = path.Clean(outPath)
|
||||
|
||||
log.Infof("calculated output directory: %s", outPath)
|
||||
tree.OutputPath = outPath
|
||||
|
||||
// first only files
|
||||
for _, f := range files {
|
||||
p := inPath + "/" + 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 := inPath + "/" + f.Name()
|
||||
if f.IsDir() {
|
||||
log.Debugf("DIR %s", p)
|
||||
newTree := new(PathConfigTree)
|
||||
if tree.Sub == nil {
|
||||
tree.Sub = make([]*PathConfigTree, 0)
|
||||
}
|
||||
tree.Sub = append(tree.Sub, newTree)
|
||||
readContentDir(inPath, outPath, f.Name(), newConfig, newTree)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type navElement struct {
|
||||
Navname string
|
||||
GoTo string
|
||||
Active bool
|
||||
|
||||
Data interface{}
|
||||
|
||||
This ThisPathConfig
|
||||
|
||||
SubMap *map[string]*navElement
|
||||
SubSlice *[]*navElement
|
||||
}
|
||||
|
||||
func buildNavigation(conf *PathConfigTree, curNavMap *map[string]*navElement, curNavSlice *[]*navElement, navActive *[]*navElement, activeNav string) {
|
||||
for _, el := range conf.Sub {
|
||||
var ignNav *string
|
||||
if p := el.Config.Path; p != nil {
|
||||
ignNav = p.IgnoreForNav
|
||||
}
|
||||
if ignNav != nil && *ignNav != "" {
|
||||
regex, err := regexp.Compile(*ignNav)
|
||||
if err != nil {
|
||||
log.Panicf("could not compile IngoreForNav regexp '%s' in '%s': %s", *ignNav, el.InputPath, err)
|
||||
}
|
||||
if regex.MatchString(path.Base(el.InputPath)) {
|
||||
log.Debugf("ignoring input directory '%s' in navigation", el.InputPath)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
elPath := strings.TrimPrefix(el.OutputPath, *outDir+"/")
|
||||
|
||||
subMap := make(map[string]*navElement)
|
||||
subSlice := make([]*navElement, 0)
|
||||
navEl := navElement{
|
||||
Active: strings.HasPrefix(activeNav, elPath),
|
||||
Data: el.Config.Data,
|
||||
SubMap: &subMap,
|
||||
SubSlice: &subSlice,
|
||||
}
|
||||
|
||||
navEl.This = el.Config.This
|
||||
|
||||
if navEl.Active {
|
||||
// add to navActive level navigation
|
||||
currentLevel := strings.Count(activeNav, "/")
|
||||
if len(*navActive) <= currentLevel {
|
||||
// not registered
|
||||
*navActive = append(*navActive, &navEl)
|
||||
}
|
||||
}
|
||||
|
||||
n := el.Config.This.Navname
|
||||
if n != nil {
|
||||
navEl.Navname = *n
|
||||
}
|
||||
g := el.Config.This.GoTo
|
||||
if g != nil {
|
||||
if strings.HasPrefix(*g, "/") {
|
||||
// abslute
|
||||
navEl.GoTo = *g
|
||||
} else {
|
||||
// relative
|
||||
navEl.GoTo = elPath + "/" + *g
|
||||
}
|
||||
} else {
|
||||
navEl.GoTo = elPath + "/"
|
||||
}
|
||||
|
||||
if activeNav != "" && activeNav != "/" {
|
||||
// calculate relative path
|
||||
bToRoot := backToRoot(activeNav)
|
||||
navEl.GoTo = bToRoot + navEl.GoTo
|
||||
navEl.GoTo = path.Clean(navEl.GoTo)
|
||||
}
|
||||
|
||||
(*curNavMap)[navEl.Navname] = &navEl
|
||||
if curNavSlice != nil {
|
||||
*curNavSlice = append(*curNavSlice, &navEl)
|
||||
}
|
||||
|
||||
buildNavigation(el, &subMap, &subSlice, navActive, activeNav)
|
||||
}
|
||||
}
|
||||
|
||||
func processContent(conf *PathConfigTree) {
|
||||
log.Debugf("trying to create output directory: %s", conf.OutputPath)
|
||||
|
||||
if dirH, err := os.Stat(conf.OutputPath); os.IsNotExist(err) {
|
||||
err := os.MkdirAll(conf.OutputPath, 0755)
|
||||
if err != nil {
|
||||
log.Panicf("could not create output directory '%s': %s", conf.OutputPath, err)
|
||||
}
|
||||
log.Noticef("created output directory: %s", conf.OutputPath)
|
||||
} else if dirH != nil {
|
||||
if dirH.IsDir() {
|
||||
log.Noticef("output directory '%s' already exists", conf.OutputPath)
|
||||
} else {
|
||||
log.Panicf("output directory '%s' is no directory", conf.OutputPath)
|
||||
}
|
||||
} else {
|
||||
log.Panicf("unknown error for output directory '%s': %s", conf.OutputPath, err)
|
||||
}
|
||||
|
||||
curNavPath := strings.TrimPrefix(conf.OutputPath, *outDir)
|
||||
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 globalConfig.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 {
|
||||
inFile := conf.InputPath + "/" + file
|
||||
log.Debugf("reading file: %s", inFile)
|
||||
|
||||
input, err := ioutil.ReadFile(inFile)
|
||||
if err != nil {
|
||||
log.Panicf("could not read '%s':%s", inFile, err)
|
||||
}
|
||||
log.Infof("processing input file '%s'", inFile)
|
||||
|
||||
newConfig := new(PathConfig)
|
||||
|
||||
regex := regexp.MustCompile("(?s)^---(.*?)\\r?\\n\\r?---\\r?\\n\\r?")
|
||||
yamlData := regex.Find(input)
|
||||
if string(yamlData) != "" {
|
||||
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
|
||||
merge(newConfig, conf.Config)
|
||||
newConfig.This = oldThis
|
||||
|
||||
log.Debug(spew.Sdump(newConfig))
|
||||
|
||||
input = regex.ReplaceAll(input, []byte(""))
|
||||
} else {
|
||||
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)
|
||||
|
||||
var options []blackfriday.Option
|
||||
|
||||
var chromaRenderer *bool
|
||||
var chromaStyle *string
|
||||
if m := newConfig.Markdown; 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),
|
||||
),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
html := blackfriday.Run(input, options...)
|
||||
|
||||
// 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(blackfriday.Run([]byte(iPart), options...))))
|
||||
}
|
||||
|
||||
log.Debugf("rendering template '%s' for '%s'", *newConfig.Template, outFile)
|
||||
templateFile := *inDir + "/templates/" + *newConfig.Template
|
||||
template := templateCache[templateFile]
|
||||
if template == nil {
|
||||
var err error
|
||||
if template, err = pongo2.FromFile(templateFile); err != nil {
|
||||
log.Panicf("could not parse template '%s': %s", templateFile, err)
|
||||
} else {
|
||||
templateCache[templateFile] = template
|
||||
}
|
||||
}
|
||||
|
||||
// build navigation
|
||||
navMap := make(map[string]*navElement)
|
||||
navSlice := make([]*navElement, 0)
|
||||
navActive := make([]*navElement, 0)
|
||||
buildNavigation(contentConfig, &navMap, &navSlice, &navActive, curNavPath)
|
||||
|
||||
// read yaml header as data for template
|
||||
ctx := make(pongo2.Context)
|
||||
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(html))
|
||||
ctx["BodyParts"] = htmlParts
|
||||
|
||||
result, err := template.Execute(ctx)
|
||||
if err != nil {
|
||||
log.Panicf("could not execute template '%s' for input file '%s': %s", templateFile, inFile, err)
|
||||
}
|
||||
|
||||
if find := globalConfig.Assets.FixTemplate.Find; find != "" {
|
||||
log.Debugf("fixing assets paths in '%s' for '%s'", templateFile, inFile)
|
||||
bToRoot := backToRoot(curNavPath)
|
||||
regex, err := regexp.Compile(find)
|
||||
if err != nil {
|
||||
log.Panicf("could not compile regexp '%s' for assets path: %s", find, err)
|
||||
}
|
||||
repl := globalConfig.Assets.FixTemplate.Replace
|
||||
repl = bToRoot + globalConfig.Assets.ToPath + "/" + repl
|
||||
repl = path.Clean(repl) + "/"
|
||||
log.Debugf("new assets paths: %s", repl)
|
||||
result = regex.ReplaceAllString(result, repl)
|
||||
}
|
||||
|
||||
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 globalConfig.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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, el := range conf.Sub {
|
||||
processContent(el)
|
||||
}
|
||||
}
|
||||
|
||||
func processAssets() {
|
||||
switch globalConfig.Assets.Action {
|
||||
case "copy":
|
||||
from := globalConfig.Assets.FromPath
|
||||
to := globalConfig.Assets.ToPath
|
||||
if !strings.HasPrefix(from, "/") {
|
||||
from = *inDir + "/" + from
|
||||
}
|
||||
if !strings.HasPrefix(to, "/") {
|
||||
to = *outDir + "/" + to
|
||||
}
|
||||
log.Noticef("copying assets from '%s' to '%s'", from, to)
|
||||
err := cpy.Copy(from, to)
|
||||
if err != nil {
|
||||
log.Panicf("could not copy assets from '%s' to '%s': %s", from, to, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
var contentConfig = new(config.PathConfigTree)
|
||||
|
||||
func main() {
|
||||
spew.Config.DisablePointerAddresses = true
|
||||
spew.Config.DisableCapacities = true
|
||||
spew.Config.DisableMethods = true
|
||||
spew.Config.DisablePointerMethods = true
|
||||
|
||||
inDir = flag.String("in", "./", "input directory")
|
||||
outDir = flag.String("out", "html", "output directory")
|
||||
inDir := flag.String("in", "./", "input directory")
|
||||
outDir := flag.String("out", "html", "output directory")
|
||||
createOutDir := flag.Bool("create", false, "create output directory if not existing")
|
||||
//clearOutDir := flag.Bool("clear", false, "clear output directory before generating website")
|
||||
logLevel := flag.String("logLevel", "info", "log level: debug, info, warning, error")
|
||||
@@ -626,47 +40,24 @@ func main() {
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
logBackend := logging.NewLogBackend(os.Stderr, "", 0)
|
||||
logBackendFormatter := logging.NewBackendFormatter(logBackend, logging.MustStringFormatter(
|
||||
`%{color}%{time:15:04:05.000} %{shortfunc} ▶ %{level:.4s} %{id:03x}%{color:reset} %{message}`,
|
||||
))
|
||||
logBackendLeveled := logging.AddModuleLevel(logBackendFormatter)
|
||||
logBackendLevel := logging.INFO
|
||||
level := "info"
|
||||
if logLevel != nil {
|
||||
switch *logLevel {
|
||||
case "debug":
|
||||
logBackendLevel = logging.DEBUG
|
||||
break
|
||||
|
||||
case "info":
|
||||
logBackendLevel = logging.INFO
|
||||
break
|
||||
|
||||
case "notice":
|
||||
logBackendLevel = logging.NOTICE
|
||||
break
|
||||
|
||||
case "warning":
|
||||
logBackendLevel = logging.WARNING
|
||||
break
|
||||
|
||||
case "error":
|
||||
logBackendLevel = logging.ERROR
|
||||
break
|
||||
|
||||
level = *logLevel
|
||||
}
|
||||
}
|
||||
logBackendLeveled.SetLevel(logBackendLevel, "")
|
||||
logging.SetBackend(logBackendLeveled)
|
||||
helper.ConfigureLogger(level)
|
||||
|
||||
if inDir == nil || *inDir == "" {
|
||||
log.Panic("input directory not specified")
|
||||
}
|
||||
iDir := path.Clean(*inDir)
|
||||
inDir = &iDir
|
||||
log.Infof("input directory: %s", *inDir)
|
||||
|
||||
if outDir == nil || *outDir == "" {
|
||||
log.Panic("output directory not specified")
|
||||
}
|
||||
oDir := path.Clean(*outDir)
|
||||
outDir = &oDir
|
||||
log.Infof("output directory: %s", *outDir)
|
||||
|
||||
if createOutDir != nil && *createOutDir {
|
||||
@@ -694,17 +85,13 @@ func main() {
|
||||
}
|
||||
|
||||
log.Debug("reading global config...")
|
||||
p := *inDir + "/config.yml"
|
||||
data, err := ioutil.ReadFile(p)
|
||||
configFilename := *inDir + "/config.yml"
|
||||
err := config.ReadGlobalConfig(configFilename)
|
||||
if err != nil {
|
||||
log.Panicf("could not read file '%s': %s", p, err)
|
||||
log.Panicf("could not read file '%s': %s", configFilename, err)
|
||||
}
|
||||
err = yaml.Unmarshal(data, globalConfig)
|
||||
if err != nil {
|
||||
log.Panicf("could not parse YAML file '%s': %s", p, err)
|
||||
}
|
||||
|
||||
log.Debug(spew.Sdump(globalConfig))
|
||||
config.Config.Directories.Input = *inDir
|
||||
config.Config.Directories.Output = *outDir
|
||||
|
||||
log.Debugf("reading input directory %s", *inDir)
|
||||
|
||||
@@ -717,29 +104,40 @@ func main() {
|
||||
defaultFilenameIgnore := "^_"
|
||||
defaultFilenameOutputExtension := "html"
|
||||
|
||||
defaultPathConfig := new(PathConfig)
|
||||
defaultPathConfig := new(config.PathConfig)
|
||||
defaultPathConfig.Template = &defaultTemplate
|
||||
defaultPathConfig.Index = &indexStruct{
|
||||
defaultPathConfig.Index = &config.IndexConfig{
|
||||
InputFile: &defaultInputFile,
|
||||
OutputFile: &defaultOutputFile,
|
||||
}
|
||||
defaultPathConfig.Path = &pathStruct{
|
||||
defaultPathConfig.Path = &config.DirnameConfig{
|
||||
Strip: &defaultPathStrip,
|
||||
IgnoreForNav: &defaultPathIgnoreForNav,
|
||||
}
|
||||
defaultPathConfig.Filename = &filenameStruct{
|
||||
defaultPathConfig.Filename = &config.FilenameConfig{
|
||||
Strip: &defaultFilenameStrip,
|
||||
Ignore: &defaultFilenameIgnore,
|
||||
OutputExtension: &defaultFilenameOutputExtension,
|
||||
}
|
||||
defaultPathConfig.Imaging = &config.ImagingConfig{
|
||||
Width: 1920,
|
||||
Height: 1920,
|
||||
Process: "fit",
|
||||
Quality: 75,
|
||||
}
|
||||
|
||||
readContentDir(*inDir+"/content", *outDir, "", defaultPathConfig, contentConfig)
|
||||
helper.ReadContentDir(*inDir+"/content", *outDir, "", defaultPathConfig, contentConfig)
|
||||
//spew.Dump(contentConfig)
|
||||
|
||||
//spew.Dump(navMap)
|
||||
|
||||
processContent(contentConfig)
|
||||
|
||||
processAssets()
|
||||
|
||||
templatesDir := *inDir + "/templates"
|
||||
helper.SetTemplateDir(templatesDir)
|
||||
filtersDir := templatesDir + "/filters"
|
||||
if _, err := os.Stat(filtersDir); !os.IsNotExist(err) {
|
||||
helper.RegisterFilters(filtersDir)
|
||||
}
|
||||
helper.ProcessContent(contentConfig, contentConfig)
|
||||
|
||||
helper.ProcessAssets()
|
||||
}
|
||||
|
||||
32
test.rest
Normal file
@@ -0,0 +1,32 @@
|
||||
GET https://mark2web.basiscms.de/api/collections/get/mark2webBlog
|
||||
?sort[date]=-1
|
||||
&limit=101
|
||||
&token=985cee34099f4d3b08f18fc22f6296
|
||||
|
||||
###
|
||||
|
||||
&filter[link._id]=5c76a0f4643334fe0400039c
|
||||
&filter[published]=true
|
||||
|
||||
###
|
||||
|
||||
GET https://mark2web.basiscms.de/api/imagestyles/style/klein
|
||||
?src=/vhosts/mark2web.basiscms.de/uploads/2019/02/27/5c767a7f3dec9computer-3368242_1920.jpg
|
||||
&output=1
|
||||
&token=89ff216524093123bf7a0a10f7b273
|
||||
|
||||
###
|
||||
|
||||
GET https://mark2web.basiscms.de/api/imagestyles/style
|
||||
|
||||
###
|
||||
|
||||
GET https://ci.basehosts.de/api/repos/apairon/mark2web/builds?page=1
|
||||
Authorization: Bearer { ci_token }
|
||||
|
||||
###
|
||||
|
||||
POST https://ci.basehosts.de/api/repos/apairon/mark2web/builds/63/promote?target=website
|
||||
Authorization: Bearer { ci_token }
|
||||
|
||||
###
|
||||
1
vendor/github.com/aymerick/raymond
generated
vendored
1
vendor/github.com/ddliu/motto
generated
vendored
Submodule
1
vendor/github.com/disintegration/imaging
generated
vendored
Submodule
1
vendor/github.com/extemporalgenome/slug
generated
vendored
Submodule
1
vendor/github.com/flosch/go-humanize
generated
vendored
Submodule
1
vendor/github.com/flosch/pongo2-addons
generated
vendored
Submodule
1
vendor/github.com/gosimple/slug
generated
vendored
1
vendor/github.com/rainycape/unidecode
generated
vendored
1
vendor/github.com/robertkrimen/otto
generated
vendored
Submodule
1
vendor/github.com/russross/blackfriday
generated
vendored
Submodule
1
vendor/golang.org/x/image
generated
vendored
Submodule
1
vendor/golang.org/x/text
generated
vendored
Submodule
1
vendor/gopkg.in/sourcemap.v1
generated
vendored
Submodule
@@ -9,3 +9,8 @@ Meta:
|
||||
Markdown:
|
||||
ChromaRenderer: True
|
||||
ChromaStyle: monokai
|
||||
|
||||
Data:
|
||||
debug: False
|
||||
matomoSiteId: 89
|
||||
token: 985cee34099f4d3b08f18fc22f6296 # cockpit api token
|
||||
@@ -2,6 +2,14 @@
|
||||
Markdown:
|
||||
ChromaRenderer: False
|
||||
|
||||
Data:
|
||||
background: /img/coffee.jpg
|
||||
|
||||
slider:
|
||||
- img: /img/coffee.jpg
|
||||
alt:
|
||||
opacity: 1
|
||||
|
||||
---
|
||||
|
||||
# mark2web
|
||||
@@ -28,25 +36,35 @@ graph TD
|
||||
classDef out stroke-width:5px,stroke:#b5c50f,fill:#ccc
|
||||
class M,C,D,A in
|
||||
class W out
|
||||
|
||||
click C "../benutzung/konfiguration" "Doku: Benutzung/Konfiguration"
|
||||
click M "../benutzung/inhalte" "Doku: Benutzung/Inhalte"
|
||||
click A "../benutzung/inhalte" "Doku: Benutzung/Inhalte"
|
||||
click D "../benutzung/templates" "Doku: Benutzung/Templates"
|
||||
```
|
||||
|
||||
<script defer>
|
||||
window.onload = function() {
|
||||
mermaid.init(undefined,$("code.language-mermaid"));
|
||||
$("code.language-mermaid").css("visibility", "visible");
|
||||
};
|
||||
</script>
|
||||
|
||||
---
|
||||
|
||||
Der Generator selbst wurde in [Go](https://golang.org/) geschrieben. Es wurden dabei eine Vielzahl existierender Packages verwendet. Unter Anderem:
|
||||
Der Generator selbst wurde in [Go](https://golang.org/) geschrieben. Es wurden dabei eine Vielzahl existierender Packages verwendet.
|
||||
|
||||
Unter Anderem:
|
||||
|
||||
- der Markdown-Parser [blackfriday](https://github.com/russross/blackfriday)
|
||||
- die Template-Sprache "Django Template Language" über das Paket [pongo2](github.com/flosch/pongo2)
|
||||
- das Logging-Paket [go-logging](github.com/op/go-logging)
|
||||
- die Template-Sprache "Django Template Language" über das Paket [pongo2](https://github.com/flosch/pongo2)
|
||||
- das Logging-Paket [go-logging](https://github.com/op/go-logging)
|
||||
- der YAML-Parser [go-yaml](https://github.com/go-yaml/yaml)
|
||||
- die Imaging Bibliothek [disintegration/imaging](github.com/disintegration/imaging)
|
||||
- der Javascript-Interpreter [otto](github.com/robertkrimen/otto) mit der Erweiterung [motto](github.com/ddliu/motto)
|
||||
|
||||
Weitere Pakete, die verwendet wurden finden Sie in den Quellen.
|
||||
|
||||
Diese Website wurde selbst mit mark2web generiert. Der entsprechende Quellcode, sowie die Quellen zu mark2web finden Sie unter:
|
||||
|
||||
**https://gitbase.de/apairon/mark2web**
|
||||
**[https://gitbase.de/apairon/mark2web](5c76a0f4643334fe0400039c)**
|
||||
@@ -1,3 +1,13 @@
|
||||
---
|
||||
Data:
|
||||
background: /img/laptop.jpg
|
||||
|
||||
slider:
|
||||
- img: /img/laptop.jpg
|
||||
alt:
|
||||
opacity: 1
|
||||
|
||||
---
|
||||
# Installation
|
||||
|
||||
Damit die korrekten Versionsinformationen dynamisch in das finale mark2web-Binary eingefügt wurde, ist eine manuelle Installation aus dem Git-Repository sinnvoll.
|
||||
@@ -17,4 +27,6 @@ git submodule update --init --recursive
|
||||
|
||||
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.
|
||||
|
||||
Später folgen vorkomplierte Releases über die Repository-Website.
|
||||
## Releases
|
||||
|
||||
Vorkompilierte Binaries finden Sie auf der [Releases-Seite auf gitbase.de](https://gitbase.de/apairon/mark2web/releases).
|
||||
BIN
website/content/de/01_Navigation/02_Installation/bild.jpg
Normal file
|
After Width: | Height: | Size: 790 KiB |
@@ -1,3 +1,14 @@
|
||||
---
|
||||
Data:
|
||||
background: /img/folder.jpg
|
||||
|
||||
slider:
|
||||
- img: /img/folder.jpg
|
||||
alt:
|
||||
opacity: 1
|
||||
|
||||
---
|
||||
|
||||
# Benutzung
|
||||
|
||||
## Ordnerstruktur
|
||||
@@ -38,6 +49,8 @@ DIR assets (kann auch abweichend benannt werden)
|
||||
DIR css
|
||||
|
||||
DIR templates
|
||||
DIR filters
|
||||
FILE myfilter.js
|
||||
FIL base.html
|
||||
FIL base_sub.html
|
||||
|
||||
|
||||
@@ -1,3 +1,14 @@
|
||||
---
|
||||
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)).
|
||||
|
||||
@@ -1,3 +1,14 @@
|
||||
---
|
||||
Data:
|
||||
background: /img/write.jpg
|
||||
|
||||
slider:
|
||||
- img: /img/write.jpg
|
||||
alt:
|
||||
opacity: 1
|
||||
|
||||
---
|
||||
|
||||
# Inhalte
|
||||
|
||||
Die Inhalte der Website werden im Ordner `content` und dessen Unterordnern gespeichert.
|
||||
|
||||
@@ -1,3 +1,14 @@
|
||||
---
|
||||
Data:
|
||||
background: /img/design.jpg
|
||||
|
||||
slider:
|
||||
- img: /img/design.jpg
|
||||
alt:
|
||||
opacity: 1
|
||||
|
||||
---
|
||||
|
||||
# Templates
|
||||
|
||||
Templates werden über das pongo2-Paket gerendert. Dieses nutzt die Template-Sprache **Django-Template**.
|
||||
|
||||
@@ -1,2 +1,9 @@
|
||||
This:
|
||||
GoTo: ordnerstruktur
|
||||
|
||||
Data:
|
||||
|
||||
slider:
|
||||
- img: /img/gears.jpg
|
||||
alt:
|
||||
opacity: 1
|
||||
|
||||
14
website/content/de/01_Navigation/04_Blog/README.md
Normal file
@@ -0,0 +1,14 @@
|
||||
---
|
||||
Template: base_blog.html
|
||||
|
||||
Data:
|
||||
background: /img/blog.jpg
|
||||
|
||||
slider:
|
||||
- img: /img/blog.jpg
|
||||
alt:
|
||||
opacity: 1
|
||||
|
||||
---
|
||||
|
||||
# Blog
|
||||
23
website/content/de/01_Navigation/04_Blog/config.yml
Normal file
@@ -0,0 +1,23 @@
|
||||
This:
|
||||
Collections:
|
||||
- Name: blog1st
|
||||
URL: 'https://mark2web.basiscms.de/api/collections/get/mark2webBlog?token={{ Data.token }}&filter[published]=true&sort[date]=-1&skip=0&limit=1'
|
||||
NavTemplate:
|
||||
EntriesAttribute: entries
|
||||
GoTo: '{{ date }}-{{ title }}'
|
||||
Navname: '{{ title }}'
|
||||
Body: '{{ body|safe }}'
|
||||
Template: base_blog_details.html
|
||||
DataKey: details
|
||||
Hidden: true # hide from nav, but use this feature for rendering detail sites
|
||||
|
||||
- Name: blog1skip
|
||||
URL: 'https://mark2web.basiscms.de/api/collections/get/mark2webBlog?token={{ Data.token }}&filter[published]=true&sort[date]=-1&skip=1&limit=100'
|
||||
NavTemplate:
|
||||
EntriesAttribute: entries
|
||||
GoTo: '{{ date }}-{{ title }}'
|
||||
Navname: '{{ title }}'
|
||||
Body: '{{ body|safe }}'
|
||||
Template: base_blog_details.html
|
||||
DataKey: details
|
||||
Hidden: true
|
||||
@@ -41,6 +41,8 @@ Ihr Besuch dieser Webseite wird aktuell von der Matomo Webanalyse erfasst. Klick
|
||||
Sie können sich hier entscheiden, ob in Ihrem Browser ein eindeutiger Webanalyse-Cookie abgelegt werden darf, um dem Betreiber der Website die Erfassung und Analyse verschiedener statistischer Daten zu ermöglichen.
|
||||
Wenn Sie sich dagegen entscheiden möchten, klicken Sie den folgenden Link, um den Matomo-Deaktivierungs-Cookie in Ihrem Browser abzulegen.
|
||||
|
||||
<iframe style="border: 0; height: 200px; width: 100%;" src="https://www.basisstats.de/index.php?module=CoreAdminHome&action=optOut&language=de&backgroundColor=ffffff&fontColor=4e4e4e&fontSize=17px&fontFamily='Open%20Sans'%2C%20sans-serif"></iframe>
|
||||
|
||||
## 4. Google Maps
|
||||
|
||||
Wir verwenden auf unserer Internetseite Google Maps zur Auffindung unseres Firmensitzes. Dieser Dienst wird von der Firma Google Inc., 1600 Amphitheatre Parkway, Mountain View, CA 94043 USA, nachfolgend „Google“ angeboten. Bei jedem Aufruf von Google Maps wird von Google ein Cookie gesetzt, um bei der Anzeige der Seite, auf der Google Maps integriert ist, Nutzereinstellungen und -daten zu verarbeiten. Dieser Cookie von Google wird im Regelfall nicht durch das Schließen des Browsers gelöscht, sondern läuft nach einer bestimmten Zeit ab, soweit er nicht von Ihnen zuvor manuell gelöscht wurde. Wenn Sie mit dieser Verarbeitung Ihrer Daten nicht einverstanden sind, besteht die Möglichkeit, den Service von Google Maps zu deaktivieren und auf diesem Weg die Übertragung von Daten an Google zu verhindern. Hierfür gibt es folgende Möglichkeiten: Sie können geeignete Browsererweiterungen installieren (wie z.B. uMatrix oder Disconnect) und diese anweisen, die Übertragung an Google zu unterbinden. Sie können auch die Java-Script-Funktion in Ihrem Browser deaktivieren. Wir weisen Sie darauf hin, dass in diesem Fall die Nutzung von Google Maps und ggf. anderer Teile unserer Internetseite nicht oder nur noch eingeschränkt möglich ist. Die Nutzung von Google Maps und der hierdurch erlangten Informationen erfolgt gemäß den Google-Nutzungsbedingungen http://www.google.de/intl/de/policies/terms/regional.html sowie der zusätzlichen Geschäftsbedingungen für Google Maps https://www.google.com/intl/de_de/help/terms_maps.html.
|
||||
|
||||
BIN
website/img/blog.jpg
Normal file
|
After Width: | Height: | Size: 714 KiB |
BIN
website/img/coffee.jpg
Normal file
|
After Width: | Height: | Size: 453 KiB |
BIN
website/img/design.jpg
Normal file
|
After Width: | Height: | Size: 436 KiB |
BIN
website/img/desk.jpg
Normal file
|
After Width: | Height: | Size: 644 KiB |
BIN
website/img/folder.jpg
Normal file
|
After Width: | Height: | Size: 524 KiB |
BIN
website/img/gears.jpg
Normal file
|
After Width: | Height: | Size: 881 KiB |
BIN
website/img/html.jpg
Normal file
|
After Width: | Height: | Size: 520 KiB |
BIN
website/img/keyboard.jpg
Normal file
|
After Width: | Height: | Size: 595 KiB |
BIN
website/img/laptop.jpg
Normal file
|
After Width: | Height: | Size: 691 KiB |
BIN
website/img/laptop2.jpg
Normal file
|
After Width: | Height: | Size: 389 KiB |
BIN
website/img/wire.jpg
Normal file
|
After Width: | Height: | Size: 171 KiB |
BIN
website/img/write.jpg
Normal file
|
After Width: | Height: | Size: 715 KiB |
@@ -24,7 +24,10 @@ hr { display:block; height:1px; border:0; border:none; padding:0;}
|
||||
input, select {vertical-align:middle;}
|
||||
input, select, textarea {-webkit-appearance: none; border-radius: 0;}
|
||||
.clear {clear:both; margin: 0; padding:0;}
|
||||
img {line-height:0;}
|
||||
img {
|
||||
line-height:0;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------
|
||||
# General Settings
|
||||
@@ -65,13 +68,14 @@ hr {height:1px; background:none; border-bottom:dotted 1px #666; margin-bottom:20
|
||||
.btn {
|
||||
padding:8px 15px;
|
||||
background:#464645;
|
||||
border: 1px solid #fff;
|
||||
color:#FFF;
|
||||
transition:all 0.3s;
|
||||
text-transform:none;
|
||||
display:inline-block;
|
||||
margin-top:20px;
|
||||
}
|
||||
.btn:hover {background:#b5c50f; color:#FFF;}
|
||||
.btn:hover {background:#b5c50f; color:#464645;}
|
||||
.center_btn { width:100%; text-align:center; }
|
||||
|
||||
/* White Color */
|
||||
@@ -150,7 +154,7 @@ label {font-weight:600;}
|
||||
# Header
|
||||
--------------------------------------------------------------*/
|
||||
#header {height:80px;}
|
||||
#header .logo {height:70%; max-width:500px;}
|
||||
#header .logo {height:70%; max-width:400px;}
|
||||
#header .logo img {height:100%;}
|
||||
|
||||
#header .container {
|
||||
@@ -171,6 +175,7 @@ label {font-weight:600;}
|
||||
z-index:300;
|
||||
background:none;
|
||||
}
|
||||
#header .logo {height:70%; max-width:100px;}
|
||||
#header .logo img {height:auto; width:100%;}
|
||||
}
|
||||
|
||||
@@ -192,7 +197,6 @@ label {font-weight:600;}
|
||||
}
|
||||
|
||||
|
||||
.boxen {}
|
||||
.boxen .col-sm-6 {padding:15px;}
|
||||
.box {
|
||||
height:100%;
|
||||
@@ -268,4 +272,24 @@ pre {
|
||||
padding: 5px;
|
||||
border-radius: 5px;
|
||||
margin-bottom: 25px;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
code.language-mermaid {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
code.language-mermaid svg {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.datum {
|
||||
background-color: #2e2e2e;
|
||||
padding: 5px;
|
||||
font-size: 0.5em;
|
||||
border-radius: 5px;
|
||||
display:inline-block;
|
||||
color: #fff;
|
||||
}
|
||||
@@ -66,7 +66,7 @@ a#pull span:nth-of-type(3) {margin-bottom:0px;}
|
||||
#mainmenu ul li a {
|
||||
padding:0px 15px;
|
||||
height:50px;
|
||||
color:#FFF;
|
||||
color: #464645;
|
||||
font-size:17px;
|
||||
display:flex;
|
||||
display:-webkit-flex;
|
||||
@@ -76,6 +76,9 @@ a#pull span:nth-of-type(3) {margin-bottom:0px;}
|
||||
flex-direction:column;
|
||||
|
||||
}
|
||||
#mainmenu ul li.active a {
|
||||
color: #fff;
|
||||
}
|
||||
#mainmenu ul li .trigger {
|
||||
width:50px;
|
||||
height:50px;
|
||||
@@ -99,6 +102,7 @@ a#pull span:nth-of-type(3) {margin-bottom:0px;}
|
||||
|
||||
#mainmenu ul li.active {
|
||||
background:#464645;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#mainmenu .sub-menu {
|
||||
@@ -153,6 +157,12 @@ a#pull span:nth-of-type(3) {margin-bottom:0px;}
|
||||
background:#b5c50f;
|
||||
/*border-top:solid 5px #781c45;*/
|
||||
}
|
||||
#mainmenu ul.sub-menu li a{
|
||||
color: #464645;
|
||||
}
|
||||
#mainmenu ul.sub-menu li.active a, #mainmenu ul.sub-menu li:hover a {
|
||||
color: #fff;
|
||||
}
|
||||
#mainmenu>ul>li>a {padding:0px 20px;}
|
||||
#mainmenu>ul>li>a, #mainmenu>ul>li>ul>li>a {transition:background 0.3s;}
|
||||
#mainmenu>ul>li>ul>li:hover>a {background:#464645;}
|
||||
|
||||
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 22 KiB |
BIN
website/project-files/img/logo_text.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
@@ -5,25 +5,40 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<!-- Favicon -->
|
||||
<link rel="icon" href="project-files/img/favicon.ico" />
|
||||
<link rel="icon" href="project-files/img/favicon.ico?{{ Timestamp }}" />
|
||||
|
||||
|
||||
<!-- Meta -->
|
||||
{% block meta %}
|
||||
<title>{{ Meta.Title }}</title>
|
||||
<meta name="description" content="{{ Meta.Description }}" />
|
||||
<meta name="keywords" content="{{ Meta.Keywords }}" />
|
||||
{% endblock meta %}
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="project-files/css/preloader.css?{{ Timestamp }}">
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="project-files/css/preloader.css">
|
||||
|
||||
|
||||
{% include "partials/matomo.html" with siteId=Data.matomoSiteId %}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="preloader">
|
||||
<div class="spinner">
|
||||
<div class="double-bounce1"></div>
|
||||
<div class="double-bounce2"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<header id="header">
|
||||
<div class="container">
|
||||
<div class="logo"><img src="project-files/img/logo.png" alt=""></div>
|
||||
<div class="logo" {% if not Data.slider and not Data.details.slider %}style="max-width: 400px;"{% endif %}>
|
||||
<a href="{{ "/"|relative_path }}">
|
||||
<picture>
|
||||
<source media="(max-width: 768px)" srcset="project-files/img/logo_text.png">
|
||||
<img src="project-files/img/logo{% if not Data.slider and not Data.details.slider %}_text{% endif %}.png" alt="mark2web Logo">
|
||||
</picture>
|
||||
</a>
|
||||
</div>
|
||||
<a id="pull">
|
||||
<span class="pull_button">
|
||||
<span></span>
|
||||
@@ -33,14 +48,24 @@
|
||||
<span class="text">Menu</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</header>
|
||||
|
||||
<!-- ========== Slider ========== -->
|
||||
<section class="slider white_arrows" data-dots="true" data-arrows="true">
|
||||
{% block slider %}
|
||||
{% for sl in Data.slider %}
|
||||
<div class="slide">
|
||||
<img
|
||||
src="{{ sl.img|image_process:"p=fill,w=1440,h=600,q=60,t=/img" }}"
|
||||
srcset="{{ sl.img|image_process:"p=fill,w=768,h=384,q=60,t=/img" }} 768w,
|
||||
{{ sl.img|image_process:"p=fill,w=1440,h=600,q=60,t=/img" }} 1440w,
|
||||
{{ sl.img|image_process:"p=fill,w=1920,h=800,q=60,t=/img" }} 1920w"
|
||||
alt="{{ sl.alt }}" style="opacity:{{ sl.opacity|default:1 }}">
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endblock slider %}
|
||||
</section>
|
||||
|
||||
|
||||
<!-- ========== Navigation ========== -->
|
||||
<div class="navholder" id="nav">
|
||||
<div class="container">
|
||||
@@ -60,7 +85,6 @@
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
@@ -69,15 +93,43 @@
|
||||
<!-- ========== Content First ========== -->
|
||||
<div class="contentfirst section_padding white_color ">
|
||||
<div class="container">
|
||||
{% block part0 %}
|
||||
{{ BodyParts.0 }}
|
||||
{% endblock part0 %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ========== Main Content ========== -->
|
||||
<div class="maincontent">
|
||||
{% if Data.background %}
|
||||
<img
|
||||
src="{{ Data.background|image_process:"p=fill,w=1440,h=810,q=30,t=/img" }}"
|
||||
srcset="{{ Data.background|image_process:"p=fill,w=768,h=768,q=30,t=/img" }} 768w,
|
||||
{{ Data.background|image_process:"p=fill,w=1440,h=810,q=30,t=/img" }} 1440w,
|
||||
{{ Data.background|image_process:"p=fill,w=1920,h=1020,q=30,t=/img" }} 1920w"
|
||||
alt="{{ Meta.Title }}" class="img2bg">
|
||||
{% endif %}
|
||||
<div class="white_section section_padding">
|
||||
<div class="container">
|
||||
{% block part1 %}
|
||||
{{ BodyParts.1 }}
|
||||
{% endblock part1 %}
|
||||
|
||||
{% if Data.debug %}
|
||||
<hr>
|
||||
<h2>DEBUG</h2>
|
||||
|
||||
<h3>This</h3>
|
||||
<pre>{{ This|json:"pretty" }}</pre>
|
||||
|
||||
<h3>Data</h3>
|
||||
<pre>{{ Data|json:"pretty" }}</pre>
|
||||
|
||||
<h3>NavMap</h3>
|
||||
<pre>{{ NavMap|json:"pretty" }}</pre>
|
||||
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -94,20 +146,19 @@
|
||||
<div class="content">
|
||||
<p>{{ nav.This.Data.teaser }}
|
||||
</p>
|
||||
<a href="{{ nav.GoTo }}" class="btn">mehr lesen »</a>
|
||||
<a href="{{ nav.GoTo }}" class="btn">mehr lesen »</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ========== Footer ========== -->
|
||||
<footer id="footer">
|
||||
<div class="container">
|
||||
@@ -142,7 +193,6 @@
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
<!-- ========== SCROLL TOP ========== -->
|
||||
<div class="scrolltop">
|
||||
<i class="fa fa-angle-up" aria-hidden="true"></i>
|
||||
@@ -151,20 +201,20 @@
|
||||
|
||||
<!-- Style -->
|
||||
<link href="https://fonts.googleapis.com/css?family=Open+Sans:400,700" rel="stylesheet">
|
||||
<link rel="stylesheet" type="text/css" href="project-files/css/grid.css">
|
||||
<link rel="stylesheet" type="text/css" href="project-files/css/main.css">
|
||||
<link rel="stylesheet" type="text/css" href="project-files/css/menu.css">
|
||||
<link rel="stylesheet" type="text/css" href="project-files/css/slider.css">
|
||||
<link rel="stylesheet" type="text/css" href="project-files/fonts/font-awesome/font-awesome.css">
|
||||
<link rel="stylesheet" type="text/css" href="project-files/js/slick/slick.css">
|
||||
<link rel="stylesheet" type="text/css" href="project-files/css/grid.css?{{ Timestamp }}">
|
||||
<link rel="stylesheet" type="text/css" href="project-files/css/main.css?{{ Timestamp }}">
|
||||
<link rel="stylesheet" type="text/css" href="project-files/css/menu.css?{{ Timestamp }}">
|
||||
<link rel="stylesheet" type="text/css" href="project-files/css/slider.css?{{ Timestamp }}">
|
||||
<link rel="stylesheet" type="text/css" href="project-files/fonts/font-awesome/font-awesome.css?{{ Timestamp }}">
|
||||
<link rel="stylesheet" type="text/css" href="project-files/js/slick/slick.css?{{ Timestamp }}">
|
||||
|
||||
<!-- Javascript -->
|
||||
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
|
||||
<script src="project-files/js/slick/slick.min.js"></script>
|
||||
<script src="project-files/js/slick/slick-custom.js"></script>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
|
||||
<script src="project-files/js/slick/slick.min.js?{{ Timestamp }}"></script>
|
||||
<script src="project-files/js/slick/slick-custom.js?{{ Timestamp }}"></script>
|
||||
|
||||
<script src="project-files/js/preloader.js"></script>
|
||||
<script src="project-files/js/functions.js"></script>
|
||||
<script src="project-files/js/preloader.js?{{ Timestamp }}"></script>
|
||||
<script src="project-files/js/functions.js?{{ Timestamp }}"></script>
|
||||
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/mermaid/8.0.0/mermaid.min.js"></script>
|
||||
|
||||
31
website/templates/base_blog.html
Normal file
@@ -0,0 +1,31 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block part0 %}
|
||||
{{ Body }}
|
||||
{% for e in NavElement.ColMap.blog1st.entries %}
|
||||
<h2>
|
||||
{{ e.title }}
|
||||
<div class="datum">{{ e.date|datum }}</div>
|
||||
</h2>
|
||||
{{ e.teaser|markdown }}
|
||||
{% if e.body %}
|
||||
<a href="{{ e.date|add:"-"|add:e.title|slugify }}" class="btn">mehr lesen »</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endblock part0 %}
|
||||
|
||||
{% block part1 %}
|
||||
{% comment %}
|
||||
limit wird für skip in Query gebraucht
|
||||
{% endcomment %}
|
||||
{% for e in NavElement.ColMap.blog1skip.entries %}
|
||||
<h2>
|
||||
{{ e.title }}
|
||||
<div class="datum">{{ e.date|datum }}</div>
|
||||
</h2>
|
||||
{{ e.teaser|markdown }}
|
||||
{% if e.body %}
|
||||
<a href="{{ e.date|add:"-"|add:e.title|slugify }}" class="btn">mehr lesen »</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endblock part1 %}
|
||||
37
website/templates/base_blog_details.html
Normal file
@@ -0,0 +1,37 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block meta %}
|
||||
<title>{{ Data.details.date|datum }} {{ Data.details.title }}</title>
|
||||
<meta name="description" content="{{ Data.details.teaser }}" />
|
||||
<meta name="keywords" content="{{ Meta.Keywords }}" />
|
||||
{% endblock meta %}
|
||||
|
||||
{% if Data.details.slider %}
|
||||
{% block slider %}
|
||||
{% for sl in Data.details.slider %}
|
||||
<div class="slide">
|
||||
<img
|
||||
src="{{ "https://mark2web.basiscms.de"|add:sl.path|image_process:"p=fill,w=1440,h=600,q=60" }}"
|
||||
srcset="{{ "https://mark2web.basiscms.de"|add:sl.path|image_process:"p=fill,w=768,h=384,q=60" }} 768w,
|
||||
{{ "https://mark2web.basiscms.de"|add:sl.path|image_process:"p=fill,w=1440,h=600,q=60" }} 1440w,
|
||||
{{ "https://mark2web.basiscms.de"|add:sl.path|image_process:"p=fill,w=1920,h=800,q=60" }} 1920w"
|
||||
alt="{{ sl.meta.title }}">
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endblock slider %}
|
||||
{% endif %}
|
||||
|
||||
{% block part0 %}
|
||||
<h1>
|
||||
{{ Data.details.title }}
|
||||
<div class="datum">{{ Data.details.date|datum }}</div>
|
||||
</h1>
|
||||
{{ Data.details.teaser|markdown }}
|
||||
{% endblock part0 %}
|
||||
|
||||
{% block part1 %}
|
||||
{{ Body }}
|
||||
<br>
|
||||
|
||||
<a href="../" class="btn">« zurück</a>
|
||||
{% endblock part1 %}
|
||||
16
website/templates/filters/datum.js
Normal file
@@ -0,0 +1,16 @@
|
||||
var strings = require('./lib/strings');
|
||||
|
||||
function datum(str, param) {
|
||||
if (!param) {
|
||||
param = "$3.$2.$1";
|
||||
}
|
||||
|
||||
/*
|
||||
this.context.Meta.Title ist hier z.B. möglich
|
||||
*/
|
||||
|
||||
return strings.replace(str, /^([0-9]+)[^0-9]+([0-9]+)[^0-9]+([0-9]+).*/, param)
|
||||
// return str.replace(/^([0-9]+)[^0-9]+([0-9]+)[^0-9]+([0-9]+).*/, param)
|
||||
}
|
||||
|
||||
module.exports = datum;
|
||||
5
website/templates/filters/lib/strings.js
Normal file
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
replace: function (str, regex, replace) {
|
||||
return str.replace(regex, replace);
|
||||
}
|
||||
}
|
||||
17
website/templates/partials/matomo.html
Normal file
@@ -0,0 +1,17 @@
|
||||
<!-- Matomo -->
|
||||
<script type="text/javascript">
|
||||
var _paq = window._paq || [];
|
||||
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
|
||||
_paq.push(["setCookieDomain", "*.mark2web.de"]);
|
||||
_paq.push(['trackPageView']);
|
||||
_paq.push(['enableLinkTracking']);
|
||||
(function() {
|
||||
var u="https://www.basisstats.de/";
|
||||
_paq.push(['setTrackerUrl', u+'matomo.php']);
|
||||
_paq.push(['setSiteId', '{{ siteId|default:0 }}']);
|
||||
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
|
||||
g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
|
||||
})();
|
||||
</script>
|
||||
<noscript><p><img src="https://www.basisstats.de/matomo.php?idsite=89&rec=1" style="border:0;" alt="" /></p></noscript>
|
||||
<!-- End Matomo Code -->
|
||||