123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257 |
- import argparse
- from functools import lru_cache
- from jinja2 import Environment, FileSystemLoader, contextfilter
- from pathlib import Path
- 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'
- 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):
- makedirs(LANGUAGES, exist_ok=True)
- lang_file = join(LANGUAGES, lang + '.txt')
- if not exists(lang_file):
- return {}
- else:
- dic = {}
- with open(lang_file) as f:
- for line in f.readlines():
- key, value = line.strip('\n').split(':')
- dic[key] = value
- return dic
- # Filters
- def add_lang_prefix(lang, path):
- if lang == DEFAULT_LANG:
- return path
- if lang not in OTHER_LANGS:
- print(f"Not registered language: `{lang}`")
- return f"/{lang}{path}"
- @contextfilter
- def lang(ctx, value):
- lang = ctx.environment.globals['lang']
- if lang == DEFAULT_LANG:
- return value
- else:
- dic = get_lang_dic(lang)
- if value in dic and dic[value]:
- return dic[value]
- else:
- dic[value] = ""
- save_dic(lang, dic)
- print(f"Not translated phrase: `{value}`")
- return value
- @contextfilter
- def lang_url(ctx, lang):
- name, _ = splitext(ctx.name)
- if name.startswith('./'):
- name = name[2:]
- if name == 'index':
- name = ''
- 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']
- for base, _, docs in walk(path):
- cur_base = Path(base)
- cur_base = Path(*cur_base.parts[1:])
- for doc in docs:
- if doc.startswith('_'):
- # Ignore this files
- continue
- src = join(base, doc)
- if args.with_index and doc != 'index.html':
- name, _ = splitext(doc)
- dst = join(cur_base, name, 'index.html')
- else:
- dst = join(cur_base, doc)
- if lang != DEFAULT_LANG:
- dst = join(lang, dst)
- dst = join(target, dst)
- dst_folder, _ = split(dst)
-
- makedirs(dst_folder, exist_ok=True)
- template = env.get_template(join(cur_base, doc))
- output = template.render()
- with open(dst, 'w') as f:
- f.write(output)
- def init_gen(args):
- # Load layout
- file_loader = FileSystemLoader(args.source)
- env = args.env = Environment(loader=file_loader)
-
- # Init gobals
- env.globals['lang'] = DEFAULT_LANG
- # Add filters
- env.filters['lang'] = lang
- env.filters['lang_url'] = lang_url
- env.filters['cur_lang'] = cur_lang
- env.filters['static'] = static
- # Clean target
- if exists(args.target):
- rmtree(args.target)
- makedirs(args.target, exist_ok=True)
- def gen_layout(args):
- env = args.env
- path = Path(args.source)
- # 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('--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)
|