|
@@ -1,22 +1,67 @@
|
|
|
import argparse
|
|
|
+from functools import lru_cache
|
|
|
from jinja2 import Environment, FileSystemLoader, contextfilter
|
|
|
from pathlib import Path
|
|
|
-from os import walk, makedirs
|
|
|
-from os.path import join, exists, splitext, split
|
|
|
-from shutil import rmtree, copy, copytree
|
|
|
-from functools import lru_cache
|
|
|
+from os import walk, makedirs, listdir, symlink, readlink
|
|
|
+from os.path import join, exists, splitext, split, islink, isdir
|
|
|
+from shutil import rmtree, copy2, copystat, Error
|
|
|
+from time import sleep
|
|
|
+from traceback import print_exc
|
|
|
|
|
|
+#TODO: load from config file (and watch it too)
|
|
|
LANGUAGES = 'languages'
|
|
|
-
|
|
|
-#TODO: load from config file
|
|
|
+ROOT = 'root'
|
|
|
+STATIC = 'static'
|
|
|
DEFAULT_LANG = 'en'
|
|
|
OTHER_LANGS = set(['es'])
|
|
|
+WATCH_INTERVAL = 1 # in secs
|
|
|
+
|
|
|
+# Utils
|
|
|
+
|
|
|
+def copytree(src, dst, symlinks=False):
|
|
|
+ names = listdir(src)
|
|
|
+ makedirs(dst, exist_ok=True)
|
|
|
+ # `exist_ok=True`, very important for hot reloading
|
|
|
+ errors = []
|
|
|
+ for name in names:
|
|
|
+ srcname = join(src, name)
|
|
|
+ dstname = join(dst, name)
|
|
|
+ try:
|
|
|
+ if symlinks and islink(srcname):
|
|
|
+ linkto = readlink(srcname)
|
|
|
+ symlink(linkto, dstname)
|
|
|
+ elif isdir(srcname):
|
|
|
+ copytree(srcname, dstname, symlinks)
|
|
|
+ else:
|
|
|
+ copy2(srcname, dstname)
|
|
|
+ # XXX What about devices, sockets etc.?
|
|
|
+ except OSError as why:
|
|
|
+ errors.append((srcname, dstname, str(why)))
|
|
|
+ # catch the Error from the recursive copytree so that we can
|
|
|
+ # continue with other files
|
|
|
+ except Error as err:
|
|
|
+ errors.extend(err.args[0])
|
|
|
+ try:
|
|
|
+ copystat(src, dst)
|
|
|
+ except OSError as why:
|
|
|
+ # can't copy file access times on Windows
|
|
|
+ if why.winerror is None:
|
|
|
+ errors.extend((src, dst, str(why)))
|
|
|
+ if errors:
|
|
|
+ raise Error(errors)
|
|
|
+
|
|
|
+def mtimes(target_dir):
|
|
|
+ ''''get modification time of files in `target_dir`'''
|
|
|
+ return { f: f.stat().st_mtime for f in Path(target_dir).rglob('*') }
|
|
|
|
|
|
+def save_dic(lang, dic):
|
|
|
+ with open(join(LANGUAGES, lang + '.txt'), 'w') as f:
|
|
|
+ for key, value in dic.items():
|
|
|
+ f.write(f"{key}:{value}\n")
|
|
|
|
|
|
@lru_cache(None)
|
|
|
def get_lang_dic(lang):
|
|
|
- if not exists(LANGUAGES):
|
|
|
- makedirs(LANGUAGES)
|
|
|
+ makedirs(LANGUAGES, exist_ok=True)
|
|
|
|
|
|
lang_file = join(LANGUAGES, lang + '.txt')
|
|
|
|
|
@@ -30,12 +75,7 @@ def get_lang_dic(lang):
|
|
|
dic[key] = value
|
|
|
return dic
|
|
|
|
|
|
-
|
|
|
-def save_dic(lang, dic):
|
|
|
- with open(join(LANGUAGES, lang + '.txt'), 'w') as f:
|
|
|
- for key, value in dic.items():
|
|
|
- f.write(f"{key}:{value}\n")
|
|
|
-
|
|
|
+# Filters
|
|
|
|
|
|
def add_lang_prefix(lang, path):
|
|
|
if lang == DEFAULT_LANG:
|
|
@@ -46,7 +86,6 @@ def add_lang_prefix(lang, path):
|
|
|
|
|
|
return f"/{lang}{path}"
|
|
|
|
|
|
-
|
|
|
@contextfilter
|
|
|
def lang(ctx, value):
|
|
|
lang = ctx.environment.globals['lang']
|
|
@@ -65,7 +104,6 @@ def lang(ctx, value):
|
|
|
print(f"Not translated phrase: `{value}`")
|
|
|
return value
|
|
|
|
|
|
-
|
|
|
@contextfilter
|
|
|
def lang_url(ctx, lang):
|
|
|
name, _ = splitext(ctx.name)
|
|
@@ -77,16 +115,15 @@ def lang_url(ctx, lang):
|
|
|
|
|
|
return add_lang_prefix(lang, f"/{name}")
|
|
|
|
|
|
-
|
|
|
@contextfilter
|
|
|
def cur_lang(ctx, path):
|
|
|
lang = ctx.environment.globals['lang']
|
|
|
return add_lang_prefix(lang, path)
|
|
|
|
|
|
-
|
|
|
def static(value):
|
|
|
return f"/static/{value}"
|
|
|
|
|
|
+# Compiler
|
|
|
|
|
|
def compile(env, path, target):
|
|
|
lang = env.globals['lang']
|
|
@@ -112,9 +149,8 @@ def compile(env, path, target):
|
|
|
|
|
|
dst = join(target, dst)
|
|
|
dst_folder, _ = split(dst)
|
|
|
-
|
|
|
- if not exists(dst_folder):
|
|
|
- makedirs(dst_folder)
|
|
|
+
|
|
|
+ makedirs(dst_folder, exist_ok=True)
|
|
|
|
|
|
template = env.get_template(join(cur_base, doc))
|
|
|
output = template.render()
|
|
@@ -122,11 +158,12 @@ def compile(env, path, target):
|
|
|
with open(dst, 'w') as f:
|
|
|
f.write(output)
|
|
|
|
|
|
-
|
|
|
-def run(args):
|
|
|
+def init_gen(args):
|
|
|
# Load layout
|
|
|
file_loader = FileSystemLoader(args.source)
|
|
|
- env = Environment(loader=file_loader)
|
|
|
+ env = args.env = Environment(loader=file_loader)
|
|
|
+
|
|
|
+ # Init gobals
|
|
|
env.globals['lang'] = DEFAULT_LANG
|
|
|
|
|
|
# Add filters
|
|
@@ -135,32 +172,86 @@ def run(args):
|
|
|
env.filters['cur_lang'] = cur_lang
|
|
|
env.filters['static'] = static
|
|
|
|
|
|
- path = Path(args.source)
|
|
|
-
|
|
|
+ # Clean target
|
|
|
if exists(args.target):
|
|
|
rmtree(args.target)
|
|
|
+ makedirs(args.target, exist_ok=True)
|
|
|
|
|
|
- if exists('root'):
|
|
|
- copytree('root', args.target)
|
|
|
- else:
|
|
|
- makedirs(args.target)
|
|
|
-
|
|
|
- compile(env, path, args.target)
|
|
|
+def gen_layout(args):
|
|
|
+ env = args.env
|
|
|
+ path = Path(args.source)
|
|
|
|
|
|
- copytree('static', join(args.target, 'static'))
|
|
|
+ # Reset gobals
|
|
|
+ env.globals['lang'] = DEFAULT_LANG
|
|
|
|
|
|
+ # compile
|
|
|
+ compile(env, path, args.target)
|
|
|
for other_lang in OTHER_LANGS:
|
|
|
env.globals['lang'] = other_lang
|
|
|
compile(env, path, args.target)
|
|
|
|
|
|
+def gen_root(args):
|
|
|
+ copytree(ROOT, args.target)
|
|
|
+
|
|
|
+def gen_static(args):
|
|
|
+ copytree(STATIC, join(args.target, 'static'))
|
|
|
+
|
|
|
+def save_generate(generator, args, msg):
|
|
|
+ print(f'* {msg}')
|
|
|
+ try:
|
|
|
+ generator(args)
|
|
|
+ print(' done!')
|
|
|
+ except:
|
|
|
+ print_exc()
|
|
|
+ return False
|
|
|
+ return True
|
|
|
+
|
|
|
+# Runtime
|
|
|
+
|
|
|
+def run(args):
|
|
|
+ print('initial compilation')
|
|
|
+ init_gen(args)
|
|
|
+ save_generate(gen_layout, args, 'generating layout')
|
|
|
+ save_generate(gen_root, args, 'copying root files')
|
|
|
+ save_generate(gen_static, args, 'copying static dir')
|
|
|
+
|
|
|
+ if not args.watch:
|
|
|
+ return
|
|
|
+
|
|
|
+ print(f'watching {args.source}/*:{LANGUAGES}/*:{STATIC}/*:{ROOT}/*')
|
|
|
+ while True:
|
|
|
+ # take modification times
|
|
|
+ source_mt = mtimes(args.source)
|
|
|
+ source_mt.update(mtimes(LANGUAGES))
|
|
|
+ static_mt = mtimes(STATIC)
|
|
|
+ root_mt = mtimes(ROOT)
|
|
|
+
|
|
|
+ sleep(WATCH_INTERVAL)
|
|
|
+
|
|
|
+ # compare modification times and recompile if different
|
|
|
+ new_source_mt = mtimes(args.source)
|
|
|
+ new_source_mt.update(mtimes(LANGUAGES))
|
|
|
+ if source_mt != new_source_mt:
|
|
|
+ source_mt = new_source_mt
|
|
|
+ save_generate(gen_layout, args, 'recompiling layout')
|
|
|
+
|
|
|
+ new_root_mt = mtimes(ROOT)
|
|
|
+ if root_mt != new_root_mt:
|
|
|
+ root_mt = new_root_mt
|
|
|
+ save_generate(gen_root, args, 'copying root files')
|
|
|
+
|
|
|
+ new_static_mt = mtimes(STATIC)
|
|
|
+ if static_mt != new_static_mt:
|
|
|
+ static_mt = new_static_mt
|
|
|
+ save_generate(gen_static, args, 'copying static files')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
parser = argparse.ArgumentParser("Site generator")
|
|
|
|
|
|
- parser.add_argument('--with_index', action='store_true', default=False)
|
|
|
+ parser.add_argument('--watch', action='store_true', default=False)
|
|
|
+ parser.add_argument('--with-index', action='store_true', default=False)
|
|
|
parser.add_argument('--source', default='layout')
|
|
|
parser.add_argument('--target', default='build')
|
|
|
|
|
|
args = parser.parse_args()
|
|
|
run(args)
|
|
|
-
|