From c4cb25e584ad496bb6cb5a189dcd5085a696adc8 Mon Sep 17 00:00:00 2001 From: Dougal Matthews Date: Sat, 4 Apr 2015 20:08:51 +0100 Subject: [PATCH] Automatically refresh the browser on edits Use python-livereload to automatically refresh your browser. Closes #163 --- mkdocs/config.py | 2 + mkdocs/serve.py | 119 ++++++----------------------------------------- requirements.txt | 2 +- setup.py | 2 +- 4 files changed, 18 insertions(+), 107 deletions(-) diff --git a/mkdocs/config.py b/mkdocs/config.py index 787fdd36..06559274 100644 --- a/mkdocs/config.py +++ b/mkdocs/config.py @@ -78,6 +78,8 @@ def load_config(filename='mkdocs.yml', options=None): options = options or {} if 'config' in options: filename = options['config'] + else: + options['config'] = filename if not os.path.exists(filename): raise ConfigurationError("Config file '%s' does not exist." % filename) with open(filename, 'r', encoding='utf-8') as fp: diff --git a/mkdocs/serve.py b/mkdocs/serve.py index 911e9b53..3b436b8e 100644 --- a/mkdocs/serve.py +++ b/mkdocs/serve.py @@ -1,72 +1,9 @@ -# coding: utf-8 -from __future__ import print_function - -from watchdog import events -from watchdog.observers.polling import PollingObserver -from mkdocs.build import build -from mkdocs.compat import httpserver, socketserver, urlunquote -from mkdocs.config import load_config -import os -import posixpath -import shutil -import sys import tempfile +from livereload import Server -class BuildEventHandler(events.FileSystemEventHandler): - """ - Perform a rebuild when anything in the theme or docs directory changes. - """ - def __init__(self, options): - super(BuildEventHandler, self).__init__() - self.options = options - - def on_any_event(self, event): - if not isinstance(event, events.DirModifiedEvent): - print('Rebuilding documentation...', end='') - config = load_config(options=self.options) - build(config, live_server=True) - print(' done') - - -class ConfigEventHandler(BuildEventHandler): - """ - Perform a rebuild when the config file changes. - """ - def on_any_event(self, event): - try: - if os.path.basename(event.src_path) == 'mkdocs.yml': - super(ConfigEventHandler, self).on_any_event(event) - except Exception as e: - print(e) - - -class FixedDirectoryHandler(httpserver.SimpleHTTPRequestHandler): - """ - Override the default implementation to allow us to specify the served - directory, instead of being hardwired to the current working directory. - """ - base_dir = os.getcwd() - - def translate_path(self, path): - # abandon query parameters - path = path.split('?', 1)[0] - path = path.split('#', 1)[0] - path = posixpath.normpath(urlunquote(path)) - words = path.split('/') - words = filter(None, words) - path = self.base_dir - for word in words: - drive, word = os.path.splitdrive(word) - head, word = os.path.split(word) - if word in (os.curdir, os.pardir): - continue - path = os.path.join(path, word) - return path - - def log_message(self, format, *args): - date_str = self.log_date_time_string() - sys.stderr.write('[%s] %s\n' % (date_str, format % args)) +from mkdocs.build import build +from mkdocs.config import load_config def serve(config, options=None): @@ -77,48 +14,20 @@ def serve(config, options=None): tempdir = tempfile.mkdtemp() options['site_dir'] = tempdir - # Only use user-friendly URLs when running the live server - options['use_directory_urls'] = True + def builder(): + config = load_config(options=options) + build(config, live_server=True) # Perform the initial build - config = load_config(options=options) - build(config, live_server=True) + builder() - # Note: We pass any command-line options through so that we - # can re-apply them if the config file is reloaded. - event_handler = BuildEventHandler(options) - config_event_handler = ConfigEventHandler(options) + server = Server() - # We could have used `Observer()`, which can be faster, but - # `PollingObserver()` works more universally. - observer = PollingObserver() - observer.schedule(event_handler, config['docs_dir'], recursive=True) - for theme_dir in config['theme_dir']: - if not os.path.exists(theme_dir): - continue - observer.schedule(event_handler, theme_dir, recursive=True) - observer.schedule(config_event_handler, '.') - observer.start() - - class TCPServer(socketserver.TCPServer): - allow_reuse_address = True - - class DocsDirectoryHandler(FixedDirectoryHandler): - base_dir = config['site_dir'] + # Watch the documentation files, the config file and the theme files. + server.watch(config['docs_dir'], builder) + server.watch(config['config'], builder) + for d in config['theme_dir']: + server.watch(d, builder) host, port = config['dev_addr'].split(':', 1) - server = TCPServer((host, int(port)), DocsDirectoryHandler) - - print('Running at: http://%s:%s/' % (host, port)) - print('Live reload enabled.') - print('Hold ctrl+c to quit.') - try: - server.serve_forever() - except KeyboardInterrupt: - print('Stopping server...') - - # Clean up - observer.stop() - observer.join() - shutil.rmtree(tempdir) - print('Quit complete') + server.serve(root=tempdir, host=host, port=port, restart_delay=0) diff --git a/requirements.txt b/requirements.txt index ac87b96d..5f8cf682 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ ghp-import>=0.4.1 Jinja2>=2.7.1 +livereload>=2.3.2 Markdown>=2.5 PyYAML>=3.10 -watchdog>=0.7.0 diff --git a/setup.py b/setup.py index 64698632..d6e55b8c 100755 --- a/setup.py +++ b/setup.py @@ -20,9 +20,9 @@ license = 'BSD' install_requires = [ 'ghp-import>=0.4.1', 'Jinja2>=2.7.1', + 'livereload>=2.3.2', 'Markdown>=2.3.1,<2.5' if PY26 else 'Markdown>=2.3.1', 'PyYAML>=3.10', - 'watchdog>=0.7.0', ] long_description = (