|
@@ -1,54 +1,127 @@
|
|
|
import argparse
|
|
|
from functools import lru_cache
|
|
|
+from importlib import import_module
|
|
|
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 shutil import rmtree, copy2, copystat, ignore_patterns, Error
|
|
|
from time import sleep
|
|
|
from traceback import print_exc
|
|
|
+import os
|
|
|
|
|
|
#TODO: load from config file (and watch it too)
|
|
|
LANGUAGES = 'languages'
|
|
|
+PLUGINS = 'plugins'
|
|
|
ROOT = 'root'
|
|
|
STATIC = 'static'
|
|
|
DEFAULT_LANG = 'en'
|
|
|
OTHER_LANGS = set(['es'])
|
|
|
WATCH_INTERVAL = 1 # in secs
|
|
|
|
|
|
+config = {
|
|
|
+ 'LANGUAGES': LANGUAGES,
|
|
|
+ 'PLUGINS': PLUGINS,
|
|
|
+ 'ROOT': ROOT,
|
|
|
+ 'STATIC': STATIC,
|
|
|
+ 'DEFAULT_LANG': DEFAULT_LANG,
|
|
|
+ 'OTHER_LANGS': OTHER_LANGS,
|
|
|
+ 'WATCH_INTERVAL': WATCH_INTERVAL
|
|
|
+}
|
|
|
+
|
|
|
# Utils
|
|
|
|
|
|
-def copytree(src, dst, symlinks=False):
|
|
|
- names = listdir(src)
|
|
|
- makedirs(dst, exist_ok=True)
|
|
|
+ignore_underscores = ignore_patterns('_*')
|
|
|
+
|
|
|
+def copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2,
|
|
|
+ ignore_dangling_symlinks=False):
|
|
|
+ """Recursively copy a directory tree.
|
|
|
+
|
|
|
+ The destination directory must not already exist.
|
|
|
+ If exception(s) occur, an Error is raised with a list of reasons.
|
|
|
+
|
|
|
+ If the optional symlinks flag is true, symbolic links in the
|
|
|
+ source tree result in symbolic links in the destination tree; if
|
|
|
+ it is false, the contents of the files pointed to by symbolic
|
|
|
+ links are copied. If the file pointed by the symlink doesn't
|
|
|
+ exist, an exception will be added in the list of errors raised in
|
|
|
+ an Error exception at the end of the copy process.
|
|
|
+
|
|
|
+ You can set the optional ignore_dangling_symlinks flag to true if you
|
|
|
+ want to silence this exception. Notice that this has no effect on
|
|
|
+ platforms that don't support os.symlink.
|
|
|
+
|
|
|
+ The optional ignore argument is a callable. If given, it
|
|
|
+ is called with the `src` parameter, which is the directory
|
|
|
+ being visited by copytree(), and `names` which is the list of
|
|
|
+ `src` contents, as returned by os.listdir():
|
|
|
+
|
|
|
+ callable(src, names) -> ignored_names
|
|
|
+
|
|
|
+ Since copytree() is called recursively, the callable will be
|
|
|
+ called once for each directory that is copied. It returns a
|
|
|
+ list of names relative to the `src` directory that should
|
|
|
+ not be copied.
|
|
|
+
|
|
|
+ The optional copy_function argument is a callable that will be used
|
|
|
+ to copy each file. It will be called with the source path and the
|
|
|
+ destination path as arguments. By default, copy2() is used, but any
|
|
|
+ function that supports the same signature (like copy()) can be used.
|
|
|
+
|
|
|
+ """
|
|
|
+ names = os.listdir(src)
|
|
|
+ if ignore is not None:
|
|
|
+ ignored_names = ignore(src, names)
|
|
|
+ else:
|
|
|
+ ignored_names = set()
|
|
|
+
|
|
|
+ os.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)
|
|
|
+ if name in ignored_names:
|
|
|
+ continue
|
|
|
+ srcname = os.path.join(src, name)
|
|
|
+ dstname = os.path.join(dst, name)
|
|
|
try:
|
|
|
- if symlinks and islink(srcname):
|
|
|
- linkto = readlink(srcname)
|
|
|
- symlink(linkto, dstname)
|
|
|
- elif isdir(srcname):
|
|
|
- copytree(srcname, dstname, symlinks)
|
|
|
+ if os.path.islink(srcname):
|
|
|
+ linkto = os.readlink(srcname)
|
|
|
+ if symlinks:
|
|
|
+ # We can't just leave it to `copy_function` because legacy
|
|
|
+ # code with a custom `copy_function` may rely on copytree
|
|
|
+ # doing the right thing.
|
|
|
+ os.symlink(linkto, dstname)
|
|
|
+ copystat(srcname, dstname, follow_symlinks=not symlinks)
|
|
|
+ else:
|
|
|
+ # ignore dangling symlink if the flag is on
|
|
|
+ if not os.path.exists(linkto) and ignore_dangling_symlinks:
|
|
|
+ continue
|
|
|
+ # otherwise let the copy occurs. copy2 will raise an error
|
|
|
+ if os.path.isdir(srcname):
|
|
|
+ copytree(srcname, dstname, symlinks, ignore,
|
|
|
+ copy_function)
|
|
|
+ else:
|
|
|
+ copy_function(srcname, dstname)
|
|
|
+ elif os.path.isdir(srcname):
|
|
|
+ copytree(srcname, dstname, symlinks, ignore, copy_function)
|
|
|
else:
|
|
|
- copy2(srcname, dstname)
|
|
|
- # XXX What about devices, sockets etc.?
|
|
|
- except OSError as why:
|
|
|
- errors.append((srcname, dstname, str(why)))
|
|
|
+ # Will raise a SpecialFileError for unsupported file types
|
|
|
+ copy_function(srcname, dstname)
|
|
|
# catch the Error from the recursive copytree so that we can
|
|
|
# continue with other files
|
|
|
except Error as err:
|
|
|
errors.extend(err.args[0])
|
|
|
+ except OSError as why:
|
|
|
+ errors.append((srcname, dstname, str(why)))
|
|
|
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)))
|
|
|
+ # Copying file access times may fail on Windows
|
|
|
+ if getattr(why, 'winerror', None) is None:
|
|
|
+ errors.append((src, dst, str(why)))
|
|
|
if errors:
|
|
|
raise Error(errors)
|
|
|
+ return dst
|
|
|
|
|
|
def mtimes(target_dir):
|
|
|
''''get modification time of files in `target_dir`'''
|
|
@@ -172,6 +245,13 @@ def init_gen(args):
|
|
|
env.filters['cur_lang'] = cur_lang
|
|
|
env.filters['static'] = static
|
|
|
|
|
|
+ # Load plugins
|
|
|
+ for mod_path in Path(PLUGINS).glob('*.py'):
|
|
|
+ mod_name = '.'.join(mod_path.with_suffix('').parts)
|
|
|
+ print(f'* loading {mod_name}')
|
|
|
+ import_module(mod_name).init_plugin(env, config)
|
|
|
+ print(' done!')
|
|
|
+
|
|
|
# Clean target
|
|
|
if exists(args.target):
|
|
|
rmtree(args.target)
|
|
@@ -191,10 +271,10 @@ def gen_layout(args):
|
|
|
compile(env, path, args.target)
|
|
|
|
|
|
def gen_root(args):
|
|
|
- copytree(ROOT, args.target)
|
|
|
+ copytree(ROOT, args.target, ignore=ignore_underscores)
|
|
|
|
|
|
def gen_static(args):
|
|
|
- copytree(STATIC, join(args.target, 'static'))
|
|
|
+ copytree(STATIC, join(args.target, 'static'), ignore=ignore_underscores)
|
|
|
|
|
|
def save_generate(generator, args, msg):
|
|
|
print(f'* {msg}')
|