mirror of
https://github.com/mkdocs/mkdocs.git
synced 2026-03-27 09:58:31 +07:00
A simple method for providing a quick/dirty reload option (refs #990)
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user