def __init__(self, markdown_params=None, error_context=None): self.error_context = error_context or StatikErrorContext() markdown_params = markdown_params or dict() if not isinstance(markdown_params, dict): raise ProjectConfigurationError( message= "Markdown configuration parameters must be a dictionary.", context=error_context) permalinks_config = markdown_params.get('permalinks', dict()) self.enable_permalinks = permalinks_config.get('enabled', False) if self.enable_permalinks in {"true", "1", 1}: self.enable_permalinks = True elif self.enable_permalinks in {"false", "0", 0}: self.enable_permalinks = False self.permalink_text = permalinks_config.get('text', "¶") self.permalink_class = permalinks_config.get('class', None) self.permalink_title = permalinks_config.get('title', None) # Required list of Markdown extensions self.extensions = copy(MarkdownConfig.DEFAULT_MARKDOWN_EXTENSIONS) # Configuration for the markdown extensions self.extension_config = {} extension_list = markdown_params.get('extensions', []) # if it's a dictionary, first convert it to our list notation if isinstance(extension_list, dict): extension_list = [] for ext_package, config in markdown_params['extensions'].items(): extension_list.append({ext_package: config}) # Try to load extensions as requested by config for extension in extension_list: if isinstance(extension, dict): ext_package, config = next(iter(extension.keys())), next( iter(extension.values())) else: ext_package, config = extension, None if ext_package not in self.extensions: self.extensions.append(ext_package) if config is not None: if ext_package in self.extension_config: self.extension_config[ext_package].update(config) else: self.extension_config[ext_package] = config
def watch(project_path, output_path, host='0.0.0.0', port=8000, min_reload_time=2.0, open_browser=True, safe_mode=False, error_context=None): """Watches the given project path for filesystem changes, and automatically rebuilds the project when changes are detected. Also serves an HTTP server on the given host/port. Args: project_path: The path to the Statik project to be watched. output_path: The path into which to write the output files. host: The host IP/hostname to which to bind when serving output files. port: The port to which to bind when serving output files. min_reload_time: The minimum time (in seconds) between reloads when files change. open_browser: Whether or not to automatically open the web browser at the served URL. safe_mode: Whether or not to run Statik in safe mode. error_context: An optional StatikErrorContext instance for detailed error reporting. """ error_context = error_context or StatikErrorContext() project = StatikProject(project_path, safe_mode=safe_mode, error_context=error_context) project.generate(output_path=output_path, in_memory=False) watch_folders = [ StatikProject.MODELS_DIR, StatikProject.DATA_DIR, StatikProject.VIEWS_DIR, StatikProject.TEMPLATES_DIR, project.config.assets_src_path ] # let the template tags folder be optional template_tags_folder = os.path.join(project.path, StatikProject.TEMPLATETAGS_DIR) if os.path.exists(template_tags_folder) and os.path.isdir( template_tags_folder): watch_folders.append(StatikProject.TEMPLATETAGS_DIR) # if theming is enabled, watch the specific theme's folder for changes if project.config.theme is not None: watch_folders.append( os.path.join(StatikProject.THEMES_DIR, project.config.theme)) watch_folders = [ f if os.path.isabs(f) else os.path.join(project.path, f) for f in watch_folders ] for folder in watch_folders: if not os.path.exists(folder) or not os.path.isdir(folder): raise MissingProjectFolderError(folder) httpwatcher.watch( output_path, watch_paths=watch_folders, on_reload=lambda: safe_wrap_project_generate(project, output_path), host=host, port=port, server_base_path=project.config.base_path, watcher_interval=min_reload_time, recursive=True, open_browser=open_browser)
def main(): parser = argparse.ArgumentParser( description="Statik: a static web site generator for developers." ) parser.add_argument( '-p', '--project', help="The path to your Statik project or project YAML configuration file (default: current directory).", ) parser.add_argument( '--quickstart', help="Statik will generate a basic directory structure for you in the project directory and exit.", action='store_true', ) parser.add_argument( '--autogen', help="Statik will generate default views and templates in the project directory for all the models.", action='store_true', ) group_generate = parser.add_argument_group('static site generation') group_generate.add_argument( '-o', '--output', help="The output path into which to place the built project (default: \"public\" directory in input " + "directory).", ) group_generate.add_argument( '--clear-output', action='store_true', help="Clears the output folder first prior to building the project. If watching " + "is initiated, this will only clear the output folder once-off." ) group_generate.add_argument( '-s', '--safe-mode', action='store_true', default=False, help="Run Statik in safe mode (which disallows unsafe query execution)" ) group_server = parser.add_argument_group('built-in server') group_server.add_argument( '-w', '--watch', help="Statik will watch the project path for changes and automatically regenerate the project. " + "This also runs a small HTTP server to serve your output files.", action='store_true', ) group_server.add_argument( '--host', help="When watching a folder for changes (--watch), this specifies the host IP address or hostname to which " + "to bind (default: localhost).", default='localhost', ) group_server.add_argument( '--port', help="When watching a folder for changes (--watch), this specifies the port to which to bind (default: 8000).", type=int, default=8000, ) group_server.add_argument( '-n', '--no-browser', action='store_true', default=False, help="Do not attempt to automatically open a web browser at the served URL when watching for changes" ) group_remote = parser.add_argument_group('remote publishing') group_remote.add_argument( '-u', '--upload', action='store', help="Upload project to remote location (supported: SFTP, netlify)", ) group_remote.add_argument( '--netlify-site-id', action='store', help="Netlify site id to upload to. (--upload=netlify must be specified too)", ) group_remote.add_argument( '-c', '--clear-remote', action='store_true', help="CAUTION: This will delete ALL files and subfolders in the remote folder before uploading the generated files. " + "If the subsequent upload fails, your website will no longer be online." ) group_info = parser.add_argument_group('information about Statik') group_info.add_argument( '-v', '--verbose', help="Whether or not to output verbose logging information (default: false).", action='store_true', ) group_info.add_argument( '--quiet', default=False, help="Run Statik in quiet mode, where there will be no output except upon error.", action='store_true' ) group_info.add_argument( '--fail-silently', default=False, help="Only relevant if running Statik in quiet mode - if Statik encounters an error, the only indication " "of this will be in the resulting error code returned by the process. No other output will be given " "on the terminal.", action='store_true' ) group_info.add_argument( '--no-colorlog', action='store_true', help="By default, Statik outputs logging data in colour. By specifying this switch, " + "coloured logging will be turned off." ) group_info.add_argument( '--version', help='Display version info for Statik', action='store_true', ) args = parser.parse_args() error_context = StatikErrorContext() try: _project_path = args.project if args.project is not None else os.getcwd() project_path, config_file_path = get_project_config_file( _project_path, StatikProject.CONFIG_FILE ) output_path = args.output if args.output is not None else \ os.path.join(project_path, 'public') configure_logging( verbose=args.verbose, quiet=args.quiet, fail_silently=args.fail_silently, colourise=not args.no_colorlog ) if args.fail_silently and not args.quiet: logger.warning("Ignoring --fail-silently switch because --quiet is not specified") if args.version: show_version() sys.exit(0) if args.clear_output: shutil.rmtree(output_path, ignore_errors=True) logger.info("Cleared output path: %s", output_path) if args.watch: watch( config_file_path, output_path, host=args.host, port=args.port, open_browser=(not args.no_browser), safe_mode=args.safe_mode, error_context=error_context ) elif args.quickstart: generate_quickstart(project_path) elif args.autogen: autogen(project_path) else: if args.host and '--host=localhost' in sys.argv[1:]: logger.warning("Ignoring --host argument because --watch is not specified") if args.port and '--port=8000' in sys.argv[1:]: logger.warning("Ignoring --port argument because --watch is not specified") if args.no_browser: logger.warning("Ignoring --no-browser argument because --watch is not specified") generate( config_file_path, output_path=output_path, in_memory=False, safe_mode=args.safe_mode, error_context=error_context ) if args.upload and args.upload == 'SFTP': upload_sftp( config_file_path, output_path, rm_remote=args.clear_remote ) elif args.netlify_site_id and args.upload == 'netlify': if args.clear_remote: logger.warning("--clear-remote is not supported when uploading to Netlify") upload_netlify( output_path, args.netlify_site_id ) else: if args.clear_remote: logger.warning("Ignoring --clear-remote switch because --upload is not specified") if args.netlify_site_id and args.upload != 'netlify' or args.netlify_site_id: logger.warning("Ignoring --netlify-site-id: --upload=netlify not specified") if args.upload: logger.warning("Upload format '{}' not supported".format(args.upload)) except StatikError as e: sys.exit(e.exit_code) except Exception as e: logger.exception("Exception caught while attempting to generate project") sys.exit(1) # success sys.exit(0)
def test_empty_error_context(self): context = StatikErrorContext() self.assertIsNone(context.render())
def test_complete_error_context(self): context = StatikErrorContext(filename="somefilename.yml", line_no=12) self.assertEqual("in file \"somefilename.yml\", line 12", context.render())
def test_error_context_with_filename_only(self): context = StatikErrorContext(filename="somefilename.yml") self.assertEqual("in file \"somefilename.yml\"", context.render())
def __init__(self, *args, **kwargs): self.error_context = kwargs.pop('error_context', StatikErrorContext()) super(MarkdownLoremIpsumExtension, self).__init__(*args, **kwargs)
def main(): parser = argparse.ArgumentParser( description="Statik: a static web site generator for developers.") parser.add_argument( '--version', help='Display version info for Statik', action='store_true', ) parser.add_argument( '--quickstart', help= "Statik will generate a basic directory structure for you in the project directory and exit.", action='store_true', ) parser.add_argument( '-p', '--project', help= "The path to your Statik project or project YAML configuration file (default: current directory).", ) parser.add_argument( '-o', '--output', help= "The output path into which to place the built project (default: \"public\" directory in input " + "directory).", ) parser.add_argument( '-w', '--watch', help= "Statik will watch the project path for changes and automatically regenerate the project. " + "This also runs a small HTTP server to serve your output files.", action='store_true', ) parser.add_argument( '--host', help= "When watching a folder for changes (--watch), this specifies the host IP address or hostname to which " + "to bind (default: localhost).", default='localhost', ) parser.add_argument( '--port', help= "When watching a folder for changes (--watch), this specifies the port to which to bind (default: 8000).", type=int, default=8000, ) parser.add_argument( '-n', '--no-browser', action='store_true', default=False, help= "Do not attempt to automatically open a web browser at the served URL when watching for changes" ) parser.add_argument( '-s', '--safe-mode', action='store_true', default=False, help="Run Statik in safe mode (which disallows unsafe query execution)" ) parser.add_argument( '-v', '--verbose', help= "Whether or not to output verbose logging information (default: false).", action='store_true', ) parser.add_argument( '--quiet', default=False, help= "Run Statik in quiet mode, where there will be no output except upon error.", action='store_true') parser.add_argument( '--fail-silently', default=False, help= "Only relevant if running Statik in quiet mode - if Statik encounters an error, the only indication " "of this will be in the resulting error code returned by the process. No other output will be given " "on the terminal.", action='store_true') parser.add_argument( '--no-colorlog', action='store_true', help= "By default, Statik outputs logging data in colour. By specifying this switch, " + "coloured logging will be turned off.") parser.add_argument( '--clear-output', action='store_true', help= "Clears the output folder first prior to building the project. If watching " + "is initiated, this will only clear the output folder once-off.") args = parser.parse_args() error_context = StatikErrorContext() try: _project_path = args.project if args.project is not None else os.getcwd( ) project_path, config_file_path = get_project_config_file( _project_path, StatikProject.CONFIG_FILE) output_path = args.output if args.output is not None else \ os.path.join(project_path, 'public') configure_logging(verbose=args.verbose, quiet=args.quiet, fail_silently=args.fail_silently, colourise=not args.no_colorlog) if args.fail_silently and not args.quiet: logger.warning( "Ignoring --fail-silently switch because --quiet is not specified" ) if args.version: show_version() sys.exit(0) if args.clear_output: shutil.rmtree(output_path, ignore_errors=True) logger.info("Cleared output path: %s", output_path) if args.watch: watch(config_file_path, output_path, host=args.host, port=args.port, open_browser=(not args.no_browser), safe_mode=args.safe_mode, error_context=error_context) elif args.quickstart: generate_quickstart(project_path) else: generate(config_file_path, output_path=output_path, in_memory=False, safe_mode=args.safe_mode, error_context=error_context) except StatikError as e: sys.exit(e.exit_code) except Exception as e: logger.exception( "Exception caught while attempting to generate project") sys.exit(1) # success sys.exit(0)
def test_empty_error_context(self): context = StatikErrorContext() self.assertIsNone(context.render())
def test_complete_error_context(self): context = StatikErrorContext(filename="somefilename.yml", line_no=12) self.assertEqual("in file \"somefilename.yml\", line 12", context.render())
def test_error_context_with_filename_only(self): context = StatikErrorContext(filename="somefilename.yml") self.assertEqual("in file \"somefilename.yml\"", context.render())