A simple method for providing a quick/dirty reload option (refs #990)

This commit is contained in:
Andrew E Slaughter
2016-07-20 14:44:17 -06:00
parent d790defd85
commit 81664fac4a
6 changed files with 92 additions and 27 deletions

View File

@@ -92,6 +92,26 @@ to support such customization.
[blocks]: ../user-guide/styling-your-docs/#overriding-template-blocks
#### Support for dirty builds. (#990)
For large sites the build time required to create the pages can become problematic,
thus a "dirty" build mode was created. This mode simply compares the modified time
of the generated html and source markdown. If the markdown has changed since the
html then the page is re-constructed. Otherwise, the page remains as is. This mode
may be invoked in both the `mkdocs serve` and `mkdocs build` commands:
```text
mkdocs serve --dirtyreload
```
```text
mkdocs build --dirty
```
It is important to note that this method for building the pages is for development
of content only, since the navigation and other links do not get updated on other
pages.
### Other Changes and Additions to Version 0.16.0
* Bugfix: Support `gh-deploy` command on Windows with Python 3 (#722)

View File

@@ -12,7 +12,7 @@ the primary working branch (usually `master`) of the git repository where you
maintain the source documentation for your project, run the following command:
```sh
mkdocs gh-deploy --clean
mkdocs gh-deploy
```
That's it! Behind the scenes, MkDocs will build your docs and use the [ghp-import]
@@ -66,7 +66,7 @@ host documentation for your project. Run the following commands from your
project's root directory to upload your documentation:
```sh
mkdocs build --clean
mkdocs build
python setup.py upload_docs --upload-dir=site
```
@@ -113,7 +113,7 @@ For example, a typical set of commands from the command line might look
something like this:
```sh
mkdocs build --clean
mkdocs build
scp -r ./site user@host:/path/to/server/root
```

View File

@@ -77,7 +77,9 @@ theme_dir_help = "The theme directory to use when building your documentation."
theme_help = "The theme to use when building your documentation."
theme_choices = utils.get_theme_names()
site_dir_help = "The directory to output the result of the documentation build."
reload_help = "Enable and disable the live reloading in the development server."
reload_help = "Enable the live reloading in the development server (this is the default)"
no_reload_help = "Disable the live reloading in the development server."
dirty_reload_help = "Enable the live reloading in the development server, but only re-build files that have changed"
commit_message_help = ("A commit message to use when commiting to the "
"Github Pages remote branch")
remote_branch_help = ("The remote branch to commit to for Github Pages. This "
@@ -101,7 +103,9 @@ def cli():
@click.option('-s', '--strict', is_flag=True, help=strict_help)
@click.option('-t', '--theme', type=click.Choice(theme_choices), help=theme_help)
@click.option('-e', '--theme-dir', type=click.Path(), help=theme_dir_help)
@click.option('--livereload/--no-livereload', default=True, help=reload_help)
@click.option('--livereload', 'livereload', flag_value='livereload', help=reload_help)
@click.option('--no-livereload', 'livereload', flag_value='no-livereload', help=no_reload_help)
@click.option('-d', '--dirtyreload', 'livereload', flag_value='dirty', help=dirty_reload_help)
@common_options
def serve_command(dev_addr, config_file, strict, theme, theme_dir, livereload):
"""Run the builtin development server"""
@@ -115,7 +119,7 @@ def serve_command(dev_addr, config_file, strict, theme, theme_dir, livereload):
strict=strict,
theme=theme,
theme_dir=theme_dir,
livereload=livereload,
livereload=livereload
)
except (exceptions.ConfigurationError, socket.error) as e:
# Avoid ugly, unhelpful traceback
@@ -123,7 +127,7 @@ def serve_command(dev_addr, config_file, strict, theme, theme_dir, livereload):
@cli.command(name="build")
@click.option('-c', '--clean', is_flag=True, help=clean_help)
@click.option('-c', '--clean/--dirty', is_flag=True, help=clean_help)
@click.option('-f', '--config-file', type=click.File('rb'), help=config_help)
@click.option('-s', '--strict', is_flag=True, help=strict_help)
@click.option('-t', '--theme', type=click.Choice(theme_choices), help=theme_help)
@@ -139,7 +143,7 @@ def build_command(clean, config_file, strict, theme, theme_dir, site_dir):
theme=theme,
theme_dir=theme_dir,
site_dir=site_dir
), clean_site_dir=clean)
), dirty=not clean)
except exceptions.ConfigurationError as e:
# Avoid ugly, unhelpful traceback
raise SystemExit('\n' + str(e))
@@ -169,7 +173,7 @@ def json_command(clean, config_file, strict, site_dir):
config_file=config_file,
strict=strict,
site_dir=site_dir
), dump_json=True, clean_site_dir=clean)
), dump_json=True, dirty=not clean)
except exceptions.ConfigurationError as e:
# Avoid ugly, unhelpful traceback
raise SystemExit('\n' + str(e))
@@ -190,7 +194,7 @@ def gh_deploy_command(config_file, clean, message, remote_branch, remote_name):
remote_branch=remote_branch,
remote_name=remote_name
)
build.build(config, clean_site_dir=clean)
build.build(config, dirty=not clean)
gh_deploy.gh_deploy(config, message=message)
except exceptions.ConfigurationError as e:
# Avoid ugly, unhelpful traceback

