From 380a04b9ec2384f76003faf14c2156b92f583500 Mon Sep 17 00:00:00 2001 From: John Stowers Date: Sat, 11 Mar 2017 18:27:17 +0100 Subject: [PATCH 01/12] Can serve on a different IP --- complexity/main.py | 7 ++++++- complexity/serve.py | 6 +++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/complexity/main.py b/complexity/main.py index f871c84..4a63f2c 100755 --- a/complexity/main.py +++ b/complexity/main.py @@ -106,6 +106,11 @@ def get_complexity_args(): default=9090, help='Port number to serve files on.' ) + parser.add_argument( + '--address', + default='127.0.0.1', + help='IP to serve files on.' + ) parser.add_argument( '--noserver', action='store_true', @@ -122,7 +127,7 @@ def main(): output_dir = complexity(project_dir=args.project_dir, no_input=False) if not args.noserver: - serve_static_site(output_dir=output_dir, port=args.port) + serve_static_site(output_dir=output_dir, address=args.address, port=args.port) if __name__ == '__main__': diff --git a/complexity/serve.py b/complexity/serve.py index be5c484..b87ebc1 100755 --- a/complexity/serve.py +++ b/complexity/serve.py @@ -21,7 +21,7 @@ import SocketServer as socketserver -def serve_static_site(output_dir, port=9090): +def serve_static_site(output_dir, address='127.0.0.1', port=9090): """ Serve a directory containing static HTML files, on a specified port. @@ -34,8 +34,8 @@ def serve_static_site(output_dir, port=9090): # of-errno-98-address-already-in-use socketserver.TCPServer.allow_reuse_address = True - httpd = socketserver.TCPServer(("", port), Handler) - print("serving at port", port) + httpd = socketserver.TCPServer((address, port), Handler) + print("serving ", address, port) try: httpd.serve_forever() From 4db0b2d8bd15cccca3501b59b2073f891f771e88 Mon Sep 17 00:00:00 2001 From: John Stowers Date: Sat, 11 Mar 2017 18:33:05 +0100 Subject: [PATCH 02/12] Add --overwrite commandline argument --- complexity/main.py | 21 +++++++++++++++------ complexity/prep.py | 4 ++++ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/complexity/main.py b/complexity/main.py index 4a63f2c..d10c006 100755 --- a/complexity/main.py +++ b/complexity/main.py @@ -19,14 +19,14 @@ from .conf import read_conf, get_unexpanded_list from .exceptions import OutputDirExistsException from .generate import generate_context, copy_assets, generate_html -from .prep import prompt_and_delete_cruft +from .prep import prompt_and_delete_cruft, delete_cruft from .serve import serve_static_site logger = logging.getLogger(__name__) -def complexity(project_dir, no_input=True): +def complexity(project_dir, overwrite=False, no_input=True): """ API equivalent to using complexity at the command line. @@ -55,9 +55,11 @@ def complexity(project_dir, no_input=True): os.path.join(project_dir, conf_dict['output_dir']) ) - # If output_dir exists, prompt before deleting. - # Abort if it can't be deleted. - if no_input: + if overwrite and os.path.exists(output_dir): + delete_cruft(output_dir) + elif no_input: + # If output_dir exists, prompt before deleting. + # Abort if it can't be deleted. if os.path.exists(output_dir): raise OutputDirExistsException( 'Please delete {0} manually and try again.' @@ -116,6 +118,13 @@ def get_complexity_args(): action='store_true', help='Don\'t run the server.' ) + parser.add_argument( + '--overwrite', + default=False, + action='store_true', + help='Overwrite the output directory without prompting.' + ) + args = parser.parse_args() return args @@ -125,7 +134,7 @@ def main(): args = get_complexity_args() - output_dir = complexity(project_dir=args.project_dir, no_input=False) + output_dir = complexity(project_dir=args.project_dir, overwrite=args.overwrite, no_input=False) if not args.noserver: serve_static_site(output_dir=output_dir, address=args.address, port=args.port) diff --git a/complexity/prep.py b/complexity/prep.py index c5c6f8c..2db437e 100755 --- a/complexity/prep.py +++ b/complexity/prep.py @@ -15,6 +15,10 @@ from . import utils +def delete_cruft(output_dir): + print('Deleting {0}'.format(output_dir)) + shutil.rmtree(output_dir) + def prompt_and_delete_cruft(output_dir): """ Asks if it's okay to delete `output_dir/`. From e7c0bf58d47424c90e05d9660ff026e62c135153 Mon Sep 17 00:00:00 2001 From: Jake Date: Sun, 18 Mar 2018 12:38:21 +0100 Subject: [PATCH 03/12] Added foldering watching and stop auto-expanding --- README.rst | 14 ++++----- complexity/generate.py | 36 ++++++++++++++-------- complexity/main.py | 70 +++++++++++++++++++++++++++++++++++------- complexity/prep.py | 11 +++++++ setup.py | 3 +- 5 files changed, 101 insertions(+), 33 deletions(-) diff --git a/README.rst b/README.rst index 901145c..f4f1b34 100644 --- a/README.rst +++ b/README.rst @@ -2,16 +2,13 @@ Complexity ========== -.. image:: https://badge.fury.io/py/complexity.png - :target: http://badge.fury.io/py/complexity - -.. image:: https://travis-ci.org/audreyr/complexity.png?branch=master - :target: https://travis-ci.org/audreyr/complexity +A refreshingly simple static site generator, for those who like to work in HTML. -.. image:: https://pypip.in/d/complexity/badge.png - :target: https://crate.io/packages/complexity?version=latest +Changes +------- -A refreshingly simple static site generator, for those who like to work in HTML. +- New url argument that allows you to (--watch) watch a folder for changes, and any changes will fire off complexity +- New config variable to turn on/off the auto-expand system Documentation ------------- @@ -69,3 +66,4 @@ Community * We love contributions. Read about `how to contribute`_. .. _`how to contribute`: https://github.com/audreyr/complexity/blob/master/CONTRIBUTING.rst + diff --git a/complexity/generate.py b/complexity/generate.py index 720699b..efb37f0 100755 --- a/complexity/generate.py +++ b/complexity/generate.py @@ -22,7 +22,7 @@ from .utils import make_sure_path_exists, unicode_open -def get_output_filename(template_filepath, output_dir, force_unexpanded): +def get_output_filename(template_filepath, output_dir, force_unexpanded, expand): """ Given an input filename, return the corresponding output filename. @@ -41,7 +41,7 @@ def get_output_filename(template_filepath, output_dir, force_unexpanded): if basename.startswith('base'): return False # Put index and unexpanded templates in the root. - elif force_unexpanded or basename == 'index.html': + elif force_unexpanded or basename == 'index.html' or expand == True: output_filename = os.path.join(output_dir, template_filepath) # Put other pages in page/index.html, for better URL formatting. else: @@ -68,7 +68,7 @@ def minify_html(html): def generate_html_file(template_filepath, output_dir, env, - context, force_unexpanded=False, minify=False): + context, force_unexpanded=False, minify=False, expand=True): """ Renders and writes a single HTML file to its corresponding output location. @@ -79,6 +79,8 @@ def generate_html_file(template_filepath, :param env: Jinja2 environment with a loader already set up. :param context: Jinja2 context that holds template variables. See http://jinja.pocoo.org/docs/api/#the-context + :param expand: Shall we expand the filenames to folder/index.html + as pretty URLS? """ # Ignore templates starting with "base". They're treated as special cases. @@ -96,7 +98,7 @@ def generate_html_file(template_filepath, rendered_html = minify_html(rendered_html) output_filename = get_output_filename(template_filepath, - output_dir, force_unexpanded) + output_dir, force_unexpanded, expand) if output_filename: make_sure_path_exists(os.path.dirname(output_filename)) @@ -107,7 +109,7 @@ def generate_html_file(template_filepath, def generate_html(templates_dir, output_dir, context=None, - unexpanded_templates=()): + unexpanded_templates=(), expand=True, quiet=False): """ Renders the HTML templates from `templates_dir`, and writes them to `output_dir`. @@ -119,6 +121,9 @@ def generate_html(templates_dir, output_dir, context=None, :paramtype output_dir: directory :param context: Jinja2 context that holds template variables. See http://jinja.pocoo.org/docs/api/#the-context + :param expand: Shall we expand the filenames to folder/index.html + as pretty URLS? + :param quiet: show no output! """ logging.debug('Templates dir is {0}'.format(templates_dir)) @@ -152,14 +157,16 @@ def generate_html(templates_dir, output_dir, context=None, )) if is_binary(os.path.join(templates_dir, template_filepath)): - print('Non-text file found: {0}. Skipping.'. - format(template_filepath)) + if quiet == False: + print('Non-text file found: {0}. Skipping.'. + format(template_filepath)) else: outfile = get_output_filename(template_filepath, output_dir, - force_unexpanded) - print('Copying {0} to {1}'.format(template_filepath, outfile)) + force_unexpanded, expand) + if quiet == False: + print('Copying {0} to {1}'.format(template_filepath, outfile)) generate_html_file(template_filepath, output_dir, env, context, - force_unexpanded) + force_unexpanded, expand) def generate_context(context_dir): @@ -211,7 +218,7 @@ def generate_context(context_dir): return context -def copy_assets(assets_dir, output_dir): +def copy_assets(assets_dir, output_dir, quiet=False): """ Copies static assets over from `assets_dir` to `output_dir`. @@ -220,6 +227,7 @@ def copy_assets(assets_dir, output_dir): :paramtype assets_dir: directory :param output_dir: The Complexity output directory, e.g. `www/`. :paramtype output_dir: directory + :param quiet: output to user """ assets = os.listdir(assets_dir) @@ -229,11 +237,13 @@ def copy_assets(assets_dir, output_dir): # Only copy allowed dirs if os.path.isdir(item_path) and item != 'scss' and item != 'less': new_dir = os.path.join(output_dir, item) - print('Copying directory {0} to {1}'.format(item, new_dir)) + if quiet == False: + print('Copying directory {0} to {1}'.format(item, new_dir)) shutil.copytree(item_path, new_dir) # Copy over files in the root of assets_dir if os.path.isfile(item_path): new_file = os.path.join(output_dir, item) - print('Copying file {0} to {1}'.format(item, new_file)) + if quiet == False: + print('Copying file {0} to {1}'.format(item, new_file)) shutil.copyfile(item_path, new_file) diff --git a/complexity/main.py b/complexity/main.py index d10c006..779396a 100755 --- a/complexity/main.py +++ b/complexity/main.py @@ -15,6 +15,7 @@ import logging import os import sys +import time from .conf import read_conf, get_unexpanded_list from .exceptions import OutputDirExistsException @@ -22,11 +23,14 @@ from .prep import prompt_and_delete_cruft, delete_cruft from .serve import serve_static_site +from watchdog.observers import Observer +from watchdog.events import FileSystemEventHandler +from time import gmtime, strftime logger = logging.getLogger(__name__) -def complexity(project_dir, overwrite=False, no_input=True): +def complexity(project_dir, overwrite=False, no_input=True, quiet=False): """ API equivalent to using complexity at the command line. @@ -40,6 +44,9 @@ def complexity(project_dir, overwrite=False, no_input=True): .. note:: You must delete `output_dir` before calling this. This also does not start the Complexity development server; you can do that from your code if desired. + + :para quiet: if True, we won't alert the end user to anything, and we'll + `just run` until we are finished """ # Get the configuration dictionary, if config exists @@ -47,7 +54,8 @@ def complexity(project_dir, overwrite=False, no_input=True): "templates_dir": "templates/", "assets_dir": "assets/", "context_dir": "context/", - "output_dir": "../www/" + "output_dir": "../www/", + "expand": True } conf_dict = read_conf(project_dir) or defaults @@ -55,7 +63,7 @@ def complexity(project_dir, overwrite=False, no_input=True): os.path.join(project_dir, conf_dict['output_dir']) ) - if overwrite and os.path.exists(output_dir): + if quiet or (overwrite and os.path.exists(output_dir)): delete_cruft(output_dir) elif no_input: # If output_dir exists, prompt before deleting. @@ -78,11 +86,11 @@ def complexity(project_dir, overwrite=False, no_input=True): # Generate and serve the HTML site unexpanded_templates = get_unexpanded_list(conf_dict) templates_dir = os.path.join(project_dir, conf_dict['templates_dir']) - generate_html(templates_dir, output_dir, context, unexpanded_templates) + generate_html(templates_dir, output_dir, context, unexpanded_templates, conf_dict['expand'], quiet) if 'assets_dir' in conf_dict: assets_dir = os.path.join(project_dir, conf_dict['assets_dir']) - copy_assets(assets_dir, output_dir) + copy_assets(assets_dir, output_dir, quiet) return output_dir @@ -124,20 +132,60 @@ def get_complexity_args(): action='store_true', help='Overwrite the output directory without prompting.' ) - + parser.add_argument( + '--watch', + action='store_true', + help='Will watch a folder for changes and then process if an event is fired' + ) args = parser.parse_args() return args +def watching_file_system(): + """ + Using watchdog, we'll monitor the filesystem for any changes, and if + we find any, we'll serve the output again (by running complexity) + """ + # Get the path we'll need to monitor, it'll be part of the arg list + args = get_complexity_args() + path = args.project_dir + + # Lets observe the folder, and notify complexity when something bad happens + observer = Observer() + event_handler = MyHandler() + observer.schedule(event_handler, path, recursive=True) + observer.start() + + # We'll now continue to look until we Ctrl-C finish + print("Watching folder " + path + " for changes:") + try: + while True: + time.sleep(1) + except KeyboardInterrupt: + observer.stop(); + + observer.join() +""" +This class handles at which points we should process the complexity system again. +We are targeting any events +""" +class MyHandler(FileSystemEventHandler): + def on_any_event(self, event): + args = get_complexity_args() + output_dir = complexity(project_dir=args.project_dir, no_input=False, quiet=True) + print(" [" + strftime("%Y-%m-%d %H:%M:%S", gmtime()) + "] -> Completed") + def main(): """ Entry point for the package, as defined in `setup.py`. """ args = get_complexity_args() - output_dir = complexity(project_dir=args.project_dir, overwrite=args.overwrite, no_input=False) - if not args.noserver: - serve_static_site(output_dir=output_dir, address=args.address, port=args.port) - + if args.watch == True: + watching_file_system() + else: + output_dir = complexity(project_dir=args.project_dir, overwrite=args.overwrite, no_input=False) + if not args.noserver: + serve_static_site(output_dir=output_dir, address=args.address, port=args.port) if __name__ == '__main__': - main() + watching_file_system() diff --git a/complexity/prep.py b/complexity/prep.py index 2db437e..7ef1b37 100755 --- a/complexity/prep.py +++ b/complexity/prep.py @@ -42,3 +42,14 @@ def prompt_and_delete_cruft(output_dir): .format(output_dir) ) return False + +def do_not_prompt_and_delete_cruft(output_dir): + """ + Doesnt ask if it's okay to delete `output_dir/`. + Just go ahead and delete it. + + :param output_dir: The Complexity output directory, e.g. `www/`. + :paramtype output_dir: directory + """ + shutil.rmtree(output_dir) + return True \ No newline at end of file diff --git a/setup.py b/setup.py index deb9613..f722e03 100755 --- a/setup.py +++ b/setup.py @@ -22,7 +22,8 @@ requirements = [ 'jinja2>=2.4', 'binaryornot>=0.1.1', - 'PyYAML>=3.10' + 'PyYAML>=3.10', + 'watchdog>=0.8.3' ] test_requirements = [] From 3b1eb052b7741647b951009974d77d80eb2a0f89 Mon Sep 17 00:00:00 2001 From: John Stowers Date: Sun, 18 Mar 2018 13:58:50 +0100 Subject: [PATCH 04/12] Make folder watching compatible with serve --- complexity/conf.py | 9 ++++++++ complexity/main.py | 51 ++++++++++++++++++++++++++++------------------ complexity/prep.py | 30 +++++++++++++++------------ 3 files changed, 57 insertions(+), 33 deletions(-) diff --git a/complexity/conf.py b/complexity/conf.py index 252b40f..561831b 100755 --- a/complexity/conf.py +++ b/complexity/conf.py @@ -14,6 +14,15 @@ import yaml +DEFAULTS = { + "templates_dir": "templates/", + "assets_dir": "assets/", + "context_dir": "context/", + "output_dir": "../www/", + "expand": True +} + + def read_conf(directory): """ Reads and parses the `complexity.yml` configuration file from a diff --git a/complexity/main.py b/complexity/main.py index 779396a..d05a1d3 100755 --- a/complexity/main.py +++ b/complexity/main.py @@ -17,7 +17,7 @@ import sys import time -from .conf import read_conf, get_unexpanded_list +from .conf import read_conf, get_unexpanded_list, DEFAULTS from .exceptions import OutputDirExistsException from .generate import generate_context, copy_assets, generate_html from .prep import prompt_and_delete_cruft, delete_cruft @@ -30,7 +30,15 @@ logger = logging.getLogger(__name__) -def complexity(project_dir, overwrite=False, no_input=True, quiet=False): +def _get_output_dir(project_dir): + conf_dict = read_conf(project_dir) or DEFAULTS + output_dir = os.path.normpath( + os.path.join(project_dir, conf_dict['output_dir']) + ) + return output_dir + + +def complexity(project_dir, overwrite=False, no_input=True, quiet=False, _leave_output=False): """ API equivalent to using complexity at the command line. @@ -50,21 +58,12 @@ def complexity(project_dir, overwrite=False, no_input=True, quiet=False): """ # Get the configuration dictionary, if config exists - defaults = { - "templates_dir": "templates/", - "assets_dir": "assets/", - "context_dir": "context/", - "output_dir": "../www/", - "expand": True - } - conf_dict = read_conf(project_dir) or defaults + conf_dict = read_conf(project_dir) or DEFAULTS - output_dir = os.path.normpath( - os.path.join(project_dir, conf_dict['output_dir']) - ) + output_dir = _get_output_dir(project_dir) - if quiet or (overwrite and os.path.exists(output_dir)): - delete_cruft(output_dir) + if overwrite: + delete_cruft(output_dir, only_contents=_leave_output) elif no_input: # If output_dir exists, prompt before deleting. # Abort if it can't be deleted. @@ -147,19 +146,23 @@ def watching_file_system(): """ # Get the path we'll need to monitor, it'll be part of the arg list args = get_complexity_args() - path = args.project_dir + path = os.path.abspath(args.project_dir) # Lets observe the folder, and notify complexity when something bad happens observer = Observer() - event_handler = MyHandler() + event_handler = MyHandler(project_dir=path) observer.schedule(event_handler, path, recursive=True) observer.start() # We'll now continue to look until we Ctrl-C finish print("Watching folder " + path + " for changes:") try: - while True: - time.sleep(1) + if args.noserver: + while True: + time.sleep(1) + else: + output_dir = _get_output_dir(args.project_dir) + serve_static_site(output_dir=output_dir, address=args.address, port=args.port) except KeyboardInterrupt: observer.stop(); @@ -170,9 +173,17 @@ def watching_file_system(): We are targeting any events """ class MyHandler(FileSystemEventHandler): + + def __init__(self, project_dir, *args, **kwargs): + super(MyHandler, self).__init__(*args, **kwargs) + self._project_dir = project_dir + def on_any_event(self, event): args = get_complexity_args() - output_dir = complexity(project_dir=args.project_dir, no_input=False, quiet=True) + complexity(project_dir=self._project_dir, no_input=False, + quiet=True, + overwrite=True, + _leave_output=True) # delete contents of www print(" [" + strftime("%Y-%m-%d %H:%M:%S", gmtime()) + "] -> Completed") def main(): diff --git a/complexity/prep.py b/complexity/prep.py index 7ef1b37..cc9bf9d 100755 --- a/complexity/prep.py +++ b/complexity/prep.py @@ -11,13 +11,27 @@ import os import shutil +import glob from . import utils -def delete_cruft(output_dir): - print('Deleting {0}'.format(output_dir)) - shutil.rmtree(output_dir) +def delete_cruft(output_dir, only_contents=False): + if only_contents: + print('Deleting {0}/*'.format(output_dir)) + for f in os.listdir(output_dir): + file_path = os.path.join(output_dir, f) + try: + if os.path.isfile(file_path): + os.unlink(file_path) + elif os.path.isdir(file_path): + shutil.rmtree(file_path, ignore_errors=True) + except Exception as e: + print('Error Deleting') + raise + else: + print('Deleting {0}/*'.format(output_dir)) + shutil.rmtree(output_dir, ignore_errors=True) def prompt_and_delete_cruft(output_dir): """ @@ -43,13 +57,3 @@ def prompt_and_delete_cruft(output_dir): ) return False -def do_not_prompt_and_delete_cruft(output_dir): - """ - Doesnt ask if it's okay to delete `output_dir/`. - Just go ahead and delete it. - - :param output_dir: The Complexity output directory, e.g. `www/`. - :paramtype output_dir: directory - """ - shutil.rmtree(output_dir) - return True \ No newline at end of file From e038f8f170f9a1e08cb45efa36aed3339d9119d9 Mon Sep 17 00:00:00 2001 From: John Stowers Date: Sun, 18 Mar 2018 13:59:00 +0100 Subject: [PATCH 05/12] Fix logic bug in expand support --- complexity/generate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/complexity/generate.py b/complexity/generate.py index efb37f0..8766d35 100755 --- a/complexity/generate.py +++ b/complexity/generate.py @@ -41,7 +41,7 @@ def get_output_filename(template_filepath, output_dir, force_unexpanded, expand) if basename.startswith('base'): return False # Put index and unexpanded templates in the root. - elif force_unexpanded or basename == 'index.html' or expand == True: + elif force_unexpanded or basename == 'index.html' or not expand: output_filename = os.path.join(output_dir, template_filepath) # Put other pages in page/index.html, for better URL formatting. else: From 0a91d0223e049b8ca5fd0590f1e382bd70ba9697 Mon Sep 17 00:00:00 2001 From: John Stowers Date: Sun, 18 Mar 2018 16:17:23 +0100 Subject: [PATCH 06/12] Ignore more types of files --- complexity/generate.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/complexity/generate.py b/complexity/generate.py index 8766d35..0cd8178 100755 --- a/complexity/generate.py +++ b/complexity/generate.py @@ -10,7 +10,7 @@ import json import logging -import os +import os.path import shutil import re @@ -108,6 +108,18 @@ def generate_html_file(template_filepath, return True +def _ignore(path): + fn = os.path.basename(path) + _, ext = os.path.splitext(path) + if is_binary(path): + return True + if fn == 'complexity.yml': + return True + if ext in ('.j2','.yml'): + return True + return False + + def generate_html(templates_dir, output_dir, context=None, unexpanded_templates=(), expand=True, quiet=False): """ @@ -156,9 +168,9 @@ def generate_html(templates_dir, output_dir, context=None, force_unexpanded )) - if is_binary(os.path.join(templates_dir, template_filepath)): + if _ignore(os.path.join(templates_dir, template_filepath)): if quiet == False: - print('Non-text file found: {0}. Skipping.'. + print('Ignore: {0}. Skipping.'. format(template_filepath)) else: outfile = get_output_filename(template_filepath, output_dir, From 5defd034e2787651ff932f094d5a5cf12f27c25b Mon Sep 17 00:00:00 2001 From: John Stowers Date: Sun, 18 Mar 2018 16:48:53 +0100 Subject: [PATCH 07/12] Add support for jinja macros dir --- complexity/conf.py | 1 + complexity/generate.py | 6 ++---- complexity/main.py | 5 ++++- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/complexity/conf.py b/complexity/conf.py index 561831b..7f38685 100755 --- a/complexity/conf.py +++ b/complexity/conf.py @@ -16,6 +16,7 @@ DEFAULTS = { "templates_dir": "templates/", + "macros_dir": "macros/", "assets_dir": "assets/", "context_dir": "context/", "output_dir": "../www/", diff --git a/complexity/generate.py b/complexity/generate.py index 0cd8178..1291ee0 100755 --- a/complexity/generate.py +++ b/complexity/generate.py @@ -120,7 +120,7 @@ def _ignore(path): return False -def generate_html(templates_dir, output_dir, context=None, +def generate_html(templates_dir, macros_dir, output_dir, context=None, unexpanded_templates=(), expand=True, quiet=False): """ Renders the HTML templates from `templates_dir`, and writes them to @@ -146,9 +146,7 @@ def generate_html(templates_dir, output_dir, context=None, ) context = context or {} - env = Environment() - # os.chdir(templates_dir) - env.loader = FileSystemLoader(templates_dir) + env = Environment(loader = FileSystemLoader([templates_dir, macros_dir])) # Create the output dir if it doesn't already exist make_sure_path_exists(output_dir) diff --git a/complexity/main.py b/complexity/main.py index d05a1d3..9e1380a 100755 --- a/complexity/main.py +++ b/complexity/main.py @@ -85,7 +85,10 @@ def complexity(project_dir, overwrite=False, no_input=True, quiet=False, _leave_ # Generate and serve the HTML site unexpanded_templates = get_unexpanded_list(conf_dict) templates_dir = os.path.join(project_dir, conf_dict['templates_dir']) - generate_html(templates_dir, output_dir, context, unexpanded_templates, conf_dict['expand'], quiet) + macros_dir = os.path.join(project_dir, conf_dict['macros_dir']) + + generate_html(templates_dir, macros_dir, output_dir, + context, unexpanded_templates, conf_dict['expand'], quiet) if 'assets_dir' in conf_dict: assets_dir = os.path.join(project_dir, conf_dict['assets_dir']) From 67ba93a04a9eb3a759f806da1bd8799fa4bedddd Mon Sep 17 00:00:00 2001 From: John Stowers Date: Mon, 19 Mar 2018 08:52:40 +0100 Subject: [PATCH 08/12] Load context from yml files too --- complexity/generate.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/complexity/generate.py b/complexity/generate.py index 1291ee0..56077b3 100755 --- a/complexity/generate.py +++ b/complexity/generate.py @@ -14,6 +14,7 @@ import shutil import re +from yaml import safe_load from binaryornot.check import is_binary from jinja2 import FileSystemLoader from jinja2.environment import Environment @@ -211,19 +212,22 @@ def generate_context(context_dir): """ context = {} - json_files = os.listdir(context_dir) + all_files = os.listdir(context_dir) + for fn in all_files: + path = os.path.join(context_dir, fn) + name, ext = os.path.splitext(fn) - for file_name in json_files: - - if file_name.endswith('json'): - - # Open the JSON file and convert to Python object - json_file = os.path.join(context_dir, file_name) - with unicode_open(json_file) as f: + obj = None + if ext == '.json': + with unicode_open(path) as f: obj = json.load(f) + elif ext in {'.yml', '.yaml'}: + with unicode_open(path) as f: + obj = safe_load(f) - # Add the Python object to the context dictionary - context[file_name[:-5]] = obj + if obj is not None: + print('Parsed {0} to context as {1}'.format(fn, name)) + context[name] = obj return context From 4506bb9a094d85980af83de2a2bb0e501d328313 Mon Sep 17 00:00:00 2001 From: John Stowers Date: Mon, 19 Mar 2018 08:53:50 +0100 Subject: [PATCH 09/12] Watch all directories defined in configuration More correct than just monitoring the root project dir, as subdirs can be independently defined outside of the project root --- complexity/main.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/complexity/main.py b/complexity/main.py index 9e1380a..1443154 100755 --- a/complexity/main.py +++ b/complexity/main.py @@ -30,14 +30,18 @@ logger = logging.getLogger(__name__) -def _get_output_dir(project_dir): +def _get_dir(project_dir, conf_dir_name): conf_dict = read_conf(project_dir) or DEFAULTS output_dir = os.path.normpath( - os.path.join(project_dir, conf_dict['output_dir']) + os.path.join(project_dir, conf_dict[conf_dir_name]) ) return output_dir +def _get_output_dir(project_dir): + return _get_dir(project_dir, 'output_dir') + + def complexity(project_dir, overwrite=False, no_input=True, quiet=False, _leave_output=False): """ API equivalent to using complexity at the command line. @@ -148,17 +152,22 @@ def watching_file_system(): we find any, we'll serve the output again (by running complexity) """ # Get the path we'll need to monitor, it'll be part of the arg list - args = get_complexity_args() - path = os.path.abspath(args.project_dir) + args = get_complexity_args() + + # make absolute because server chdirs + proj_dir = os.path.abspath(args.project_dir) # Lets observe the folder, and notify complexity when something bad happens observer = Observer() - event_handler = MyHandler(project_dir=path) - observer.schedule(event_handler, path, recursive=True) + event_handler = MyHandler(project_dir=proj_dir) + + for dir_ in ("templates_dir", "macros_dir", "assets_dir", "context_dir"): + path = _get_dir(proj_dir, dir_) + print("Watching folder " + path + " for changes:") + observer.schedule(event_handler, path, recursive=True) observer.start() # We'll now continue to look until we Ctrl-C finish - print("Watching folder " + path + " for changes:") try: if args.noserver: while True: From c8c86cbdb9596a6ffac933827e33ab09882a0fa1 Mon Sep 17 00:00:00 2001 From: John Stowers Date: Tue, 20 Mar 2018 09:52:12 +0100 Subject: [PATCH 10/12] Can override settings on commandline --- complexity/main.py | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/complexity/main.py b/complexity/main.py index 1443154..4b34c8e 100755 --- a/complexity/main.py +++ b/complexity/main.py @@ -16,6 +16,7 @@ import os import sys import time +import json from .conf import read_conf, get_unexpanded_list, DEFAULTS from .exceptions import OutputDirExistsException @@ -42,7 +43,7 @@ def _get_output_dir(project_dir): return _get_dir(project_dir, 'output_dir') -def complexity(project_dir, overwrite=False, no_input=True, quiet=False, _leave_output=False): +def complexity(project_dir, overwrite=False, no_input=True, quiet=False, settings_json=None, _leave_output=False): """ API equivalent to using complexity at the command line. @@ -80,11 +81,28 @@ def complexity(project_dir, overwrite=False, no_input=True, quiet=False, _leave_ sys.exit() # Generate the context data - context = None + context = {} if 'context_dir' in conf_dict: context_dir = os.path.join(project_dir, conf_dict['context_dir']) if os.path.exists(context_dir): - context = generate_context(context_dir) + context.update(generate_context(context_dir)) + + # update the context with anything in the project conf + context.update(conf_dict.get('context', {})) + + # json settings comes last + if settings_json: + # noinspection PyBroadException + try: + settings = json.loads(settings_json) + if isinstance(settings, dict): + if 'settings' in context: + context['settings'].update(settings) + else: + context['settings'] = settings + print("Updated settings from command-line") + except Exception as e: + pass # Generate and serve the HTML site unexpanded_templates = get_unexpanded_list(conf_dict) @@ -143,6 +161,11 @@ def get_complexity_args(): action='store_true', help='Will watch a folder for changes and then process if an event is fired' ) + parser.add_argument( + '--settings', + type=str, default='{}', + help='JSON settings to apply (update) to the loaded context' + ) args = parser.parse_args() return args @@ -195,6 +218,7 @@ def on_any_event(self, event): complexity(project_dir=self._project_dir, no_input=False, quiet=True, overwrite=True, + settings_json=args.settings, _leave_output=True) # delete contents of www print(" [" + strftime("%Y-%m-%d %H:%M:%S", gmtime()) + "] -> Completed") @@ -206,7 +230,8 @@ def main(): if args.watch == True: watching_file_system() else: - output_dir = complexity(project_dir=args.project_dir, overwrite=args.overwrite, no_input=False) + output_dir = complexity(project_dir=args.project_dir, overwrite=args.overwrite, no_input=False, + settings_json=args.settings) if not args.noserver: serve_static_site(output_dir=output_dir, address=args.address, port=args.port) From 69abf9c151b2486c9d416d6aa2d245d587238630 Mon Sep 17 00:00:00 2001 From: John Stowers Date: Tue, 20 Mar 2018 17:23:10 +0100 Subject: [PATCH 11/12] Can specify multipl macro dirs --- complexity/conf.py | 2 +- complexity/generate.py | 8 ++++++-- complexity/main.py | 18 ++++++++++++++---- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/complexity/conf.py b/complexity/conf.py index 7f38685..8aeafbd 100755 --- a/complexity/conf.py +++ b/complexity/conf.py @@ -16,10 +16,10 @@ DEFAULTS = { "templates_dir": "templates/", - "macros_dir": "macros/", "assets_dir": "assets/", "context_dir": "context/", "output_dir": "../www/", + "macro_dirs": ["macros/"], "expand": True } diff --git a/complexity/generate.py b/complexity/generate.py index 56077b3..6c934b7 100755 --- a/complexity/generate.py +++ b/complexity/generate.py @@ -121,7 +121,7 @@ def _ignore(path): return False -def generate_html(templates_dir, macros_dir, output_dir, context=None, +def generate_html(templates_dir, macro_dirs, output_dir, context=None, unexpanded_templates=(), expand=True, quiet=False): """ Renders the HTML templates from `templates_dir`, and writes them to @@ -147,7 +147,11 @@ def generate_html(templates_dir, macros_dir, output_dir, context=None, ) context = context or {} - env = Environment(loader = FileSystemLoader([templates_dir, macros_dir])) + + _dirs = [templates_dir] + _dirs.extend(macro_dirs) + + env = Environment(loader=FileSystemLoader(_dirs)) # Create the output dir if it doesn't already exist make_sure_path_exists(output_dir) diff --git a/complexity/main.py b/complexity/main.py index 4b34c8e..7c5c19a 100755 --- a/complexity/main.py +++ b/complexity/main.py @@ -107,9 +107,9 @@ def complexity(project_dir, overwrite=False, no_input=True, quiet=False, setting # Generate and serve the HTML site unexpanded_templates = get_unexpanded_list(conf_dict) templates_dir = os.path.join(project_dir, conf_dict['templates_dir']) - macros_dir = os.path.join(project_dir, conf_dict['macros_dir']) + macro_dirs = [os.path.join(project_dir, _dir) for _dir in conf_dict['macro_dirs']] - generate_html(templates_dir, macros_dir, output_dir, + generate_html(templates_dir, macro_dirs, output_dir, context, unexpanded_templates, conf_dict['expand'], quiet) if 'assets_dir' in conf_dict: @@ -169,6 +169,7 @@ def get_complexity_args(): args = parser.parse_args() return args + def watching_file_system(): """ Using watchdog, we'll monitor the filesystem for any changes, and if @@ -184,8 +185,17 @@ def watching_file_system(): observer = Observer() event_handler = MyHandler(project_dir=proj_dir) - for dir_ in ("templates_dir", "macros_dir", "assets_dir", "context_dir"): - path = _get_dir(proj_dir, dir_) + paths = [] + for _dir in ("templates_dir", "assets_dir", "context_dir"): + paths.append(_get_dir(proj_dir, _dir)) + + # from _get_dir above + cd = read_conf(proj_dir) or DEFAULTS + for _dir in cd['macro_dirs']: + _path = os.path.normpath(os.path.join(proj_dir, _dir)) + paths.append(_path) + + for path in paths: print("Watching folder " + path + " for changes:") observer.schedule(event_handler, path, recursive=True) observer.start() From e17ef6b4a1e3e0c061b83c9ff8d3f6b8b68e4a05 Mon Sep 17 00:00:00 2001 From: John Stowers Date: Tue, 20 Mar 2018 17:24:41 +0100 Subject: [PATCH 12/12] Only update settings from command line if non-empty --- complexity/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/complexity/main.py b/complexity/main.py index 7c5c19a..c3e2b09 100755 --- a/complexity/main.py +++ b/complexity/main.py @@ -95,7 +95,7 @@ def complexity(project_dir, overwrite=False, no_input=True, quiet=False, setting # noinspection PyBroadException try: settings = json.loads(settings_json) - if isinstance(settings, dict): + if isinstance(settings, dict) and settings: if 'settings' in context: context['settings'].update(settings) else: