Exemple #1
0
    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
Exemple #2
0
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)
Exemple #3
0
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)
Exemple #4
0
 def test_empty_error_context(self):
     context = StatikErrorContext()
     self.assertIsNone(context.render())
Exemple #5
0
 def test_complete_error_context(self):
     context = StatikErrorContext(filename="somefilename.yml", line_no=12)
     self.assertEqual("in file \"somefilename.yml\", line 12",
                      context.render())
Exemple #6
0
 def test_error_context_with_filename_only(self):
     context = StatikErrorContext(filename="somefilename.yml")
     self.assertEqual("in file \"somefilename.yml\"", context.render())
Exemple #7
0
 def __init__(self, *args, **kwargs):
     self.error_context = kwargs.pop('error_context', StatikErrorContext())
     super(MarkdownLoremIpsumExtension, self).__init__(*args, **kwargs)
Exemple #8
0
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)
Exemple #9
0
 def test_empty_error_context(self):
     context = StatikErrorContext()
     self.assertIsNone(context.render())
Exemple #10
0
 def test_complete_error_context(self):
     context = StatikErrorContext(filename="somefilename.yml", line_no=12)
     self.assertEqual("in file \"somefilename.yml\", line 12", context.render())
Exemple #11
0
 def test_error_context_with_filename_only(self):
     context = StatikErrorContext(filename="somefilename.yml")
     self.assertEqual("in file \"somefilename.yml\"", context.render())