View File

@@ -32,6 +32,15 @@ log = logging.getLogger(__name__)
log.addFilter(DuplicateFilter())
def get_complete_paths(config, page):
"""
Return the complete input/output paths for the supplied page.
"""
input_path = os.path.join(config['docs_dir'], page.input_path)
output_path = os.path.join(config['site_dir'], page.output_path)
return input_path, output_path
def convert_markdown(markdown_source, config, site_navigation=None):
"""
Convert the Markdown source file to HTML as per the config and
@@ -172,11 +181,12 @@ def build_template(template_name, env, config, site_navigation=None):
return True
def _build_page(page, config, site_navigation, env, dump_json):
def _build_page(page, config, site_navigation, env, dump_json, dirty=False):
# Get the input/output paths
input_path, output_path = get_complete_paths(config, page)
# Read the input file
input_path = os.path.join(config['docs_dir'], page.input_path)
try:
input_content = io.open(input_path, 'r', encoding='utf-8').read()
except IOError:
@@ -214,7 +224,6 @@ def _build_page(page, config, site_navigation, env, dump_json):
output_content = template.render(context)
# Write the output file.
output_path = os.path.join(config['site_dir'], page.output_path)
if dump_json:
json_context = {
'content': context['content'],
@@ -250,7 +259,7 @@ def build_extra_templates(extra_templates, config, site_navigation=None):
utils.write_file(output_content.encode('utf-8'), output_path)
def build_pages(config, dump_json=False):
def build_pages(config, dump_json=False, dirty=False):
"""
Builds all the pages and writes them into the build directory.
"""
@@ -302,6 +311,13 @@ def build_pages(config, dump_json=False):
for page in site_navigation.walk_pages():
try:
# When --dirty is used, only build the page if the markdown has been modified since the
# previous build of the output.
input_path, output_path = get_complete_paths(config, page)
if dirty and (utils.modified_time(input_path) < utils.modified_time(output_path)):
continue
log.debug("Building page %s", page.input_path)
build_result = _build_page(page, config, site_navigation, env,
dump_json)
@@ -317,20 +333,25 @@ def build_pages(config, dump_json=False):
utils.write_file(search_index.encode('utf-8'), json_output_path)
def build(config, live_server=False, dump_json=False, clean_site_dir=False):
def build(config, live_server=False, dump_json=False, dirty=False):
"""
Perform a full site build.
"""
if clean_site_dir:
if not dirty:
log.info("Cleaning site directory")
utils.clean_directory(config['site_dir'])
else:
# Warn user about problems that may occur with --dirty option
log.warning("A 'dirty' build is being performed, this will likely lead to inaccurate navigation and other"
" links within your site. This option is designed for site development purposes only.")
if not live_server:
log.info("Building documentation to directory: %s", config['site_dir'])
if not clean_site_dir and site_directory_contains_stale_files(config['site_dir']):
if dirty and site_directory_contains_stale_files(config['site_dir']):
log.info("The directory contains stale files. Use --clean to remove them.")
if dump_json:
build_pages(config, dump_json=True)
build_pages(config, dump_json=True, dirty=dirty)
return
# Reversed as we want to take the media files from the builtin theme
@@ -339,14 +360,14 @@ def build(config, live_server=False, dump_json=False, clean_site_dir=False):
for theme_dir in reversed(config['theme_dir']):
log.debug("Copying static assets from theme: %s", theme_dir)
utils.copy_media_files(
theme_dir, config['site_dir'], exclude=['*.py', '*.pyc', '*.html']
theme_dir, config['site_dir'], exclude=['*.py', '*.pyc', '*.html'], dirty=dirty
)
log.debug("Copying static assets from the docs dir.")
utils.copy_media_files(config['docs_dir'], config['site_dir'])
utils.copy_media_files(config['docs_dir'], config['site_dir'], dirty=dirty)
log.debug("Building markdown pages.")
build_pages(config)
build_pages(config, dirty=dirty)
def site_directory_contains_stale_files(site_directory):

View File

@@ -51,7 +51,7 @@ def _static_server(host, port, site_dir):
def serve(config_file=None, dev_addr=None, strict=None, theme=None,
theme_dir=None, livereload=True):
theme_dir=None, livereload='livereload'):
"""
Start the MkDocs development server
@@ -59,6 +59,7 @@ def serve(config_file=None, dev_addr=None, strict=None, theme=None,
it will rebuild the documentation and refresh the page automatically
whenever a file is edited.
"""
# Create a temporary build directory, and set some options to serve it
tempdir = tempfile.mkdtemp()
@@ -69,10 +70,12 @@ def serve(config_file=None, dev_addr=None, strict=None, theme=None,
dev_addr=dev_addr,
strict=strict,
theme=theme,
theme_dir=theme_dir,
theme_dir=theme_dir
)
config['site_dir'] = tempdir
build(config, live_server=True, clean_site_dir=True)
live_server = livereload in ['dirty', 'livereload']
dirty = livereload == 'dirtyreload'
build(config, live_server=live_server, dirty=dirty)
return config
# Perform the initial build
@@ -81,7 +84,7 @@ def serve(config_file=None, dev_addr=None, strict=None, theme=None,
host, port = config['dev_addr'].split(':', 1)
try:
if livereload:
if livereload in ['livereload', 'dirtyreload']:
_livereload(host, port, config, builder, tempdir)
else:
_static_server(host, port, tempdir)

View File

@@ -81,6 +81,17 @@ def yaml_load(source, loader=yaml.Loader):
source.close()
def modified_time(file_path):
"""
Return the modified time of the supplied file. If the file does not exists zero is returned.
see build_pages for use.
"""
if os.path.exists(file_path):
return os.path.getmtime(file_path)
else:
return 0.0
def reduce_list(data_set):
""" Reduce duplicate items in a list and preserve order """
seen = set()
@@ -92,6 +103,7 @@ def copy_file(source_path, output_path):
"""
Copy source_path to output_path, making sure any parent directories exist.
"""
output_dir = os.path.dirname(output_path)
if not os.path.exists(output_dir):
os.makedirs(output_dir)
@@ -129,7 +141,7 @@ def clean_directory(directory):
os.unlink(path)
def copy_media_files(from_dir, to_dir, exclude=None):
def copy_media_files(from_dir, to_dir, exclude=None, dirty=False):
"""
Recursively copy all files except markdown and exclude[ed] files into another directory.
@@ -155,6 +167,11 @@ def copy_media_files(from_dir, to_dir, exclude=None):
if not is_markdown_file(filename):
source_path = os.path.join(source_dir, filename)
output_path = os.path.join(output_dir, filename)
# Do not copy when using --dirty if the file has not been modified
if dirty and (modified_time(source_path) < modified_time(output_path)):
continue
copy_file(source_path, output_path)