mirror of
https://github.com/mkdocs/mkdocs.git
synced 2026-03-27 09:58:31 +07:00
Refactor serve URL handling, pass only the URL to build (#3501)
Co-authored-by: Philipp Temminghoff <philipptemminghoff@googlemail.com>
This commit is contained in:
@@ -22,8 +22,6 @@ from mkdocs.utils import templates
|
||||
if TYPE_CHECKING:
|
||||
from mkdocs.config.defaults import MkDocsConfig
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from mkdocs.livereload import LiveReloadServer
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -247,9 +245,7 @@ def _build_page(
|
||||
config._current_page = None
|
||||
|
||||
|
||||
def build(
|
||||
config: MkDocsConfig, live_server: LiveReloadServer | None = None, dirty: bool = False
|
||||
) -> None:
|
||||
def build(config: MkDocsConfig, *, serve_url: str | None = None, dirty: bool = False) -> None:
|
||||
"""Perform a full site build."""
|
||||
logger = logging.getLogger('mkdocs')
|
||||
|
||||
@@ -259,7 +255,7 @@ def build(
|
||||
if config.strict:
|
||||
logging.getLogger('mkdocs').addHandler(warning_counter)
|
||||
|
||||
inclusion = InclusionLevel.all if live_server else InclusionLevel.is_included
|
||||
inclusion = InclusionLevel.all if serve_url else InclusionLevel.is_included
|
||||
|
||||
try:
|
||||
start = time.monotonic()
|
||||
@@ -280,7 +276,7 @@ def build(
|
||||
" links within your site. This option is designed for site development purposes only."
|
||||
)
|
||||
|
||||
if not live_server: # pragma: no cover
|
||||
if not serve_url: # pragma: no cover
|
||||
log.info(f"Building documentation to directory: {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.")
|
||||
@@ -306,8 +302,8 @@ def build(
|
||||
for file in files.documentation_pages(inclusion=inclusion):
|
||||
log.debug(f"Reading: {file.src_uri}")
|
||||
if file.page is None and file.inclusion.is_not_in_nav():
|
||||
if live_server and file.inclusion.is_excluded():
|
||||
excluded.append(urljoin(live_server.url, file.url))
|
||||
if serve_url and file.inclusion.is_excluded():
|
||||
excluded.append(urljoin(serve_url, file.url))
|
||||
Page(None, file, config)
|
||||
assert file.page is not None
|
||||
_populate_page(file.page, config, files, dirty)
|
||||
|
||||
@@ -9,7 +9,7 @@ from urllib.parse import urlsplit
|
||||
|
||||
from mkdocs.commands.build import build
|
||||
from mkdocs.config import load_config
|
||||
from mkdocs.livereload import LiveReloadServer
|
||||
from mkdocs.livereload import LiveReloadServer, _serve_url
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from mkdocs.config.defaults import MkDocsConfig
|
||||
@@ -37,9 +37,6 @@ def serve(
|
||||
# string is returned. And it makes MkDocs temp dirs easier to identify.
|
||||
site_dir = tempfile.mkdtemp(prefix='mkdocs_')
|
||||
|
||||
def mount_path(config: MkDocsConfig):
|
||||
return urlsplit(config.site_url or '/').path
|
||||
|
||||
def get_config():
|
||||
config = load_config(
|
||||
config_file=config_file,
|
||||
@@ -47,7 +44,6 @@ def serve(
|
||||
**kwargs,
|
||||
)
|
||||
config.watch.extend(watch)
|
||||
config.site_url = f'http://{config.dev_addr}{mount_path(config)}'
|
||||
return config
|
||||
|
||||
is_clean = build_type == 'clean'
|
||||
@@ -56,16 +52,20 @@ def serve(
|
||||
config = get_config()
|
||||
config.plugins.on_startup(command=('build' if is_clean else 'serve'), dirty=is_dirty)
|
||||
|
||||
host, port = config.dev_addr
|
||||
mount_path = urlsplit(config.site_url or '/').path
|
||||
config.site_url = serve_url = _serve_url(host, port, mount_path)
|
||||
|
||||
def builder(config: MkDocsConfig | None = None):
|
||||
log.info("Building documentation...")
|
||||
if config is None:
|
||||
config = get_config()
|
||||
config.site_url = serve_url
|
||||
|
||||
build(config, live_server=None if is_clean else server, dirty=is_dirty)
|
||||
build(config, serve_url=None if is_clean else serve_url, dirty=is_dirty)
|
||||
|
||||
host, port = config.dev_addr
|
||||
server = LiveReloadServer(
|
||||
builder=builder, host=host, port=port, root=site_dir, mount_path=mount_path(config)
|
||||
builder=builder, host=host, port=port, root=site_dir, mount_path=mount_path
|
||||
)
|
||||
|
||||
def error_handler(code) -> bytes | None:
|
||||
|
||||
@@ -81,6 +81,15 @@ class _LoggerAdapter(logging.LoggerAdapter):
|
||||
log = _LoggerAdapter(logging.getLogger(__name__), {})
|
||||
|
||||
|
||||
def _normalize_mount_path(mount_path: str) -> str:
|
||||
"""Ensure the mount path starts and ends with a slash."""
|
||||
return ("/" + mount_path.lstrip("/")).rstrip("/") + "/"
|
||||
|
||||
|
||||
def _serve_url(host: str, port: int, path: str) -> str:
|
||||
return f"http://{host}:{port}{_normalize_mount_path(path)}"
|
||||
|
||||
|
||||
class LiveReloadServer(socketserver.ThreadingMixIn, wsgiref.simple_server.WSGIServer):
|
||||
daemon_threads = True
|
||||
poll_response_timeout = 60
|
||||
@@ -96,16 +105,14 @@ class LiveReloadServer(socketserver.ThreadingMixIn, wsgiref.simple_server.WSGISe
|
||||
shutdown_delay: float = 0.25,
|
||||
) -> None:
|
||||
self.builder = builder
|
||||
self.server_name = host
|
||||
self.server_port = port
|
||||
try:
|
||||
if isinstance(ipaddress.ip_address(host), ipaddress.IPv6Address):
|
||||
self.address_family = socket.AF_INET6
|
||||
except Exception:
|
||||
pass
|
||||
self.root = os.path.abspath(root)
|
||||
self.mount_path = ("/" + mount_path.lstrip("/")).rstrip("/") + "/"
|
||||
self.url = f"http://{self.server_name}:{self.server_port}{self.mount_path}"
|
||||
self.mount_path = _normalize_mount_path(mount_path)
|
||||
self.url = _serve_url(host, port, mount_path)
|
||||
self.build_delay = 0.1
|
||||
self.shutdown_delay = shutdown_delay
|
||||
# To allow custom error pages.
|
||||
|
||||
@@ -16,7 +16,6 @@ import markdown.preprocessors
|
||||
from mkdocs.commands import build
|
||||
from mkdocs.config import base
|
||||
from mkdocs.exceptions import PluginError
|
||||
from mkdocs.livereload import LiveReloadServer
|
||||
from mkdocs.structure.files import File, Files
|
||||
from mkdocs.structure.nav import get_navigation
|
||||
from mkdocs.structure.pages import Page
|
||||
@@ -36,13 +35,6 @@ def build_page(title, path, config, md_src=''):
|
||||
return page, files
|
||||
|
||||
|
||||
def testing_server(root, builder=lambda: None, mount_path="/"):
|
||||
with mock.patch("socket.socket"):
|
||||
return LiveReloadServer(
|
||||
builder, host="localhost", port=123, root=root, mount_path=mount_path
|
||||
)
|
||||
|
||||
|
||||
class BuildTests(PathAssertionMixin, unittest.TestCase):
|
||||
def _get_env_with_null_translations(self, config):
|
||||
env = config.theme.get_env()
|
||||
@@ -596,7 +588,7 @@ class BuildTests(PathAssertionMixin, unittest.TestCase):
|
||||
exclude_docs='ba*.md',
|
||||
)
|
||||
|
||||
with self.subTest(live_server=None):
|
||||
with self.subTest(serve_url=None):
|
||||
expected_logs = '''
|
||||
INFO:Doc file 'test/foo.md' contains a link to 'test/bar.md' which is excluded from the built site.
|
||||
'''
|
||||
@@ -606,8 +598,8 @@ class BuildTests(PathAssertionMixin, unittest.TestCase):
|
||||
self.assertPathNotExists(site_dir, 'test', 'baz.html')
|
||||
self.assertPathNotExists(site_dir, '.zoo.html')
|
||||
|
||||
server = testing_server(site_dir, mount_path='/documentation/')
|
||||
with self.subTest(live_server=server):
|
||||
serve_url = 'http://localhost:123/documentation/'
|
||||
with self.subTest(serve_url=serve_url):
|
||||
expected_logs = '''
|
||||
INFO:Doc file 'test/bar.md' contains a relative link 'nonexistent.md', but the target 'test/nonexistent.md' is not found among documentation files.
|
||||
INFO:Doc file 'test/foo.md' contains a link to 'test/bar.md' which is excluded from the built site.
|
||||
@@ -617,7 +609,7 @@ class BuildTests(PathAssertionMixin, unittest.TestCase):
|
||||
- http://localhost:123/documentation/test/baz.html
|
||||
'''
|
||||
with self._assert_build_logs(expected_logs):
|
||||
build.build(cfg, live_server=server)
|
||||
build.build(cfg, serve_url=serve_url)
|
||||
|
||||
foo_path = Path(site_dir, 'test', 'foo.html')
|
||||
self.assertTrue(foo_path.is_file())
|
||||
@@ -639,13 +631,13 @@ class BuildTests(PathAssertionMixin, unittest.TestCase):
|
||||
def test_conflicting_readme_and_index(self, site_dir, docs_dir):
|
||||
cfg = load_config(docs_dir=docs_dir, site_dir=site_dir, use_directory_urls=False)
|
||||
|
||||
for server in None, testing_server(site_dir):
|
||||
with self.subTest(live_server=server):
|
||||
for serve_url in None, 'http://localhost:123/':
|
||||
with self.subTest(serve_url=serve_url):
|
||||
expected_logs = '''
|
||||
WARNING:Excluding 'foo/README.md' from the site because it conflicts with 'foo/index.md'.
|
||||
'''
|
||||
with self._assert_build_logs(expected_logs):
|
||||
build.build(cfg, live_server=server)
|
||||
build.build(cfg, serve_url=serve_url)
|
||||
|
||||
index_path = Path(site_dir, 'foo', 'index.html')
|
||||
self.assertPathIsFile(index_path)
|
||||
@@ -663,10 +655,10 @@ class BuildTests(PathAssertionMixin, unittest.TestCase):
|
||||
docs_dir=docs_dir, site_dir=site_dir, use_directory_urls=False, exclude_docs='index.md'
|
||||
)
|
||||
|
||||
for server in None, testing_server(site_dir):
|
||||
with self.subTest(live_server=server):
|
||||
for serve_url in None, 'http://localhost:123/':
|
||||
with self.subTest(serve_url=serve_url):
|
||||
with self._assert_build_logs(''):
|
||||
build.build(cfg, live_server=server)
|
||||
build.build(cfg, serve_url=serve_url)
|
||||
|
||||
index_path = Path(site_dir, 'foo', 'index.html')
|
||||
self.assertPathIsFile(index_path)
|
||||
@@ -693,9 +685,9 @@ class BuildTests(PathAssertionMixin, unittest.TestCase):
|
||||
assert f is not None
|
||||
config.nav = Path(f.abs_src_path).read_text().splitlines()
|
||||
|
||||
for server in None, testing_server(site_dir):
|
||||
for serve_url in None, 'http://localhost:123/':
|
||||
for exclude in 'full', 'nav', None:
|
||||
with self.subTest(live_server=server, exclude=exclude):
|
||||
with self.subTest(serve_url=serve_url, exclude=exclude):
|
||||
cfg = load_config(
|
||||
docs_dir=docs_dir,
|
||||
site_dir=site_dir,
|
||||
@@ -711,13 +703,13 @@ class BuildTests(PathAssertionMixin, unittest.TestCase):
|
||||
INFO:The following pages exist in the docs directory, but are not included in the "nav" configuration:
|
||||
- SUMMARY.md
|
||||
'''
|
||||
if exclude == 'full' and server:
|
||||
if exclude == 'full' and serve_url:
|
||||
expected_logs = '''
|
||||
INFO:The following pages are being built only for the preview but will be excluded from `mkdocs build` per `exclude_docs`:
|
||||
- http://localhost:123/SUMMARY.html
|
||||
'''
|
||||
with self._assert_build_logs(expected_logs):
|
||||
build.build(cfg, live_server=server)
|
||||
build.build(cfg, serve_url=serve_url)
|
||||
|
||||
foo_path = Path(site_dir, 'foo.html')
|
||||
self.assertPathIsFile(foo_path)
|
||||
@@ -727,7 +719,7 @@ class BuildTests(PathAssertionMixin, unittest.TestCase):
|
||||
)
|
||||
|
||||
summary_path = Path(site_dir, 'SUMMARY.html')
|
||||
if exclude == 'full' and not server:
|
||||
if exclude == 'full' and not serve_url:
|
||||
self.assertPathNotExists(summary_path)
|
||||
else:
|
||||
self.assertPathExists(summary_path)
|
||||
|
||||
@@ -39,6 +39,8 @@ def testing_server(root, builder=lambda: None, mount_path="/"):
|
||||
mount_path=mount_path,
|
||||
polling_interval=0.2,
|
||||
)
|
||||
server.server_name = "localhost"
|
||||
server.server_port = 0
|
||||
server.setup_environ()
|
||||
server.observer.start()
|
||||
thread = threading.Thread(target=server._build_loop, daemon=True)
|
||||
|
||||
Reference in New Issue
Block a user