/
serve.py
150 lines (114 loc) · 4.8 KB
/
serve.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
import logging
import shutil
import tempfile
import sys
from os.path import isfile, join
from mkdocs.commands.build import build
from mkdocs.config import load_config
log = logging.getLogger(__name__)
def _init_asyncio_patch():
"""
Select compatible event loop for Tornado 5+.
As of Python 3.8, the default event loop on Windows is `proactor`,
however Tornado requires the old default "selector" event loop.
As Tornado has decided to leave this to users to set, MkDocs needs
to set it. See https://github.com/tornadoweb/tornado/issues/2608.
"""
if sys.platform.startswith("win") and sys.version_info >= (3, 8):
import asyncio
try:
from asyncio import WindowsSelectorEventLoopPolicy
except ImportError:
pass # Can't assign a policy which doesn't exist.
else:
if not isinstance(asyncio.get_event_loop_policy(), WindowsSelectorEventLoopPolicy):
asyncio.set_event_loop_policy(WindowsSelectorEventLoopPolicy())
def _get_handler(site_dir, StaticFileHandler):
from tornado.template import Loader
class WebHandler(StaticFileHandler):
def write_error(self, status_code, **kwargs):
if status_code in (404, 500):
error_page = '{}.html'.format(status_code)
if isfile(join(site_dir, error_page)):
self.write(Loader(site_dir).load(error_page).generate())
else:
super().write_error(status_code, **kwargs)
return WebHandler
def _livereload(host, port, config, builder, site_dir):
# We are importing here for anyone that has issues with livereload. Even if
# this fails, the --no-livereload alternative should still work.
_init_asyncio_patch()
from livereload import Server
import livereload.handlers
class LiveReloadServer(Server):
def get_web_handlers(self, script):
handlers = super().get_web_handlers(script)
# replace livereload handler
return [(handlers[0][0], _get_handler(site_dir, livereload.handlers.StaticFileHandler), handlers[0][2],)]
server = LiveReloadServer()
# Watch the documentation files, the config file and the theme files.
server.watch(config['docs_dir'], builder)
server.watch(config['config_file_path'], builder)
for d in config['theme'].dirs:
server.watch(d, builder)
# Run `serve` plugin events.
server = config['plugins'].run_event('serve', server, config=config, builder=builder)
server.serve(root=site_dir, host=host, port=port, restart_delay=0)
def _static_server(host, port, site_dir):
# Importing here to separate the code paths from the --livereload
# alternative.
_init_asyncio_patch()
from tornado import ioloop
from tornado import web
application = web.Application([
(r"/(.*)", _get_handler(site_dir, web.StaticFileHandler), {
"path": site_dir,
"default_filename": "index.html"
}),
])
application.listen(port=port, address=host)
log.info('Running at: http://%s:%s/', host, port)
log.info('Hold ctrl+c to quit.')
try:
ioloop.IOLoop.instance().start()
except KeyboardInterrupt:
log.info('Stopping server...')
def serve(config_file=None, dev_addr=None, strict=None, theme=None,
theme_dir=None, livereload='livereload', **kwargs):
"""
Start the MkDocs development server
By default it will serve the documentation on http://localhost:8000/ and
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
# PY2 returns a byte string by default. The Unicode prefix ensures a Unicode
# string is returned. And it makes MkDocs temp dirs easier to identify.
site_dir = tempfile.mkdtemp(prefix='mkdocs_')
def builder():
log.info("Building documentation...")
config = load_config(
config_file=config_file,
dev_addr=dev_addr,
strict=strict,
theme=theme,
theme_dir=theme_dir,
site_dir=site_dir,
**kwargs
)
# Override a few config settings after validation
config['site_url'] = 'http://{}/'.format(config['dev_addr'])
live_server = livereload in ['dirty', 'livereload']
dirty = livereload == 'dirty'
build(config, live_server=live_server, dirty=dirty)
return config
try:
# Perform the initial build
config = builder()
host, port = config['dev_addr']
if livereload in ['livereload', 'dirty']:
_livereload(host, port, config, builder, site_dir)
else:
_static_server(host, port, site_dir)
finally:
shutil.rmtree(site_dir)