Selaa lähdekoodia

Add plugin to load images

Danilo Gómez Gómez 5 vuotta sitten
vanhempi
commit
4829b45573
5 muutettua tiedostoa jossa 187 lisäystä ja 27 poistoa
  1. 2 1
      .gitignore
  2. 9 6
      layout/work.html
  3. 15 0
      layout/work/poster.html
  4. 61 0
      plugins/alucho_images.py
  5. 100 20
      sitegen.py

+ 2 - 1
.gitignore

@@ -1,2 +1,3 @@
 build/
-.vscode/
+.vscode/
+__pycache__/

+ 9 - 6
layout/work.html

@@ -1,15 +1,18 @@
-{% extends "_base.html"%}
+{% extends "_base.html" %}
 
 {% set work_selected = True %}
 {% set all_selected = False if no_all_selected else True %}
 
 {% block main %}
-{% for _ in range(15) %}
+{% for image in images('work/all') %}
 	<figure class="v">
-		<img src="{{ 'image/tile.jpg' | static }}" alt="Ejemplo">
-		<figcaption>
-			<h2>Club De Jazz - 2019</h2>
-			poster
+		<img src="{{ image.filenames[0] | static }}" alt="Ejemplo">
+		<!-- <svg width="1px" height="1px" style="background-color: gray"></svg> -->
+		<figcaption class="hbox">
+			<div class="caption">
+				<h1>{{ image.name }} / {{ image.year }}</h1>
+				{{ image.category }}
+			</div>
 		</figcaption>
 	</figure>
 {% endfor %}

+ 15 - 0
layout/work/poster.html

@@ -1,3 +1,18 @@
 {% extends "_work_no_all.html"%}
 
 {% set poster_selected = True %}
+
+{% block main %}
+{% for image in images('work/poster') %}
+	<figure class="v">
+		<img src="{{ 'image/tile.jpg' | static }}" alt="Ejemplo">
+		<!-- <svg width="1px" height="1px" style="background-color: gray"></svg> -->
+		<figcaption class="hbox">
+			<div class="caption">
+				<h1>{{ image.name }} / {{ image.year }}</h1>
+				{{ image.category }}
+			</div>
+		</figcaption>
+	</figure>
+{% endfor %}
+{% endblock %}

+ 61 - 0
plugins/alucho_images.py

@@ -0,0 +1,61 @@
+from collections import namedtuple
+from json import loads as from_json
+from pathlib import Path
+
+'''
+This plugin helps loading images for each section.
+Images should be located at
+    <static-folder>/<image-path>/<section-name>/<image-folder>
+
+eg.
+    static/image/section1/folder-for-my-image/
+    static/image/section2/subsection/folder-for-another-image/
+    ...
+
+Each image folder should contain a file named `_metadata.json`
+with the following information
+    {
+        "name": "<image-name>",
+        "year": "<year>",
+        "category": "<category-name>",
+        "images": ["<main-image-name>", "<image-variant-1>", "<image-variant-2>", ...]
+    }
+
+Along with the image files which should be called
+    <main-image-name>
+    <image-variant-1>
+    <image-variant-2>
+    ...
+
+'''
+
+IMAGE_PATH = 'image' # relative to the static path
+STATIC = ''
+
+Image = namedtuple('Image', ['name', 'year', 'category', 'description', 'filenames'])
+
+# Functions
+
+def images(section):
+    sec_path = Path(STATIC, IMAGE_PATH, section)
+    for path in sec_path.iterdir():
+        if path.name.startswith('_'):
+            continue
+        metafile = path.joinpath('_metadata.json')
+        if metafile.exists():
+            img_base = path.relative_to(STATIC)
+            metadata = from_json(metafile.read_text())
+            img = Image(
+                metadata.get('name'),
+                metadata.get('year'),
+                metadata.get('category'),
+                metadata.get('description'),
+                [img_base.joinpath(img_name) for img_name in metadata.get('images', [])])
+            print(img)
+            yield img
+
+
+def init_plugin(env, config):
+    global STATIC
+    STATIC = config['STATIC']
+    env.globals['images'] = images

+ 100 - 20
sitegen.py

@@ -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}')