Compare commits
4 Commits
a681d21f56
...
6a1243678a
Author | SHA1 | Date | |
---|---|---|---|
|
6a1243678a | ||
|
56f900e1d4 | ||
ea964ae874 | |||
eea58fd25f |
9
Makefile
@ -332,16 +332,19 @@ $(OUT_DIR)/%.html: $(PROJECT_DIR)/%.html | $(OUT_DIRS) $(_DEP_DIRS)
|
|||||||
@#awk -i inplace '{FS="" sub(/<!--.*-->/,"")}1' $@
|
@#awk -i inplace '{FS="" sub(/<!--.*-->/,"")}1' $@
|
||||||
@#awk -i inplace '{if (NF != 0) print}' $@
|
@#awk -i inplace '{if (NF != 0) print}' $@
|
||||||
|
|
||||||
|
|
||||||
|
# SASS
|
||||||
$(OUT_DIR)/%.css: $(PROJECT_DIR)/%.sass | $(OUT_DIRS) $(_DEP_DIRS)
|
$(OUT_DIR)/%.css: $(PROJECT_DIR)/%.sass | $(OUT_DIRS) $(_DEP_DIRS)
|
||||||
@printf $(FMT_OUT_CSS) "$<" "$@";
|
@printf $(FMT_OUT_CSS) "$<" "$@";
|
||||||
@$(_SASS_CMD) --indented $< $@
|
@$(_SASS_CMD) --indented "$<" "$@" || { rm "$@"; exit 1; }
|
||||||
@# generate a dependecy file from the source map and delete the map
|
|
||||||
@depfile=$(patsubst $(OUT_DIR)/%,$(DEP_DIR)/%,$@).d; echo -n "$@: " > "$$depfile"; \
|
@depfile=$(patsubst $(OUT_DIR)/%,$(DEP_DIR)/%,$@).d; echo -n "$@: " > "$$depfile"; \
|
||||||
jq -r '.sources | @sh' $@.map | tr -d \' | sed 's|file://||g' >> "$$depfile"; \
|
jq -r '.sources | @sh' $@.map | tr -d \' | sed 's|file://||g' >> "$$depfile"; \
|
||||||
rm $@.map
|
rm $@.map
|
||||||
|
@# generate a dependecy file from the source map and delete the map
|
||||||
|
# SCSS
|
||||||
$(OUT_DIR)/%.css: $(PROJECT_DIR)/%.scss | $(OUT_DIRS) $(_DEP_DIRS)
|
$(OUT_DIR)/%.css: $(PROJECT_DIR)/%.scss | $(OUT_DIRS) $(_DEP_DIRS)
|
||||||
@printf $(FMT_OUT_CSS) "$<" "$@";
|
@printf $(FMT_OUT_CSS) "$<" "$@";
|
||||||
@$(_SASS_CMD) --no-indented $< $@
|
@$(_SASS_CMD) --no-indented "$<" "$@" || { rm "$@"; exit 1; }
|
||||||
@# generate a dependecy file from the source map and delete the map
|
@# generate a dependecy file from the source map and delete the map
|
||||||
@depfile=$(patsubst $(OUT_DIR)/%,$(DEP_DIR)/%,$@).d; echo -n "$@: " > "$$depfile"; \
|
@depfile=$(patsubst $(OUT_DIR)/%,$(DEP_DIR)/%,$@).d; echo -n "$@: " > "$$depfile"; \
|
||||||
jq -r '.sources | @sh' $@.map | tr -d \' | sed 's|file://||g' >> "$$depfile"; \
|
jq -r '.sources | @sh' $@.map | tr -d \' | sed 's|file://||g' >> "$$depfile"; \
|
||||||
|
1
example/.dependencies/de/de-only.html.d
Normal file
@ -0,0 +1 @@
|
|||||||
|
build/de/de-only.html: src/include/head.html src/de/de-only.html
|
1
example/.dependencies/de/index.html.d
Normal file
@ -0,0 +1 @@
|
|||||||
|
build/de/index.html: src/include/head.html src/include/index.de.md src/common/index.html
|
1
example/.dependencies/en/en-only.html.d
Normal file
@ -0,0 +1 @@
|
|||||||
|
build/en/en-only.html: src/include/head.html src/en/en-only.html
|
1
example/.dependencies/en/index.html.d
Normal file
@ -0,0 +1 @@
|
|||||||
|
build/en/index.html: src/include/head.html src/include/index.en.md src/common/index.html
|
1
example/.dependencies/style/main.css.d
Normal file
@ -0,0 +1 @@
|
|||||||
|
build/style/main.css: /home/matth/Projekte/web/bUwUma/example/src/style/main.sass /home/matth/Projekte/web/bUwUma/example/src/include/style/images.sass /home/matth/Projekte/web/bUwUma/example/src/include/style/sidenav.sass
|
1
example/.sitemap.pkl
Normal file
@ -0,0 +1 @@
|
|||||||
|
<EFBFBD>}<7D>.
|
375
example/Makefile
Normal file
@ -0,0 +1,375 @@
|
|||||||
|
# ABOUT
|
||||||
|
# - In this Makefile, 'building a file' means:
|
||||||
|
# - if the file has a '.html' extension: run the html preprocessor on the file and place the output in the output directory
|
||||||
|
# - elif the file has a '.sass' or '.scss' extension: run the sass compiler on the file and place the output in the output directory
|
||||||
|
# - else: copy the file to the output directory
|
||||||
|
# - Folder structure from source directories will be preserved in the output directory
|
||||||
|
# - Abbreviations:
|
||||||
|
# - FLS: files
|
||||||
|
# - DIR: directory
|
||||||
|
# - SRC: source
|
||||||
|
# - LANG: language
|
||||||
|
# - PP: preprocessor
|
||||||
|
# - DEP: dependency
|
||||||
|
|
||||||
|
#
|
||||||
|
# NORMAL SETTINGS
|
||||||
|
# change these to fir your project
|
||||||
|
#
|
||||||
|
|
||||||
|
# root dir for the project, most other paths are relative to PROJECT_DIR
|
||||||
|
# [absolute or relative to current working directory]
|
||||||
|
PROJECT_DIR = src
|
||||||
|
|
||||||
|
# path where final website will be in
|
||||||
|
# [absolute or relative to current working directory]
|
||||||
|
OUT_DIR = build
|
||||||
|
|
||||||
|
# SOURCE FILES:
|
||||||
|
# all SRC_FLS and all files (recursively) in the SRC_DIRS will be built
|
||||||
|
# all files in PROJECT_DIR (not recursively) are source files
|
||||||
|
# [relative to PROJECT_DIR]
|
||||||
|
SRC_DIRS = de en script
|
||||||
|
SRC_FLS =
|
||||||
|
|
||||||
|
# CSS FILES:
|
||||||
|
# directories which may contain sass and scss to compile sass to a correspondig css in OUT_DIR/CSS_DIR (also css, it will simply be copied)
|
||||||
|
# [relative to PROJECT_DIR]
|
||||||
|
CSS_DIRS = style
|
||||||
|
CSS_FILES =
|
||||||
|
|
||||||
|
# RESOURCE FILES:
|
||||||
|
# all RESOURCE_FLS and all files in the RESOURCE_DIRS will be copied to OUT_DIR
|
||||||
|
# [relative to PROJECT_DIR]
|
||||||
|
RESOURCE_DIRS = resources
|
||||||
|
RESOURCE_FLS =
|
||||||
|
|
||||||
|
# MULTI-LANG SOURCE FILES:
|
||||||
|
# the files in COMMON_DIR will be built for all LANGS:
|
||||||
|
# for example:
|
||||||
|
# LANGS = de en
|
||||||
|
# PROJECT_DIR/COMMON_DIR/home.html
|
||||||
|
# -> OUT_DIR/de/home.html
|
||||||
|
# -> OUT_DIR/en/home.html
|
||||||
|
# foreach html-file in COMMON_DIR:
|
||||||
|
# foreach lang in LANGS:
|
||||||
|
# run HTML_PP_CMD with --var lang=lang on file and output to OUT_DIR without the COMMON_DIR prefix, so COMMON_DIR/subdir/file.html -> OUT_DIR/lang/subdir/file.html
|
||||||
|
# For all .html files, the proprocessor will make the variable `lang` available, for example lang=de
|
||||||
|
# All non-html files will handled the same way, but without the preprocessor being run on them. They are simply copied.
|
||||||
|
# leave COMMON_DIR blank to disable multi-lang feature
|
||||||
|
# [relative to PROJECT_DIR]
|
||||||
|
COMMON_DIR = common
|
||||||
|
LANGS = de en
|
||||||
|
|
||||||
|
# FAVICON
|
||||||
|
# image from which the favicons will be generated
|
||||||
|
# leave FAVICON_SRC blank to not generate favicons
|
||||||
|
# [relative to PROJECT_DIR]
|
||||||
|
FAVICON_SRC = resources/favicon.png
|
||||||
|
# directory where all genreated favicons will be placed
|
||||||
|
# [relative to OUT_DIR]
|
||||||
|
FAVICON_DIR = favicon
|
||||||
|
# in addition to the ones below, a favicon.ico containing the 16x16, 32x32 and 48x48will be generated
|
||||||
|
# all apple-touch-icon-XXxXX.png sizes
|
||||||
|
APPLE_ICON_SIZES = 180x180
|
||||||
|
# all mstile-XXxXX.png sizes
|
||||||
|
WINDOWS_ICON_SIZES = 150x150
|
||||||
|
# all android-chrome-XXxXX.png sizes
|
||||||
|
ANDROID_ICON_SIZES = 192x192 512x512
|
||||||
|
# all favicon-XXxXX.png sizes
|
||||||
|
FAVICON_ICON_SIZES = 16x16 32x32 48x48
|
||||||
|
|
||||||
|
# THUMBNAILS:
|
||||||
|
# thumbnails for all resource files having an extension in THUMB_FOR_TYPES will be generated and placed relative to THUMB_OUT_DIR
|
||||||
|
# leave THUMB_OUT_DIR blank to not generate thumbnails
|
||||||
|
# [relative to OUT_DIR]
|
||||||
|
THUMB_OUT_DIR =
|
||||||
|
# build thumbnails for these types: supported: mp3, flac, wav, pdf and all image formats that magick can handle
|
||||||
|
THUMB_FOR_TYPES = png gif jpg jpeg webp pdf mp4 mp3 flac wav
|
||||||
|
# filetype for the thumbnails. (pdfs will always have .jpg)
|
||||||
|
THUMB_TYPE = jpg
|
||||||
|
# size for the thumbnails (not respected by pdf)
|
||||||
|
THUMB_SIZE = 300
|
||||||
|
|
||||||
|
# SITEMAP
|
||||||
|
# leave SITEMAP blank to not generate a sitemap
|
||||||
|
# [relative to OUT_DIR]
|
||||||
|
SITEMAP = sitemap.xml
|
||||||
|
# base url of the website, without trailing /
|
||||||
|
WEBSITE_URL = https://example.com
|
||||||
|
# file required during build process for sitemap generation [absolute or relative to current working directory]
|
||||||
|
SITEMAP_TEMP_FILE = .sitemap.pkl
|
||||||
|
# comment to keep the file extension on sitemap entries
|
||||||
|
SITEMAP_REMOVE_EXT = 1
|
||||||
|
|
||||||
|
# PREPROCESSOR
|
||||||
|
# path to of the files that should be included
|
||||||
|
# [relative to PROJECT_DIR]
|
||||||
|
INCLUDE_DIR = include
|
||||||
|
# additional search paths passed to sass compiler
|
||||||
|
# [relative to PROJECT_DIR]
|
||||||
|
SASS_INCLUDE_DIRS = include/style
|
||||||
|
|
||||||
|
|
||||||
|
# ADVANCED
|
||||||
|
# the command to run the html preprocessor
|
||||||
|
HTML_PP_CMD = python3 html-preprocessor --exit-on light
|
||||||
|
# command to compile sass and scss files with
|
||||||
|
# --indented is added for sass and --no-indented for scss
|
||||||
|
# --source-maps-urls=absolute is appended for generating dependency files
|
||||||
|
SASS_CMD = sass --color
|
||||||
|
|
||||||
|
# [absolute or relative to current working directory]
|
||||||
|
DEP_DIR = .dependencies
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# NOT SETTINGS ANYMORE
|
||||||
|
# DO NOT CHANGE ANYTHING HERE UNLESS YOU KNOW WHAT YOU ARE DOING!
|
||||||
|
#
|
||||||
|
# all variables starting with _ are relative to PROJECT_DIR
|
||||||
|
|
||||||
|
# make everything relative to PROJECT_DIR
|
||||||
|
_SRC_DIRS = $(addprefix $(PROJECT_DIR)/, $(SRC_DIRS))
|
||||||
|
_SRC_FLS = $(addprefix $(PROJECT_DIR)/, $(SRC_FLS))
|
||||||
|
_CSS_FLS = $(addprefix $(PROJECT_DIR)/, $(CSS_FLS))
|
||||||
|
_CSS_DIRS = $(addprefix $(PROJECT_DIR)/, $(CSS_DIRS))
|
||||||
|
_SASS_INCLUDE_DIRS = $(addprefix $(PROJECT_DIR)/, $(SASS_INCLUDE_DIRS))
|
||||||
|
_RES_DIRS = $(addprefix $(PROJECT_DIR)/, $(RESOURCE_DIRS))
|
||||||
|
_RES_FLS = $(addprefix $(PROJECT_DIR)/, $(RESOURCE_FLS))
|
||||||
|
_COMMON_DIR = $(addprefix $(PROJECT_DIR)/, $(COMMON_DIR))
|
||||||
|
_INCLUDE_DIR = $(addprefix $(PROJECT_DIR)/, $(INCLUDE_DIR))
|
||||||
|
|
||||||
|
# NORMAL SRC
|
||||||
|
# all SRC_DIRS + CSS_DIRS + all subdirs of each srcdir
|
||||||
|
_SRC_SUB_DIRS = $(foreach srcdir, $(_SRC_DIRS) $(_CSS_DIRS), $(shell find $(srcdir)/ -type d 2>/dev/null))
|
||||||
|
# add files in project dir
|
||||||
|
_SRC_FLS += $(shell find $(PROJECT_DIR)/ -maxdepth 1 -type f)
|
||||||
|
# add files src dirs, recursively
|
||||||
|
_SRC_FLS += $(foreach srcdir, $(_SRC_DIRS), $(shell find $(srcdir)/ -type f 2>/dev/null))
|
||||||
|
_CSS_FLS += $(foreach srcdir, $(_CSS_DIRS), $(shell find $(srcdir)/ -type f 2>/dev/null))
|
||||||
|
|
||||||
|
OUT_DIRS = $(OUT_DIR)/ $(patsubst $(PROJECT_DIR)/%, $(OUT_DIR)/%, $(_SRC_SUB_DIRS))
|
||||||
|
# path of the (css/sass) source files after being processed
|
||||||
|
OUT_FLS = $(patsubst $(PROJECT_DIR)/%, $(OUT_DIR)/%, $(_SRC_FLS))
|
||||||
|
OUT_FLS += $(patsubst $(PROJECT_DIR)/%, $(OUT_DIR)/%, $(foreach cssfile, $(_CSS_FLS), $(shell echo $(cssfile) | sed 's/\.s[ac]ss$$/.css/')))
|
||||||
|
|
||||||
|
# RESOURCES
|
||||||
|
_RES_SUB_DIRS = $(foreach srcdir, $(_RES_DIRS), $(shell find $(srcdir)/ -type d 2>/dev/null))
|
||||||
|
_RES_FLS += $(foreach srcdir, $(_RES_DIRS), $(shell find $(srcdir)/ -type f 2>/dev/null))
|
||||||
|
RES_OUT_DIRS = $(OUT_DIR)/ $(patsubst $(PROJECT_DIR)/%, $(OUT_DIR)/%, $(_RES_SUB_DIRS))
|
||||||
|
RES_OUT_FLS = $(patsubst $(PROJECT_DIR)/%, $(OUT_DIR)/%, $(_RES_FLS))
|
||||||
|
|
||||||
|
# MULTILANG
|
||||||
|
ifdef COMMON_DIR
|
||||||
|
_ML_SRC_FLS = $(shell find $(_COMMON_DIR)/ -type f)
|
||||||
|
_ML_SRC_SUB_DIRS= $(shell find $(_COMMON_DIR)/ -type d)
|
||||||
|
# will contain one subdir for each lang, each of which contains every file from ML_SRC_FLS
|
||||||
|
ML_OUT_DIR = $(OUT_DIR)
|
||||||
|
ML_OUT_LANG_DIRS= $(foreach lang, $(LANGS), $(addprefix $(ML_OUT_DIR)/, $(lang)))
|
||||||
|
ML_OUT_DIRS = $(foreach lang, $(LANGS), $(patsubst $(_COMMON_DIR)/%, $(ML_OUT_DIR)/$(lang)/%, $(_ML_SRC_SUB_DIRS)))
|
||||||
|
ML_OUT_FLS = $(foreach lang, $(LANGS), $(patsubst $(_COMMON_DIR)/%, $(ML_OUT_DIR)/$(lang)/%, $(_ML_SRC_FLS)))
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifdef FAVICON_DIR
|
||||||
|
FAVICON_OUT_DIR = $(addprefix $(OUT_DIR)/,$(FAVICON_DIR))
|
||||||
|
else
|
||||||
|
FAVICON_OUT_DIR = $(OUT_DIR)
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifdef FAVICON_SRC
|
||||||
|
_FAVICON = $(addprefix $(PROJECT_DIR)/,$(FAVICON_SRC))
|
||||||
|
FAVICON_ICO = $(FAVICON_OUT_DIR)/favicon.ico
|
||||||
|
APPLE_ICONS = $(addsuffix .png,$(addprefix apple-touch-icon-,$(APPLE_ICON_SIZES)))
|
||||||
|
WINDOWS_ICONS = $(addsuffix .png,$(addprefix mstile-,$(WINDOWS_ICON_SIZES)))
|
||||||
|
ANDROID_ICONS = $(addsuffix .png,$(addprefix android-chrome-,$(ANDROID_ICON_SIZES)))
|
||||||
|
FAVICON_ICONS = $(addsuffix .png,$(addprefix favicon-,$(FAVICON_ICON_SIZES)))
|
||||||
|
FAVICONS_PNG = $(addprefix $(FAVICON_OUT_DIR)/,$(APPLE_ICONS) $(WINDOWS_ICONS) $(ANDROID_ICONS) $(FAVICON_ICONS))
|
||||||
|
FAVICONS = $(FAVICONS_PNG) $(FAVICON_ICO)
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifdef THUMB_OUT_DIR
|
||||||
|
# files for which to generate thumbnails
|
||||||
|
_THUMB_FLS = $(filter $(foreach type, $(THUMB_FOR_TYPES), %.$(type)), $(_RES_FLS))
|
||||||
|
THUMB_OUT_FLS = $(addsuffix .jpg, $(basename $(patsubst $(PROJECT_DIR)/%, $(OUT_DIR)/$(THUMB_OUT_DIR)/%, $(_THUMB_FLS))))
|
||||||
|
THUMB_OUT_DIRS = $(sort $(dir $(THUMB_OUT_FLS))) # sort for removing duplicates
|
||||||
|
endif
|
||||||
|
|
||||||
|
# needed for creating them
|
||||||
|
_DEP_DIRS = $(sort $(patsubst $(OUT_DIR)/%, $(DEP_DIR)/%, $(OUT_DIRS) $(ML_OUT_DIRS)))
|
||||||
|
# needed for reading
|
||||||
|
_DEP_FLS = $(shell find $(DEP_DIR) -type f -name '*.d' 2>/dev/null)
|
||||||
|
|
||||||
|
ifdef SITEMAP
|
||||||
|
SITEMAP_OUT = $(addprefix $(OUT_DIR)/, $(SITEMAP))
|
||||||
|
HTML_PP_CMD += --sitemap-temp-file "$(SITEMAP_TEMP_FILE)" --sitemap-base-url $(WEBSITE_URL) --sitemap-webroot-dir "$(OUT_DIR)"
|
||||||
|
endif
|
||||||
|
ifdef SITEMAP_REMOVE_EXT
|
||||||
|
HTML_PP_CMD += --sitemap-remove-ext
|
||||||
|
endif
|
||||||
|
# SASS, add load-paths
|
||||||
|
_SASS_CMD = $(SASS_CMD) $(foreach includedir, $(_SASS_INCLUDE_DIRS), --load-path=$(includedir)) --source-map-urls=absolute
|
||||||
|
|
||||||
|
# PRINTING
|
||||||
|
FMT_VAR_SRC ="Variable '\e[1;34m%s\e[0m': \e[0;33m%s\e[0m\n"
|
||||||
|
FMT_VAR_OUT ="Variable '\e[1;34m%s\e[0m': \e[0;35m%s\e[0m\n"
|
||||||
|
FMT_DIR ="\e[1;34mMaking directory\e[0m: \e[0;35m%s\e[0m\n"
|
||||||
|
FMT_OUT_HTML ="\e[1;34mBuilding html\e[0m: \e[1;33m%s\e[0m at \e[1;35m%s\e[0m\n"
|
||||||
|
FMT_OUT_CSS ="\e[1;34mBuilding css\e[0m: \e[1;33m%s\e[0m at \e[1;35m%s\e[0m\n"
|
||||||
|
FMT_OUT_THUMB ="\e[1;34mGenerating thumbnail\e[0m: \e[1;33m%s\e[0m at \e[1;35m%s\e[0m\n"
|
||||||
|
FMT_OUT_SITEMAP ="\e[1;34mGenerating sitemap\e[0m: \e[1;35m%s\e[0m\n"
|
||||||
|
FMT_OUT_FAVICON ="\e[1;34mGenerating favicon\e[0m: \e[1;33m%s\e[0m at \e[1;35m%s\e[0m\n"
|
||||||
|
FMT_OUT_OTHER ="\e[1;34mBuilding\e[0m: \e[1;33m%s\e[0m at \e[1;35m%s\e[0m\n"
|
||||||
|
FMT_OUT_ML_HTML ="\e[1;34mBuilding html\e[0m in lang \e[1;34m%s\e[0m: \e[1;33m%s\e[0m at \e[1;35m%s\e[0m\n"
|
||||||
|
FMT_OUT_ML_OTHER ="\e[1;34mBuilding\e[0m in lang \e[1;34m%s\e[0m: \e[1;33m%s\e[0m at \e[1;35m%s\e[0m\n"
|
||||||
|
# .SUFFIXES:
|
||||||
|
# .SUFFIXES: .html .md
|
||||||
|
|
||||||
|
.PHONY: default normal multilang resources sitemap favicons thumbnails print start stop clean cleaner
|
||||||
|
|
||||||
|
.DEFAULT_GOAL = all
|
||||||
|
|
||||||
|
# include all the dependency makefiles
|
||||||
|
include $(_DEP_FLS)
|
||||||
|
|
||||||
|
all: normal multilang resources thumbnails sitemap favicons
|
||||||
|
normal: $(OUT_FLS)
|
||||||
|
sitemap: $(SITEMAP_OUT)
|
||||||
|
favicons: $(FAVICONS) $(FAVICON_ICO)
|
||||||
|
multilang: $(ML_OUT_FLS)
|
||||||
|
resources: $(RES_OUT_FLS)
|
||||||
|
thumbnails: $(THUMB_OUT_FLS)
|
||||||
|
|
||||||
|
print:
|
||||||
|
@printf $(FMT_VAR_SRC) "PROJECT_DIR" "$(PROJECT_DIR)"
|
||||||
|
@printf $(FMT_VAR_OUT) "OUT_DIRS" "$(OUT_DIRS)"
|
||||||
|
@printf $(FMT_VAR_SRC) "_INCLUDE_DIR" "$(_INCLUDE_DIR)"
|
||||||
|
@printf $(FMT_VAR_SRC) "_SRC_FLS" "$(_SRC_FLS)"
|
||||||
|
@printf $(FMT_VAR_OUT) "OUT_FLS" "$(OUT_FLS)"
|
||||||
|
@printf $(FMT_VAR_SRC) "_RES_FLS" "$(_RES_FLS)"
|
||||||
|
@printf $(FMT_VAR_OUT) "RES_OUT_FLS" "$(RES_OUT_FLS)"
|
||||||
|
@printf $(FMT_VAR_OUT) "_CSS_FLS" "$(_CSS_FLS)"
|
||||||
|
ifdef COMMON_DIR
|
||||||
|
@printf $(FMT_VAR_SRC) "_ML_SRC_FLS" "$(_ML_SRC_FLS)"
|
||||||
|
@printf $(FMT_VAR_OUT) "ML_OUT_FLS" "$(ML_OUT_FLS)"
|
||||||
|
endif
|
||||||
|
@printf $(FMT_VAR_SRC) "_DEP_FLS" "$(_DEP_FLS)"
|
||||||
|
ifdef THUMB_OUT_DIR
|
||||||
|
@printf $(FMT_VAR_SRC) "THUMB_OUT_DIR" "$(THUMB_OUT_DIR)"
|
||||||
|
@printf $(FMT_VAR_OUT) "_THUMB_FLS" "$(_THUMB_FLS)"
|
||||||
|
@printf $(FMT_VAR_OUT) "THUMB_OUT_FLS" "$(THUMB_OUT_FLS)"
|
||||||
|
@printf $(FMT_VAR_OUT) "THUMB_OUT_DIRS" "$(THUMB_OUT_DIRS)"
|
||||||
|
endif
|
||||||
|
@# @printf $(FMT_VAR_SRC) "y" "$(y)"
|
||||||
|
|
||||||
|
# DIRECTORIES
|
||||||
|
$(sort $(ML_OUT_DIRS) $(_DEP_DIRS) $(RES_OUT_DIRS) $(OUT_DIRS) $(THUMB_OUT_DIRS) $(FAVICON_OUT_DIR)):
|
||||||
|
@printf $(FMT_DIR) "$@"
|
||||||
|
@mkdir -p $@
|
||||||
|
|
||||||
|
# MULTILANG RULES
|
||||||
|
ifdef COMMON_DIR
|
||||||
|
# $@ is the target to trigger the rule, but all languages have to be built now
|
||||||
|
$(foreach out_dir, $(ML_OUT_LANG_DIRS), $(out_dir)/%.html): $(_COMMON_DIR)/%.html | $(ML_OUT_DIRS) $(_DEP_DIRS)
|
||||||
|
@RAW_TARGET=`echo $@ $(foreach lang, $(LANGS), | sed 's|$(ML_OUT_DIR)/$(lang)/||')`;\
|
||||||
|
for lang in $(LANGS); do \
|
||||||
|
target=$(ML_OUT_DIR)/$$lang/$$RAW_TARGET;\
|
||||||
|
printf $(FMT_OUT_ML_HTML) "$$lang" "$<" "$$target"; \
|
||||||
|
$(HTML_PP_CMD) --input "$<" --output "$$target" --var include_dir=$(_INCLUDE_DIR) --var lang=$$lang --output-deps "`echo $${target}.d | sed 's|$(OUT_DIR)/|$(DEP_DIR)/|'`"; \
|
||||||
|
done
|
||||||
|
|
||||||
|
|
||||||
|
# rule for all not html files
|
||||||
|
$(foreach out_dir, $(ML_OUT_LANG_DIRS), $(out_dir)/%): $(_COMMON_DIR)/% | $(ML_OUT_DIRS)
|
||||||
|
@lang=`echo $(patsubst $(ML_OUT_DIR)/%, %, $@) | awk -F "/" '{print $$1}'`; \
|
||||||
|
printf $(FMT_OUT_ML_OTHER) "$$lang" "$<" "$@" ; \
|
||||||
|
cp $< $@
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifdef FAVICONS
|
||||||
|
# must be first
|
||||||
|
$(FAVICON_ICO): $(_FAVICON) | $(FAVICON_OUT_DIR)
|
||||||
|
@printf $(FMT_OUT_FAVICON) "$<" "$@"
|
||||||
|
@convert "$<" -define icon:auto-resize=16,32,48 "$@"
|
||||||
|
|
||||||
|
$(FAVICONS_PNG): $(_FAVICON) | $(FAVICON_OUT_DIR)
|
||||||
|
@printf $(FMT_OUT_FAVICON) "$<" "$@"
|
||||||
|
@# resize to 512x512 and pad with transparency in case resize did not resize to correct size
|
||||||
|
@size=$$(echo "$@" | grep -o -P '\d{2,4}x\d{2,4}');\
|
||||||
|
convert "$<" -resize "$${size}" -background none -gravity center -extent "$${size}" "$@"
|
||||||
|
endif
|
||||||
|
|
||||||
|
|
||||||
|
# THUMBNAILS
|
||||||
|
$(OUT_DIR)/$(THUMB_OUT_DIR)/%.jpg: | $(THUMB_OUT_DIRS)
|
||||||
|
@fulltarget="$@"; \
|
||||||
|
target="$(patsubst $(OUT_DIR)/$(THUMB_OUT_DIR)/%.jpg,%,$@)"; \
|
||||||
|
sources=($(_THUMB_FLS)); \
|
||||||
|
source=$$(printf "%s\n" $${sources[@]} | grep "$$target"'\.'); \
|
||||||
|
printf $(FMT_OUT_THUMB) "$$source" "$$fulltarget"; \
|
||||||
|
case "$${source##*.}" in \
|
||||||
|
"mp4-use-magick-as-well") ffmpegthumbnailer -i "$$source" -o "$$fulltarget" -s 300 -q 5;; \
|
||||||
|
"pdf") pdftoppm -f 1 -singlefile -jpeg -r 50 "$$source" "$${fulltarget%.*}";; \
|
||||||
|
"mp3"|"flac"|"wav") ffmpeg -hide_banner -i "$$source" "$$fulltarget" -y >/dev/null;; \
|
||||||
|
*) magick "$${source}[0]" -thumbnail '$(THUMB_SIZE)x$(THUMB_SIZE)>' "$@";; \
|
||||||
|
esac
|
||||||
|
|
||||||
|
# SITEMAP
|
||||||
|
ifdef SITEMAP_OUT
|
||||||
|
$(SITEMAP_OUT): $(OUT_FLS) $(ML_OUT_FLS) # build sitemap after all other files
|
||||||
|
@printf $(FMT_OUT_SITEMAP) "$@"
|
||||||
|
@$(HTML_PP_CMD) --sitemap-generate "$@"
|
||||||
|
endif
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# (NORMAL/RE-)SOURCE RULES
|
||||||
|
#
|
||||||
|
$(OUT_DIR)/%.html: $(PROJECT_DIR)/%.html | $(OUT_DIRS) $(_DEP_DIRS)
|
||||||
|
@printf $(FMT_OUT_HTML) "$<" "$@";
|
||||||
|
@$(HTML_PP_CMD) --input "$<" --output "$@" --var include_dir=$(_INCLUDE_DIR) --output-deps "$(subst $(DEP_DIR)/$(PROJECT_DIR), $(DEP_DIR), $(DEP_DIR)/$<.d)";
|
||||||
|
@# remove comments and empty lines. two separate lines bc the substitution might create new empty lines
|
||||||
|
@#awk -i inplace '{FS="" sub(/<!--.*-->/,"")}1' $@
|
||||||
|
@#awk -i inplace '{if (NF != 0) print}' $@
|
||||||
|
|
||||||
|
|
||||||
|
# SASS
|
||||||
|
$(OUT_DIR)/%.css: $(PROJECT_DIR)/%.sass | $(OUT_DIRS) $(_DEP_DIRS)
|
||||||
|
@printf $(FMT_OUT_CSS) "$<" "$@";
|
||||||
|
@$(_SASS_CMD) --indented "$<" "$@" || { rm "$@"; exit 1; }
|
||||||
|
@depfile=$(patsubst $(OUT_DIR)/%,$(DEP_DIR)/%,$@).d; echo -n "$@: " > "$$depfile"; \
|
||||||
|
jq -r '.sources | @sh' $@.map | tr -d \' | sed 's|file://||g' >> "$$depfile"; \
|
||||||
|
rm $@.map
|
||||||
|
@# generate a dependecy file from the source map and delete the map
|
||||||
|
# SCSS
|
||||||
|
$(OUT_DIR)/%.css: $(PROJECT_DIR)/%.scss | $(OUT_DIRS) $(_DEP_DIRS)
|
||||||
|
@printf $(FMT_OUT_CSS) "$<" "$@";
|
||||||
|
@$(_SASS_CMD) --no-indented "$<" "$@" || { rm "$@"; exit 1; }
|
||||||
|
@# generate a dependecy file from the source map and delete the map
|
||||||
|
@depfile=$(patsubst $(OUT_DIR)/%,$(DEP_DIR)/%,$@).d; echo -n "$@: " > "$$depfile"; \
|
||||||
|
jq -r '.sources | @sh' $@.map | tr -d \' | sed 's|file://||g' >> "$$depfile"; \
|
||||||
|
rm $@.map
|
||||||
|
|
||||||
|
# this rule must be last!
|
||||||
|
$(OUT_DIR)/%: $(PROJECT_DIR)/% | $(OUT_DIRS) $(RES_OUT_DIRS)
|
||||||
|
@printf $(FMT_OUT_OTHER) "$<" "$@"
|
||||||
|
@cp -r $< $@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# .DEFAULT:
|
||||||
|
# @echo "MISSING RULE: $@"
|
||||||
|
|
||||||
|
start:
|
||||||
|
/usr/sbin/nginx -c nginx.conf -p $(shell pwd)&
|
||||||
|
firefox http://localhost:8080/
|
||||||
|
stop:
|
||||||
|
killall nginx
|
||||||
|
|
||||||
|
clean:
|
||||||
|
-@rm $(OUT_FLS) $(ML_OUT_FLS) $(SITEMAP_TEMP_FILE) $(SITEMAP) 2>/dev/null
|
||||||
|
-@rm -r $(DEP_DIR) 2>/dev/null
|
||||||
|
|
||||||
|
cleaner:
|
||||||
|
-@rm -r $(OUT_DIR)
|
||||||
|
-@rm -r $(DEP_DIR) 2>/dev/null
|
32
example/build/de/de-only.html
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="content-type" content="text/html">
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="description" content="Diese Seite ist nur auf deutsch">
|
||||||
|
<meta name="keywords" content="deutsche,keywords,beispiel">
|
||||||
|
<meta name="author" content="Max Mustermann">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Coole Seite</title>
|
||||||
|
<link rel="icon" type="image/x-icon" href="/favicon/favicon.ico">
|
||||||
|
<link rel="shortcut-icon" href="/favicon/favicon.ico">
|
||||||
|
<link rel="icon" type="image/png" sizes="16x16" href="/favicon/favicon-16x16.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="/favicon/favicon-32x32.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" type="image/png" href="/favicon/apple-touch-icon-180x180.png">
|
||||||
|
<link rel="manifest" href="/site.webmanifest">
|
||||||
|
<link rel="stylesheet" href="/style/main.css", id="mainStyleSheet">
|
||||||
|
</head>
|
||||||
|
<script src="/script/test.js"></script>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<h1>Hallo</h1>
|
||||||
|
<div class="img_text">
|
||||||
|
<img class="example" src="/resources/example.svg" alt="Beispielbild" title="bUwUma"/>
|
||||||
|
<p>
|
||||||
|
Diese Seite ist nur auf deutsch verfügbar
|
||||||
|
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
52
example/build/de/index.html
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="content-type" content="text/html">
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="description" content="Voreingestellte Beschreibung">
|
||||||
|
<meta name="keywords" content="Index,Beispiel">
|
||||||
|
<meta name="author" content="Max Mustermann">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Index</title>
|
||||||
|
<link rel="icon" type="image/x-icon" href="/favicon/favicon.ico">
|
||||||
|
<link rel="shortcut-icon" href="/favicon/favicon.ico">
|
||||||
|
<link rel="icon" type="image/png" sizes="16x16" href="/favicon/favicon-16x16.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="/favicon/favicon-32x32.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" type="image/png" href="/favicon/apple-touch-icon-180x180.png">
|
||||||
|
<link rel="manifest" href="/site.webmanifest">
|
||||||
|
<link rel="stylesheet" href="/style/main.css", id="mainStyleSheet">
|
||||||
|
</head>
|
||||||
|
<script src="/script/test.js"></script>
|
||||||
|
<body>
|
||||||
|
<div class="sidenav">
|
||||||
|
<ul>
|
||||||
|
<li class="menudrop">☰</li>
|
||||||
|
<li class="sidenav_section_name">Abschnitte</li>
|
||||||
|
<li class="sidenav_section_links">
|
||||||
|
<ul>
|
||||||
|
<li class="sidenav_link"><a href="#interesting">Dieser Abschnitt ist sehr interessant</a></li>
|
||||||
|
<li class="sidenav_link"><a href="#section3">Geänderter Name!</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li class="sidenav_section_name">Sonstiges:</li>
|
||||||
|
<li class="sidenav_section_links">
|
||||||
|
<ul>
|
||||||
|
<li class="sidenav_link"><a href="/en/index.html">Index Englisch</a></li>
|
||||||
|
<li class="sidenav_link"><a href="/en/en-only.html">Nur englische Seite</a></li>
|
||||||
|
<li class="sidenav_link"><a href="/de/de-only.html">Nur deutsche Seite</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<main>
|
||||||
|
<h1>Willkommen auf der Deutschen Version</h1>
|
||||||
|
<p>Das Navigationsmenü wurde anhand der Überschriften erstellt und einige extra links aus <code>src/common/index.html</code> wurden eingefügt.</p>
|
||||||
|
<h2 id="interesting">Dieser Abschnitt ist sehr interessant</h2>
|
||||||
|
<p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est.</p>
|
||||||
|
<h2>Diese Überschrift bekommt keinen Eintrag, weil sie keine id hat</h2>
|
||||||
|
<p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.</p>
|
||||||
|
<h3 id="section3">Dieser Abschnitt hat im Menü einen custom Namen</h3>
|
||||||
|
<p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam.</p>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
32
example/build/en/en-only.html
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="content-type" content="text/html">
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="description" content="Default description">
|
||||||
|
<meta name="keywords" content="english,keywords,example">
|
||||||
|
<meta name="author" content="Max Mustermann">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>English only</title>
|
||||||
|
<link rel="icon" type="image/x-icon" href="/favicon/favicon.ico">
|
||||||
|
<link rel="shortcut-icon" href="/favicon/favicon.ico">
|
||||||
|
<link rel="icon" type="image/png" sizes="16x16" href="/favicon/favicon-16x16.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="/favicon/favicon-32x32.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" type="image/png" href="/favicon/apple-touch-icon-180x180.png">
|
||||||
|
<link rel="manifest" href="/site.webmanifest">
|
||||||
|
<link rel="stylesheet" href="/style/main.css", id="mainStyleSheet">
|
||||||
|
</head>
|
||||||
|
<script src="/script/test.js"></script>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<h1>Hello there!</h1>
|
||||||
|
<div class="img_text">
|
||||||
|
<img class="example" src="/resources/example.svg" alt="Example image" title="bUwUma"/>
|
||||||
|
<p>
|
||||||
|
This site is only available in engisch.
|
||||||
|
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
52
example/build/en/index.html
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="content-type" content="text/html">
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="description" content="Default description">
|
||||||
|
<meta name="keywords" content="Index,Example">
|
||||||
|
<meta name="author" content="Max Mustermann">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Index</title>
|
||||||
|
<link rel="icon" type="image/x-icon" href="/favicon/favicon.ico">
|
||||||
|
<link rel="shortcut-icon" href="/favicon/favicon.ico">
|
||||||
|
<link rel="icon" type="image/png" sizes="16x16" href="/favicon/favicon-16x16.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="/favicon/favicon-32x32.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" type="image/png" href="/favicon/apple-touch-icon-180x180.png">
|
||||||
|
<link rel="manifest" href="/site.webmanifest">
|
||||||
|
<link rel="stylesheet" href="/style/main.css", id="mainStyleSheet">
|
||||||
|
</head>
|
||||||
|
<script src="/script/test.js"></script>
|
||||||
|
<body>
|
||||||
|
<div class="sidenav">
|
||||||
|
<ul>
|
||||||
|
<li class="menudrop">☰</li>
|
||||||
|
<li class="sidenav_section_name">Sidenav Section</li>
|
||||||
|
<li class="sidenav_section_links">
|
||||||
|
<ul>
|
||||||
|
<li class="sidenav_link"><a href="#interesting">This section is super interesting</a></li>
|
||||||
|
<li class="sidenav_link"><a href="#section3">Custom named section!</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li class="sidenav_section_name">Other:</li>
|
||||||
|
<li class="sidenav_section_links">
|
||||||
|
<ul>
|
||||||
|
<li class="sidenav_link"><a href="/de/index.html">Index Deutsch</a></li>
|
||||||
|
<li class="sidenav_link"><a href="/en/en-only.html">Englisch only page</a></li>
|
||||||
|
<li class="sidenav_link"><a href="/de/de-only.html">German only page</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<main>
|
||||||
|
<h1>Welcome to the english version</h1>
|
||||||
|
<p>The navigation menu was generated from the headings and some custom links defined in <code>src/common/index.html</code>.</p>
|
||||||
|
<h2 id="interesting">This section is super interesting</h2>
|
||||||
|
<p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est.</p>
|
||||||
|
<h2>This heading does not get an entry, because it has no id</h2>
|
||||||
|
<p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.</p>
|
||||||
|
<h3 id="section3">This section has a custom name in the menu</h3>
|
||||||
|
<p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam.</p>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
BIN
example/build/favicon/android-chrome-192x192.png
Normal file
After Width: | Height: | Size: 9.0 KiB |
BIN
example/build/favicon/android-chrome-512x512.png
Normal file
After Width: | Height: | Size: 44 KiB |
BIN
example/build/favicon/apple-touch-icon-180x180.png
Normal file
After Width: | Height: | Size: 8.5 KiB |
BIN
example/build/favicon/favicon-16x16.png
Normal file
After Width: | Height: | Size: 804 B |
BIN
example/build/favicon/favicon-32x32.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
example/build/favicon/favicon-48x48.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
example/build/favicon/favicon.ico
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
example/build/favicon/mstile-150x150.png
Normal file
After Width: | Height: | Size: 6.4 KiB |
64
example/build/resources/example.svg
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="48"
|
||||||
|
height="48"
|
||||||
|
viewBox="0 0 12.700001 12.700001"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1"
|
||||||
|
inkscape:version="1.3.1 (91b66b0783, 2023-11-16, custom)"
|
||||||
|
sodipodi:docname="example.svg"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview1"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
inkscape:zoom="9.3924909"
|
||||||
|
inkscape:cx="33.537429"
|
||||||
|
inkscape:cy="32.898621"
|
||||||
|
inkscape:window-width="1900"
|
||||||
|
inkscape:window-height="1034"
|
||||||
|
inkscape:window-x="8"
|
||||||
|
inkscape:window-y="34"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1" />
|
||||||
|
<defs
|
||||||
|
id="defs1" />
|
||||||
|
<g
|
||||||
|
inkscape:label="Ebene 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(-86.894793,-82.841363)">
|
||||||
|
<path
|
||||||
|
style="fill:#ff0000;stroke-width:0.0981705"
|
||||||
|
d="M 87.064776,94.98403 99.462221,94.21962 97.232099,83.103959 c -2.136109,2.741712 -5.04605,3.605895 -8.670918,2.735482 0.842445,5.641412 -0.255599,7.531012 -1.496405,9.144589 z"
|
||||||
|
id="path5"
|
||||||
|
sodipodi:nodetypes="ccccc" />
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-size:1.57072px;fill:#ffffff;stroke-width:0.0981705"
|
||||||
|
x="93.495735"
|
||||||
|
y="90.043213"
|
||||||
|
id="text1"><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
|
style="font-size:1.57072px;text-align:center;text-anchor:middle;fill:#ffffff;stroke-width:0.0981705"
|
||||||
|
x="93.495735"
|
||||||
|
y="90.043213"
|
||||||
|
id="tspan3">bUwUma</tspan><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
|
style="font-size:1.57072px;text-align:center;text-anchor:middle;fill:#ffffff;stroke-width:0.0981705"
|
||||||
|
x="93.495735"
|
||||||
|
y="92.006615"
|
||||||
|
id="tspan5">---</tspan></text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
64
example/build/resources/favicon.png
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="48"
|
||||||
|
height="48"
|
||||||
|
viewBox="0 0 12.700001 12.700001"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1"
|
||||||
|
inkscape:version="1.3.1 (91b66b0783, 2023-11-16, custom)"
|
||||||
|
sodipodi:docname="favicon.png"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview1"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
inkscape:zoom="9.3924909"
|
||||||
|
inkscape:cx="33.537429"
|
||||||
|
inkscape:cy="32.898621"
|
||||||
|
inkscape:window-width="1900"
|
||||||
|
inkscape:window-height="1034"
|
||||||
|
inkscape:window-x="8"
|
||||||
|
inkscape:window-y="34"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1" />
|
||||||
|
<defs
|
||||||
|
id="defs1" />
|
||||||
|
<g
|
||||||
|
inkscape:label="Ebene 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(-86.894793,-82.841363)">
|
||||||
|
<path
|
||||||
|
style="fill:#ff0000;stroke-width:0.0981705"
|
||||||
|
d="M 87.064776,94.98403 99.462221,94.21962 97.232099,83.103959 c -2.136109,2.741712 -5.04605,3.605895 -8.670918,2.735482 0.842445,5.641412 -0.255599,7.531012 -1.496405,9.144589 z"
|
||||||
|
id="path5"
|
||||||
|
sodipodi:nodetypes="ccccc" />
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-size:1.57072px;fill:#ffffff;stroke-width:0.0981705"
|
||||||
|
x="93.495735"
|
||||||
|
y="90.043213"
|
||||||
|
id="text1"><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
|
style="font-size:1.57072px;text-align:center;text-anchor:middle;fill:#ffffff;stroke-width:0.0981705"
|
||||||
|
x="93.495735"
|
||||||
|
y="90.043213"
|
||||||
|
id="tspan3">bUwUma</tspan><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
|
style="font-size:1.57072px;text-align:center;text-anchor:middle;fill:#ffffff;stroke-width:0.0981705"
|
||||||
|
x="93.495735"
|
||||||
|
y="92.006615"
|
||||||
|
id="tspan5">---</tspan></text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
1
example/build/script/test.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
console.log("This script will indeed be loaded!");
|
3
example/build/sitemap.xml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||||
|
</urlset>
|
42
example/build/style/main.css
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
body {
|
||||||
|
background-color: #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
color: blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
border: solid 1px #cc0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.img_text img {
|
||||||
|
width: 30%;
|
||||||
|
height: auto;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
.img_text p {
|
||||||
|
width: 69%;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidenav {
|
||||||
|
border: solid 2px blue;
|
||||||
|
}
|
||||||
|
.sidenav ul {
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
|
.sidenav li {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.sidenav:hover li {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.sidenav .menudrop {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.sidenav:hover .menudrop, .sidenav li {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*# sourceMappingURL=main.css.map */
|
904
example/html-preprocessor
Executable file
@ -0,0 +1,904 @@
|
|||||||
|
#!/bin/python3
|
||||||
|
import os
|
||||||
|
from os import path
|
||||||
|
import re
|
||||||
|
from sys import argv
|
||||||
|
from collections.abc import Callable
|
||||||
|
import argparse
|
||||||
|
import pickle
|
||||||
|
|
||||||
|
"""
|
||||||
|
TODO:
|
||||||
|
- more testing
|
||||||
|
- reintroduce the nav_selected class on nav feature
|
||||||
|
"""
|
||||||
|
"""
|
||||||
|
************************************************************ SETTINGS ************************************************************
|
||||||
|
"""
|
||||||
|
sidenav_format = """\
|
||||||
|
<div class="sidenav">
|
||||||
|
<ul>
|
||||||
|
<li class="menudrop">☰</li>
|
||||||
|
#sidenav-content
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
sidenav_content_link = "<li class=\"sidenav_link\"><a href=\"#link\">#name</a></li>"
|
||||||
|
sidenav_content_section = """\
|
||||||
|
<li class="sidenav_section_name">#name</li>
|
||||||
|
<li class="sidenav_section_links">
|
||||||
|
<ul>
|
||||||
|
#links
|
||||||
|
</ul>
|
||||||
|
</li>"""
|
||||||
|
|
||||||
|
exit_on_include_failure = False
|
||||||
|
|
||||||
|
sitemap_begin = """\
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n"""
|
||||||
|
sitemap_end = "</urlset>"
|
||||||
|
|
||||||
|
"""
|
||||||
|
************************************************************ REGULAR EXPRESSIONS ************************************************************
|
||||||
|
"""
|
||||||
|
# SIDENAV
|
||||||
|
# heading with id
|
||||||
|
re_sidenav_heading = r"<h\d.*id=(?:\"|\')([a-zA-Z0-9_\-]+)(?:\"|\').*>(.+)</h\d>"
|
||||||
|
# custom entry
|
||||||
|
re_sidenav_custom = r"href=(?:\"|\')([^\"\' ]+)(?:\"|\') +name=(?:\"|\')(.+)(?:\"|\')"
|
||||||
|
|
||||||
|
# commas
|
||||||
|
re_set_map = r"([a-zA-Z0-9_]+) *\? *\{( *(?:[a-zA-Z0-9_*]+ *: *[^,]*, *)+[a-zA-Z0-9_*]+ *: *[^,]*) *,? *\}"
|
||||||
|
# semicolons
|
||||||
|
re_set_map_alt = r"([a-zA-Z0-9_]+) *\? *\{( *(?:[a-zA-Z0-9_*]+ *: *[^;]* *; *)+[a-zA-Z0-9_*]+ *: *[^;]*) *;? *\}"
|
||||||
|
|
||||||
|
""" #$(myvar) """
|
||||||
|
re_variable_use = r"#\$\(([a-zA-Z0-9_]+)\)"
|
||||||
|
|
||||||
|
""" only in comments """
|
||||||
|
re_preprocessor_command = r"[\t ]*#([a-zA-Z]+) *(.*)[\t ]*"
|
||||||
|
|
||||||
|
# https://www.w3.org/TR/NOTE-datetime
|
||||||
|
re_w3cdate = r"\d{4}-(?)]-\d{2}"
|
||||||
|
r"\d{4}-(?:0[1-9]|1[0-2])-(?:[0-2]\d|3[01])(T(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d([\+\-](?:0\d|1[0-2]):[0-5]\d)?)?"
|
||||||
|
|
||||||
|
COMMENT_BEGIN = "<!--"
|
||||||
|
COMMENT_END = "-->"
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
************************************************************ GLOBALS ************************************************************
|
||||||
|
"""
|
||||||
|
glob_dependcies: list[str] = []
|
||||||
|
|
||||||
|
exit_codes = {
|
||||||
|
"FileNotFound": 2,
|
||||||
|
"MarkdownConversionError": 3,
|
||||||
|
}
|
||||||
|
error_levels = {
|
||||||
|
"light": 0,
|
||||||
|
"serious": 1,
|
||||||
|
"critical": 2,
|
||||||
|
}
|
||||||
|
exit_on_error_level = error_levels["serious"]
|
||||||
|
|
||||||
|
# url that the currently processed file have
|
||||||
|
current_file_url = ""
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
************************************************************ UTILITY ************************************************************
|
||||||
|
"""
|
||||||
|
|
||||||
|
RED = '\033[91m'
|
||||||
|
GREEN = '\033[92m'
|
||||||
|
YELLOW = '\033[93m'
|
||||||
|
BLUE = '\033[94m'
|
||||||
|
MAGENTA = '\033[95m'
|
||||||
|
CYAN = '\033[96m'
|
||||||
|
GRAY = '\033[97m'
|
||||||
|
RESET = '\033[0m'
|
||||||
|
BOLD = '\033[1m'
|
||||||
|
WHITE = '\033[37m'
|
||||||
|
|
||||||
|
|
||||||
|
DEBUG = False
|
||||||
|
def pdebug(*args, **keys):
|
||||||
|
fname, *_args = args
|
||||||
|
if DEBUG: print(f"{CYAN}{fname}{GRAY}", *_args, RESET, **keys)
|
||||||
|
|
||||||
|
TRACE = False
|
||||||
|
def ptrace(*args, **keys):
|
||||||
|
fname, *_args = args
|
||||||
|
if TRACE: print(f"{BLUE}{fname}{GRAY}", *_args, RESET, **keys)
|
||||||
|
|
||||||
|
def error(*args, level:int=exit_on_error_level, exit_code:int=1, **keys):
|
||||||
|
fname, *_args = args
|
||||||
|
if level >= exit_on_error_level:
|
||||||
|
print(f"{RED}ERROR: {fname}{RESET}", *_args, RESET, **keys)
|
||||||
|
exit(exit_code)
|
||||||
|
else:
|
||||||
|
print(f"{YELLOW}WARNING: {fname}{RESET}", *_args, RESET, **keys)
|
||||||
|
|
||||||
|
def line_is_link_to_path(line, path):
|
||||||
|
# check if the line is a link to html thats currently being processed
|
||||||
|
match = re.search(r"<a href=(\"|\')(.+)(\"|\')>(.+)</a>", line)
|
||||||
|
if match:
|
||||||
|
# get filename
|
||||||
|
match = re.match(r"[a-zA-Z0-9_\-]+\.html", match.groups()[1])
|
||||||
|
if match and match.group() in path:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def pos2line(s: str, pos:int):
|
||||||
|
return s[:pos].count('\n') + 1
|
||||||
|
|
||||||
|
|
||||||
|
def generate_dependecy_file(filename:str, deps:list[str]):
|
||||||
|
line1 = f"{filename}:"
|
||||||
|
s = ""
|
||||||
|
for dep in deps:
|
||||||
|
line1 += f" {dep}"
|
||||||
|
s += f"{dep}:\n"
|
||||||
|
return line1 #+ "\n" + s
|
||||||
|
|
||||||
|
def evaluate_condition(input_string) -> bool:
|
||||||
|
words = re.split(r"(==|!=|&&|\|\|)", input_string.replace(" ", ""))
|
||||||
|
for i in range(len(words)):
|
||||||
|
if words[i] not in ["==", "!=", "&&", "||"]:
|
||||||
|
words[i] = '"' + words[i].replace('"', r'\"') + '"'
|
||||||
|
|
||||||
|
condition = "".join(words).replace("&&", " and ").replace("||", " or ")
|
||||||
|
ptrace("evaluate_conditon", f"Evaluating condition {condition}")
|
||||||
|
try:
|
||||||
|
return eval(condition)
|
||||||
|
except SyntaxError:
|
||||||
|
error("evaluate_conditon", f"Pythonized condition is invalid: {condition}", level=error_levels["light"])
|
||||||
|
return False
|
||||||
|
|
||||||
|
"""
|
||||||
|
************************************************************ SITEMAP ************************************************************
|
||||||
|
"""
|
||||||
|
class Sitemap:
|
||||||
|
urls:dict = {}
|
||||||
|
def __init__(self, url=None):
|
||||||
|
self.url = url
|
||||||
|
self.priority = None
|
||||||
|
self.changefreq = None
|
||||||
|
self.lastmod = None
|
||||||
|
|
||||||
|
def set_url(self, url):
|
||||||
|
self.url = url
|
||||||
|
|
||||||
|
def set_priority(self, priority):
|
||||||
|
try:
|
||||||
|
priority = float(priority)
|
||||||
|
except ValueError:
|
||||||
|
error("Sitemap.set_priority", f"invalid priority: '{priority}'", level=error_levels["serious"])
|
||||||
|
if not (type(priority) == float and 0.0 <= priority and priority <= 1.0):
|
||||||
|
error("Sitemap.set_priority", f"invalid priority: '{priority}'", level=error_levels["serious"])
|
||||||
|
self.priority = priority
|
||||||
|
|
||||||
|
def set_changefreq(self, changefreq):
|
||||||
|
if not (type(changefreq) == str and changefreq in ["always", "hourly", "daily", "weekly", "monthly", "yearly", "never"]):
|
||||||
|
error("Sitemap.set_changefreq", f"invalid changefreq: '{changefreq}'", level=error_levels["serious"])
|
||||||
|
self.changefreq = changefreq
|
||||||
|
|
||||||
|
def set_lastmod(self, lastmod):
|
||||||
|
if not (type(lastmod) == str and re.fullmatch(re_w3cdate, lastmod)):
|
||||||
|
error("Sitemap.set_lastmod", f"invalid lastmod: '{lastmod}'", level=error_levels["serious"])
|
||||||
|
self.lastmod = lastmod
|
||||||
|
|
||||||
|
def get_entry(self):
|
||||||
|
s = f"<url>\n\t<loc>{self.url}</loc>"
|
||||||
|
if self.priority is not None: s += f"\n\t<priority>{self.priority}</priority>"
|
||||||
|
if self.changefreq is not None: s += f"\n\t<changefreq>{self.changefreq}</changefreq>"
|
||||||
|
if self.lastmod is not None: s += f"\n\t<lastmod>{self.lastmod}</lastmod>"
|
||||||
|
s += "\n</url>"
|
||||||
|
return s
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"Sitemap(url={self.url}, priority={self.priority}, changefreq={self.changefreq}, lastmod={self.lastmod})"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def gen_sidemap():
|
||||||
|
s = sitemap_begin
|
||||||
|
for url in Sitemap.urls.values():
|
||||||
|
s += "\t" + url.get_entry().replace("\n", "\n\t").strip("\t") + "\n"
|
||||||
|
s += sitemap_end
|
||||||
|
return s
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def cmd_sitemap(args:str, variables:dict[str,str]) -> str:
|
||||||
|
space = args.find(" ")
|
||||||
|
if space < 0:
|
||||||
|
space = len(args)
|
||||||
|
cmd = args[:space]
|
||||||
|
cmd_args = ""
|
||||||
|
|
||||||
|
if 0 < space and space < len(args) - 1:
|
||||||
|
cmd_args = args[space+1:].strip(" ")
|
||||||
|
pdebug("cmd_sitemap", f"cmd='{cmd}' cmd_args='{cmd_args}'")
|
||||||
|
if not current_file_url in Sitemap.urls:
|
||||||
|
Sitemap.urls[current_file_url] = Sitemap()
|
||||||
|
if cmd == "include":
|
||||||
|
if cmd_args:
|
||||||
|
Sitemap.urls[current_file_url].set_url(cmd_args)
|
||||||
|
else:
|
||||||
|
Sitemap.urls[current_file_url].set_url(current_file_url)
|
||||||
|
elif cmd == "priority":
|
||||||
|
Sitemap.urls[current_file_url].set_priority(cmd_args)
|
||||||
|
elif cmd == "changefreq":
|
||||||
|
Sitemap.urls[current_file_url].set_changefreq(cmd_args)
|
||||||
|
elif cmd == "lastmod":
|
||||||
|
Sitemap.urls[current_file_url].set_lastmod(cmd_args)
|
||||||
|
else:
|
||||||
|
error("cmd_sitemap", f"Invalid command '{cmd}'", error_levels["serious"])
|
||||||
|
ptrace("cmd_sitemap", f"Sitemap[{current_file_url}] is now: {Sitemap.urls[current_file_url]}")
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
************************************************************ SIDENAV ************************************************************
|
||||||
|
"""
|
||||||
|
def replace_and_respect_indent(string, replace, replacement):
|
||||||
|
"""
|
||||||
|
replace all occurences of 'replace' with 'replacement', add the whitespaces in front of 'replace' to every line of 'replacement'
|
||||||
|
"""
|
||||||
|
i = string.find(replace)
|
||||||
|
while i >= 0:
|
||||||
|
line_begin = string.rfind("\n", 0, i) + 1
|
||||||
|
indent = string[line_begin:i]
|
||||||
|
string = string[:line_begin] + replacement.replace("\n", "\n" + indent) + string[i+len(replace):]
|
||||||
|
i = string.find(replace)
|
||||||
|
return string
|
||||||
|
|
||||||
|
class Sidenav:
|
||||||
|
class Link:
|
||||||
|
def __init__(self, name: str, link: str):
|
||||||
|
self.link = link
|
||||||
|
self.name = name
|
||||||
|
def __repr__(self):
|
||||||
|
return f"Link: name={self.name}, link={self.link}"
|
||||||
|
|
||||||
|
def get(self):
|
||||||
|
return sidenav_content_link.replace("#name", self.name).replace("#link", self.link)
|
||||||
|
class Section:
|
||||||
|
def __init__(self, name: str):
|
||||||
|
self.name = name
|
||||||
|
self.links = []
|
||||||
|
def add_link(self, link):
|
||||||
|
self.links.append(link)
|
||||||
|
def __repr__(self):
|
||||||
|
return f"Section: name={self.name}"
|
||||||
|
def get(self):
|
||||||
|
links = "".join([ link.get() + "\n" for link in self.links ])
|
||||||
|
return replace_and_respect_indent(sidenav_content_section.replace("#name", self.name), "#links", links)
|
||||||
|
entries: list[Link|Section] = []
|
||||||
|
skip_next = False
|
||||||
|
custom_name = None
|
||||||
|
@staticmethod
|
||||||
|
def addEntry(name: str, link: str):
|
||||||
|
if Sidenav.skip_next:
|
||||||
|
Sidenav.skip_next = None
|
||||||
|
return
|
||||||
|
if Sidenav.custom_name:
|
||||||
|
name = Sidenav.custom_name
|
||||||
|
Sidenav.custom_name = None
|
||||||
|
if len(Sidenav.entries) > 0 and type(Sidenav.entries[-1]) == Sidenav.Section:
|
||||||
|
Sidenav.entries[-1].add_link(Sidenav.Link(name, link))
|
||||||
|
else:
|
||||||
|
Sidenav.entries.append(Sidenav.Link(name, link))
|
||||||
|
@staticmethod
|
||||||
|
def addSection(name):
|
||||||
|
Sidenav.entries.append(Sidenav.Section(name))
|
||||||
|
@staticmethod
|
||||||
|
def setCustomName(name: str):
|
||||||
|
Sidenav.custom_name = name
|
||||||
|
@staticmethod
|
||||||
|
def skipNext():
|
||||||
|
Sidenav.skip_next = True
|
||||||
|
@staticmethod
|
||||||
|
def generate() -> str:
|
||||||
|
pdebug("Sidenav.generate", f"found the following entries: {Sidenav.entries}")
|
||||||
|
entries = "".join([entry.get() + "\n" for entry in Sidenav.entries])
|
||||||
|
return replace_and_respect_indent(sidenav_format, "#sidenav-content", entries)
|
||||||
|
@staticmethod
|
||||||
|
def cmd_sidenav(args:str, variables:dict[str,str]) -> str:
|
||||||
|
space = args.find(" ")
|
||||||
|
if space < 0:
|
||||||
|
space = len(args)
|
||||||
|
cmd = args[:space]
|
||||||
|
cmd_args = ""
|
||||||
|
if 0 < space and space < len(args) - 1:
|
||||||
|
cmd_args = args[space+1:].strip(" ")
|
||||||
|
pdebug("cmd_sidenav", f"cmd='{cmd}' cmd_args='{cmd_args}'")
|
||||||
|
if cmd == "skip":
|
||||||
|
Sidenav.skipNext()
|
||||||
|
elif cmd == "section":
|
||||||
|
Sidenav.addSection(cmd_args)
|
||||||
|
elif cmd == "name":
|
||||||
|
Sidenav.setCustomName(cmd_args)
|
||||||
|
elif cmd == "custom":
|
||||||
|
match = re.fullmatch(re_sidenav_custom, cmd_args)
|
||||||
|
if match:
|
||||||
|
Sidenav.addEntry(match.groups()[1], match.groups()[0])
|
||||||
|
else:
|
||||||
|
error("cmd_sidenav", f"Invalid argument for command 'custom': '{cmd_args}'", level=error_levels["light"])
|
||||||
|
elif cmd == "include":
|
||||||
|
return Sidenav.generate()
|
||||||
|
else:
|
||||||
|
error("cmd_sidenav", f"Invalid command: '{cmd}'", level=error_levels["light"])
|
||||||
|
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
************************************************************ COMMANDS ************************************************************
|
||||||
|
All these commands take one arg with trimmed whitespaces.
|
||||||
|
The arg may be anything
|
||||||
|
|
||||||
|
They all need to return a string, which will be placed
|
||||||
|
into the source file at the place where the command was.
|
||||||
|
"""
|
||||||
|
def cmd_include(args: str, variables:dict[str, str]={}) -> str:
|
||||||
|
args = args.split(' ')
|
||||||
|
pdebug("cmd_include", f"args='{args}', variables='{variables}'")
|
||||||
|
filename = args[0]
|
||||||
|
content = ""
|
||||||
|
try:
|
||||||
|
with open(filename) as file:
|
||||||
|
content = file.read()
|
||||||
|
if len(args) > 1: # if section was specified
|
||||||
|
target_section = args[1]
|
||||||
|
p = HTMLParser(content, {})
|
||||||
|
p.pos["start"] = p.pos["end"] = -1
|
||||||
|
while p.i < len(p): # at start of new line or end of comment
|
||||||
|
p.find_line_end()
|
||||||
|
ptrace("cmd_include", f"Processing at i={p.i} in line {pos2line(p.file, p.i)}: '{p[p.i:p.pos['line_end']]}'")
|
||||||
|
if not p.find_comment_begin(): continue
|
||||||
|
if not p.find_comment_end(): continue
|
||||||
|
p.replace_multiline_comments()
|
||||||
|
|
||||||
|
match = p.find_command()
|
||||||
|
if match:
|
||||||
|
command = match.groups()[0]
|
||||||
|
cmd_args = match.groups()[1].replace('\t', ' ').strip(' ')
|
||||||
|
pdebug("cmd_include", f"Found command '{command}' with args '{cmd_args}'")
|
||||||
|
if command == "section":
|
||||||
|
if cmd_args.startswith(target_section):
|
||||||
|
p.pos["start"] = max(p.pos["cmt_end"] + len(COMMENT_END), p.pos["line_end"] + 1)
|
||||||
|
elif p.pos["start"] >= 0: #end
|
||||||
|
p.pos["end"] = max(p.pos["cmt_end"] + len(COMMENT_END), p.pos["line_end"] + 1)
|
||||||
|
# p.pos["end"] = p.pos["cmt_beg"]
|
||||||
|
p.replace_command_with_output("")
|
||||||
|
p.command_end() # remove the command (+comment)
|
||||||
|
if p.pos["start"] >= 0 and p.pos["end"] > 0: break
|
||||||
|
continue
|
||||||
|
# section cmd in multiline comment is not supported, so simply jump to end of comment
|
||||||
|
p.i = p.pos["cmt_end"] + len(COMMENT_END)
|
||||||
|
p.pos["cmt_beg"] = -1
|
||||||
|
p.pos["cmd_beg"] = -1
|
||||||
|
p.pos["cmt_end"] = -1
|
||||||
|
p.pos["cmd_end"] = -1
|
||||||
|
if p.pos["start"] >= 0:
|
||||||
|
if p.pos["end"] < 0:
|
||||||
|
p.pos["end"] = len(p)
|
||||||
|
content = p[p.pos["start"]:p.pos["end"]]
|
||||||
|
else:
|
||||||
|
error("cmd_include", f"Could not find section {target_section} in file {filename}")
|
||||||
|
except FileNotFoundError:
|
||||||
|
error("cmd_include", f"Could not open file '{filename}'", level=error_levels["serious"], exit_code=exit_codes["FileNotFound"])
|
||||||
|
content = f"<!-- Could not include '{filename}' -->"
|
||||||
|
if filename.endswith(".md"):
|
||||||
|
try:
|
||||||
|
from markdown import markdown
|
||||||
|
content = markdown(content, output_format="xhtml")
|
||||||
|
except:
|
||||||
|
error("cmd_include", f"Could convert markdown to html for file '{filename}'. Is python-markdown installed?", level=error_levels["critical"], exit_code=exit_codes["MarkdownConversionError"])
|
||||||
|
content = f"<!-- Could not convert to html: '{filename}' -->"
|
||||||
|
glob_dependcies.append(filename)
|
||||||
|
return content
|
||||||
|
|
||||||
|
def cmd_section(args: str, variables:dict[str, str]={}) -> str:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def cmd_return(args: str, variables:dict[str, str]={}) -> str:
|
||||||
|
# re_set_map = r"([a-zA-Z0-9_]+)\?\{(([a-zA-Z0-9_]+:.+,)*([a-zA-Z0-9_]+:.+))\}"
|
||||||
|
# <!-- #set section=lang?{*:Fallback,de:Abschnitt,en:Section} -->
|
||||||
|
space = args.find(' ')
|
||||||
|
pdebug("cmd_set", f"varname='{args[:space]}, 'arg='{args[space+1:]}', variables='{variables}'")
|
||||||
|
if not (space > 0 and space < len(args)-1):
|
||||||
|
variables[args] = ""
|
||||||
|
pdebug("cmd_set", f"Setting to empty string: {args}")
|
||||||
|
else:
|
||||||
|
varname = args[:space]
|
||||||
|
variables[varname] = ""
|
||||||
|
# check if map assignment with either , or ;
|
||||||
|
separator = ','
|
||||||
|
match = re.fullmatch(re_set_map, args[space+1:].strip(' '))
|
||||||
|
if not match:
|
||||||
|
match = re.fullmatch(re_set_map_alt, args[space+1:].strip(' '))
|
||||||
|
separator = ';'
|
||||||
|
if match:
|
||||||
|
pdebug("cmd_set", f"Map {match.group()}")
|
||||||
|
depends = match.groups()[0]
|
||||||
|
if not depends in variables:
|
||||||
|
pdebug("cmd_set", f"Setting from map, but depends='{depends}' is not in variables")
|
||||||
|
return ""
|
||||||
|
depends_val = variables[depends]
|
||||||
|
for option in match.groups()[1].split(separator):
|
||||||
|
option = option.strip(" ")
|
||||||
|
pdebug("cmd_set", f"Found option {option}")
|
||||||
|
colon = option.find(':') # we will find one, regex guarantees
|
||||||
|
if option[:colon].strip(" ") == depends_val or option[:colon].strip(" ") == "*":
|
||||||
|
variables[varname] = option[colon+1:].strip(" ")
|
||||||
|
|
||||||
|
else: # simple asignment
|
||||||
|
value = args[space+1:].strip(" ")
|
||||||
|
variables[varname] = value
|
||||||
|
pdebug("cmd_set", f"Assignment {varname} -> {value}")
|
||||||
|
return variables[varname]
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def cmd_set(args: str, variables:dict[str, str]={}) -> str:
|
||||||
|
cmd_return(args, variables)
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def cmd_unset(args: str, variables:dict[str, str]={}) -> str:
|
||||||
|
variable = args.strip(' ')
|
||||||
|
if variable not in variables:
|
||||||
|
pdebug("cmd_unset", f"variable '{variable}' is not set", level=error_levels["light"])
|
||||||
|
else:
|
||||||
|
variables.pop(variable)
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def cmd_default(args: str, variables:dict[str, str]={}) -> str:
|
||||||
|
separator = args.find(' ')
|
||||||
|
if args[:separator] not in variables:
|
||||||
|
cmd_return(args, variables)
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_comment(args: str, variables:dict[str, str]={}) -> str:
|
||||||
|
return f"<!-- {args} -->"
|
||||||
|
def cmd_uncomment(args: str, variables:dict[str, str]={}) -> str:
|
||||||
|
return args
|
||||||
|
|
||||||
|
def cmd_error(args: str, variables:dict[str, str]={}) -> str:
|
||||||
|
error("cmd_error", f"Encounted 'error' command: {args}", level=error_levels["critical"])
|
||||||
|
return ""
|
||||||
|
def cmd_warning(args: str, variables:dict[str, str]={}) -> str:
|
||||||
|
error("cmd_warning", f"Encounted 'warning' command: {args}", level=error_levels["light"])
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
command2function:dict[str, Callable[[str, dict[str,str]], str]] = {
|
||||||
|
"include": cmd_include,
|
||||||
|
"section": cmd_section,
|
||||||
|
"return": cmd_return,
|
||||||
|
"set": cmd_set,
|
||||||
|
"unset": cmd_unset,
|
||||||
|
"default": cmd_default,
|
||||||
|
"comment": cmd_comment,
|
||||||
|
"uncomment": cmd_uncomment,
|
||||||
|
"sidenav": Sidenav.cmd_sidenav,
|
||||||
|
"sitemap": Sitemap.cmd_sitemap,
|
||||||
|
"warning": cmd_warning,
|
||||||
|
"error": cmd_error,
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
************************************************************ PARSING ************************************************************
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Parser():
|
||||||
|
"""
|
||||||
|
General purpose parser class
|
||||||
|
It has states and positions in a text, which are updated when portions of the text are replaced or removed
|
||||||
|
"""
|
||||||
|
def __init__(self, file):
|
||||||
|
self.file = file
|
||||||
|
self.pos: dict[str, int] = {}
|
||||||
|
self.state: dict[str, bool] = {}
|
||||||
|
|
||||||
|
def remove(self, start, stop, ignore_bounds=[]):
|
||||||
|
"""remove range [start, stop) of text and update positions"""
|
||||||
|
delete_length = stop - start
|
||||||
|
nl, esl = "\n", "\\n"
|
||||||
|
|
||||||
|
ptrace("Parser.remove", f"Deleting range [{start}, {stop}) of length {delete_length}: '{self.file[start:stop].replace(nl, esl)}'")
|
||||||
|
assert(stop >= start)
|
||||||
|
assert(stop <= len(self.file))
|
||||||
|
self.file = self.file[:start] + self.file[stop:]
|
||||||
|
for k,pos in self.pos.items():
|
||||||
|
if pos >= stop: self.pos[k] -= delete_length
|
||||||
|
elif pos > start and not k in ignore_bounds: error("Parser.remove", f"Position {k}={pos} within deleted range [{start},{stop})", level=error_levels["light"])
|
||||||
|
|
||||||
|
def replace(self, start, stop, replacement, ignore_bounds=[]):
|
||||||
|
assert(stop >= start)
|
||||||
|
assert(stop <= len(self.file))
|
||||||
|
ptrace("Parser.replace", f"Replacing range [{start}, {stop}): '{self.file[start:stop]}' with '{replacement}'")
|
||||||
|
self.file = self.file[:start] + replacement + self.file[stop:]
|
||||||
|
length_difference = stop - start - len(replacement)
|
||||||
|
for k,pos in self.pos.items():
|
||||||
|
if pos >= stop: self.pos[k] -= length_difference
|
||||||
|
elif pos > start and k not in ignore_bounds: error("Parser.replace", f"Position {k}={pos} within replaced range [{start},{stop})", level=error_levels["light"])
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
return self.file[key]
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self.file)
|
||||||
|
|
||||||
|
|
||||||
|
class HTMLParser(Parser):
|
||||||
|
"""
|
||||||
|
Parse a html file
|
||||||
|
Each function operates the positon indicated by i until the position "line_end"
|
||||||
|
"""
|
||||||
|
def __init__(self, file, variables:dict[str, str], remove_comments=False):
|
||||||
|
super().__init__(file)
|
||||||
|
self.i = 0
|
||||||
|
self.variables = variables
|
||||||
|
self.pos["cmt_beg"] = -1
|
||||||
|
self.pos["cmt_end"] = -1
|
||||||
|
self.pos["cmd_beg"] = -1
|
||||||
|
self.pos["cmd_end"] = -1
|
||||||
|
self.pos["line_end"] = -1
|
||||||
|
self.pos["conditional_block_beg"] = -1 # char pos of the first char of the last block, if waiting for elif, else or endif
|
||||||
|
self.state["cmd_in_cmt"] = False
|
||||||
|
self.state["last_condition"] = False # if the last if condition was true
|
||||||
|
self.remove_comments = remove_comments
|
||||||
|
|
||||||
|
|
||||||
|
def use_variables(self):
|
||||||
|
"""replace variable usages in the current line"""
|
||||||
|
self.replace(self.i, self.pos["line_end"], substitute_variables(self[self.i:self.pos["line_end"]], self.variables))
|
||||||
|
ptrace("HTMLParser.use_variables", f"Line after variable substitution:", self.file[self.i:self.pos["line_end"]])
|
||||||
|
|
||||||
|
def add_sidenav_headings(self):
|
||||||
|
"""check if heading for sidenav in line"""
|
||||||
|
match = re.search(re_sidenav_heading, self[self.i:self.pos["line_end"]])
|
||||||
|
if match:
|
||||||
|
Sidenav.addEntry(match.groups()[1], f"#{match.groups()[0]}")
|
||||||
|
ptrace("HTMLParser.add_sidenav_headings:", f"Found heading with id:", match.groups())
|
||||||
|
|
||||||
|
def get_leading_whitespaces(self):
|
||||||
|
"""returns the whitespaces at the start of the line"""
|
||||||
|
# find last newline
|
||||||
|
line_beg = self.file.rfind("\n", 0, self.i)
|
||||||
|
if line_beg < 0: line_beg = 0
|
||||||
|
else: line_beg += 1 # start after newline
|
||||||
|
match = re.match(r"^([ \t]*)", self.file[line_beg:self.pos['line_end']])
|
||||||
|
if not match: return ""
|
||||||
|
else: return match.groups()[0]
|
||||||
|
|
||||||
|
|
||||||
|
# Parsing functions
|
||||||
|
def find_line_end(self):
|
||||||
|
"""
|
||||||
|
line_end -> position of next newline char or EOF
|
||||||
|
"""
|
||||||
|
self.pos["line_end"] = self.file.find('\n', self.i+1)
|
||||||
|
if self.pos["line_end"] < 0: self.pos["line_end"] = len(self)
|
||||||
|
|
||||||
|
|
||||||
|
def find_comment_begin(self) -> bool:
|
||||||
|
"""
|
||||||
|
find the beginning of a comment in the current line
|
||||||
|
if comment begin was found, jump into the comment, return True
|
||||||
|
cmt_beg -> beginning of COMMENT_BEGIN
|
||||||
|
i -> first character after COMMENT_BEGIN / line_end + 1
|
||||||
|
|
||||||
|
"""
|
||||||
|
# look for comment begin
|
||||||
|
if self.pos["cmt_beg"] < 0: # if not in comment, find next comment
|
||||||
|
self.pos["cmt_beg"] = self.file.find(COMMENT_BEGIN, self.i, self.pos["line_end"])
|
||||||
|
if self.pos["cmt_beg"] < 0:
|
||||||
|
self.i = self.pos["line_end"] + 1
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
# jump to comment_begin
|
||||||
|
old_i = self.i
|
||||||
|
self.i = self.pos["cmt_beg"] + len(COMMENT_BEGIN) # after comment begin
|
||||||
|
ptrace(f"HTMLParser.find_comment_begin", f"Found comment begin, jumping from pos {old_i} to {self.i}")
|
||||||
|
return True
|
||||||
|
return True # still in previous comment
|
||||||
|
|
||||||
|
|
||||||
|
def find_comment_end(self):
|
||||||
|
"""
|
||||||
|
call after find_comment_begin returns true to update the cmt_end
|
||||||
|
call continue when returning false
|
||||||
|
cmt_end -> beginning of COMMENT_END / ---
|
||||||
|
cmt_beg -> --- / -1 when invalid comment
|
||||||
|
"""
|
||||||
|
# in comment, i at the character after COMMENT_BEGIN
|
||||||
|
self.pos["cmt_end"] = self.file.find(COMMENT_END, self.i) #, self.pos["line_end"])
|
||||||
|
# sanity checks
|
||||||
|
if self.pos["cmt_end"] < 0:
|
||||||
|
error("HTMLParser.find_comment_end", f"Comment starting in line {pos2line(self.file, self.pos['cmt_beg'])} is never ended.", level=error_levels["serious"])
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
tmp_next_begin = self.file.find(COMMENT_BEGIN, self.i)
|
||||||
|
if 0 < tmp_next_begin and tmp_next_begin < self.pos["cmt_end"]:
|
||||||
|
error("HTMLParser.find_comment_end", f"Found next comment begin before the comment starting in line {pos2line(self.file, self.pos['cmt_beg'])} is ended! Skipping comment. Comment without proper closing tags: '{self.file[self.i:self.pos['line_end']]}'", level=error_levels["light"])
|
||||||
|
self.pos["cmt_beg"] = -1
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def replace_multiline_comments(self):
|
||||||
|
"""
|
||||||
|
if in a multiline comment, turn every line into a separate comment
|
||||||
|
"""
|
||||||
|
# not a multiline comment
|
||||||
|
if self.pos["line_end"] > self.pos["cmt_end"]: return
|
||||||
|
indent = self.get_leading_whitespaces()
|
||||||
|
self.replace(self.pos["cmt_beg"], self.pos["cmt_end"], self.file[self.pos["cmt_beg"]:self.pos["cmt_end"]].replace("\n", "-->\n" + indent + "<!--"), ignore_bounds=["line_end"])
|
||||||
|
self.find_line_end()
|
||||||
|
self.find_comment_end()
|
||||||
|
|
||||||
|
|
||||||
|
def find_command(self):
|
||||||
|
# either at newline (if in multiline comment) or at comment end
|
||||||
|
self.pos["cmd_beg"] = self.i
|
||||||
|
self.pos["cmd_end"] = min(self.pos["line_end"], self.pos["cmt_end"])
|
||||||
|
assert self.pos["cmd_end"] >= self.i, f"cmd_end={self.pos['cmd_end']}, i={self.i}, line_end={self.pos['line_end']}, cmt_end={self.pos['cmt_end']}"
|
||||||
|
ptrace("HTMLParser.find_command", f"Possible command end: {self.pos['cmd_end']}, possible command: '{self[self.i:self.pos['cmd_end']]}'")
|
||||||
|
|
||||||
|
# find commands
|
||||||
|
match = re.fullmatch(re_preprocessor_command, self[self.i:self.pos["cmd_end"]].strip(" "))
|
||||||
|
if match:
|
||||||
|
self.state["cmd_in_cmt"] = True
|
||||||
|
return match
|
||||||
|
|
||||||
|
def replace_command_with_output(self, command_output):
|
||||||
|
# keep indent level
|
||||||
|
indent = self.get_leading_whitespaces()
|
||||||
|
self.replace(self.i, self.pos["cmd_end"], command_output.replace("\n", "\n" + indent))
|
||||||
|
ptrace(f"HTMLParser.replace_command_with_output", f"After command, the line is now '{self.file[self.i:self.pos['line_end']]}'")
|
||||||
|
|
||||||
|
def command_end(self):
|
||||||
|
if self.pos["cmd_end"] == self.pos["cmt_end"]: # reached end of comment
|
||||||
|
if self.state["cmd_in_cmt"] or self.remove_comments:
|
||||||
|
remove_newline = 0
|
||||||
|
if self[self.pos["cmt_beg"]-1] == '\n' and self[self.pos["cmt_end"]+len(COMMENT_END)] == '\n': # if the comment consumes the whole line, remove the entire line
|
||||||
|
remove_newline = 1
|
||||||
|
if self.state["cmd_in_cmt"]: # remove comment tags if a command was found
|
||||||
|
ptrace("HTMLParser.command_end", f"Deleting opening comment tags")
|
||||||
|
self.remove(self.pos["cmt_beg"], self.pos["cmt_beg"] + len(COMMENT_BEGIN))
|
||||||
|
self.remove(self.pos["cmt_end"], self.pos["cmt_end"] + len(COMMENT_END) + remove_newline, ignore_bounds=["cmt_end", "cmd_end", "line_end"])
|
||||||
|
# process the line again, because a command might have inserted new comments
|
||||||
|
self.i -= len(COMMENT_BEGIN)
|
||||||
|
elif self.remove_comments: # remove entire comment
|
||||||
|
self.remove(self.pos["cmt_beg"], self.pos["cmt_end"] + len(COMMENT_END) + remove_newline, ignore_bounds=["cmt_end", "cmd_beg", "cmd_end", "line_end"])
|
||||||
|
self.i = self.pos["cmt_beg"]
|
||||||
|
self.state["cmd_in_cmt"] = False
|
||||||
|
self.pos["cmt_beg"] = -1
|
||||||
|
self.pos["cmd_beg"] = -1
|
||||||
|
self.pos["cmt_end"] = -1
|
||||||
|
self.pos["cmd_end"] = -1
|
||||||
|
else: # multiline comment
|
||||||
|
self.pos["cmt_end"] = -1
|
||||||
|
self.pos["cmd_end"] = -1
|
||||||
|
self.i = self.pos["line_end"] + 1
|
||||||
|
ptrace(f"HTMLParser.command_end", f"Multiline comment, jumping to next line.")
|
||||||
|
# i = possible_command_end commented, because if something containing new commands is inserted we need to parse that as well
|
||||||
|
|
||||||
|
|
||||||
|
def parse_file(_file:str, variables:dict[str,str], remove_comments):
|
||||||
|
p = HTMLParser(_file, variables, remove_comments=remove_comments)
|
||||||
|
sidenav_include_pos = -1
|
||||||
|
|
||||||
|
while p.i < len(p): # at start of new line or end of comment
|
||||||
|
p.find_line_end()
|
||||||
|
ptrace("parse_file", f"Processing at i={p.i} in line {pos2line(p.file, p.i)}: '{p[p.i:p.pos['line_end']]}'")
|
||||||
|
|
||||||
|
p.use_variables()
|
||||||
|
p.add_sidenav_headings()
|
||||||
|
|
||||||
|
if not p.find_comment_begin(): continue
|
||||||
|
|
||||||
|
if not p.find_comment_end(): continue
|
||||||
|
p.replace_multiline_comments()
|
||||||
|
|
||||||
|
match = p.find_command()
|
||||||
|
if match:
|
||||||
|
command = match.groups()[0]
|
||||||
|
args = match.groups()[1].replace('\t', ' ').strip(' ')
|
||||||
|
pdebug("parse_file", f"Found command '{command}' with args '{args}'")
|
||||||
|
# delete from previous block if
|
||||||
|
if command in ["elif", "else", "endif"]:
|
||||||
|
if p.pos["conditional_block_beg"] < 0: error("parse_file", f"Misplaced '{command}' in line {pos2line(p.file, p.i)}")
|
||||||
|
if p.state["last_condition"]:
|
||||||
|
# delete block from here at next endif
|
||||||
|
p.state["last_condition"] = False
|
||||||
|
else:
|
||||||
|
# delete block from last condition statement
|
||||||
|
ptrace("parse_file", f"> Deleting block from last condition")
|
||||||
|
p.remove(p.pos["conditional_block_beg"], p.pos["cmt_beg"])
|
||||||
|
p.i = p.pos["cmd_beg"]
|
||||||
|
p.pos["conditional_block_beg"] = p.i
|
||||||
|
if command == "endif":
|
||||||
|
p.pos["conditional_block_beg"] = -1
|
||||||
|
p.state["last_condition"] = False
|
||||||
|
p.state["any_condition"] = False
|
||||||
|
# evaluate ifs
|
||||||
|
if command == "if":
|
||||||
|
p.pos["conditional_block_beg"] = p.i
|
||||||
|
p.state["last_condition"] = evaluate_condition(args)
|
||||||
|
p.state["any_condition"] = p.state["last_condition"]
|
||||||
|
pdebug("parse_file", f"Command {command} condition evaluated to {p.state['last_condition']}")
|
||||||
|
cmd_output = ""
|
||||||
|
elif command =="elif":
|
||||||
|
p.pos["conditional_block_beg"] = p.i
|
||||||
|
p.state["last_condition"] = evaluate_condition(args) if not p.state["any_condition"] else False
|
||||||
|
if p.state["last_condition"]:
|
||||||
|
p.state["any_condition"] = True
|
||||||
|
pdebug("parse_file", f"Command {command} condition evaluated to {p.state['last_condition']}")
|
||||||
|
cmd_output = ""
|
||||||
|
elif command == "else":
|
||||||
|
p.pos["conditional_block_beg"] = p.i
|
||||||
|
p.state["last_condition"] = True if not p.state["any_condition"] else False
|
||||||
|
cmd_output = ""
|
||||||
|
elif p.pos["conditional_block_beg"] < 0 or p.state["last_condition"]:
|
||||||
|
if command == "sidenav" and args == "include": # if args contains anything else this wont work
|
||||||
|
sidenav_include_pos = p.pos["cmt_beg"] # remove the comment
|
||||||
|
cmd_output = ""
|
||||||
|
elif command == "endif":
|
||||||
|
cmd_output = ""
|
||||||
|
elif command not in command2function:
|
||||||
|
error("parse_file", f"Invalid command in line {pos2line(p.file, p.i)}: {command}", level=error_levels["light"])
|
||||||
|
cmd_output = ""
|
||||||
|
else:
|
||||||
|
cmd_output = command2function[command](args, variables)
|
||||||
|
else:
|
||||||
|
cmd_output = ""
|
||||||
|
p.replace_command_with_output(cmd_output)
|
||||||
|
else:
|
||||||
|
pdebug("parse_file", f"Did not find command in comment {p.file[p.pos['cmt_beg']:p.pos['cmt_end']+len(COMMENT_END)]}")
|
||||||
|
|
||||||
|
p.command_end()
|
||||||
|
|
||||||
|
if sidenav_include_pos >= 0:
|
||||||
|
p.i = sidenav_include_pos # required before get_leading_whitespaces
|
||||||
|
p.find_line_end() # required before get_leading_whitespaces
|
||||||
|
indent = p.get_leading_whitespaces()
|
||||||
|
return p.file[:sidenav_include_pos] + Sidenav.generate().replace("\n", "\n" + indent) + p.file[sidenav_include_pos:]
|
||||||
|
else:
|
||||||
|
return p.file
|
||||||
|
|
||||||
|
|
||||||
|
def substitute_variables(html:str, variables:dict[str, str]):
|
||||||
|
"""
|
||||||
|
find usage of variables and replace them with their value
|
||||||
|
"""
|
||||||
|
matches = []
|
||||||
|
for match in re.finditer(re_variable_use, html):
|
||||||
|
matches.append(match)
|
||||||
|
html_list = list(html)
|
||||||
|
for match in reversed(matches):
|
||||||
|
pdebug("substitute_variables", f"Found variable usage {match.groups()[0]}, match from {match.start()} to {match.end()}")
|
||||||
|
value = ""
|
||||||
|
if match.groups()[0] in variables: value = variables[match.groups()[0]]
|
||||||
|
else:
|
||||||
|
pdebug("substitute_variables", f"Variable {match.groups()[0]} is used but not defined")
|
||||||
|
for _ in range(match.start(), match.end()):
|
||||||
|
html_list.pop(match.start())
|
||||||
|
html_list.insert(match.start(), value.strip(" "))
|
||||||
|
return ''.join(html_list)
|
||||||
|
|
||||||
|
"""
|
||||||
|
************************************************************ COMMAND LINE ************************************************************
|
||||||
|
"""
|
||||||
|
if __name__ == "__main__":
|
||||||
|
parser = argparse.ArgumentParser(prog="bUwUma html preprocessor")
|
||||||
|
parser.add_argument("--input", action="store", help="path to the input file", default="")
|
||||||
|
parser.add_argument("--output", action="store", help="output to this file", default="")
|
||||||
|
parser.add_argument("--inplace", action="store_true", help="overwrite input file")
|
||||||
|
parser.add_argument("--preserve-comments", action="store_true", help="do not remove normal html comments", default=False)
|
||||||
|
parser.add_argument("--var", action="append", help="set a variable --var varname=value", default=[])
|
||||||
|
parser.add_argument("--output-deps", action="store", help="output a Makefile listing all dependencies", default="")
|
||||||
|
parser.add_argument("--sitemap-generate", action="store", help="generate the sitemap from the sitemap-temp-file", default="")
|
||||||
|
parser.add_argument("--sitemap-temp-file", action="store", help="file for storing sitemap data during build process", default="/tmp/sitemap.pkl")
|
||||||
|
parser.add_argument("--sitemap-webroot-dir", action="store", help="directory of the webroot, without trailing /. This will be removed from the output path for generating the sitemap url entry", default="")
|
||||||
|
parser.add_argument("--sitemap-base-url", action="store", help="base url of the website, without trailing /", default="https://www.example.com")
|
||||||
|
parser.add_argument("--sitemap-remove-ext", action="store_true", help="remove the file extenstion in the sitemap entry")
|
||||||
|
parser.add_argument("--exit-on", action="store", help="exit when an error of the given severity occures", choices=["light", "serious", "critical"], default="serious")
|
||||||
|
parser.add_argument("--debug", action="store_true", help="be more verbose", default=False)
|
||||||
|
parser.add_argument("--trace", action="store_true", help="be extremly verbose", default=False)
|
||||||
|
variables:dict[str, str] = {}
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
for var in args.var:
|
||||||
|
sep = var.find('=')
|
||||||
|
if sep > 0 and sep < len(var) - 1:
|
||||||
|
variables[var[:sep].strip(" ")] = var[sep+1:].strip(" ")
|
||||||
|
else:
|
||||||
|
parser.error(f"Invalid argument: --var '{var}'\n\tUsage: --var <varname>=<value>")
|
||||||
|
|
||||||
|
args.input = args.input.strip(" ")
|
||||||
|
args.output = args.output.strip(" ")
|
||||||
|
args.output_deps = args.output_deps.strip(" ")
|
||||||
|
args.sitemap_temp_file = args.sitemap_temp_file.strip(" ")
|
||||||
|
args.sitemap_generate = args.sitemap_generate.strip(" ")
|
||||||
|
TRACE = args.trace
|
||||||
|
if args.trace: args.debug = True
|
||||||
|
DEBUG = args.debug
|
||||||
|
|
||||||
|
# either input file or sitemap_generate is required
|
||||||
|
if not (bool(args.input) ^ bool(args.sitemap_generate)):
|
||||||
|
parser.error(f"Exactly one if --input or --sitemap-generate must be given")
|
||||||
|
|
||||||
|
if args.input:
|
||||||
|
if args.sitemap_webroot_dir:
|
||||||
|
current_file_url = args.sitemap_base_url + args.output.replace(args.sitemap_webroot_dir, "")
|
||||||
|
else:
|
||||||
|
current_file_url = args.sitemap_base_url + args.output
|
||||||
|
|
||||||
|
if args.sitemap_remove_ext:
|
||||||
|
current_file_url = os.path.splitext(current_file_url)[0]
|
||||||
|
|
||||||
|
pdebug("main", f"current_file={current_file_url}")
|
||||||
|
|
||||||
|
# sanity checks
|
||||||
|
if not path.isfile(args.input):
|
||||||
|
parser.error(f"Invalid input file:: {args.input}")
|
||||||
|
if args.output:
|
||||||
|
if not path.isdir(path.dirname(args.output)):
|
||||||
|
parser.error(f"Invalid path to output file - directory does not exist: '{path.dirname(args.output)}'")
|
||||||
|
elif args.inplace:
|
||||||
|
args.output = args.input
|
||||||
|
if args.inplace and args.output:
|
||||||
|
parser.error(f"Only one of --output or --inplace mut be given")
|
||||||
|
if args.output_deps:
|
||||||
|
if not path.isdir(path.dirname(args.output_deps)):
|
||||||
|
parser.error(f"Invalid path to dependency file - directory does not exist: '{path.dirname(args.output_deps)}'")
|
||||||
|
if not args.output:
|
||||||
|
parser.error(f"--output-deps requires either --output <file> our --inplace")
|
||||||
|
|
||||||
|
if args.sitemap_temp_file:
|
||||||
|
if path.isfile(args.sitemap_temp_file):
|
||||||
|
with open(args.sitemap_temp_file, "rb") as file:
|
||||||
|
Sitemap.urls = pickle.load(file)
|
||||||
|
|
||||||
|
# get html
|
||||||
|
with open(args.input, "r") as file:
|
||||||
|
target_html = file.read()
|
||||||
|
|
||||||
|
output_html = parse_file(target_html, variables, not args.preserve_comments)
|
||||||
|
# remove empty lines
|
||||||
|
output_html = re.sub(r"[\t\r ]*\n(?:[\t\r ]*\n)+", r"\n", output_html)
|
||||||
|
|
||||||
|
# pdebug(f"Output: {output_html}")
|
||||||
|
|
||||||
|
# save
|
||||||
|
if args.output:
|
||||||
|
with open(args.output, "w") as file:
|
||||||
|
file.write(output_html)
|
||||||
|
else:
|
||||||
|
print(output_html)
|
||||||
|
|
||||||
|
if args.output_deps:
|
||||||
|
if args.output != args.input:
|
||||||
|
glob_dependcies.append(args.input)
|
||||||
|
depfile = generate_dependecy_file(args.output, glob_dependcies)
|
||||||
|
pdebug("main", f"Writing dependency file to {os.path.abspath(args.output_deps)}: {depfile}")
|
||||||
|
with open(args.output_deps, "w") as file:
|
||||||
|
file.write(depfile)
|
||||||
|
if args.sitemap_temp_file:
|
||||||
|
with open(args.sitemap_temp_file, "wb") as file:
|
||||||
|
pickle.dump(Sitemap.urls, file)
|
||||||
|
else: # sitemap_generate
|
||||||
|
if not path.isfile(args.sitemap_temp_file):
|
||||||
|
parser.error(f"Invalid sitemap-temp-file: '{args.sitemap_temp_file}'")
|
||||||
|
with open(args.sitemap_temp_file, "rb") as file:
|
||||||
|
Sitemap.urls = pickle.load(file)
|
||||||
|
sitemap = Sitemap.gen_sidemap()
|
||||||
|
pdebug("main", f"Writing sitemap to {os.path.abspath(args.sitemap_generate)}")
|
||||||
|
with open(args.sitemap_generate, "w") as file:
|
||||||
|
file.write(sitemap)
|
34
example/nginx.conf
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
worker_processes 1;
|
||||||
|
error_log stderr;
|
||||||
|
daemon off;
|
||||||
|
pid nginx.pid;
|
||||||
|
|
||||||
|
events {
|
||||||
|
worker_connections 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
include /etc/nginx/mime.types;
|
||||||
|
default_type application/octet-stream;
|
||||||
|
|
||||||
|
sendfile on;
|
||||||
|
keepalive_timeout 65;
|
||||||
|
|
||||||
|
access_log /dev/null;
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 8080;
|
||||||
|
server_name localhost;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
root build;
|
||||||
|
index /en/index.html;
|
||||||
|
}
|
||||||
|
#error_page 404 /404.html;
|
||||||
|
|
||||||
|
error_page 500 502 503 504 /50x.html;
|
||||||
|
location = /50x.html {
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1
example/nginx.pid
Normal file
@ -0,0 +1 @@
|
|||||||
|
6663
|
37
example/src/common/index.html
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="#$(lang)">
|
||||||
|
<!--
|
||||||
|
Since it is in the COMMON_DIR, this file will be built twice: /en/index.html and /de/index.html
|
||||||
|
The lang variable is automatically set for this file
|
||||||
|
#set title Index
|
||||||
|
#set keywords lang?{*:Index,Example;de:Index,Beispiel}
|
||||||
|
The include_dir variable is set in the bUwUma Makefile and is automatically set,
|
||||||
|
#include #$(include_dir)/head.html
|
||||||
|
-->
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<!--
|
||||||
|
#sidenav include
|
||||||
|
#set sidenav_section_name lang?{*:Sidenav Section;de:Abschnitte}
|
||||||
|
#sidenav section #$(sidenav_section_name)
|
||||||
|
-->
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<!-- #include #$(include_dir)/index.#$(lang).md -->
|
||||||
|
</main>
|
||||||
|
<!--
|
||||||
|
#set sidenav_section_other lang?{*:Other;de:Sonstiges}
|
||||||
|
#sidenav section #$(sidenav_section_other):
|
||||||
|
#if #$(lang) == de
|
||||||
|
#sidenav custom href="/en/index.html" name="Index Englisch"
|
||||||
|
#else
|
||||||
|
#sidenav custom href="/de/index.html" name="Index Deutsch"
|
||||||
|
#endif
|
||||||
|
#set sidenav_link lang?{*:Englisch only page;de:Nur englische Seite}
|
||||||
|
#sidenav custom href="/en/en-only.html" name="#$(sidenav_link)"
|
||||||
|
#set sidenav_link lang?{*:German only page;de:Nur deutsche Seite}
|
||||||
|
#sidenav custom href="/de/de-only.html" name="#$(sidenav_link)"
|
||||||
|
-->
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
26
example/src/de/de-only.html
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<!--
|
||||||
|
The lang variable must be set manually (for the included head), since this site is not in COMMON_DIR
|
||||||
|
#set lang de
|
||||||
|
-->
|
||||||
|
<html lang="#$(lang)">
|
||||||
|
<!--
|
||||||
|
#set title Coole Seite
|
||||||
|
#set description Diese Seite ist nur auf deutsch
|
||||||
|
#include #$(include_dir)/head.html
|
||||||
|
-->
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<h1>Hallo</h1>
|
||||||
|
<div class="img_text">
|
||||||
|
#$(example_img)
|
||||||
|
<p>
|
||||||
|
Diese Seite ist nur auf deutsch verfügbar
|
||||||
|
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
25
example/src/en/en-only.html
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<!--
|
||||||
|
The lang variable must be set manually (for the included head), since this site is not in COMMON_DIR
|
||||||
|
#set lang en
|
||||||
|
-->
|
||||||
|
<html lang="#$(lang)">
|
||||||
|
<!--
|
||||||
|
#set title English only
|
||||||
|
#include #$(include_dir)/head.html
|
||||||
|
-->
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<h1>Hello there!</h1>
|
||||||
|
<div class="img_text">
|
||||||
|
#$(example_img)
|
||||||
|
<p>
|
||||||
|
This site is only available in engisch.
|
||||||
|
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
32
example/src/include/head.html
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<head>
|
||||||
|
<!--
|
||||||
|
If not overridden, these values will be used, depending on the lang variable
|
||||||
|
#default description lang?{*:Default description,de:Voreingestellte Beschreibung}
|
||||||
|
#default keywords lang?{*:english,keywords,example;de:deutsche,keywords,beispiel}
|
||||||
|
#default title lang?{*:bUwUma works!;de:bUwUma funktioniert!}
|
||||||
|
#default stylesheet /style/main.css
|
||||||
|
-->
|
||||||
|
<meta http-equiv="content-type" content="text/html">
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="description" content="#$(description)">
|
||||||
|
<meta name="keywords" content="#$(keywords)">
|
||||||
|
<meta name="author" content="Max Mustermann">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>#$(title)</title>
|
||||||
|
<link rel="icon" type="image/x-icon" href="/favicon/favicon.ico">
|
||||||
|
<link rel="shortcut-icon" href="/favicon/favicon.ico">
|
||||||
|
<link rel="icon" type="image/png" sizes="16x16" href="/favicon/favicon-16x16.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="/favicon/favicon-32x32.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" type="image/png" href="/favicon/apple-touch-icon-180x180.png">
|
||||||
|
<link rel="manifest" href="/site.webmanifest">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="#$(stylesheet)", id="mainStyleSheet">
|
||||||
|
<!-- #uncomment #$(extra_head) -->
|
||||||
|
</head>
|
||||||
|
<script src="/script/test.js"></script>
|
||||||
|
<!--
|
||||||
|
This image can now be included by simply using the variable example_img
|
||||||
|
#set img_title bUwUma
|
||||||
|
#set img_alt lang?{*:Example image;de:Beispielbild}
|
||||||
|
#set example_img <img class="example" src="/resources/example.svg" alt="#$(img_alt)" title="#$(img_title)"/>
|
||||||
|
-->
|
14
example/src/include/index.de.md
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# Willkommen auf der Deutschen Version
|
||||||
|
|
||||||
|
|
||||||
|
Das Navigationsmenü wurde anhand der Überschriften erstellt und einige extra links aus `src/common/index.html` wurden eingefügt.
|
||||||
|
|
||||||
|
<h2 id="interesting">Dieser Abschnitt ist sehr interessant</h2>
|
||||||
|
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est.
|
||||||
|
|
||||||
|
## Diese Überschrift bekommt keinen Eintrag, weil sie keine id hat
|
||||||
|
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
|
||||||
|
|
||||||
|
<!-- #sidenav name Geänderter Name! -->
|
||||||
|
<h3 id="section3">Dieser Abschnitt hat im Menü einen custom Namen</h3>
|
||||||
|
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam.
|
13
example/src/include/index.en.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# Welcome to the english version
|
||||||
|
|
||||||
|
The navigation menu was generated from the headings and some custom links defined in `src/common/index.html`.
|
||||||
|
|
||||||
|
<h2 id="interesting">This section is super interesting</h2>
|
||||||
|
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est.
|
||||||
|
|
||||||
|
## This heading does not get an entry, because it has no id
|
||||||
|
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
|
||||||
|
|
||||||
|
<!-- #sidenav name Custom named section! -->
|
||||||
|
<h3 id="section3">This section has a custom name in the menu</h3>
|
||||||
|
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam.
|
9
example/src/include/style/images.sass
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
.img_text
|
||||||
|
img
|
||||||
|
width: 30%
|
||||||
|
height: auto
|
||||||
|
float: left
|
||||||
|
|
||||||
|
p
|
||||||
|
width: 69%
|
||||||
|
float: left
|
12
example/src/include/style/sidenav.sass
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
.sidenav
|
||||||
|
border: solid 2px blue
|
||||||
|
ul
|
||||||
|
width: fit-content
|
||||||
|
li
|
||||||
|
display: none
|
||||||
|
&:hover li
|
||||||
|
display: block
|
||||||
|
.menudrop
|
||||||
|
display: block
|
||||||
|
&:hover .menudrop, li
|
||||||
|
display: none
|
64
example/src/resources/example.svg
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="48"
|
||||||
|
height="48"
|
||||||
|
viewBox="0 0 12.700001 12.700001"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1"
|
||||||
|
inkscape:version="1.3.1 (91b66b0783, 2023-11-16, custom)"
|
||||||
|
sodipodi:docname="example.svg"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview1"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
inkscape:zoom="9.3924909"
|
||||||
|
inkscape:cx="33.537429"
|
||||||
|
inkscape:cy="32.898621"
|
||||||
|
inkscape:window-width="1900"
|
||||||
|
inkscape:window-height="1034"
|
||||||
|
inkscape:window-x="8"
|
||||||
|
inkscape:window-y="34"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1" />
|
||||||
|
<defs
|
||||||
|
id="defs1" />
|
||||||
|
<g
|
||||||
|
inkscape:label="Ebene 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(-86.894793,-82.841363)">
|
||||||
|
<path
|
||||||
|
style="fill:#ff0000;stroke-width:0.0981705"
|
||||||
|
d="M 87.064776,94.98403 99.462221,94.21962 97.232099,83.103959 c -2.136109,2.741712 -5.04605,3.605895 -8.670918,2.735482 0.842445,5.641412 -0.255599,7.531012 -1.496405,9.144589 z"
|
||||||
|
id="path5"
|
||||||
|
sodipodi:nodetypes="ccccc" />
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-size:1.57072px;fill:#ffffff;stroke-width:0.0981705"
|
||||||
|
x="93.495735"
|
||||||
|
y="90.043213"
|
||||||
|
id="text1"><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
|
style="font-size:1.57072px;text-align:center;text-anchor:middle;fill:#ffffff;stroke-width:0.0981705"
|
||||||
|
x="93.495735"
|
||||||
|
y="90.043213"
|
||||||
|
id="tspan3">bUwUma</tspan><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
|
style="font-size:1.57072px;text-align:center;text-anchor:middle;fill:#ffffff;stroke-width:0.0981705"
|
||||||
|
x="93.495735"
|
||||||
|
y="92.006615"
|
||||||
|
id="tspan5">---</tspan></text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
64
example/src/resources/favicon.png
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="48"
|
||||||
|
height="48"
|
||||||
|
viewBox="0 0 12.700001 12.700001"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1"
|
||||||
|
inkscape:version="1.3.1 (91b66b0783, 2023-11-16, custom)"
|
||||||
|
sodipodi:docname="favicon.png"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview1"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
inkscape:zoom="9.3924909"
|
||||||
|
inkscape:cx="33.537429"
|
||||||
|
inkscape:cy="32.898621"
|
||||||
|
inkscape:window-width="1900"
|
||||||
|
inkscape:window-height="1034"
|
||||||
|
inkscape:window-x="8"
|
||||||
|
inkscape:window-y="34"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1" />
|
||||||
|
<defs
|
||||||
|
id="defs1" />
|
||||||
|
<g
|
||||||
|
inkscape:label="Ebene 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(-86.894793,-82.841363)">
|
||||||
|
<path
|
||||||
|
style="fill:#ff0000;stroke-width:0.0981705"
|
||||||
|
d="M 87.064776,94.98403 99.462221,94.21962 97.232099,83.103959 c -2.136109,2.741712 -5.04605,3.605895 -8.670918,2.735482 0.842445,5.641412 -0.255599,7.531012 -1.496405,9.144589 z"
|
||||||
|
id="path5"
|
||||||
|
sodipodi:nodetypes="ccccc" />
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-size:1.57072px;fill:#ffffff;stroke-width:0.0981705"
|
||||||
|
x="93.495735"
|
||||||
|
y="90.043213"
|
||||||
|
id="text1"><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
|
style="font-size:1.57072px;text-align:center;text-anchor:middle;fill:#ffffff;stroke-width:0.0981705"
|
||||||
|
x="93.495735"
|
||||||
|
y="90.043213"
|
||||||
|
id="tspan3">bUwUma</tspan><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
|
style="font-size:1.57072px;text-align:center;text-anchor:middle;fill:#ffffff;stroke-width:0.0981705"
|
||||||
|
x="93.495735"
|
||||||
|
y="92.006615"
|
||||||
|
id="tspan5">---</tspan></text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
1
example/src/script/test.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
console.log("This script will indeed be loaded!");
|
15
example/src/style/main.sass
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
// This sass file be compiled to css
|
||||||
|
|
||||||
|
body
|
||||||
|
background-color: #eee
|
||||||
|
|
||||||
|
h1
|
||||||
|
color: blue
|
||||||
|
|
||||||
|
p
|
||||||
|
border: solid 1px #cc0
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@import "images"
|
||||||
|
@import "sidenav"
|
@ -24,7 +24,13 @@ sidenav_format = """\
|
|||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
sidenav_content_link = "<li class=\"sidenav_link\"><a href=\"#link\">#name</a></li>"
|
sidenav_content_link = "<li class=\"sidenav_link\"><a href=\"#link\">#name</a></li>"
|
||||||
sidenav_content_section = "<li class=\"sidenav_section\">#name</li>"
|
sidenav_content_section = """\
|
||||||
|
<li class="sidenav_section_name">#name</li>
|
||||||
|
<li class="sidenav_section_links">
|
||||||
|
<ul>
|
||||||
|
#links
|
||||||
|
</ul>
|
||||||
|
</li>"""
|
||||||
|
|
||||||
exit_on_include_failure = False
|
exit_on_include_failure = False
|
||||||
|
|
||||||
@ -236,11 +242,40 @@ class Sitemap:
|
|||||||
"""
|
"""
|
||||||
************************************************************ SIDENAV ************************************************************
|
************************************************************ SIDENAV ************************************************************
|
||||||
"""
|
"""
|
||||||
|
def replace_and_respect_indent(string, replace, replacement):
|
||||||
|
"""
|
||||||
|
replace all occurences of 'replace' with 'replacement', add the whitespaces in front of 'replace' to every line of 'replacement'
|
||||||
|
"""
|
||||||
|
i = string.find(replace)
|
||||||
|
while i >= 0:
|
||||||
|
line_begin = string.rfind("\n", 0, i) + 1
|
||||||
|
indent = string[line_begin:i]
|
||||||
|
string = string[:line_begin] + replacement.replace("\n", "\n" + indent) + string[i+len(replace):]
|
||||||
|
i = string.find(replace)
|
||||||
|
return string
|
||||||
|
|
||||||
class Sidenav:
|
class Sidenav:
|
||||||
LINK = 0
|
class Link:
|
||||||
SECTION = 1
|
def __init__(self, name: str, link: str):
|
||||||
# 0: link, 1: section
|
self.link = link
|
||||||
entries: list[tuple[int, str, str]] = []
|
self.name = name
|
||||||
|
def __repr__(self):
|
||||||
|
return f"Link: name={self.name}, link={self.link}"
|
||||||
|
|
||||||
|
def get(self):
|
||||||
|
return sidenav_content_link.replace("#name", self.name).replace("#link", self.link)
|
||||||
|
class Section:
|
||||||
|
def __init__(self, name: str):
|
||||||
|
self.name = name
|
||||||
|
self.links = []
|
||||||
|
def add_link(self, link):
|
||||||
|
self.links.append(link)
|
||||||
|
def __repr__(self):
|
||||||
|
return f"Section: name={self.name}"
|
||||||
|
def get(self):
|
||||||
|
links = "".join([ link.get() + "\n" for link in self.links ])
|
||||||
|
return replace_and_respect_indent(sidenav_content_section.replace("#name", self.name), "#links", links)
|
||||||
|
entries: list[Link|Section] = []
|
||||||
skip_next = False
|
skip_next = False
|
||||||
custom_name = None
|
custom_name = None
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -251,10 +286,13 @@ class Sidenav:
|
|||||||
if Sidenav.custom_name:
|
if Sidenav.custom_name:
|
||||||
name = Sidenav.custom_name
|
name = Sidenav.custom_name
|
||||||
Sidenav.custom_name = None
|
Sidenav.custom_name = None
|
||||||
Sidenav.entries.append((Sidenav.LINK, name, link))
|
if len(Sidenav.entries) > 0 and type(Sidenav.entries[-1]) == Sidenav.Section:
|
||||||
|
Sidenav.entries[-1].add_link(Sidenav.Link(name, link))
|
||||||
|
else:
|
||||||
|
Sidenav.entries.append(Sidenav.Link(name, link))
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def addSection(name):
|
def addSection(name):
|
||||||
Sidenav.entries.append((Sidenav.SECTION, name, ""))
|
Sidenav.entries.append(Sidenav.Section(name))
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def setCustomName(name: str):
|
def setCustomName(name: str):
|
||||||
Sidenav.custom_name = name
|
Sidenav.custom_name = name
|
||||||
@ -264,26 +302,8 @@ class Sidenav:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def generate() -> str:
|
def generate() -> str:
|
||||||
pdebug("Sidenav.generate", f"found the following entries: {Sidenav.entries}")
|
pdebug("Sidenav.generate", f"found the following entries: {Sidenav.entries}")
|
||||||
sidenav:list[str] = sidenav_format.split('\n')
|
entries = "".join([entry.get() + "\n" for entry in Sidenav.entries])
|
||||||
content_i = -1
|
return replace_and_respect_indent(sidenav_format, "#sidenav-content", entries)
|
||||||
for i in range(len(sidenav)): # find in which line the entries need to be placed
|
|
||||||
if "#sidenav-content" in sidenav[i]:
|
|
||||||
content_i = i
|
|
||||||
break
|
|
||||||
if content_i >= 0:
|
|
||||||
indent = sidenav.pop(content_i).replace("#sidenav-content", "")
|
|
||||||
added_links = []
|
|
||||||
for i in reversed(range(len(Sidenav.entries))):
|
|
||||||
entry = Sidenav.entries[i]
|
|
||||||
if entry[0] == Sidenav.LINK:
|
|
||||||
if entry[2] in added_links: continue # no duplicates
|
|
||||||
added_links.append(entry[2])
|
|
||||||
sidenav.insert(content_i, indent + sidenav_content_link.replace("#name", entry[1]).replace("#link", entry[2]))
|
|
||||||
else:
|
|
||||||
sidenav.insert(content_i, indent + sidenav_content_section.replace("#name", entry[1]))
|
|
||||||
sidenav_s = ""
|
|
||||||
for line in sidenav: sidenav_s += line + "\n" # cant use "".join because of newlines
|
|
||||||
return sidenav_s
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def cmd_sidenav(args:str, variables:dict[str,str]) -> str:
|
def cmd_sidenav(args:str, variables:dict[str,str]) -> str:
|
||||||
space = args.find(" ")
|
space = args.find(" ")
|
||||||
|
14
nginx.conf
@ -3,13 +3,9 @@ error_log stderr;
|
|||||||
daemon off;
|
daemon off;
|
||||||
pid nginx.pid;
|
pid nginx.pid;
|
||||||
|
|
||||||
events {
|
|
||||||
worker_connections 1024;
|
|
||||||
}
|
|
||||||
|
|
||||||
http {
|
http {
|
||||||
include /etc/nginx/mime.types;
|
include /etc/nginx/mime.types;
|
||||||
default_type application/octet-stream;
|
default_type application/octet-stream;
|
||||||
|
|
||||||
sendfile on;
|
sendfile on;
|
||||||
keepalive_timeout 65;
|
keepalive_timeout 65;
|
||||||
@ -20,13 +16,9 @@ http {
|
|||||||
listen 8080;
|
listen 8080;
|
||||||
server_name localhost;
|
server_name localhost;
|
||||||
|
|
||||||
#charset koi8-r;
|
|
||||||
|
|
||||||
#access_log logs/host.access.log main;
|
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
root /home/user/www/;
|
root /home/user/www/;
|
||||||
index de/index.html;
|
index /en/index.html;
|
||||||
}
|
}
|
||||||
#error_page 404 /404.html;
|
#error_page 404 /404.html;
|
||||||
|
|
||||||
|