Browse Source

Add plugin to load images

Danilo Gómez Gómez 5 years ago
parent
commit
4829b45573
5 changed files with 187 additions and 27 deletions
  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/
 build/
-.vscode/
+.vscode/
+__pycache__/

+ 9 - 6
layout/work.html

@@ -1,15 +1,18 @@
-{% extends "_base.html"%}
+{% extends "_base.html" %}
 
 
 {% set work_selected = True %}
 {% set work_selected = True %}
 {% set all_selected = False if no_all_selected else True %}
 {% set all_selected = False if no_all_selected else True %}
 
 
 {% block main %}
 {% block main %}
-{% for _ in range(15) %}
+{% for image in images('work/all') %}
 	<figure class="v">
 	<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>
 		</figcaption>
 	</figure>
 	</figure>
 {% endfor %}
 {% endfor %}

+ 15 - 0
layout/work/poster.html

@@ -1,3 +1,18 @@
 {% extends "_work_no_all.html"%}
 {% extends "_work_no_all.html"%}
 
 
 {% set poster_selected = True %}
 {% 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
 import argparse
 from functools import lru_cache
 from functools import lru_cache
+from importlib import import_module
 from jinja2 import Environment, FileSystemLoader, contextfilter
 from jinja2 import Environment, FileSystemLoader, contextfilter
 from pathlib import Path
 from pathlib import Path
 from os import walk, makedirs, listdir, symlink, readlink
 from os import walk, makedirs, listdir, symlink, readlink
 from os.path import join, exists, splitext, split, islink, isdir
 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 time import sleep
 from traceback import print_exc
 from traceback import print_exc
+import os
 
 
 #TODO: load from config file (and watch it too)
 #TODO: load from config file (and watch it too)
 LANGUAGES = 'languages'
 LANGUAGES = 'languages'
+PLUGINS = 'plugins'
 ROOT = 'root'
 ROOT = 'root'
 STATIC = 'static'
 STATIC = 'static'
 DEFAULT_LANG = 'en'
 DEFAULT_LANG = 'en'
 OTHER_LANGS = set(['es'])
 OTHER_LANGS = set(['es'])
 WATCH_INTERVAL = 1 # in secs
 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
 # 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
     # `exist_ok=True`, very important for hot reloading
     errors = []
     errors = []
     for name in names:
     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:
         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:
             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
         # catch the Error from the recursive copytree so that we can
         # continue with other files
         # continue with other files
         except Error as err:
         except Error as err:
             errors.extend(err.args[0])
             errors.extend(err.args[0])
+        except OSError as why:
+            errors.append((srcname, dstname, str(why)))
     try:
     try:
         copystat(src, dst)
         copystat(src, dst)
     except OSError as why:
     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:
     if errors:
         raise Error(errors)
         raise Error(errors)
+    return dst
 
 
 def mtimes(target_dir):
 def mtimes(target_dir):
     ''''get modification time of files in `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['cur_lang'] = cur_lang
     env.filters['static'] = static
     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
     # Clean target
     if exists(args.target):
     if exists(args.target):
         rmtree(args.target)
         rmtree(args.target)
@@ -191,10 +271,10 @@ def gen_layout(args):
         compile(env, path, args.target)
         compile(env, path, args.target)
 
 
 def gen_root(args):
 def gen_root(args):
-    copytree(ROOT, args.target)
+    copytree(ROOT, args.target, ignore=ignore_underscores)
 
 
 def gen_static(args):
 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):
 def save_generate(generator, args, msg):
     print(f'* {msg}')
     print(f'* {msg}')