'+ title + '
' + summary +'
diff --git a/build.py b/build.py index 71e91692c..236f8d6c9 100644 --- a/build.py +++ b/build.py @@ -5,9 +5,11 @@ from time import sleep from shutil import rmtree -from subprocess import run, Popen +from subprocess import run, call, Popen, PIPE -import psutil +import errno +import time +import unicodedata from tf.core.helpers import console @@ -28,6 +30,7 @@ ), ) +URL = "https://annotation.github.io/text-fabric/" AN_BASE = os.path.expanduser("~/github/annotation") TUT_BASE = f"{AN_BASE}/tutorials" TF_BASE = f"{AN_BASE}/text-fabric" @@ -36,6 +39,10 @@ PACKAGE = "text-fabric" SCRIPT = "/Library/Frameworks/Python.framework/Versions/3.7/bin/text-fabric" +SRC = "site" +REMOTE = "origin" +BRANCH = "gh-pages" + currentVersion = None newVersion = None @@ -59,9 +66,9 @@ --help help : print help and exit -adocs : serve apidocs locally -bdocs : build apidocs docs : serve docs locally +pdocs : build docs +sdocs : ship docs clean : clean local develop build l : local develop build i : local non-develop build @@ -93,9 +100,9 @@ def readArgs(): if arg not in { "a", "t", - "adocs", - "bdocs", "docs", + "pdocs", + "sdocs", "clean", "l", "lp", @@ -226,28 +233,181 @@ def commitTut(msg): os.chdir(f"{TF_BASE}") -def shipDocs(): - codestats() - apidocs() - run(["mkdocs", "gh-deploy"]) - - -def serveApidocs(): - # proc = Popen(["pdoc3", "--http", ":", "tf"]) - proc = Popen( - [ - "pdoc3", - "--force", - "--html", - "--output-dir", - "docs", - "--template-dir", - "docs/templates", - "--http", - ":", - "tf", - ] - ) +if sys.version_info[0] == 3: + + def enc(text): + if isinstance(text, bytes): + return text + return text.encode() + + def dec(text): + if isinstance(text, bytes): + return text.decode("utf-8") + return text + + def write(pipe, data): + try: + pipe.stdin.write(data) + except OSError as e: + if e.errno != errno.EPIPE: + raise + + +else: + + def enc(text): + if isinstance(text, unicode): # noqa: F821 + return text.encode("utf-8") + return text + + def dec(text): + if isinstance(text, unicode): # noqa: F821 + return text + return text.decode("utf-8") + + def write(pipe, data): + pipe.stdin.write(data) + + +# COPIED FROM MKDOCS AND MODIFIED + + +def normalize_path(path): + # Fix unicode pathnames on OS X + # See: https://stackoverflow.com/a/5582439/44289 + if sys.platform == "darwin": + return unicodedata.normalize("NFKC", dec(path)) + return path + + +def try_rebase(remote, branch): + cmd = ["git", "rev-list", "--max-count=1", "{}/{}".format(remote, branch)] + p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE) + (rev, _) = p.communicate() + if p.wait() != 0: + return True + cmd = ["git", "update-ref", "refs/heads/%s" % branch, dec(rev.strip())] + if call(cmd) != 0: + return False + return True + + +def get_config(key): + p = Popen(["git", "config", key], stdin=PIPE, stdout=PIPE) + (value, _) = p.communicate() + return value.decode("utf-8").strip() + + +def get_prev_commit(branch): + cmd = ["git", "rev-list", "--max-count=1", branch, "--"] + p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE) + (rev, _) = p.communicate() + if p.wait() != 0: + return None + return rev.decode("utf-8").strip() + + +def mk_when(timestamp=None): + if timestamp is None: + timestamp = int(time.time()) + currtz = "%+05d" % (-1 * time.timezone / 36) # / 3600 * 100 + return "{} {}".format(timestamp, currtz) + + +def start_commit(pipe, branch, message): + uname = dec(get_config("user.name")) + email = dec(get_config("user.email")) + write(pipe, enc("commit refs/heads/%s\n" % branch)) + write(pipe, enc("committer {} <{}> {}\n".format(uname, email, mk_when()))) + write(pipe, enc("data %d\n%s\n" % (len(message), message))) + head = get_prev_commit(branch) + if head: + write(pipe, enc("from %s\n" % head)) + write(pipe, enc("deleteall\n")) + + +def add_file(pipe, srcpath, tgtpath): + with open(srcpath, "rb") as handle: + if os.access(srcpath, os.X_OK): + write(pipe, enc("M 100755 inline %s\n" % tgtpath)) + else: + write(pipe, enc("M 100644 inline %s\n" % tgtpath)) + data = handle.read() + write(pipe, enc("data %d\n" % len(data))) + write(pipe, enc(data)) + write(pipe, enc("\n")) + + +def add_nojekyll(pipe): + write(pipe, enc("M 100644 inline .nojekyll\n")) + write(pipe, enc("data 0\n")) + write(pipe, enc("\n")) + + +def gitpath(fname): + norm = os.path.normpath(fname) + return "/".join(norm.split(os.path.sep)) + + +def ghp_import(): + if not try_rebase(REMOTE, BRANCH): + print("Failed to rebase %s branch.", BRANCH) + + cmd = ["git", "fast-import", "--date-format=raw", "--quiet"] + kwargs = {"stdin": PIPE} + if sys.version_info >= (3, 2, 0): + kwargs["universal_newlines"] = False + pipe = Popen(cmd, **kwargs) + start_commit(pipe, BRANCH, "docs update") + for path, _, fnames in os.walk(SRC): + for fn in fnames: + fpath = os.path.join(path, fn) + fpath = normalize_path(fpath) + gpath = gitpath(os.path.relpath(fpath, start=SRC)) + add_file(pipe, fpath, gpath) + add_nojekyll(pipe) + write(pipe, enc("\n")) + pipe.stdin.close() + if pipe.wait() != 0: + sys.stdout.write(enc("Failed to process commit.\n")) + + cmd = ["git", "push", REMOTE, BRANCH] + proc = Popen(cmd, stdout=PIPE, stderr=PIPE) + (out, err) = proc.communicate() + result = proc.wait() == 0 + + return result, dec(err) + + +def gh_deploy(): + (result, error) = ghp_import() + if not result: + print("Failed to deploy to GitHub with error: \n%s", error) + raise SystemExit(1) + else: + print("Your documentation should shortly be available at: " + URL) + + +# END COPIED FROM MKDOCS AND MODIFIED + + +PDOC3 = [ + "pdoc3", + "--force", + "--html", + "--output-dir", + "site", + "--template-dir", + "docs/templates", +] +PDOC3STR = " ".join(PDOC3) + + +def pdoc3serve(): + """Build the docs into site and serve them. + """ + + proc = Popen([*PDOC3, "--http", ":", "tf"]) sleep(1) run("open http://localhost:8080/tf", shell=True) try: @@ -257,52 +417,13 @@ def serveApidocs(): proc.terminate() -def serveDocs(): - codestats() - apidocs() - killProcesses() - proc = Popen(["mkdocs", "serve"]) - sleep(3) - run("open http://127.0.0.1:8000", shell=True) - try: - proc.wait() - except KeyboardInterrupt: - pass - proc.terminate() - +def pdoc3(): + """Build the docs into site. + """ -def killProcesses(): - myself = os.getpid() - for proc in psutil.process_iter(attrs=["pid", "name"]): - pid = proc.info["pid"] - if pid == myself: - continue - if filterProcess(proc): - try: - proc.terminate() - console(f"mkdocs [{pid}] terminated") - except psutil.NoSuchProcess: - console(f"mkdocs [{pid}] already terminated") - - -def filterProcess(proc): - procName = proc.info["name"] - commandName = "" if procName is None else procName.lower() - found = False - if commandName.endswith("python"): - parts = proc.cmdline() - if len(parts) >= 3: - if parts[1].endswith("mkdocs") and parts[2] == "serve": - found = True - if parts[1] == "build.py" and parts[2] == "docs": - found = True - return found - - -def apidocs(): cmdLines = [ "rm -rf site", - "pdoc3 --force --html --output-dir site --template-dir docs/templates tf", + f"{PDOC3STR} tf", "mv site/tf/* site", "rmdir site/tf", "cp -r docs/images site", @@ -313,35 +434,12 @@ def apidocs(): run(cmdLine, shell=True) -def codestats(): - xd = ( - ".pytest_cache,__pycache__,node_modules,.tmp,.git,_temp," - ".ipynb_checkpoints,images,fonts,favicons,compiled" - ) - xdtf = xd + ",applib,convert,compose,core,search,server,writing" - xdtest = xd + ",tf" - rfFmt = "docs/Code/Stats{}.md" - cmdLine = ( - "cloc" - " --no-autogen" - " --exclude_dir={}" - " --exclude-list-file={}" - f" --report-file={rfFmt}" - " --md" - " {}" - ) - nex = "cloc_exclude.lst" - run(cmdLine.format(xd, nex, "", ". ../app-*/code"), shell=True) - run(cmdLine.format(xdtf, nex, "Toplevel", "tf"), shell=True) - run(cmdLine.format(xd, nex, "Applib", "tf/applib"), shell=True) - run(cmdLine.format(xd, nex, "Apps", "../app-*/code"), shell=True) - run(cmdLine.format(xd, nex, "Compose", "tf/compose"), shell=True) - run(cmdLine.format(xd, nex, "Convert", "tf/convert"), shell=True) - run(cmdLine.format(xd, nex, "Core", "tf/core"), shell=True) - run(cmdLine.format(xd, nex, "Search", "tf/search"), shell=True) - run(cmdLine.format(xd, nex, "Server", "tf/server"), shell=True) - run(cmdLine.format(xd, nex, "Writing", "tf/writing"), shell=True) - run(cmdLine.format(xdtest, nex, "Test", "test/generic"), shell=True) +def shipDocs(): + """Build the docs into site and ship them. + """ + + pdoc3() + gh_deploy() def tfbrowse(dataset, remaining): @@ -390,12 +488,12 @@ def main(): tfbrowse(msg, remaining) elif task == "t": tftest(msg, remaining) - elif task == "adocs": - serveApidocs() - elif task == "bdocs": - apidocs() elif task == "docs": - serveDocs() + pdoc3serve() + elif task == "pdocs": + pdoc3() + elif task == "sdocs": + shipDocs() elif task == "clean": clean() elif task == "l": diff --git a/dist/text-fabric-8.1.1.tar.gz b/dist/text-fabric-8.1.2.tar.gz similarity index 50% rename from dist/text-fabric-8.1.1.tar.gz rename to dist/text-fabric-8.1.2.tar.gz index 6bab7273a..d79acb14f 100644 Binary files a/dist/text-fabric-8.1.1.tar.gz and b/dist/text-fabric-8.1.2.tar.gz differ diff --git a/docs/About/releases.md b/docs/About/releases.md index b556ac113..789579e8d 100644 --- a/docs/About/releases.md +++ b/docs/About/releases.md @@ -38,7 +38,16 @@ text-fabric appName:hot ### 8.1 -#### 8.1.0 +#### 8.1.2 + +2020-05-22 + +Thoroughly reorganized docs. +All available documentation has now moved into docstrings. +The formatted docstrings form the online documentation as well. +See `tf`. + +#### 8.1.0, 8.1.1 2020-05-14 @@ -224,7 +233,7 @@ Also: a bug fix to the walker conversion, again: thanks Ernst for spotting it. 2020-02-13 Fixed a few bugs in the `cv.stop()` function in the -[walker conversion](https://annotation.github.io/text-fabric/Create/Convert/) +walker conversion, see `tf.convert.walker`. Thanks to Ernst Boogert for spotting them. @@ -777,7 +786,7 @@ Further tweaks in layout of `plain()`. 2019-03-13 API addition for `E` (edges): -[`E.feature.b()`](https://annotation.github.io/text-fabric/Api/Features/#edge-features) +`E.feature.b()` gives the symmetrical closure of the edges under `feature`. That means it combines the results of `E.feature.f()` and `E.feature.t()`. diff --git a/docs/About/use.md b/docs/About/use.md index 3de3814ec..5776c4273 100644 --- a/docs/About/use.md +++ b/docs/About/use.md @@ -1,7 +1,7 @@ # Usage Below `xxx` should be replaced by the name of an official -[TF-app](https://annotation.github.io/text-fabric/About/Corpora/), +TF-app, `tf.about.corpora`, or a path to an app on your system, or a path to TF dataset on your system. You can work with the TF-browser or with the TF-API. diff --git a/docs/templates/logo.mako b/docs/templates/logo.mako index b81d91d25..40b76f141 100644 --- a/docs/templates/logo.mako +++ b/docs/templates/logo.mako @@ -1,3 +1 @@ - -
diff --git a/mkdocs/__init__.py b/mkdocs/__init__.py deleted file mode 100644 index 4260d182c..000000000 --- a/mkdocs/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env python - - -# For acceptable version formats, see https://www.python.org/dev/peps/pep-0440/ -__version__ = '1.1' diff --git a/mkdocs/__main__.py b/mkdocs/__main__.py deleted file mode 100644 index a1cc793c0..000000000 --- a/mkdocs/__main__.py +++ /dev/null @@ -1,199 +0,0 @@ -#!/usr/bin/env python - -import os -import sys -import logging -import click - -# TODO: Remove this check at some point in the future. -# (also remove flake8's 'ignore E402' comments below) -if sys.version_info[0] < 3: # pragma: no cover - raise ImportError('A recent version of Python 3 is required.') - -from mkdocs import __version__ # noqa: E402 -from mkdocs import utils # noqa: E402 -from mkdocs import exceptions # noqa: E402 -from mkdocs import config # noqa: E402 -from mkdocs.commands import build, gh_deploy, new, serve # noqa: E402 - -log = logging.getLogger(__name__) - -# Disable the warning that Click displays (as of Click version 5.0) when users -# use unicode_literals in Python 2. -# See http://click.pocoo.org/dev/python3/#unicode-literals for more details. -click.disable_unicode_literals_warning = True - - -class State: - ''' Maintain logging level.''' - - def __init__(self, log_name='mkdocs', level=logging.INFO): - self.logger = logging.getLogger(log_name) - self.logger.propagate = False - stream = logging.StreamHandler() - formatter = logging.Formatter("%(levelname)-7s - %(message)s ") - stream.setFormatter(formatter) - self.logger.addHandler(stream) - - self.logger.setLevel(level) - - -pass_state = click.make_pass_decorator(State, ensure=True) - -clean_help = "Remove old files from the site_dir before building (the default)." -config_help = "Provide a specific MkDocs config" -dev_addr_help = ("IP address and port to serve documentation locally (default: " - "localhost:8000)") -strict_help = ("Enable strict mode. This will cause MkDocs to abort the build " - "on any warnings.") -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." -use_directory_urls_help = "Use directory URLs when building pages (the default)." -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 committing to the " - "Github Pages remote branch. Commit {sha} and MkDocs {version} are available as expansions") -remote_branch_help = ("The remote branch to commit to for Github Pages. This " - "overrides the value specified in config") -remote_name_help = ("The remote name to commit to for Github Pages. This " - "overrides the value specified in config") -force_help = "Force the push to the repository." -ignore_version_help = "Ignore check that build is not being deployed with an older version of MkDocs." - - -def add_options(opts): - def inner(f): - for i in reversed(opts): - f = i(f) - return f - - return inner - - -def verbose_option(f): - def callback(ctx, param, value): - state = ctx.ensure_object(State) - if value: - state.logger.setLevel(logging.DEBUG) - return click.option('-v', '--verbose', - is_flag=True, - expose_value=False, - help='Enable verbose output', - callback=callback)(f) - - -def quiet_option(f): - def callback(ctx, param, value): - state = ctx.ensure_object(State) - if value: - state.logger.setLevel(logging.ERROR) - return click.option('-q', '--quiet', - is_flag=True, - expose_value=False, - help='Silence warnings', - callback=callback)(f) - - -common_options = add_options([quiet_option, verbose_option]) -common_config_options = add_options([ - click.option('-f', '--config-file', type=click.File('rb'), help=config_help), - # Don't override config value if user did not specify --strict flag - # Conveniently, load_config drops None values - click.option('-s', '--strict', is_flag=True, default=None, 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), - # As with --strict, set the default to None so that this doesn't incorrectly - # override the config file - click.option('--use-directory-urls/--no-directory-urls', is_flag=True, default=None, help=use_directory_urls_help) -]) - -pgk_dir = os.path.dirname(os.path.abspath(__file__)) - - -@click.group(context_settings={'help_option_names': ['-h', '--help']}) -@click.version_option( - '{} from {} (Python {})'.format(__version__, pgk_dir, sys.version[:3]), - '-V', '--version') -@common_options -def cli(): - """ - MkDocs - Project documentation with Markdown. - """ - - -@cli.command(name="serve") -@click.option('-a', '--dev-addr', help=dev_addr_help, metavar='' + summary +'
No results found
"); - } -} - -function doSearch () { - var query = document.getElementById('mkdocs-search-query').value; - if (query.length > 2) { - if (!window.Worker) { - displayResults(search(query)); - } else { - searchWorker.postMessage({query: query}); - } - } else { - // Clear results for short queries - displayResults([]); - } -} - -function initSearch () { - var search_input = document.getElementById('mkdocs-search-query'); - if (search_input) { - search_input.addEventListener("keyup", doSearch); - } - var term = getSearchTermFromLocation(); - if (term) { - search_input.value = term; - doSearch(); - } -} - -function onWorkerMessage (e) { - if (e.data.allowSearch) { - initSearch(); - } else if (e.data.results) { - var results = e.data.results; - displayResults(results); - } -} - -if (!window.Worker) { - console.log('Web Worker API not supported'); - // load index in main thread - $.getScript(joinUrl(base_url, "search/worker.js")).done(function () { - console.log('Loaded worker'); - init(); - window.postMessage = function (msg) { - onWorkerMessage({data: msg}); - }; - }).fail(function (jqxhr, settings, exception) { - console.error('Could not load worker.js'); - }); -} else { - // Wrap search in a web worker - var searchWorker = new Worker(joinUrl(base_url, "search/worker.js")); - searchWorker.postMessage({init: true}); - searchWorker.onmessage = onWorkerMessage; -} diff --git a/mkdocs/contrib/search/templates/search/worker.js b/mkdocs/contrib/search/templates/search/worker.js deleted file mode 100644 index a3ccc07f2..000000000 --- a/mkdocs/contrib/search/templates/search/worker.js +++ /dev/null @@ -1,128 +0,0 @@ -var base_path = 'function' === typeof importScripts ? '.' : '/search/'; -var allowSearch = false; -var index; -var documents = {}; -var lang = ['en']; -var data; - -function getScript(script, callback) { - console.log('Loading script: ' + script); - $.getScript(base_path + script).done(function () { - callback(); - }).fail(function (jqxhr, settings, exception) { - console.log('Error: ' + exception); - }); -} - -function getScriptsInOrder(scripts, callback) { - if (scripts.length === 0) { - callback(); - return; - } - getScript(scripts[0], function() { - getScriptsInOrder(scripts.slice(1), callback); - }); -} - -function loadScripts(urls, callback) { - if( 'function' === typeof importScripts ) { - importScripts.apply(null, urls); - callback(); - } else { - getScriptsInOrder(urls, callback); - } -} - -function onJSONLoaded () { - data = JSON.parse(this.responseText); - var scriptsToLoad = ['lunr.js']; - if (data.config && data.config.lang && data.config.lang.length) { - lang = data.config.lang; - } - if (lang.length > 1 || lang[0] !== "en") { - scriptsToLoad.push('lunr.stemmer.support.js'); - if (lang.length > 1) { - scriptsToLoad.push('lunr.multi.js'); - } - for (var i=0; i < lang.length; i++) { - if (lang[i] != 'en') { - scriptsToLoad.push(['lunr', lang[i], 'js'].join('.')); - } - } - } - loadScripts(scriptsToLoad, onScriptsLoaded); -} - -function onScriptsLoaded () { - console.log('All search scripts loaded, building Lunr index...'); - if (data.config && data.config.separator && data.config.separator.length) { - lunr.tokenizer.separator = new RegExp(data.config.separator); - } - if (data.index) { - index = lunr.Index.load(data.index); - data.docs.forEach(function (doc) { - documents[doc.location] = doc; - }); - console.log('Lunr pre-built index loaded, search ready'); - } else { - index = lunr(function () { - if (lang.length === 1 && lang[0] !== "en" && lunr[lang[0]]) { - this.use(lunr[lang[0]]); - } else if (lang.length > 1) { - this.use(lunr.multiLanguage.apply(null, lang)); // spread operator not supported in all browsers: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator#Browser_compatibility - } - this.field('title'); - this.field('text'); - this.ref('location'); - - for (var i=0; i < data.docs.length; i++) { - var doc = data.docs[i]; - this.add(doc); - documents[doc.location] = doc; - } - }); - console.log('Lunr index built, search ready'); - } - allowSearch = true; - postMessage({allowSearch: allowSearch}); -} - -function init () { - var oReq = new XMLHttpRequest(); - oReq.addEventListener("load", onJSONLoaded); - var index_path = base_path + '/search_index.json'; - if( 'function' === typeof importScripts ){ - index_path = 'search_index.json'; - } - oReq.open("GET", index_path); - oReq.send(); -} - -function search (query) { - if (!allowSearch) { - console.error('Assets for search still loading'); - return; - } - - var resultDocuments = []; - var results = index.search(query); - for (var i=0; i < results.length; i++){ - var result = results[i]; - doc = documents[result.ref]; - doc.summary = doc.text.substring(0, 200); - resultDocuments.push(doc); - } - return resultDocuments; -} - -if( 'function' === typeof importScripts ) { - onmessage = function (e) { - if (e.data.init) { - init(); - } else if (e.data.query) { - postMessage({ results: search(e.data.query) }); - } else { - console.error("Worker - Unrecognized message: " + e); - } - }; -} diff --git a/mkdocs/exceptions.py b/mkdocs/exceptions.py deleted file mode 100644 index f968df356..000000000 --- a/mkdocs/exceptions.py +++ /dev/null @@ -1,9 +0,0 @@ -from click import ClickException - - -class MkDocsException(ClickException): - """Base exceptions for all MkDocs Exceptions""" - - -class ConfigurationError(MkDocsException): - """Error in configuration""" diff --git a/mkdocs/plugins.py b/mkdocs/plugins.py deleted file mode 100644 index 02e5c4ec6..000000000 --- a/mkdocs/plugins.py +++ /dev/null @@ -1,100 +0,0 @@ -""" -Implements the plugin API for MkDocs. - -""" - - -import pkg_resources -import logging -from collections import OrderedDict - -from mkdocs.config.base import Config - - -log = logging.getLogger('mkdocs.plugins') - - -EVENTS = ( - 'config', 'pre_build', 'files', 'nav', 'env', 'pre_template', 'template_context', - 'post_template', 'pre_page', 'page_read_source', 'page_markdown', - 'page_content', 'page_context', 'post_page', 'post_build', 'serve' -) - - -def get_plugins(): - """ Return a dict of all installed Plugins by name. """ - - plugins = pkg_resources.iter_entry_points(group='mkdocs.plugins') - - return {plugin.name: plugin for plugin in plugins} - - -class BasePlugin: - """ - Plugin base class. - - All plugins should subclass this class. - """ - - config_scheme = () - config = {} - - def load_config(self, options, config_file_path=None): - """ Load config from a dict of options. Returns a tuple of (errors, warnings).""" - - self.config = Config(schema=self.config_scheme, config_file_path=config_file_path) - self.config.load_dict(options) - - return self.config.validate() - - -class PluginCollection(OrderedDict): - """ - A collection of plugins. - - In addition to being a dict of Plugin instances, each event method is registered - upon being added. All registered methods for a given event can then be run in order - by calling `run_event`. - """ - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.events = {x: [] for x in EVENTS} - - def _register_event(self, event_name, method): - """ Register a method for an event. """ - self.events[event_name].append(method) - - def __setitem__(self, key, value, **kwargs): - if not isinstance(value, BasePlugin): - raise TypeError( - '{0}.{1} only accepts values which are instances of {3}.{4} ' - 'sublcasses'.format(self.__module__, self.__name__, - BasePlugin.__module__, BasePlugin.__name__)) - super().__setitem__(key, value, **kwargs) - # Register all of the event methods defined for this Plugin. - for event_name in (x for x in dir(value) if x.startswith('on_')): - method = getattr(value, event_name) - if callable(method): - self._register_event(event_name[3:], method) - - def run_event(self, name, item=None, **kwargs): - """ - Run all registered methods of an event. - - `item` is the object to be modified or replaced and returned by the event method. - If it isn't given the event method creates a new object to be returned. - All other keywords are variables for context, but would not generally - be modified by the event method. - """ - - pass_item = item is not None - for method in self.events[name]: - if pass_item: - result = method(item, **kwargs) - else: - result = method(**kwargs) - # keep item if method returned `None` - if result is not None: - item = result - return item diff --git a/mkdocs/structure/__init__.py b/mkdocs/structure/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/mkdocs/structure/files.py b/mkdocs/structure/files.py deleted file mode 100644 index a693fadb8..000000000 --- a/mkdocs/structure/files.py +++ /dev/null @@ -1,274 +0,0 @@ -import fnmatch -import os -import logging -from functools import cmp_to_key -from urllib.parse import quote as urlquote - -from mkdocs import utils - - -log = logging.getLogger(__name__) -log.addFilter(utils.warning_filter) - - -class Files: - """ A collection of File objects. """ - def __init__(self, files): - self._files = files - self.src_paths = {file.src_path: file for file in files} - - def __iter__(self): - return iter(self._files) - - def __len__(self): - return len(self._files) - - def __contains__(self, path): - return path in self.src_paths - - def get_file_from_path(self, path): - """ Return a File instance with File.src_path equal to path. """ - return self.src_paths.get(os.path.normpath(path)) - - def append(self, file): - """ Append file to Files collection. """ - self._files.append(file) - self.src_paths[file.src_path] = file - - def copy_static_files(self, dirty=False): - """ Copy static files from source to destination. """ - for file in self: - if not file.is_documentation_page(): - file.copy_file(dirty) - - def documentation_pages(self): - """ Return iterable of all Markdown page file objects. """ - return [file for file in self if file.is_documentation_page()] - - def static_pages(self): - """ Return iterable of all static page file objects. """ - return [file for file in self if file.is_static_page()] - - def media_files(self): - """ Return iterable of all file objects which are not documentation or static pages. """ - return [file for file in self if file.is_media_file()] - - def javascript_files(self): - """ Return iterable of all javascript file objects. """ - return [file for file in self if file.is_javascript()] - - def css_files(self): - """ Return iterable of all CSS file objects. """ - return [file for file in self if file.is_css()] - - def add_files_from_theme(self, env, config): - """ Retrieve static files from Jinja environment and add to collection. """ - def filter(name): - # '.*' filters dot files/dirs at root level whereas '*/.*' filters nested levels - patterns = ['.*', '*/.*', '*.py', '*.pyc', '*.html', '*readme*', 'mkdocs_theme.yml'] - patterns.extend('*{}'.format(x) for x in utils.markdown_extensions) - patterns.extend(config['theme'].static_templates) - for pattern in patterns: - if fnmatch.fnmatch(name.lower(), pattern): - return False - return True - for path in env.list_templates(filter_func=filter): - # Theme files do not override docs_dir files - path = os.path.normpath(path) - if path not in self: - for dir in config['theme'].dirs: - # Find the first theme dir which contains path - if os.path.isfile(os.path.join(dir, path)): - self.append(File(path, dir, config['site_dir'], config['use_directory_urls'])) - break - - -class File: - """ - A MkDocs File object. - - Points to the source and destination locations of a file. - - The `path` argument must be a path that exists relative to `src_dir`. - - The `src_dir` and `dest_dir` must be absolute paths on the local file system. - - The `use_directory_urls` argument controls how destination paths are generated. If `False`, a Markdown file is - mapped to an HTML file of the same name (the file extension is changed to `.html`). If True, a Markdown file is - mapped to an HTML index file (`index.html`) nested in a directory using the "name" of the file in `path`. The - `use_directory_urls` argument has no effect on non-Markdown files. - - File objects have the following properties, which are Unicode strings: - - File.src_path - The pure path of the source file relative to the source directory. - - File.abs_src_path - The absolute concrete path of the source file. - - File.dest_path - The pure path of the destination file relative to the destination directory. - - File.abs_dest_path - The absolute concrete path of the destination file. - - File.url - The url of the destination file relative to the destination directory as a string. - """ - def __init__(self, path, src_dir, dest_dir, use_directory_urls): - self.page = None - self.src_path = os.path.normpath(path) - self.abs_src_path = os.path.normpath(os.path.join(src_dir, self.src_path)) - self.name = self._get_stem() - self.dest_path = self._get_dest_path(use_directory_urls) - self.abs_dest_path = os.path.normpath(os.path.join(dest_dir, self.dest_path)) - self.url = self._get_url(use_directory_urls) - - def __eq__(self, other): - - def sub_dict(d): - return {key: value for key, value in d.items() if key in ['src_path', 'abs_src_path', 'url']} - - return (isinstance(other, self.__class__) and sub_dict(self.__dict__) == sub_dict(other.__dict__)) - - def __ne__(self, other): - return not self.__eq__(other) - - def _get_stem(self): - """ Return the name of the file without it's extension. """ - filename = os.path.basename(self.src_path) - stem, ext = os.path.splitext(filename) - return 'index' if stem in ('index', 'README') else stem - - def _get_dest_path(self, use_directory_urls): - """ Return destination path based on source path. """ - if self.is_documentation_page(): - if use_directory_urls: - parent, filename = os.path.split(self.src_path) - if self.name == 'index': - # index.md or README.md => index.html - return os.path.join(parent, 'index.html') - else: - # foo.md => foo/index.html - return os.path.join(parent, self.name, 'index.html') - else: - # foo.md => foo.html - root, ext = os.path.splitext(self.src_path) - return root + '.html' - return self.src_path - - def _get_url(self, use_directory_urls): - """ Return url based in destination path. """ - url = self.dest_path.replace(os.path.sep, '/') - dirname, filename = os.path.split(url) - if use_directory_urls and filename == 'index.html': - if dirname == '': - url = '.' - else: - url = dirname + '/' - return urlquote(url) - - def url_relative_to(self, other): - """ Return url for file relative to other file. """ - return utils.get_relative_url(self.url, other.url if isinstance(other, File) else other) - - def copy_file(self, dirty=False): - """ Copy source file to destination, ensuring parent directories exist. """ - if dirty and not self.is_modified(): - log.debug("Skip copying unmodified file: '{}'".format(self.src_path)) - else: - log.debug("Copying media file: '{}'".format(self.src_path)) - utils.copy_file(self.abs_src_path, self.abs_dest_path) - - def is_modified(self): - if os.path.isfile(self.abs_dest_path): - return os.path.getmtime(self.abs_dest_path) < os.path.getmtime(self.abs_src_path) - return True - - def is_documentation_page(self): - """ Return True if file is a Markdown page. """ - return os.path.splitext(self.src_path)[1] in utils.markdown_extensions - - def is_static_page(self): - """ Return True if file is a static page (html, xml, json). """ - return os.path.splitext(self.src_path)[1] in ( - '.html', - '.htm', - '.xml', - '.json', - ) - - def is_media_file(self): - """ Return True if file is not a documentation or static page. """ - return not (self.is_documentation_page() or self.is_static_page()) - - def is_javascript(self): - """ Return True if file is a JavaScript file. """ - return os.path.splitext(self.src_path)[1] in ( - '.js', - '.javascript', - ) - - def is_css(self): - """ Return True if file is a CSS file. """ - return os.path.splitext(self.src_path)[1] in ( - '.css', - ) - - -def get_files(config): - """ Walk the `docs_dir` and return a Files collection. """ - files = [] - exclude = ['.*', '/templates'] - - for source_dir, dirnames, filenames in os.walk(config['docs_dir'], followlinks=True): - relative_dir = os.path.relpath(source_dir, config['docs_dir']) - - for dirname in list(dirnames): - path = os.path.normpath(os.path.join(relative_dir, dirname)) - # Skip any excluded directories - if _filter_paths(basename=dirname, path=path, is_dir=True, exclude=exclude): - dirnames.remove(dirname) - dirnames.sort() - - for filename in _sort_files(filenames): - path = os.path.normpath(os.path.join(relative_dir, filename)) - # Skip any excluded files - if _filter_paths(basename=filename, path=path, is_dir=False, exclude=exclude): - continue - # Skip README.md if an index file also exists in dir - if filename.lower() == 'readme.md' and 'index.md' in filenames: - log.warning("Both index.md and readme.md found. Skipping readme.md from {}".format(source_dir)) - continue - files.append(File(path, config['docs_dir'], config['site_dir'], config['use_directory_urls'])) - - return Files(files) - - -def _sort_files(filenames): - """ Always sort `index` or `README` as first filename in list. """ - - def compare(x, y): - if x == y: - return 0 - if os.path.splitext(y)[0] in ['index', 'README']: - return 1 - if os.path.splitext(x)[0] in ['index', 'README'] or x < y: - return -1 - return 1 - - return sorted(filenames, key=cmp_to_key(compare)) - - -def _filter_paths(basename, path, is_dir, exclude): - """ .gitignore style file filtering. """ - for item in exclude: - # Items ending in '/' apply only to directories. - if item.endswith('/') and not is_dir: - continue - # Items starting with '/' apply to the whole path. - # In any other cases just the basename is used. - match = path if item.startswith('/') else basename - if fnmatch.fnmatch(match, item.strip('/')): - return True - return False diff --git a/mkdocs/structure/nav.py b/mkdocs/structure/nav.py deleted file mode 100644 index 077ec19ab..000000000 --- a/mkdocs/structure/nav.py +++ /dev/null @@ -1,192 +0,0 @@ -import logging -from urllib.parse import urlparse - -from mkdocs.structure.pages import Page -from mkdocs.utils import nest_paths, warning_filter - -log = logging.getLogger(__name__) -log.addFilter(warning_filter) - - -class Navigation: - def __init__(self, items, pages): - self.items = items # Nested List with full navigation of Sections, Pages, and Links. - self.pages = pages # Flat List of subset of Pages in nav, in order. - - self.homepage = None - for page in pages: - if page.is_homepage: - self.homepage = page - break - - def __repr__(self): - return '\n'.join([item._indent_print() for item in self]) - - def __iter__(self): - return iter(self.items) - - def __len__(self): - return len(self.items) - - -class Section: - def __init__(self, title, children): - self.title = title - self.children = children - - self.parent = None - self.active = False - - self.is_section = True - self.is_page = False - self.is_link = False - - def __repr__(self): - return "Section(title='{}')".format(self.title) - - def _get_active(self): - """ Return active status of section. """ - return self.__active - - def _set_active(self, value): - """ Set active status of section and ancestors. """ - self.__active = bool(value) - if self.parent is not None: - self.parent.active = bool(value) - - active = property(_get_active, _set_active) - - @property - def ancestors(self): - if self.parent is None: - return [] - return [self.parent] + self.parent.ancestors - - def _indent_print(self, depth=0): - ret = ['{}{}'.format(' ' * depth, repr(self))] - for item in self.children: - ret.append(item._indent_print(depth + 1)) - return '\n'.join(ret) - - -class Link: - def __init__(self, title, url): - self.title = title - self.url = url - self.parent = None - - # These should never change but are included for consistency with sections and pages. - self.children = None - self.active = False - self.is_section = False - self.is_page = False - self.is_link = True - - def __repr__(self): - title = "'{}'".format(self.title) if (self.title is not None) else '[blank]' - return "Link(title={}, url='{}')".format(title, self.url) - - @property - def ancestors(self): - if self.parent is None: - return [] - return [self.parent] + self.parent.ancestors - - def _indent_print(self, depth=0): - return '{}{}'.format(' ' * depth, repr(self)) - - -def get_navigation(files, config): - """ Build site navigation from config and files.""" - nav_config = config['nav'] or nest_paths(f.src_path for f in files.documentation_pages()) - items = _data_to_navigation(nav_config, files, config) - if not isinstance(items, list): - items = [items] - - # Get only the pages from the navigation, ignoring any sections and links. - pages = _get_by_type(items, Page) - - # Include next, previous and parent links. - _add_previous_and_next_links(pages) - _add_parent_links(items) - - missing_from_config = [file for file in files.documentation_pages() if file.page is None] - if missing_from_config: - log.info( - 'The following pages exist in the docs directory, but are not ' - 'included in the "nav" configuration:\n - {}'.format( - '\n - '.join([file.src_path for file in missing_from_config])) - ) - # Any documentation files not found in the nav should still have an associated page, so we - # create them here. The Page object will automatically be assigned to `file.page` during - # its creation (and this is the only way in which these page objects are accessable). - for file in missing_from_config: - Page(None, file, config) - - links = _get_by_type(items, Link) - for link in links: - scheme, netloc, path, params, query, fragment = urlparse(link.url) - if scheme or netloc: - log.debug( - "An external link to '{}' is included in " - "the 'nav' configuration.".format(link.url) - ) - elif link.url.startswith('/'): - log.debug( - "An absolute path to '{}' is included in the 'nav' configuration, " - "which presumably points to an external resource.".format(link.url) - ) - else: - msg = ( - "A relative path to '{}' is included in the 'nav' configuration, " - "which is not found in the documentation files".format(link.url) - ) - log.warning(msg) - return Navigation(items, pages) - - -def _data_to_navigation(data, files, config): - if isinstance(data, dict): - return [ - _data_to_navigation((key, value), files, config) - if isinstance(value, str) else - Section(title=key, children=_data_to_navigation(value, files, config)) - for key, value in data.items() - ] - elif isinstance(data, list): - return [ - _data_to_navigation(item, files, config)[0] - if isinstance(item, dict) and len(item) == 1 else - _data_to_navigation(item, files, config) - for item in data - ] - title, path = data if isinstance(data, tuple) else (None, data) - file = files.get_file_from_path(path) - if file: - return Page(title, file, config) - return Link(title, path) - - -def _get_by_type(nav, T): - ret = [] - for item in nav: - if isinstance(item, T): - ret.append(item) - elif item.children: - ret.extend(_get_by_type(item.children, T)) - return ret - - -def _add_parent_links(nav): - for item in nav: - if item.is_section: - for child in item.children: - child.parent = item - _add_parent_links(item.children) - - -def _add_previous_and_next_links(pages): - bookended = [None] + pages + [None] - zipped = zip(bookended[:-2], bookended[1:-1], bookended[2:]) - for page0, page1, page2 in zipped: - page1.previous_page, page1.next_page = page0, page2 diff --git a/mkdocs/structure/pages.py b/mkdocs/structure/pages.py deleted file mode 100644 index afc35a1ae..000000000 --- a/mkdocs/structure/pages.py +++ /dev/null @@ -1,252 +0,0 @@ -import os -import datetime -import logging -from urllib.parse import urlparse, urlunparse, urljoin -from urllib.parse import unquote as urlunquote - -import markdown -from markdown.extensions import Extension -from markdown.treeprocessors import Treeprocessor -from markdown.util import AMP_SUBSTITUTE - -from mkdocs.structure.toc import get_toc -from mkdocs.utils import meta, get_markdown_title, warning_filter - -log = logging.getLogger(__name__) -log.addFilter(warning_filter) - - -class Page: - def __init__(self, title, file, config): - file.page = self - self.file = file - self.title = title - - # Navigation attributes - self.parent = None - self.children = None - self.previous_page = None - self.next_page = None - self.active = False - - self.is_section = False - self.is_page = True - self.is_link = False - - # Support SOURCE_DATE_EPOCH environment variable for "reproducible" builds. - # See https://reproducible-builds.org/specs/source-date-epoch/ - if 'SOURCE_DATE_EPOCH' in os.environ: - self.update_date = datetime.datetime.utcfromtimestamp( - int(os.environ['SOURCE_DATE_EPOCH']) - ).strftime("%Y-%m-%d") - else: - self.update_date = datetime.datetime.now().strftime("%Y-%m-%d") - - self._set_canonical_url(config.get('site_url', None)) - self._set_edit_url(config.get('repo_url', None), config.get('edit_uri', None)) - - # Placeholders to be filled in later in the build process. - self.markdown = None - self.content = None - self.toc = [] - self.meta = {} - - def __eq__(self, other): - - def sub_dict(d): - return {key: value for key, value in d.items() if key in ['title', 'file']} - - return (isinstance(other, self.__class__) and sub_dict(self.__dict__) == sub_dict(other.__dict__)) - - def __ne__(self, other): - return not self.__eq__(other) - - def __repr__(self): - title = "'{}'".format(self.title) if (self.title is not None) else '[blank]' - return "Page(title={}, url='{}')".format(title, self.abs_url or self.file.url) - - def _indent_print(self, depth=0): - return '{}{}'.format(' ' * depth, repr(self)) - - def _get_active(self): - """ Return active status of page. """ - return self.__active - - def _set_active(self, value): - """ Set active status of page and ancestors. """ - self.__active = bool(value) - if self.parent is not None: - self.parent.active = bool(value) - - active = property(_get_active, _set_active) - - @property - def is_index(self): - return self.file.name == 'index' - - @property - def is_top_level(self): - return self.parent is None - - @property - def is_homepage(self): - return self.is_top_level and self.is_index and self.file.url == '.' - - @property - def url(self): - return '' if self.file.url == '.' else self.file.url - - @property - def ancestors(self): - if self.parent is None: - return [] - return [self.parent] + self.parent.ancestors - - def _set_canonical_url(self, base): - if base: - if not base.endswith('/'): - base += '/' - self.canonical_url = urljoin(base, self.url) - self.abs_url = urlparse(self.canonical_url).path - else: - self.canonical_url = None - self.abs_url = None - - def _set_edit_url(self, repo_url, edit_uri): - if repo_url and edit_uri: - src_path = self.file.src_path.replace('\\', '/') - self.edit_url = urljoin(repo_url, edit_uri + src_path) - else: - self.edit_url = None - - def read_source(self, config): - source = config['plugins'].run_event( - 'page_read_source', page=self, config=config - ) - if source is None: - try: - with open(self.file.abs_src_path, 'r', encoding='utf-8-sig', errors='strict') as f: - source = f.read() - except OSError: - log.error('File not found: {}'.format(self.file.src_path)) - raise - except ValueError: - log.error('Encoding error reading file: {}'.format(self.file.src_path)) - raise - - self.markdown, self.meta = meta.get_data(source) - self._set_title() - - def _set_title(self): - """ - Set the title for a Markdown document. - - Check these in order and use the first that returns a valid title: - - value provided on init (passed in from config) - - value of metadata 'title' - - content of the first H1 in Markdown content - - convert filename to title - """ - if self.title is not None: - return - - if 'title' in self.meta: - self.title = self.meta['title'] - return - - title = get_markdown_title(self.markdown) - - if title is None: - if self.is_homepage: - title = 'Home' - else: - title = self.file.name.replace('-', ' ').replace('_', ' ') - # Capitalize if the filename was all lowercase, otherwise leave it as-is. - if title.lower() == title: - title = title.capitalize() - - self.title = title - - def render(self, config, files): - """ - Convert the Markdown source file to HTML as per the config. - """ - - extensions = [ - _RelativePathExtension(self.file, files) - ] + config['markdown_extensions'] - - md = markdown.Markdown( - extensions=extensions, - extension_configs=config['mdx_configs'] or {} - ) - self.content = md.convert(self.markdown) - self.toc = get_toc(getattr(md, 'toc_tokens', [])) - - -class _RelativePathTreeprocessor(Treeprocessor): - def __init__(self, file, files): - self.file = file - self.files = files - - def run(self, root): - """ - Update urls on anchors and images to make them relative - - Iterates through the full document tree looking for specific - tags and then makes them relative based on the site navigation - """ - for element in root.iter(): - if element.tag == 'a': - key = 'href' - elif element.tag == 'img': - key = 'src' - else: - continue - - url = element.get(key) - new_url = self.path_to_url(url) - element.set(key, new_url) - - return root - - def path_to_url(self, url): - scheme, netloc, path, params, query, fragment = urlparse(url) - - if (scheme or netloc or not path or url.startswith('/') - or AMP_SUBSTITUTE in url or '.' not in os.path.split(path)[-1]): - # Ignore URLs unless they are a relative link to a source file. - # AMP_SUBSTITUTE is used internally by Markdown only for email. - # No '.' in the last part of a path indicates path does not point to a file. - return url - - # Determine the filepath of the target. - target_path = os.path.join(os.path.dirname(self.file.src_path), urlunquote(path)) - target_path = os.path.normpath(target_path).lstrip(os.sep) - - # Validate that the target exists in files collection. - if target_path not in self.files: - log.warning( - "Documentation file '{}' contains a link to '{}' which is not found " - "in the documentation files.".format(self.file.src_path, target_path) - ) - return url - target_file = self.files.get_file_from_path(target_path) - path = target_file.url_relative_to(self.file) - components = (scheme, netloc, path, params, query, fragment) - return urlunparse(components) - - -class _RelativePathExtension(Extension): - """ - The Extension class is what we pass to markdown, it then - registers the Treeprocessor. - """ - - def __init__(self, file, files): - self.file = file - self.files = files - - def extendMarkdown(self, md): - relpath = _RelativePathTreeprocessor(self.file, self.files) - md.treeprocessors.register(relpath, "relpath", 0) diff --git a/mkdocs/structure/toc.py b/mkdocs/structure/toc.py deleted file mode 100644 index 143292a93..000000000 --- a/mkdocs/structure/toc.py +++ /dev/null @@ -1,62 +0,0 @@ -""" -Deals with generating the per-page table of contents. - -For the sake of simplicity we use the Python-Markdown `toc` extension to -generate a list of dicts for each toc item, and then store it as AnchorLinks to -maintain compatibility with older versions of MkDocs. -""" - - -def get_toc(toc_tokens): - toc = [_parse_toc_token(i) for i in toc_tokens] - # For the table of contents, always mark the first element as active - if len(toc): - toc[0].active = True - return TableOfContents(toc) - - -class TableOfContents: - """ - Represents the table of contents for a given page. - """ - def __init__(self, items): - self.items = items - - def __iter__(self): - return iter(self.items) - - def __len__(self): - return len(self.items) - - def __str__(self): - return ''.join([str(item) for item in self]) - - -class AnchorLink: - """ - A single entry in the table of contents. - """ - def __init__(self, title, id, level): - self.title, self.id, self.level = title, id, level - self.children = [] - - @property - def url(self): - return '#' + self.id - - def __str__(self): - return self.indent_print() - - def indent_print(self, depth=0): - indent = ' ' * depth - ret = '{}{} - {}\n'.format(indent, self.title, self.url) - for item in self.children: - ret += item.indent_print(depth + 1) - return ret - - -def _parse_toc_token(token): - anchor = AnchorLink(token['name'], token['id'], token['level']) - for i in token['children']: - anchor.children.append(_parse_toc_token(i)) - return anchor diff --git a/mkdocs/templates/sitemap.xml b/mkdocs/templates/sitemap.xml deleted file mode 100644 index fee00eff6..000000000 --- a/mkdocs/templates/sitemap.xml +++ /dev/null @@ -1,22 +0,0 @@ -{%- macro nav_item(item) -%} - {%- if item.children -%} - {%- for child in item.children -%} - {{ nav_item(child) }} - {%- endfor -%} - {%- else %} - {%- if not item.is_link -%} -page content
') - - @tempdir(files={'testing.html': 'page content
'}) - def test_populate_page_dirty_modified(self, site_dir): - cfg = load_config(site_dir=site_dir) - file = File('testing.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) - page = Page('Foo', file, cfg) - build._populate_page(page, cfg, Files([file]), dirty=True) - self.assertTrue(page.markdown.startswith('# Welcome to MkDocs')) - self.assertTrue(page.content.startswith('page content
'}) - def test_populate_page_dirty_not_modified(self, site_dir, docs_dir): - cfg = load_config(docs_dir=docs_dir, site_dir=site_dir) - file = File('index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) - page = Page('Foo', file, cfg) - build._populate_page(page, cfg, Files([file]), dirty=True) - # Content is empty as file read was skipped - self.assertEqual(page.markdown, None) - self.assertEqual(page.content, None) - - @tempdir(files={'index.md': 'new page content'}) - @mock.patch('mkdocs.structure.pages.open', side_effect=OSError('Error message.')) - def test_populate_page_read_error(self, docs_dir, mock_open): - cfg = load_config(docs_dir=docs_dir) - file = File('missing.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) - page = Page('Foo', file, cfg) - with self.assertLogs('mkdocs', level='ERROR') as cm: - self.assertRaises(OSError, build._populate_page, page, cfg, Files([file])) - self.assertEqual( - cm.output, [ - 'ERROR:mkdocs.structure.pages:File not found: missing.md', - "ERROR:mkdocs.commands.build:Error reading page 'missing.md': Error message." - ] - ) - self.assert_mock_called_once(mock_open) - - # Test build._build_page - - @tempdir() - def test_build_page(self, site_dir): - cfg = load_config(site_dir=site_dir, nav=['index.md'], plugins=[]) - files = Files([File('index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls'])]) - nav = get_navigation(files, cfg) - page = files.documentation_pages()[0].page - # Fake populate page - page.title = 'Title' - page.markdown = 'page content' - page.content = 'page content
' - build._build_page(page, cfg, files, nav, cfg['theme'].get_env()) - self.assertPathIsFile(site_dir, 'index.html') - - # TODO: fix this. It seems that jinja2 chokes on the mock object. Not sure how to resolve. - # @tempdir() - # @mock.patch('jinja2.environment.Template') - # def test_build_page_empty(self, site_dir, mock_template): - # mock_template.render = mock.Mock(return_value='') - # cfg = load_config(site_dir=site_dir, nav=['index.md'], plugins=[]) - # files = Files([File('index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls'])]) - # nav = get_navigation(files, cfg) - # page = files.documentation_pages()[0].page - # # Fake populate page - # page.title = '' - # page.markdown = '' - # page.content = '' - # with self.assertLogs('mkdocs', level='INFO') as cm: - # build._build_page(page, cfg, files, nav, cfg['theme'].get_env()) - # self.assertEqual( - # cm.output, - # ["INFO:mkdocs.commands.build:Page skipped: 'index.md'. Generated empty output."] - # ) - # self.assert_mock_called_once(mock_template.render) - # self.assertPathNotFile(site_dir, 'index.html') - - @tempdir(files={'index.md': 'page content'}) - @tempdir(files={'index.html': 'page content
'}) - @mock.patch('mkdocs.utils.write_file') - def test_build_page_dirty_modified(self, site_dir, docs_dir, mock_write_file): - cfg = load_config(docs_dir=docs_dir, site_dir=site_dir, nav=['index.md'], plugins=[]) - files = Files([File('index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls'])]) - nav = get_navigation(files, cfg) - page = files.documentation_pages()[0].page - # Fake populate page - page.title = 'Title' - page.markdown = 'new page content' - page.content = 'new page content
' - build._build_page(page, cfg, files, nav, cfg['theme'].get_env(), dirty=True) - mock_write_file.assert_not_called() - - @tempdir(files={'testing.html': 'page content
'}) - @mock.patch('mkdocs.utils.write_file') - def test_build_page_dirty_not_modified(self, site_dir, mock_write_file): - cfg = load_config(site_dir=site_dir, nav=['testing.md'], plugins=[]) - files = Files([File('testing.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls'])]) - nav = get_navigation(files, cfg) - page = files.documentation_pages()[0].page - # Fake populate page - page.title = 'Title' - page.markdown = 'page content' - page.content = 'page content
' - build._build_page(page, cfg, files, nav, cfg['theme'].get_env(), dirty=True) - self.assert_mock_called_once(mock_write_file) - - @tempdir() - def test_build_page_custom_template(self, site_dir): - cfg = load_config(site_dir=site_dir, nav=['index.md'], plugins=[]) - files = Files([File('index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls'])]) - nav = get_navigation(files, cfg) - page = files.documentation_pages()[0].page - # Fake populate page - page.title = 'Title' - page.meta = {'template': '404.html'} - page.markdown = 'page content' - page.content = 'page content
' - build._build_page(page, cfg, files, nav, cfg['theme'].get_env()) - self.assertPathIsFile(site_dir, 'index.html') - - @tempdir() - @mock.patch('mkdocs.utils.write_file', side_effect=OSError('Error message.')) - def test_build_page_error(self, site_dir, mock_write_file): - cfg = load_config(site_dir=site_dir, nav=['index.md'], plugins=[]) - files = Files([File('index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls'])]) - nav = get_navigation(files, cfg) - page = files.documentation_pages()[0].page - # Fake populate page - page.title = 'Title' - page.markdown = 'page content' - page.content = 'page content
' - with self.assertLogs('mkdocs', level='ERROR') as cm: - self.assertRaises(OSError, build._build_page, page, cfg, files, nav, cfg['theme'].get_env()) - self.assertEqual( - cm.output, - ["ERROR:mkdocs.commands.build:Error building page 'index.md': Error message."] - ) - self.assert_mock_called_once(mock_write_file) - - # Test build.build - - @tempdir(files={ - 'index.md': 'page content', - 'empty.md': '', - 'img.jpg': '', - 'static.html': 'content', - '.hidden': 'content', - '.git/hidden': 'content' - }) - @tempdir() - def test_copying_media(self, site_dir, docs_dir): - cfg = load_config(docs_dir=docs_dir, site_dir=site_dir) - build.build(cfg) - - # Verify that only non-empty md file (coverted to html), static HTML file and image are copied. - self.assertPathIsFile(site_dir, 'index.html') - self.assertPathIsFile(site_dir, 'img.jpg') - self.assertPathIsFile(site_dir, 'static.html') - self.assertPathNotExists(site_dir, 'empty.md') - self.assertPathNotExists(site_dir, '.hidden') - self.assertPathNotExists(site_dir, '.git/hidden') - - @tempdir(files={'index.md': 'page content'}) - @tempdir() - def test_copy_theme_files(self, site_dir, docs_dir): - cfg = load_config(docs_dir=docs_dir, site_dir=site_dir) - build.build(cfg) - - # Verify only theme media are copied, not templates or Python files. - self.assertPathIsFile(site_dir, 'index.html') - self.assertPathIsFile(site_dir, '404.html') - self.assertPathIsDir(site_dir, 'js') - self.assertPathIsDir(site_dir, 'css') - self.assertPathIsDir(site_dir, 'img') - self.assertPathIsDir(site_dir, 'fonts') - self.assertPathNotExists(site_dir, '__init__.py') - self.assertPathNotExists(site_dir, '__init__.pyc') - self.assertPathNotExists(site_dir, 'base.html') - self.assertPathNotExists(site_dir, 'content.html') - self.assertPathNotExists(site_dir, 'main.html') - - # Test build.site_directory_contains_stale_files - - @tempdir(files=['index.html']) - def test_site_dir_contains_stale_files(self, site_dir): - self.assertTrue(build.site_directory_contains_stale_files(site_dir)) - - @tempdir() - def test_not_site_dir_contains_stale_files(self, site_dir): - self.assertFalse(build.site_directory_contains_stale_files(site_dir)) diff --git a/mkdocs/tests/cli_tests.py b/mkdocs/tests/cli_tests.py deleted file mode 100644 index b701682d6..000000000 --- a/mkdocs/tests/cli_tests.py +++ /dev/null @@ -1,707 +0,0 @@ -#!/usr/bin/env python - -import unittest -from unittest import mock -import logging -import io - -from click.testing import CliRunner - -from mkdocs import __main__ as cli - - -class CLITests(unittest.TestCase): - - def setUp(self): - self.runner = CliRunner() - - @mock.patch('mkdocs.commands.serve.serve', autospec=True) - def test_serve_default(self, mock_serve): - - result = self.runner.invoke( - cli.cli, ["serve"], catch_exceptions=False) - - self.assertEqual(result.exit_code, 0) - mock_serve.assert_called_once_with( - dev_addr=None, - livereload='livereload', - config_file=None, - strict=None, - theme=None, - theme_dir=None, - use_directory_urls=None - ) - - @mock.patch('mkdocs.commands.serve.serve', autospec=True) - def test_serve_config_file(self, mock_serve): - - result = self.runner.invoke( - cli.cli, ["serve", "--config-file", "mkdocs.yml"], catch_exceptions=False) - - self.assertEqual(result.exit_code, 0) - self.assertEqual(mock_serve.call_count, 1) - args, kwargs = mock_serve.call_args - self.assertTrue('config_file' in kwargs) - self.assertIsInstance(kwargs['config_file'], io.BufferedReader) - self.assertEqual(kwargs['config_file'].name, 'mkdocs.yml') - - @mock.patch('mkdocs.commands.serve.serve', autospec=True) - def test_serve_dev_addr(self, mock_serve): - - result = self.runner.invoke( - cli.cli, ["serve", '--dev-addr', '0.0.0.0:80'], catch_exceptions=False) - - self.assertEqual(result.exit_code, 0) - mock_serve.assert_called_once_with( - dev_addr='0.0.0.0:80', - livereload='livereload', - config_file=None, - strict=None, - theme=None, - theme_dir=None, - use_directory_urls=None - ) - - @mock.patch('mkdocs.commands.serve.serve', autospec=True) - def test_serve_strict(self, mock_serve): - - result = self.runner.invoke( - cli.cli, ["serve", '--strict'], catch_exceptions=False) - - self.assertEqual(result.exit_code, 0) - mock_serve.assert_called_once_with( - dev_addr=None, - livereload='livereload', - config_file=None, - strict=True, - theme=None, - theme_dir=None, - use_directory_urls=None - ) - - @mock.patch('mkdocs.commands.serve.serve', autospec=True) - def test_serve_theme(self, mock_serve): - - result = self.runner.invoke( - cli.cli, ["serve", '--theme', 'readthedocs'], catch_exceptions=False) - - self.assertEqual(result.exit_code, 0) - mock_serve.assert_called_once_with( - dev_addr=None, - livereload='livereload', - config_file=None, - strict=None, - theme='readthedocs', - theme_dir=None, - use_directory_urls=None - ) - - @mock.patch('mkdocs.commands.serve.serve', autospec=True) - def test_serve_theme_dir(self, mock_serve): - - result = self.runner.invoke( - cli.cli, ["serve", '--theme-dir', 'custom'], catch_exceptions=False) - - self.assertEqual(result.exit_code, 0) - mock_serve.assert_called_once_with( - dev_addr=None, - livereload='livereload', - config_file=None, - strict=None, - theme=None, - theme_dir='custom', - use_directory_urls=None - ) - - @mock.patch('mkdocs.commands.serve.serve', autospec=True) - def test_serve_use_directory_urls(self, mock_serve): - - result = self.runner.invoke( - cli.cli, ["serve", '--use-directory-urls'], catch_exceptions=False) - - self.assertEqual(result.exit_code, 0) - mock_serve.assert_called_once_with( - dev_addr=None, - livereload='livereload', - config_file=None, - strict=None, - theme=None, - theme_dir=None, - use_directory_urls=True - ) - - @mock.patch('mkdocs.commands.serve.serve', autospec=True) - def test_serve_no_directory_urls(self, mock_serve): - - result = self.runner.invoke( - cli.cli, ["serve", '--no-directory-urls'], catch_exceptions=False) - - self.assertEqual(result.exit_code, 0) - mock_serve.assert_called_once_with( - dev_addr=None, - livereload='livereload', - config_file=None, - strict=None, - theme=None, - theme_dir=None, - use_directory_urls=False - ) - - @mock.patch('mkdocs.commands.serve.serve', autospec=True) - def test_serve_livereload(self, mock_serve): - - result = self.runner.invoke( - cli.cli, ["serve", '--livereload'], catch_exceptions=False) - - self.assertEqual(result.exit_code, 0) - mock_serve.assert_called_once_with( - dev_addr=None, - livereload='livereload', - config_file=None, - strict=None, - theme=None, - theme_dir=None, - use_directory_urls=None - ) - - @mock.patch('mkdocs.commands.serve.serve', autospec=True) - def test_serve_no_livereload(self, mock_serve): - - result = self.runner.invoke( - cli.cli, ["serve", '--no-livereload'], catch_exceptions=False) - - self.assertEqual(result.exit_code, 0) - mock_serve.assert_called_once_with( - dev_addr=None, - livereload='no-livereload', - config_file=None, - strict=None, - theme=None, - theme_dir=None, - use_directory_urls=None - ) - - @mock.patch('mkdocs.commands.serve.serve', autospec=True) - def test_serve_dirtyreload(self, mock_serve): - - result = self.runner.invoke( - cli.cli, ["serve", '--dirtyreload'], catch_exceptions=False) - - self.assertEqual(result.exit_code, 0) - mock_serve.assert_called_once_with( - dev_addr=None, - livereload='dirty', - config_file=None, - strict=None, - theme=None, - theme_dir=None, - use_directory_urls=None - ) - - @mock.patch('mkdocs.config.load_config', autospec=True) - @mock.patch('mkdocs.commands.build.build', autospec=True) - def test_build_defaults(self, mock_build, mock_load_config): - - result = self.runner.invoke( - cli.cli, ['build'], catch_exceptions=False) - - self.assertEqual(result.exit_code, 0) - self.assertEqual(mock_build.call_count, 1) - args, kwargs = mock_build.call_args - self.assertTrue('dirty' in kwargs) - self.assertFalse(kwargs['dirty']) - mock_load_config.assert_called_once_with( - config_file=None, - strict=None, - theme=None, - theme_dir=None, - use_directory_urls=None, - site_dir=None - ) - logger = logging.getLogger('mkdocs') - self.assertEqual(logger.level, logging.INFO) - - @mock.patch('mkdocs.config.load_config', autospec=True) - @mock.patch('mkdocs.commands.build.build', autospec=True) - def test_build_clean(self, mock_build, mock_load_config): - - result = self.runner.invoke( - cli.cli, ['build', '--clean'], catch_exceptions=False) - - self.assertEqual(result.exit_code, 0) - self.assertEqual(mock_build.call_count, 1) - args, kwargs = mock_build.call_args - self.assertTrue('dirty' in kwargs) - self.assertFalse(kwargs['dirty']) - - @mock.patch('mkdocs.config.load_config', autospec=True) - @mock.patch('mkdocs.commands.build.build', autospec=True) - def test_build_dirty(self, mock_build, mock_load_config): - - result = self.runner.invoke( - cli.cli, ['build', '--dirty'], catch_exceptions=False) - - self.assertEqual(result.exit_code, 0) - self.assertEqual(mock_build.call_count, 1) - args, kwargs = mock_build.call_args - self.assertTrue('dirty' in kwargs) - self.assertTrue(kwargs['dirty']) - - @mock.patch('mkdocs.config.load_config', autospec=True) - @mock.patch('mkdocs.commands.build.build', autospec=True) - def test_build_config_file(self, mock_build, mock_load_config): - - result = self.runner.invoke( - cli.cli, ['build', '--config-file', 'mkdocs.yml'], catch_exceptions=False) - - self.assertEqual(result.exit_code, 0) - self.assertEqual(mock_build.call_count, 1) - self.assertEqual(mock_load_config.call_count, 1) - args, kwargs = mock_load_config.call_args - self.assertTrue('config_file' in kwargs) - self.assertIsInstance(kwargs['config_file'], io.BufferedReader) - self.assertEqual(kwargs['config_file'].name, 'mkdocs.yml') - - @mock.patch('mkdocs.config.load_config', autospec=True) - @mock.patch('mkdocs.commands.build.build', autospec=True) - def test_build_strict(self, mock_build, mock_load_config): - - result = self.runner.invoke( - cli.cli, ['build', '--strict'], catch_exceptions=False) - - self.assertEqual(result.exit_code, 0) - self.assertEqual(mock_build.call_count, 1) - mock_load_config.assert_called_once_with( - config_file=None, - strict=True, - theme=None, - theme_dir=None, - use_directory_urls=None, - site_dir=None - ) - - @mock.patch('mkdocs.config.load_config', autospec=True) - @mock.patch('mkdocs.commands.build.build', autospec=True) - def test_build_theme(self, mock_build, mock_load_config): - - result = self.runner.invoke( - cli.cli, ['build', '--theme', 'readthedocs'], catch_exceptions=False) - - self.assertEqual(result.exit_code, 0) - self.assertEqual(mock_build.call_count, 1) - mock_load_config.assert_called_once_with( - config_file=None, - strict=None, - theme='readthedocs', - theme_dir=None, - use_directory_urls=None, - site_dir=None - ) - - @mock.patch('mkdocs.config.load_config', autospec=True) - @mock.patch('mkdocs.commands.build.build', autospec=True) - def test_build_theme_dir(self, mock_build, mock_load_config): - - result = self.runner.invoke( - cli.cli, ['build', '--theme-dir', 'custom'], catch_exceptions=False) - - self.assertEqual(result.exit_code, 0) - self.assertEqual(mock_build.call_count, 1) - mock_load_config.assert_called_once_with( - config_file=None, - strict=None, - theme=None, - theme_dir='custom', - use_directory_urls=None, - site_dir=None - ) - - @mock.patch('mkdocs.config.load_config', autospec=True) - @mock.patch('mkdocs.commands.build.build', autospec=True) - def test_build_use_directory_urls(self, mock_build, mock_load_config): - - result = self.runner.invoke( - cli.cli, ['build', '--use-directory-urls'], catch_exceptions=False) - - self.assertEqual(result.exit_code, 0) - self.assertEqual(mock_build.call_count, 1) - mock_load_config.assert_called_once_with( - config_file=None, - strict=None, - theme=None, - theme_dir=None, - use_directory_urls=True, - site_dir=None - ) - - @mock.patch('mkdocs.config.load_config', autospec=True) - @mock.patch('mkdocs.commands.build.build', autospec=True) - def test_build_no_directory_urls(self, mock_build, mock_load_config): - - result = self.runner.invoke( - cli.cli, ['build', '--no-directory-urls'], catch_exceptions=False) - - self.assertEqual(result.exit_code, 0) - self.assertEqual(mock_build.call_count, 1) - mock_load_config.assert_called_once_with( - config_file=None, - strict=None, - theme=None, - theme_dir=None, - use_directory_urls=False, - site_dir=None - ) - - @mock.patch('mkdocs.config.load_config', autospec=True) - @mock.patch('mkdocs.commands.build.build', autospec=True) - def test_build_site_dir(self, mock_build, mock_load_config): - - result = self.runner.invoke( - cli.cli, ['build', '--site-dir', 'custom'], catch_exceptions=False) - - self.assertEqual(result.exit_code, 0) - self.assertEqual(mock_build.call_count, 1) - mock_load_config.assert_called_once_with( - config_file=None, - strict=None, - theme=None, - theme_dir=None, - use_directory_urls=None, - site_dir='custom', - ) - - @mock.patch('mkdocs.config.load_config', autospec=True) - @mock.patch('mkdocs.commands.build.build', autospec=True) - def test_build_verbose(self, mock_build, mock_load_config): - - result = self.runner.invoke( - cli.cli, ['build', '--verbose'], catch_exceptions=False) - - self.assertEqual(result.exit_code, 0) - self.assertEqual(mock_build.call_count, 1) - logger = logging.getLogger('mkdocs') - self.assertEqual(logger.level, logging.DEBUG) - - @mock.patch('mkdocs.config.load_config', autospec=True) - @mock.patch('mkdocs.commands.build.build', autospec=True) - def test_build_quiet(self, mock_build, mock_load_config): - - result = self.runner.invoke( - cli.cli, ['build', '--quiet'], catch_exceptions=False) - - self.assertEqual(result.exit_code, 0) - self.assertEqual(mock_build.call_count, 1) - logger = logging.getLogger('mkdocs') - self.assertEqual(logger.level, logging.ERROR) - - @mock.patch('mkdocs.commands.new.new', autospec=True) - def test_new(self, mock_new): - - result = self.runner.invoke( - cli.cli, ["new", "project"], catch_exceptions=False) - - self.assertEqual(result.exit_code, 0) - mock_new.assert_called_once_with('project') - - @mock.patch('mkdocs.config.load_config', autospec=True) - @mock.patch('mkdocs.commands.build.build', autospec=True) - @mock.patch('mkdocs.commands.gh_deploy.gh_deploy', autospec=True) - def test_gh_deploy_defaults(self, mock_gh_deploy, mock_build, mock_load_config): - - result = self.runner.invoke( - cli.cli, ['gh-deploy'], catch_exceptions=False) - - self.assertEqual(result.exit_code, 0) - self.assertEqual(mock_gh_deploy.call_count, 1) - g_args, g_kwargs = mock_gh_deploy.call_args - self.assertTrue('message' in g_kwargs) - self.assertEqual(g_kwargs['message'], None) - self.assertTrue('force' in g_kwargs) - self.assertEqual(g_kwargs['force'], False) - self.assertTrue('ignore_version' in g_kwargs) - self.assertEqual(g_kwargs['ignore_version'], False) - self.assertEqual(mock_build.call_count, 1) - b_args, b_kwargs = mock_build.call_args - self.assertTrue('dirty' in b_kwargs) - self.assertFalse(b_kwargs['dirty']) - mock_load_config.assert_called_once_with( - remote_branch=None, - remote_name=None, - config_file=None, - strict=None, - theme=None, - theme_dir=None, - use_directory_urls=None, - site_dir=None - ) - - @mock.patch('mkdocs.config.load_config', autospec=True) - @mock.patch('mkdocs.commands.build.build', autospec=True) - @mock.patch('mkdocs.commands.gh_deploy.gh_deploy', autospec=True) - def test_gh_deploy_clean(self, mock_gh_deploy, mock_build, mock_load_config): - - result = self.runner.invoke( - cli.cli, ['gh-deploy', '--clean'], catch_exceptions=False) - - self.assertEqual(result.exit_code, 0) - self.assertEqual(mock_gh_deploy.call_count, 1) - self.assertEqual(mock_build.call_count, 1) - args, kwargs = mock_build.call_args - self.assertTrue('dirty' in kwargs) - self.assertFalse(kwargs['dirty']) - - @mock.patch('mkdocs.config.load_config', autospec=True) - @mock.patch('mkdocs.commands.build.build', autospec=True) - @mock.patch('mkdocs.commands.gh_deploy.gh_deploy', autospec=True) - def test_gh_deploy_dirty(self, mock_gh_deploy, mock_build, mock_load_config): - - result = self.runner.invoke( - cli.cli, ['gh-deploy', '--dirty'], catch_exceptions=False) - - self.assertEqual(result.exit_code, 0) - self.assertEqual(mock_gh_deploy.call_count, 1) - self.assertEqual(mock_build.call_count, 1) - args, kwargs = mock_build.call_args - self.assertTrue('dirty' in kwargs) - self.assertTrue(kwargs['dirty']) - - @mock.patch('mkdocs.config.load_config', autospec=True) - @mock.patch('mkdocs.commands.build.build', autospec=True) - @mock.patch('mkdocs.commands.gh_deploy.gh_deploy', autospec=True) - def test_gh_deploy_config_file(self, mock_gh_deploy, mock_build, mock_load_config): - - result = self.runner.invoke( - cli.cli, ['gh-deploy', '--config-file', 'mkdocs.yml'], catch_exceptions=False) - - self.assertEqual(result.exit_code, 0) - self.assertEqual(mock_gh_deploy.call_count, 1) - self.assertEqual(mock_build.call_count, 1) - self.assertEqual(mock_load_config.call_count, 1) - args, kwargs = mock_load_config.call_args - self.assertTrue('config_file' in kwargs) - self.assertIsInstance(kwargs['config_file'], io.BufferedReader) - self.assertEqual(kwargs['config_file'].name, 'mkdocs.yml') - - @mock.patch('mkdocs.config.load_config', autospec=True) - @mock.patch('mkdocs.commands.build.build', autospec=True) - @mock.patch('mkdocs.commands.gh_deploy.gh_deploy', autospec=True) - def test_gh_deploy_message(self, mock_gh_deploy, mock_build, mock_load_config): - - result = self.runner.invoke( - cli.cli, ['gh-deploy', '--message', 'A commit message'], catch_exceptions=False) - - self.assertEqual(result.exit_code, 0) - self.assertEqual(mock_gh_deploy.call_count, 1) - g_args, g_kwargs = mock_gh_deploy.call_args - self.assertTrue('message' in g_kwargs) - self.assertEqual(g_kwargs['message'], 'A commit message') - self.assertEqual(mock_build.call_count, 1) - self.assertEqual(mock_load_config.call_count, 1) - - @mock.patch('mkdocs.config.load_config', autospec=True) - @mock.patch('mkdocs.commands.build.build', autospec=True) - @mock.patch('mkdocs.commands.gh_deploy.gh_deploy', autospec=True) - def test_gh_deploy_remote_branch(self, mock_gh_deploy, mock_build, mock_load_config): - - result = self.runner.invoke( - cli.cli, ['gh-deploy', '--remote-branch', 'foo'], catch_exceptions=False) - - self.assertEqual(result.exit_code, 0) - self.assertEqual(mock_gh_deploy.call_count, 1) - self.assertEqual(mock_build.call_count, 1) - mock_load_config.assert_called_once_with( - remote_branch='foo', - remote_name=None, - config_file=None, - strict=None, - theme=None, - theme_dir=None, - use_directory_urls=None, - site_dir=None - ) - - @mock.patch('mkdocs.config.load_config', autospec=True) - @mock.patch('mkdocs.commands.build.build', autospec=True) - @mock.patch('mkdocs.commands.gh_deploy.gh_deploy', autospec=True) - def test_gh_deploy_remote_name(self, mock_gh_deploy, mock_build, mock_load_config): - - result = self.runner.invoke( - cli.cli, ['gh-deploy', '--remote-name', 'foo'], catch_exceptions=False) - - self.assertEqual(result.exit_code, 0) - self.assertEqual(mock_gh_deploy.call_count, 1) - self.assertEqual(mock_build.call_count, 1) - mock_load_config.assert_called_once_with( - remote_branch=None, - remote_name='foo', - config_file=None, - strict=None, - theme=None, - theme_dir=None, - use_directory_urls=None, - site_dir=None - ) - - @mock.patch('mkdocs.config.load_config', autospec=True) - @mock.patch('mkdocs.commands.build.build', autospec=True) - @mock.patch('mkdocs.commands.gh_deploy.gh_deploy', autospec=True) - def test_gh_deploy_force(self, mock_gh_deploy, mock_build, mock_load_config): - - result = self.runner.invoke( - cli.cli, ['gh-deploy', '--force'], catch_exceptions=False) - - self.assertEqual(result.exit_code, 0) - self.assertEqual(mock_gh_deploy.call_count, 1) - g_args, g_kwargs = mock_gh_deploy.call_args - self.assertTrue('force' in g_kwargs) - self.assertEqual(g_kwargs['force'], True) - self.assertEqual(mock_build.call_count, 1) - self.assertEqual(mock_load_config.call_count, 1) - - @mock.patch('mkdocs.config.load_config', autospec=True) - @mock.patch('mkdocs.commands.build.build', autospec=True) - @mock.patch('mkdocs.commands.gh_deploy.gh_deploy', autospec=True) - def test_gh_deploy_ignore_version(self, mock_gh_deploy, mock_build, mock_load_config): - - result = self.runner.invoke( - cli.cli, ['gh-deploy', '--ignore-version'], catch_exceptions=False) - - self.assertEqual(result.exit_code, 0) - self.assertEqual(mock_gh_deploy.call_count, 1) - g_args, g_kwargs = mock_gh_deploy.call_args - self.assertTrue('ignore_version' in g_kwargs) - self.assertEqual(g_kwargs['ignore_version'], True) - self.assertEqual(mock_build.call_count, 1) - self.assertEqual(mock_load_config.call_count, 1) - - @mock.patch('mkdocs.config.load_config', autospec=True) - @mock.patch('mkdocs.commands.build.build', autospec=True) - @mock.patch('mkdocs.commands.gh_deploy.gh_deploy', autospec=True) - def test_gh_deploy_strict(self, mock_gh_deploy, mock_build, mock_load_config): - - result = self.runner.invoke( - cli.cli, ['gh-deploy', '--strict'], catch_exceptions=False) - - self.assertEqual(result.exit_code, 0) - self.assertEqual(mock_gh_deploy.call_count, 1) - self.assertEqual(mock_build.call_count, 1) - mock_load_config.assert_called_once_with( - remote_branch=None, - remote_name=None, - config_file=None, - strict=True, - theme=None, - theme_dir=None, - use_directory_urls=None, - site_dir=None - ) - - @mock.patch('mkdocs.config.load_config', autospec=True) - @mock.patch('mkdocs.commands.build.build', autospec=True) - @mock.patch('mkdocs.commands.gh_deploy.gh_deploy', autospec=True) - def test_gh_deploy_theme(self, mock_gh_deploy, mock_build, mock_load_config): - - result = self.runner.invoke( - cli.cli, ['gh-deploy', '--theme', 'readthedocs'], catch_exceptions=False) - - self.assertEqual(result.exit_code, 0) - self.assertEqual(mock_gh_deploy.call_count, 1) - self.assertEqual(mock_build.call_count, 1) - mock_load_config.assert_called_once_with( - remote_branch=None, - remote_name=None, - config_file=None, - strict=None, - theme='readthedocs', - theme_dir=None, - use_directory_urls=None, - site_dir=None - ) - - @mock.patch('mkdocs.config.load_config', autospec=True) - @mock.patch('mkdocs.commands.build.build', autospec=True) - @mock.patch('mkdocs.commands.gh_deploy.gh_deploy', autospec=True) - def test_gh_deploy_theme_dir(self, mock_gh_deploy, mock_build, mock_load_config): - - result = self.runner.invoke( - cli.cli, ['gh-deploy', '--theme-dir', 'custom'], catch_exceptions=False) - - self.assertEqual(result.exit_code, 0) - self.assertEqual(mock_gh_deploy.call_count, 1) - self.assertEqual(mock_build.call_count, 1) - mock_load_config.assert_called_once_with( - remote_branch=None, - remote_name=None, - config_file=None, - strict=None, - theme=None, - theme_dir='custom', - use_directory_urls=None, - site_dir=None - ) - - @mock.patch('mkdocs.config.load_config', autospec=True) - @mock.patch('mkdocs.commands.build.build', autospec=True) - @mock.patch('mkdocs.commands.gh_deploy.gh_deploy', autospec=True) - def test_gh_deploy_use_directory_urls(self, mock_gh_deploy, mock_build, mock_load_config): - - result = self.runner.invoke( - cli.cli, ['gh-deploy', '--use-directory-urls'], catch_exceptions=False) - - self.assertEqual(result.exit_code, 0) - self.assertEqual(mock_gh_deploy.call_count, 1) - self.assertEqual(mock_build.call_count, 1) - mock_load_config.assert_called_once_with( - remote_branch=None, - remote_name=None, - config_file=None, - strict=None, - theme=None, - theme_dir=None, - use_directory_urls=True, - site_dir=None - ) - - @mock.patch('mkdocs.config.load_config', autospec=True) - @mock.patch('mkdocs.commands.build.build', autospec=True) - @mock.patch('mkdocs.commands.gh_deploy.gh_deploy', autospec=True) - def test_gh_deploy_no_directory_urls(self, mock_gh_deploy, mock_build, mock_load_config): - - result = self.runner.invoke( - cli.cli, ['gh-deploy', '--no-directory-urls'], catch_exceptions=False) - - self.assertEqual(result.exit_code, 0) - self.assertEqual(mock_gh_deploy.call_count, 1) - self.assertEqual(mock_build.call_count, 1) - mock_load_config.assert_called_once_with( - remote_branch=None, - remote_name=None, - config_file=None, - strict=None, - theme=None, - theme_dir=None, - use_directory_urls=False, - site_dir=None - ) - - @mock.patch('mkdocs.config.load_config', autospec=True) - @mock.patch('mkdocs.commands.build.build', autospec=True) - @mock.patch('mkdocs.commands.gh_deploy.gh_deploy', autospec=True) - def test_gh_deploy_site_dir(self, mock_gh_deploy, mock_build, mock_load_config): - - result = self.runner.invoke( - cli.cli, ['gh-deploy', '--site-dir', 'custom'], catch_exceptions=False) - - self.assertEqual(result.exit_code, 0) - self.assertEqual(mock_gh_deploy.call_count, 1) - self.assertEqual(mock_build.call_count, 1) - mock_load_config.assert_called_once_with( - remote_branch=None, - remote_name=None, - config_file=None, - strict=None, - theme=None, - theme_dir=None, - use_directory_urls=None, - site_dir='custom' - ) diff --git a/mkdocs/tests/config/__init__.py b/mkdocs/tests/config/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/mkdocs/tests/config/base_tests.py b/mkdocs/tests/config/base_tests.py deleted file mode 100644 index 6bfc8bc69..000000000 --- a/mkdocs/tests/config/base_tests.py +++ /dev/null @@ -1,274 +0,0 @@ -import os -import tempfile -import unittest -from tempfile import TemporaryDirectory - -from mkdocs import exceptions -from mkdocs.config import base, defaults -from mkdocs.config.config_options import BaseConfigOption - - -class ConfigBaseTests(unittest.TestCase): - - def test_unrecognised_keys(self): - - c = base.Config(schema=defaults.DEFAULT_SCHEMA) - c.load_dict({ - 'not_a_valid_config_option': "test" - }) - - failed, warnings = c.validate() - - self.assertEqual(warnings, [ - ('not_a_valid_config_option', - 'Unrecognised configuration name: not_a_valid_config_option') - ]) - - def test_missing_required(self): - - c = base.Config(schema=defaults.DEFAULT_SCHEMA) - - errors, warnings = c.validate() - - self.assertEqual(len(errors), 1) - self.assertEqual(errors[0][0], 'site_name') - self.assertEqual(str(errors[0][1]), 'Required configuration not provided.') - - self.assertEqual(len(warnings), 0) - - def test_load_from_file(self): - """ - Users can explicitly set the config file using the '--config' option. - Allows users to specify a config other than the default `mkdocs.yml`. - """ - - temp_dir = TemporaryDirectory() - config_file = open(os.path.join(temp_dir.name, 'mkdocs.yml'), 'w') - os.mkdir(os.path.join(temp_dir.name, 'docs')) - try: - config_file.write("site_name: MkDocs Test\n") - config_file.flush() - config_file.close() - - cfg = base.load_config(config_file=config_file.name) - self.assertTrue(isinstance(cfg, base.Config)) - self.assertEqual(cfg['site_name'], 'MkDocs Test') - finally: - os.remove(config_file.name) - temp_dir.cleanup() - - def test_load_from_missing_file(self): - - self.assertRaises(exceptions.ConfigurationError, - base.load_config, config_file='missing_file.yml') - - def test_load_from_open_file(self): - """ - `load_config` can accept an open file descriptor. - """ - - temp_dir = TemporaryDirectory() - temp_path = temp_dir.name - config_fname = os.path.join(temp_path, 'mkdocs.yml') - - config_file = open(config_fname, 'w+') - os.mkdir(os.path.join(temp_path, 'docs')) - try: - config_file.write("site_name: MkDocs Test\n") - config_file.flush() - - cfg = base.load_config(config_file=config_file) - self.assertTrue(isinstance(cfg, base.Config)) - self.assertEqual(cfg['site_name'], 'MkDocs Test') - # load_config will always close the file - self.assertTrue(config_file.closed) - finally: - temp_dir.cleanup() - - def test_load_from_closed_file(self): - """ - The `serve` command with auto-reload may pass in a closed file descriptor. - Ensure `load_config` reloads the closed file. - """ - - temp_dir = TemporaryDirectory() - config_file = open(os.path.join(temp_dir.name, 'mkdocs.yml'), 'w') - os.mkdir(os.path.join(temp_dir.name, 'docs')) - - try: - config_file.write("site_name: MkDocs Test\n") - config_file.flush() - config_file.close() - - cfg = base.load_config(config_file=config_file) - self.assertTrue(isinstance(cfg, base.Config)) - self.assertEqual(cfg['site_name'], 'MkDocs Test') - finally: - temp_dir.cleanup() - - def test_load_from_deleted_file(self): - """ - Deleting the config file could trigger a server reload. - """ - - config_file = tempfile.NamedTemporaryFile('w', delete=False) - try: - config_file.write("site_name: MkDocs Test\n") - config_file.flush() - config_file.close() - finally: - os.remove(config_file.name) - self.assertRaises(exceptions.ConfigurationError, - base.load_config, config_file=config_file) - - def test_load_missing_required(self): - """ - `site_name` is a required setting. - """ - - config_file = tempfile.NamedTemporaryFile('w', delete=False) - try: - config_file.write( - "site_dir: output\nsite_uri: https://www.mkdocs.org\n") - config_file.flush() - config_file.close() - - self.assertRaises(exceptions.ConfigurationError, - base.load_config, config_file=config_file.name) - finally: - os.remove(config_file.name) - - def test_pre_validation_error(self): - class InvalidConfigOption(BaseConfigOption): - def pre_validation(self, config, key_name): - raise base.ValidationError('pre_validation error') - - c = base.Config(schema=(('invalid_option', InvalidConfigOption()), )) - - errors, warnings = c.validate() - - self.assertEqual(len(errors), 1) - self.assertEqual(errors[0][0], 'invalid_option') - self.assertEqual(str(errors[0][1]), 'pre_validation error') - self.assertTrue(isinstance(errors[0][1], base.ValidationError)) - self.assertEqual(len(warnings), 0) - - def test_run_validation_error(self): - class InvalidConfigOption(BaseConfigOption): - def run_validation(self, value): - raise base.ValidationError('run_validation error') - - c = base.Config(schema=(('invalid_option', InvalidConfigOption()), )) - - errors, warnings = c.validate() - - self.assertEqual(len(errors), 1) - self.assertEqual(errors[0][0], 'invalid_option') - self.assertEqual(str(errors[0][1]), 'run_validation error') - self.assertTrue(isinstance(errors[0][1], base.ValidationError)) - self.assertEqual(len(warnings), 0) - - def test_post_validation_error(self): - class InvalidConfigOption(BaseConfigOption): - def post_validation(self, config, key_name): - raise base.ValidationError('post_validation error') - - c = base.Config(schema=(('invalid_option', InvalidConfigOption()), )) - - errors, warnings = c.validate() - - self.assertEqual(len(errors), 1) - self.assertEqual(errors[0][0], 'invalid_option') - self.assertEqual(str(errors[0][1]), 'post_validation error') - self.assertTrue(isinstance(errors[0][1], base.ValidationError)) - self.assertEqual(len(warnings), 0) - - def test_pre_and_run_validation_errors(self): - """ A pre_validation error does not stop run_validation from running. """ - class InvalidConfigOption(BaseConfigOption): - def pre_validation(self, config, key_name): - raise base.ValidationError('pre_validation error') - - def run_validation(self, value): - raise base.ValidationError('run_validation error') - - c = base.Config(schema=(('invalid_option', InvalidConfigOption()), )) - - errors, warnings = c.validate() - - self.assertEqual(len(errors), 2) - self.assertEqual(errors[0][0], 'invalid_option') - self.assertEqual(str(errors[0][1]), 'pre_validation error') - self.assertTrue(isinstance(errors[0][1], base.ValidationError)) - self.assertEqual(errors[1][0], 'invalid_option') - self.assertEqual(str(errors[1][1]), 'run_validation error') - self.assertTrue(isinstance(errors[1][1], base.ValidationError)) - self.assertEqual(len(warnings), 0) - - def test_run_and_post_validation_errors(self): - """ A run_validation error stops post_validation from running. """ - class InvalidConfigOption(BaseConfigOption): - def run_validation(self, value): - raise base.ValidationError('run_validation error') - - def post_validation(self, config, key_name): - raise base.ValidationError('post_validation error') - - c = base.Config(schema=(('invalid_option', InvalidConfigOption()), )) - - errors, warnings = c.validate() - - self.assertEqual(len(errors), 1) - self.assertEqual(errors[0][0], 'invalid_option') - self.assertEqual(str(errors[0][1]), 'run_validation error') - self.assertTrue(isinstance(errors[0][1], base.ValidationError)) - self.assertEqual(len(warnings), 0) - - def test_validation_warnings(self): - class InvalidConfigOption(BaseConfigOption): - def pre_validation(self, config, key_name): - self.warnings.append('pre_validation warning') - - def run_validation(self, value): - self.warnings.append('run_validation warning') - - def post_validation(self, config, key_name): - self.warnings.append('post_validation warning') - - c = base.Config(schema=(('invalid_option', InvalidConfigOption()), )) - - errors, warnings = c.validate() - - self.assertEqual(len(errors), 0) - self.assertEqual(warnings, [ - ('invalid_option', 'pre_validation warning'), - ('invalid_option', 'run_validation warning'), - ('invalid_option', 'post_validation warning'), - ]) - - def test_load_from_file_with_relative_paths(self): - """ - When explicitly setting a config file, paths should be relative to the - config file, not the working directory. - """ - - config_dir = TemporaryDirectory() - config_fname = os.path.join(config_dir.name, 'mkdocs.yml') - docs_dir = os.path.join(config_dir.name, 'src') - os.mkdir(docs_dir) - - config_file = open(config_fname, 'w') - - try: - config_file.write("docs_dir: src\nsite_name: MkDocs Test\n") - config_file.flush() - config_file.close() - - cfg = base.load_config(config_file=config_file) - self.assertTrue(isinstance(cfg, base.Config)) - self.assertEqual(cfg['site_name'], 'MkDocs Test') - self.assertEqual(cfg['docs_dir'], docs_dir) - self.assertEqual(cfg.config_file_path, config_fname) - self.assertIsInstance(cfg.config_file_path, str) - finally: - config_dir.cleanup() diff --git a/mkdocs/tests/config/config_options_tests.py b/mkdocs/tests/config/config_options_tests.py deleted file mode 100644 index da55c1fc5..000000000 --- a/mkdocs/tests/config/config_options_tests.py +++ /dev/null @@ -1,756 +0,0 @@ -import os -import sys -import unittest -from unittest.mock import patch - -import mkdocs -from mkdocs.config import config_options -from mkdocs.config.base import Config - - -class OptionallyRequiredTest(unittest.TestCase): - - def test_empty(self): - - option = config_options.OptionallyRequired() - value = option.validate(None) - self.assertEqual(value, None) - - self.assertEqual(option.is_required(), False) - - def test_required(self): - - option = config_options.OptionallyRequired(required=True) - self.assertRaises(config_options.ValidationError, - option.validate, None) - - self.assertEqual(option.is_required(), True) - - def test_required_no_default(self): - - option = config_options.OptionallyRequired(required=True) - value = option.validate(2) - self.assertEqual(2, value) - - def test_default(self): - - option = config_options.OptionallyRequired(default=1) - value = option.validate(None) - self.assertEqual(1, value) - - def test_replace_default(self): - - option = config_options.OptionallyRequired(default=1) - value = option.validate(2) - self.assertEqual(2, value) - - -class TypeTest(unittest.TestCase): - - def test_single_type(self): - - option = config_options.Type(str) - value = option.validate("Testing") - self.assertEqual(value, "Testing") - - def test_multiple_types(self): - option = config_options.Type((list, tuple)) - - value = option.validate([1, 2, 3]) - self.assertEqual(value, [1, 2, 3]) - - value = option.validate((1, 2, 3)) - self.assertEqual(value, (1, 2, 3)) - - self.assertRaises(config_options.ValidationError, - option.validate, {'a': 1}) - - def test_length(self): - option = config_options.Type(str, length=7) - - value = option.validate("Testing") - self.assertEqual(value, "Testing") - - self.assertRaises(config_options.ValidationError, - option.validate, "Testing Long") - - -class ChoiceTest(unittest.TestCase): - - def test_valid_choice(self): - option = config_options.Choice(('python', 'node')) - value = option.validate('python') - self.assertEqual(value, 'python') - - def test_invalid_choice(self): - option = config_options.Choice(('python', 'node')) - self.assertRaises( - config_options.ValidationError, option.validate, 'go') - - def test_invalid_choices(self): - self.assertRaises(ValueError, config_options.Choice, '') - self.assertRaises(ValueError, config_options.Choice, []) - self.assertRaises(ValueError, config_options.Choice, 5) - - -class IpAddressTest(unittest.TestCase): - - def test_valid_address(self): - addr = '127.0.0.1:8000' - - option = config_options.IpAddress() - value = option.validate(addr) - self.assertEqual(str(value), addr) - self.assertEqual(value.host, '127.0.0.1') - self.assertEqual(value.port, 8000) - - def test_valid_IPv6_address(self): - addr = '[::1]:8000' - - option = config_options.IpAddress() - value = option.validate(addr) - self.assertEqual(str(value), addr) - self.assertEqual(value.host, '[::1]') - self.assertEqual(value.port, 8000) - - def test_named_address(self): - addr = 'localhost:8000' - - option = config_options.IpAddress() - value = option.validate(addr) - self.assertEqual(str(value), addr) - self.assertEqual(value.host, 'localhost') - self.assertEqual(value.port, 8000) - - def test_default_address(self): - addr = '127.0.0.1:8000' - - option = config_options.IpAddress(default=addr) - value = option.validate(None) - self.assertEqual(str(value), addr) - self.assertEqual(value.host, '127.0.0.1') - self.assertEqual(value.port, 8000) - - def test_invalid_address_format(self): - option = config_options.IpAddress() - self.assertRaises( - config_options.ValidationError, - option.validate, '127.0.0.18000' - ) - - def test_invalid_address_type(self): - option = config_options.IpAddress() - self.assertRaises( - config_options.ValidationError, - option.validate, 123 - ) - - def test_invalid_address_port(self): - option = config_options.IpAddress() - self.assertRaises( - config_options.ValidationError, - option.validate, '127.0.0.1:foo' - ) - - def test_invalid_address_missing_port(self): - option = config_options.IpAddress() - self.assertRaises( - config_options.ValidationError, - option.validate, '127.0.0.1' - ) - - -class URLTest(unittest.TestCase): - - def test_valid_url(self): - - url = "https://mkdocs.org" - - option = config_options.URL() - value = option.validate(url) - self.assertEqual(value, url) - - def test_invalid_url(self): - - option = config_options.URL() - self.assertRaises(config_options.ValidationError, - option.validate, "www.mkdocs.org") - - def test_invalid(self): - - option = config_options.URL() - self.assertRaises(config_options.ValidationError, - option.validate, 1) - - -class RepoURLTest(unittest.TestCase): - - def test_repo_name_github(self): - - option = config_options.RepoURL() - config = {'repo_url': "https://github.com/mkdocs/mkdocs"} - option.post_validation(config, 'repo_url') - self.assertEqual(config['repo_name'], "GitHub") - - def test_repo_name_bitbucket(self): - - option = config_options.RepoURL() - config = {'repo_url': "https://bitbucket.org/gutworth/six/"} - option.post_validation(config, 'repo_url') - self.assertEqual(config['repo_name'], "Bitbucket") - - def test_repo_name_gitlab(self): - - option = config_options.RepoURL() - config = {'repo_url': "https://gitlab.com/gitlab-org/gitlab-ce/"} - option.post_validation(config, 'repo_url') - self.assertEqual(config['repo_name'], "GitLab") - - def test_repo_name_custom(self): - - option = config_options.RepoURL() - config = {'repo_url': "https://launchpad.net/python-tuskarclient"} - option.post_validation(config, 'repo_url') - self.assertEqual(config['repo_name'], "Launchpad") - - def test_edit_uri_github(self): - - option = config_options.RepoURL() - config = {'repo_url': "https://github.com/mkdocs/mkdocs"} - option.post_validation(config, 'repo_url') - self.assertEqual(config['edit_uri'], 'edit/master/docs/') - - def test_edit_uri_bitbucket(self): - - option = config_options.RepoURL() - config = {'repo_url': "https://bitbucket.org/gutworth/six/"} - option.post_validation(config, 'repo_url') - self.assertEqual(config['edit_uri'], 'src/default/docs/') - - def test_edit_uri_gitlab(self): - - option = config_options.RepoURL() - config = {'repo_url': "https://gitlab.com/gitlab-org/gitlab-ce/"} - option.post_validation(config, 'repo_url') - self.assertEqual(config['edit_uri'], 'edit/master/docs/') - - def test_edit_uri_custom(self): - - option = config_options.RepoURL() - config = {'repo_url': "https://launchpad.net/python-tuskarclient"} - option.post_validation(config, 'repo_url') - self.assertEqual(config.get('edit_uri'), '') - - def test_repo_name_custom_and_empty_edit_uri(self): - - option = config_options.RepoURL() - config = {'repo_url': "https://github.com/mkdocs/mkdocs", - 'repo_name': 'mkdocs'} - option.post_validation(config, 'repo_url') - self.assertEqual(config.get('edit_uri'), 'edit/master/docs/') - - -class DirTest(unittest.TestCase): - - def test_valid_dir(self): - - d = os.path.dirname(__file__) - option = config_options.Dir(exists=True) - value = option.validate(d) - self.assertEqual(d, value) - - def test_missing_dir(self): - - d = os.path.join("not", "a", "real", "path", "I", "hope") - option = config_options.Dir() - value = option.validate(d) - self.assertEqual(os.path.abspath(d), value) - - def test_missing_dir_but_required(self): - - d = os.path.join("not", "a", "real", "path", "I", "hope") - option = config_options.Dir(exists=True) - self.assertRaises(config_options.ValidationError, - option.validate, d) - - def test_file(self): - d = __file__ - option = config_options.Dir(exists=True) - self.assertRaises(config_options.ValidationError, - option.validate, d) - - def test_incorrect_type_attribute_error(self): - option = config_options.Dir() - self.assertRaises(config_options.ValidationError, - option.validate, 1) - - def test_incorrect_type_type_error(self): - option = config_options.Dir() - self.assertRaises(config_options.ValidationError, - option.validate, []) - - def test_dir_unicode(self): - cfg = Config( - [('dir', config_options.Dir())], - config_file_path=os.path.join(os.path.abspath('.'), 'mkdocs.yml'), - ) - - test_config = { - 'dir': 'юникод' - } - - cfg.load_dict(test_config) - - fails, warns = cfg.validate() - - self.assertEqual(len(fails), 0) - self.assertEqual(len(warns), 0) - self.assertIsInstance(cfg['dir'], str) - - def test_dir_filesystemencoding(self): - cfg = Config( - [('dir', config_options.Dir())], - config_file_path=os.path.join(os.path.abspath('.'), 'mkdocs.yml'), - ) - - test_config = { - 'dir': 'Übersicht'.encode(encoding=sys.getfilesystemencoding()) - } - - cfg.load_dict(test_config) - - fails, warns = cfg.validate() - - # str does not include byte strings so validation fails - self.assertEqual(len(fails), 1) - self.assertEqual(len(warns), 0) - - def test_dir_bad_encoding_fails(self): - cfg = Config( - [('dir', config_options.Dir())], - config_file_path=os.path.join(os.path.abspath('.'), 'mkdocs.yml'), - ) - - test_config = { - 'dir': 'юникод'.encode(encoding='ISO 8859-5') - } - - cfg.load_dict(test_config) - - fails, warns = cfg.validate() - - self.assertEqual(len(fails), 1) - self.assertEqual(len(warns), 0) - - def test_config_dir_prepended(self): - base_path = os.path.abspath('.') - cfg = Config( - [('dir', config_options.Dir())], - config_file_path=os.path.join(base_path, 'mkdocs.yml'), - ) - - test_config = { - 'dir': 'foo' - } - - cfg.load_dict(test_config) - - fails, warns = cfg.validate() - - self.assertEqual(len(fails), 0) - self.assertEqual(len(warns), 0) - self.assertIsInstance(cfg['dir'], str) - self.assertEqual(cfg['dir'], os.path.join(base_path, 'foo')) - - def test_dir_is_config_dir_fails(self): - cfg = Config( - [('dir', config_options.Dir())], - config_file_path=os.path.join(os.path.abspath('.'), 'mkdocs.yml'), - ) - - test_config = { - 'dir': '.' - } - - cfg.load_dict(test_config) - - fails, warns = cfg.validate() - - self.assertEqual(len(fails), 1) - self.assertEqual(len(warns), 0) - - -class SiteDirTest(unittest.TestCase): - - def validate_config(self, config): - """ Given a config with values for site_dir and doc_dir, run site_dir post_validation. """ - site_dir = config_options.SiteDir() - docs_dir = config_options.Dir() - - fname = os.path.join(os.path.abspath('..'), 'mkdocs.yml') - - config['docs_dir'] = docs_dir.validate(config['docs_dir']) - config['site_dir'] = site_dir.validate(config['site_dir']) - - schema = [ - ('site_dir', site_dir), - ('docs_dir', docs_dir), - ] - cfg = Config(schema, fname) - cfg.load_dict(config) - failed, warned = cfg.validate() - - if failed: - raise config_options.ValidationError(failed) - - return True - - def test_doc_dir_in_site_dir(self): - - j = os.path.join - # The parent dir is not the same on every system, so use the actual dir name - parent_dir = mkdocs.__file__.split(os.sep)[-3] - - test_configs = ( - {'docs_dir': j('site', 'docs'), 'site_dir': 'site'}, - {'docs_dir': 'docs', 'site_dir': '.'}, - {'docs_dir': '.', 'site_dir': '.'}, - {'docs_dir': 'docs', 'site_dir': ''}, - {'docs_dir': '', 'site_dir': ''}, - {'docs_dir': j('..', parent_dir, 'docs'), 'site_dir': 'docs'}, - {'docs_dir': 'docs', 'site_dir': '/'} - ) - - for test_config in test_configs: - self.assertRaises(config_options.ValidationError, - self.validate_config, test_config) - - def test_site_dir_in_docs_dir(self): - - j = os.path.join - - test_configs = ( - {'docs_dir': 'docs', 'site_dir': j('docs', 'site')}, - {'docs_dir': '.', 'site_dir': 'site'}, - {'docs_dir': '', 'site_dir': 'site'}, - {'docs_dir': '/', 'site_dir': 'site'}, - ) - - for test_config in test_configs: - self.assertRaises(config_options.ValidationError, - self.validate_config, test_config) - - def test_common_prefix(self): - """ Legitimate settings with common prefixes should not fail validation. """ - - test_configs = ( - {'docs_dir': 'docs', 'site_dir': 'docs-site'}, - {'docs_dir': 'site-docs', 'site_dir': 'site'}, - ) - - for test_config in test_configs: - assert self.validate_config(test_config) - - -class ThemeTest(unittest.TestCase): - - def test_theme_as_string(self): - - option = config_options.Theme() - value = option.validate("mkdocs") - self.assertEqual({'name': 'mkdocs'}, value) - - def test_uninstalled_theme_as_string(self): - - option = config_options.Theme() - self.assertRaises(config_options.ValidationError, - option.validate, "mkdocs2") - - def test_theme_default(self): - option = config_options.Theme(default='mkdocs') - value = option.validate(None) - self.assertEqual({'name': 'mkdocs'}, value) - - def test_theme_as_simple_config(self): - - config = { - 'name': 'mkdocs' - } - option = config_options.Theme() - value = option.validate(config) - self.assertEqual(config, value) - - def test_theme_as_complex_config(self): - - config = { - 'name': 'mkdocs', - 'custom_dir': 'custom', - 'static_templates': ['sitemap.html'], - 'show_sidebar': False - } - option = config_options.Theme() - value = option.validate(config) - self.assertEqual(config, value) - - def test_theme_name_is_none(self): - - config = { - 'name': None - } - option = config_options.Theme() - value = option.validate(config) - self.assertEqual(config, value) - - def test_theme_config_missing_name(self): - - config = { - 'custom_dir': 'custom', - } - option = config_options.Theme() - self.assertRaises(config_options.ValidationError, - option.validate, config) - - def test_uninstalled_theme_as_config(self): - - config = { - 'name': 'mkdocs2' - } - option = config_options.Theme() - self.assertRaises(config_options.ValidationError, - option.validate, config) - - def test_theme_invalid_type(self): - - config = ['mkdocs2'] - option = config_options.Theme() - self.assertRaises(config_options.ValidationError, - option.validate, config) - - -class NavTest(unittest.TestCase): - - def test_old_format(self): - - option = config_options.Nav() - self.assertRaises( - config_options.ValidationError, - option.validate, - [['index.md', ], ] - ) - - def test_provided_dict(self): - - option = config_options.Nav() - value = option.validate([ - 'index.md', - {"Page": "page.md"} - ]) - self.assertEqual(['index.md', {'Page': 'page.md'}], value) - - option.post_validation({'extra_stuff': []}, 'extra_stuff') - - def test_provided_empty(self): - - option = config_options.Nav() - value = option.validate([]) - self.assertEqual(None, value) - - option.post_validation({'extra_stuff': []}, 'extra_stuff') - - def test_invalid_type(self): - - option = config_options.Nav() - self.assertRaises(config_options.ValidationError, - option.validate, {}) - - def test_invalid_config(self): - - option = config_options.Nav() - self.assertRaises(config_options.ValidationError, - option.validate, [[], 1]) - - -class PrivateTest(unittest.TestCase): - - def test_defined(self): - - option = config_options.Private() - self.assertRaises(config_options.ValidationError, - option.validate, 'somevalue') - - -class MarkdownExtensionsTest(unittest.TestCase): - - @patch('markdown.Markdown') - def test_simple_list(self, mockMd): - option = config_options.MarkdownExtensions() - config = { - 'markdown_extensions': ['foo', 'bar'] - } - config['markdown_extensions'] = option.validate(config['markdown_extensions']) - option.post_validation(config, 'markdown_extensions') - self.assertEqual({ - 'markdown_extensions': ['foo', 'bar'], - 'mdx_configs': {} - }, config) - - @patch('markdown.Markdown') - def test_list_dicts(self, mockMd): - option = config_options.MarkdownExtensions() - config = { - 'markdown_extensions': [ - {'foo': {'foo_option': 'foo value'}}, - {'bar': {'bar_option': 'bar value'}}, - {'baz': None} - ] - } - config['markdown_extensions'] = option.validate(config['markdown_extensions']) - option.post_validation(config, 'markdown_extensions') - self.assertEqual({ - 'markdown_extensions': ['foo', 'bar', 'baz'], - 'mdx_configs': { - 'foo': {'foo_option': 'foo value'}, - 'bar': {'bar_option': 'bar value'} - } - }, config) - - @patch('markdown.Markdown') - def test_mixed_list(self, mockMd): - option = config_options.MarkdownExtensions() - config = { - 'markdown_extensions': [ - 'foo', - {'bar': {'bar_option': 'bar value'}} - ] - } - config['markdown_extensions'] = option.validate(config['markdown_extensions']) - option.post_validation(config, 'markdown_extensions') - self.assertEqual({ - 'markdown_extensions': ['foo', 'bar'], - 'mdx_configs': { - 'bar': {'bar_option': 'bar value'} - } - }, config) - - @patch('markdown.Markdown') - def test_builtins(self, mockMd): - option = config_options.MarkdownExtensions(builtins=['meta', 'toc']) - config = { - 'markdown_extensions': ['foo', 'bar'] - } - config['markdown_extensions'] = option.validate(config['markdown_extensions']) - option.post_validation(config, 'markdown_extensions') - self.assertEqual({ - 'markdown_extensions': ['meta', 'toc', 'foo', 'bar'], - 'mdx_configs': {} - }, config) - - def test_duplicates(self): - option = config_options.MarkdownExtensions(builtins=['meta', 'toc']) - config = { - 'markdown_extensions': ['meta', 'toc'] - } - config['markdown_extensions'] = option.validate(config['markdown_extensions']) - option.post_validation(config, 'markdown_extensions') - self.assertEqual({ - 'markdown_extensions': ['meta', 'toc'], - 'mdx_configs': {} - }, config) - - def test_builtins_config(self): - option = config_options.MarkdownExtensions(builtins=['meta', 'toc']) - config = { - 'markdown_extensions': [ - {'toc': {'permalink': True}} - ] - } - config['markdown_extensions'] = option.validate(config['markdown_extensions']) - option.post_validation(config, 'markdown_extensions') - self.assertEqual({ - 'markdown_extensions': ['meta', 'toc'], - 'mdx_configs': {'toc': {'permalink': True}} - }, config) - - @patch('markdown.Markdown') - def test_configkey(self, mockMd): - option = config_options.MarkdownExtensions(configkey='bar') - config = { - 'markdown_extensions': [ - {'foo': {'foo_option': 'foo value'}} - ] - } - config['markdown_extensions'] = option.validate(config['markdown_extensions']) - option.post_validation(config, 'markdown_extensions') - self.assertEqual({ - 'markdown_extensions': ['foo'], - 'bar': { - 'foo': {'foo_option': 'foo value'} - } - }, config) - - def test_none(self): - option = config_options.MarkdownExtensions(default=[]) - config = { - 'markdown_extensions': None - } - config['markdown_extensions'] = option.validate(config['markdown_extensions']) - option.post_validation(config, 'markdown_extensions') - self.assertEqual({ - 'markdown_extensions': [], - 'mdx_configs': {} - }, config) - - @patch('markdown.Markdown') - def test_not_list(self, mockMd): - option = config_options.MarkdownExtensions() - self.assertRaises(config_options.ValidationError, - option.validate, 'not a list') - - @patch('markdown.Markdown') - def test_invalid_config_option(self, mockMd): - option = config_options.MarkdownExtensions() - config = { - 'markdown_extensions': [ - {'foo': 'not a dict'} - ] - } - self.assertRaises( - config_options.ValidationError, - option.validate, config['markdown_extensions'] - ) - - @patch('markdown.Markdown') - def test_invalid_config_item(self, mockMd): - option = config_options.MarkdownExtensions() - config = { - 'markdown_extensions': [ - ['not a dict'] - ] - } - self.assertRaises( - config_options.ValidationError, - option.validate, config['markdown_extensions'] - ) - - @patch('markdown.Markdown') - def test_invalid_dict_item(self, mockMd): - option = config_options.MarkdownExtensions() - config = { - 'markdown_extensions': [ - {'key1': 'value', 'key2': 'too many keys'} - ] - } - self.assertRaises( - config_options.ValidationError, - option.validate, config['markdown_extensions'] - ) - - def test_unknown_extension(self): - option = config_options.MarkdownExtensions() - config = { - 'markdown_extensions': ['unknown'] - } - self.assertRaises( - config_options.ValidationError, - option.validate, config['markdown_extensions'] - ) diff --git a/mkdocs/tests/config/config_tests.py b/mkdocs/tests/config/config_tests.py deleted file mode 100644 index 8d562dc53..000000000 --- a/mkdocs/tests/config/config_tests.py +++ /dev/null @@ -1,268 +0,0 @@ -#!/usr/bin/env python - -import os -import tempfile -import unittest -from tempfile import TemporaryDirectory - -import mkdocs -from mkdocs import config -from mkdocs.config import config_options -from mkdocs.exceptions import ConfigurationError -from mkdocs.tests.base import dedent - - -class ConfigTests(unittest.TestCase): - def test_missing_config_file(self): - - def load_missing_config(): - config.load_config(config_file='bad_filename.yaml') - self.assertRaises(ConfigurationError, load_missing_config) - - def test_missing_site_name(self): - c = config.Config(schema=config.DEFAULT_SCHEMA) - c.load_dict({}) - errors, warnings = c.validate() - self.assertEqual(len(errors), 1) - self.assertEqual(errors[0][0], 'site_name') - self.assertEqual(str(errors[0][1]), 'Required configuration not provided.') - - self.assertEqual(len(warnings), 0) - - def test_empty_config(self): - def load_empty_config(): - config.load_config(config_file='/dev/null') - self.assertRaises(ConfigurationError, load_empty_config) - - def test_nonexistant_config(self): - def load_empty_config(): - config.load_config(config_file='/path/that/is/not/real') - self.assertRaises(ConfigurationError, load_empty_config) - - def test_invalid_config(self): - file_contents = dedent(""" - - ['index.md', 'Introduction'] - - ['index.md', 'Introduction'] - - ['index.md', 'Introduction'] - """) - config_file = tempfile.NamedTemporaryFile('w', delete=False) - try: - config_file.write(file_contents) - config_file.flush() - config_file.close() - - self.assertRaises( - ConfigurationError, - config.load_config, config_file=open(config_file.name, 'rb') - ) - finally: - os.remove(config_file.name) - - def test_config_option(self): - """ - Users can explicitly set the config file using the '--config' option. - Allows users to specify a config other than the default `mkdocs.yml`. - """ - expected_result = { - 'site_name': 'Example', - 'pages': [ - {'Introduction': 'index.md'} - ], - } - file_contents = dedent(""" - site_name: Example - pages: - - 'Introduction': 'index.md' - """) - with TemporaryDirectory() as temp_path: - os.mkdir(os.path.join(temp_path, 'docs')) - config_path = os.path.join(temp_path, 'mkdocs.yml') - config_file = open(config_path, 'w') - - config_file.write(file_contents) - config_file.flush() - config_file.close() - - result = config.load_config(config_file=config_file.name) - self.assertEqual(result['site_name'], expected_result['site_name']) - self.assertEqual(result['pages'], expected_result['pages']) - - def test_theme(self): - with TemporaryDirectory() as mytheme, TemporaryDirectory() as custom: - configs = [ - dict(), # default theme - {"theme": "readthedocs"}, # builtin theme - {"theme": {'name': 'readthedocs'}}, # builtin as complex - {"theme": {'name': None, 'custom_dir': mytheme}}, # custom only as complex - {"theme": {'name': 'readthedocs', 'custom_dir': custom}}, # builtin and custom as complex - { # user defined variables - 'theme': { - 'name': 'mkdocs', - 'static_templates': ['foo.html'], - 'show_sidebar': False, - 'some_var': 'bar' - } - } - ] - - mkdocs_dir = os.path.abspath(os.path.dirname(mkdocs.__file__)) - mkdocs_templates_dir = os.path.join(mkdocs_dir, 'templates') - theme_dir = os.path.abspath(os.path.join(mkdocs_dir, 'themes')) - - results = ( - { - 'dirs': [os.path.join(theme_dir, 'mkdocs'), mkdocs_templates_dir], - 'static_templates': ['404.html', 'sitemap.xml'], - 'vars': { - 'include_search_page': False, - 'search_index_only': False, - 'highlightjs': True, - 'hljs_style': 'github', - 'hljs_languages': [], - 'navigation_depth': 2, - 'nav_style': 'primary', - 'shortcuts': {'help': 191, 'next': 78, 'previous': 80, 'search': 83} - } - }, { - 'dirs': [os.path.join(theme_dir, 'readthedocs'), mkdocs_templates_dir], - 'static_templates': ['404.html', 'sitemap.xml'], - 'vars': { - 'include_search_page': True, - 'search_index_only': False, - 'highlightjs': True, - 'hljs_languages': [], - 'include_homepage_in_sidebar': True, - 'prev_next_buttons_location': 'bottom', - 'navigation_depth': 4, - 'sticky_navigation': True, - 'titles_only': False, - 'collapse_navigation': True - } - }, { - 'dirs': [os.path.join(theme_dir, 'readthedocs'), mkdocs_templates_dir], - 'static_templates': ['404.html', 'sitemap.xml'], - 'vars': { - 'include_search_page': True, - 'search_index_only': False, - 'highlightjs': True, - 'hljs_languages': [], - 'include_homepage_in_sidebar': True, - 'prev_next_buttons_location': 'bottom', - 'navigation_depth': 4, - 'sticky_navigation': True, - 'titles_only': False, - 'collapse_navigation': True - } - }, { - 'dirs': [mytheme, mkdocs_templates_dir], - 'static_templates': ['sitemap.xml'], - 'vars': {} - }, { - 'dirs': [custom, os.path.join(theme_dir, 'readthedocs'), mkdocs_templates_dir], - 'static_templates': ['404.html', 'sitemap.xml'], - 'vars': { - 'include_search_page': True, - 'search_index_only': False, - 'highlightjs': True, - 'hljs_languages': [], - 'include_homepage_in_sidebar': True, - 'prev_next_buttons_location': 'bottom', - 'navigation_depth': 4, - 'sticky_navigation': True, - 'titles_only': False, - 'collapse_navigation': True - } - }, { - 'dirs': [os.path.join(theme_dir, 'mkdocs'), mkdocs_templates_dir], - 'static_templates': ['404.html', 'sitemap.xml', 'foo.html'], - 'vars': { - 'show_sidebar': False, - 'some_var': 'bar', - 'include_search_page': False, - 'search_index_only': False, - 'highlightjs': True, - 'hljs_style': 'github', - 'hljs_languages': [], - 'navigation_depth': 2, - 'nav_style': 'primary', - 'shortcuts': {'help': 191, 'next': 78, 'previous': 80, 'search': 83} - } - } - ) - - for config_contents, result in zip(configs, results): - - c = config.Config(schema=(('theme', config_options.Theme(default='mkdocs')),)) - c.load_dict(config_contents) - errors, warnings = c.validate() - self.assertEqual(len(errors), 0) - self.assertEqual(c['theme'].dirs, result['dirs']) - self.assertEqual(c['theme'].static_templates, set(result['static_templates'])) - self.assertEqual({k: c['theme'][k] for k in iter(c['theme'])}, result['vars']) - - def test_empty_nav(self): - conf = config.Config(schema=config.DEFAULT_SCHEMA) - conf.load_dict({ - 'site_name': 'Example', - 'config_file_path': os.path.join(os.path.abspath('.'), 'mkdocs.yml') - }) - conf.validate() - self.assertEqual(conf['nav'], None) - - def test_copy_pages_to_nav(self): - # TODO: remove this when pages config setting is fully deprecated. - conf = config.Config(schema=config.DEFAULT_SCHEMA) - conf.load_dict({ - 'site_name': 'Example', - 'pages': ['index.md', 'about.md'], - 'config_file_path': os.path.join(os.path.abspath('.'), 'mkdocs.yml') - }) - conf.validate() - self.assertEqual(conf['nav'], ['index.md', 'about.md']) - - def test_dont_overwrite_nav_with_pages(self): - # TODO: remove this when pages config setting is fully deprecated. - conf = config.Config(schema=config.DEFAULT_SCHEMA) - conf.load_dict({ - 'site_name': 'Example', - 'pages': ['index.md', 'about.md'], - 'nav': ['foo.md', 'bar.md'], - 'config_file_path': os.path.join(os.path.abspath('.'), 'mkdocs.yml') - }) - conf.validate() - self.assertEqual(conf['nav'], ['foo.md', 'bar.md']) - - def test_doc_dir_in_site_dir(self): - - j = os.path.join - - test_configs = ( - {'docs_dir': j('site', 'docs'), 'site_dir': 'site'}, - {'docs_dir': 'docs', 'site_dir': '.'}, - {'docs_dir': '.', 'site_dir': '.'}, - {'docs_dir': 'docs', 'site_dir': ''}, - {'docs_dir': '', 'site_dir': ''}, - {'docs_dir': 'docs', 'site_dir': 'docs'}, - ) - - conf = { - 'config_file_path': j(os.path.abspath('..'), 'mkdocs.yml') - } - - for test_config in test_configs: - - patch = conf.copy() - patch.update(test_config) - - # Same as the default schema, but don't verify the docs_dir exists. - c = config.Config(schema=( - ('docs_dir', config_options.Dir(default='docs')), - ('site_dir', config_options.SiteDir(default='site')), - ('config_file_path', config_options.Type(str)) - )) - c.load_dict(patch) - - errors, warnings = c.validate() - - self.assertEqual(len(errors), 1) - self.assertEqual(warnings, []) diff --git a/mkdocs/tests/gh_deploy_tests.py b/mkdocs/tests/gh_deploy_tests.py deleted file mode 100644 index 65f0c66ab..000000000 --- a/mkdocs/tests/gh_deploy_tests.py +++ /dev/null @@ -1,199 +0,0 @@ -import unittest -from unittest import mock - -from mkdocs.tests.base import load_config -from mkdocs.commands import gh_deploy -from mkdocs import __version__ - - -class TestGitHubDeploy(unittest.TestCase): - - def assert_mock_called_once(self, mock): - """assert that the mock was called only once. - - The `mock.assert_called_once()` method was added in PY36. - TODO: Remove this when PY35 support is dropped. - """ - try: - mock.assert_called_once() - except AttributeError: - if not mock.call_count == 1: - msg = ("Expected '%s' to have been called once. Called %s times." % - (mock._mock_name or 'mock', self.call_count)) - raise AssertionError(msg) - - @mock.patch('subprocess.Popen') - def test_is_cwd_git_repo(self, mock_popeno): - - mock_popeno().wait.return_value = 0 - - self.assertTrue(gh_deploy._is_cwd_git_repo()) - - @mock.patch('subprocess.Popen') - def test_is_cwd_not_git_repo(self, mock_popeno): - - mock_popeno().wait.return_value = 1 - - self.assertFalse(gh_deploy._is_cwd_git_repo()) - - @mock.patch('subprocess.Popen') - def test_get_current_sha(self, mock_popeno): - - mock_popeno().communicate.return_value = (b'6d98394\n', b'') - - self.assertEqual(gh_deploy._get_current_sha('.'), '6d98394') - - @mock.patch('subprocess.Popen') - def test_get_remote_url_ssh(self, mock_popeno): - - mock_popeno().communicate.return_value = ( - b'git@github.com:mkdocs/mkdocs.git\n', - b'' - ) - - expected = ('git@', 'mkdocs/mkdocs.git') - self.assertEqual(expected, gh_deploy._get_remote_url('origin')) - - @mock.patch('subprocess.Popen') - def test_get_remote_url_http(self, mock_popeno): - - mock_popeno().communicate.return_value = ( - b'https://github.com/mkdocs/mkdocs.git\n', - b'' - ) - - expected = ('https://', 'mkdocs/mkdocs.git') - self.assertEqual(expected, gh_deploy._get_remote_url('origin')) - - @mock.patch('subprocess.Popen') - def test_get_remote_url_enterprise(self, mock_popeno): - - mock_popeno().communicate.return_value = ( - b'https://notgh.com/mkdocs/mkdocs.git\n', - b'' - ) - - expected = (None, None) - self.assertEqual(expected, gh_deploy._get_remote_url('origin')) - - @mock.patch('mkdocs.commands.gh_deploy._is_cwd_git_repo', return_value=True) - @mock.patch('mkdocs.commands.gh_deploy._get_current_sha', return_value='shashas') - @mock.patch('mkdocs.commands.gh_deploy._get_remote_url', return_value=(None, None)) - @mock.patch('mkdocs.commands.gh_deploy._check_version') - @mock.patch('mkdocs.commands.gh_deploy.ghp_import.ghp_import', return_value=(True, '')) - def test_deploy(self, mock_import, check_version, get_remote, get_sha, is_repo): - - config = load_config( - remote_branch='test', - ) - gh_deploy.gh_deploy(config) - - @mock.patch('mkdocs.commands.gh_deploy._is_cwd_git_repo', return_value=True) - @mock.patch('mkdocs.commands.gh_deploy._get_current_sha', return_value='shashas') - @mock.patch('mkdocs.commands.gh_deploy._get_remote_url', return_value=(None, None)) - @mock.patch('mkdocs.commands.gh_deploy._check_version') - @mock.patch('mkdocs.commands.gh_deploy.ghp_import.ghp_import', return_value=(True, '')) - @mock.patch('os.path.isfile', return_value=False) - def test_deploy_no_cname(self, mock_isfile, mock_import, check_version, get_remote, - get_sha, is_repo): - - config = load_config( - remote_branch='test', - ) - gh_deploy.gh_deploy(config) - - @mock.patch('mkdocs.commands.gh_deploy._is_cwd_git_repo', return_value=True) - @mock.patch('mkdocs.commands.gh_deploy._get_current_sha', return_value='shashas') - @mock.patch('mkdocs.commands.gh_deploy._get_remote_url', return_value=( - 'git@', 'mkdocs/mkdocs.git')) - @mock.patch('mkdocs.commands.gh_deploy._check_version') - @mock.patch('mkdocs.commands.gh_deploy.ghp_import.ghp_import', return_value=(True, '')) - def test_deploy_hostname(self, mock_import, check_version, get_remote, get_sha, is_repo): - - config = load_config( - remote_branch='test', - ) - gh_deploy.gh_deploy(config) - - @mock.patch('mkdocs.commands.gh_deploy._is_cwd_git_repo', return_value=True) - @mock.patch('mkdocs.commands.gh_deploy._get_current_sha', return_value='shashas') - @mock.patch('mkdocs.commands.gh_deploy._get_remote_url', return_value=(None, None)) - @mock.patch('mkdocs.commands.gh_deploy._check_version') - @mock.patch('mkdocs.commands.gh_deploy.ghp_import.ghp_import', return_value=(True, '')) - def test_deploy_ignore_version_default(self, mock_import, check_version, get_remote, get_sha, is_repo): - - config = load_config( - remote_branch='test', - ) - gh_deploy.gh_deploy(config) - self.assert_mock_called_once(check_version) - - @mock.patch('mkdocs.commands.gh_deploy._is_cwd_git_repo', return_value=True) - @mock.patch('mkdocs.commands.gh_deploy._get_current_sha', return_value='shashas') - @mock.patch('mkdocs.commands.gh_deploy._get_remote_url', return_value=(None, None)) - @mock.patch('mkdocs.commands.gh_deploy._check_version') - @mock.patch('mkdocs.commands.gh_deploy.ghp_import.ghp_import', return_value=(True, '')) - def test_deploy_ignore_version(self, mock_import, check_version, get_remote, get_sha, is_repo): - - config = load_config( - remote_branch='test', - ) - gh_deploy.gh_deploy(config, ignore_version=True) - check_version.assert_not_called() - - @mock.patch('mkdocs.commands.gh_deploy._is_cwd_git_repo', return_value=True) - @mock.patch('mkdocs.commands.gh_deploy._get_current_sha', return_value='shashas') - @mock.patch('mkdocs.commands.gh_deploy._check_version') - @mock.patch('mkdocs.utils.ghp_import.ghp_import') - @mock.patch('mkdocs.commands.gh_deploy.log') - def test_deploy_error(self, mock_log, mock_import, check_version, get_sha, is_repo): - error_string = 'TestError123' - mock_import.return_value = (False, error_string) - - config = load_config( - remote_branch='test', - ) - - self.assertRaises(SystemExit, gh_deploy.gh_deploy, config) - mock_log.error.assert_called_once_with('Failed to deploy to GitHub with error: \n%s', - error_string) - - -class TestGitHubDeployLogs(unittest.TestCase): - - @mock.patch('subprocess.Popen') - def test_mkdocs_newer(self, mock_popeno): - - mock_popeno().communicate.return_value = (b'Deployed 12345678 with MkDocs version: 0.1.2\n', b'') - - with self.assertLogs('mkdocs', level='INFO') as cm: - gh_deploy._check_version('gh-pages') - self.assertEqual( - cm.output, ['INFO:mkdocs.commands.gh_deploy:Previous deployment was done with MkDocs ' - 'version 0.1.2; you are deploying with a newer version ({})'.format(__version__)] - ) - - @mock.patch('subprocess.Popen') - def test_mkdocs_older(self, mock_popeno): - - mock_popeno().communicate.return_value = (b'Deployed 12345678 with MkDocs version: 10.1.2\n', b'') - - with self.assertLogs('mkdocs', level='ERROR') as cm: - self.assertRaises(SystemExit, gh_deploy._check_version, 'gh-pages') - self.assertEqual( - cm.output, ['ERROR:mkdocs.commands.gh_deploy:Deployment terminated: Previous deployment was made with ' - 'MkDocs version 10.1.2; you are attempting to deploy with an older version ({}). Use ' - '--ignore-version to deploy anyway.'.format(__version__)] - ) - - @mock.patch('subprocess.Popen') - def test_version_unknown(self, mock_popeno): - - mock_popeno().communicate.return_value = (b'No version specified\n', b'') - - with self.assertLogs('mkdocs', level='WARNING') as cm: - gh_deploy._check_version('gh-pages') - self.assertEqual( - cm.output, - ['WARNING:mkdocs.commands.gh_deploy:Version check skipped: No version specified in previous deployment.'] - ) diff --git a/mkdocs/tests/integration.py b/mkdocs/tests/integration.py deleted file mode 100644 index f4a3f2aba..000000000 --- a/mkdocs/tests/integration.py +++ /dev/null @@ -1,70 +0,0 @@ -""" -# MkDocs Integration tests - -This is a simple integration test that builds the MkDocs -documentation against all of the builtin themes. - -From the root of the MkDocs git repo, use: - - python -m mkdocs.tests.integration --help - - -TODOs - - Build with different configuration options. - - Build documentation other than just MkDocs as it is relatively simple. -""" - - -import click -import logging -import os -import subprocess - -from mkdocs import utils - -log = logging.getLogger('mkdocs') - -DIR = os.path.dirname(__file__) -MKDOCS_CONFIG = os.path.abspath(os.path.join(DIR, '../../mkdocs.yml')) -MKDOCS_THEMES = utils.get_theme_names() -TEST_PROJECTS = os.path.abspath(os.path.join(DIR, 'integration')) - - -@click.command() -@click.option('--output', - help="The output directory to use when building themes", - type=click.Path(file_okay=False, writable=True), - required=True) -def main(output=None): - - log.propagate = False - stream = logging.StreamHandler() - formatter = logging.Formatter( - "\033[1m\033[1;32m *** %(message)s *** \033[0m") - stream.setFormatter(formatter) - log.addHandler(stream) - log.setLevel(logging.DEBUG) - - base_cmd = ['mkdocs', 'build', '-s', '-v', '--site-dir', ] - - log.debug("Building installed themes.") - for theme in sorted(MKDOCS_THEMES): - log.debug("Building theme: {}".format(theme)) - project_dir = os.path.dirname(MKDOCS_CONFIG) - out = os.path.join(output, theme) - command = base_cmd + [out, '--theme', theme] - subprocess.check_call(command, cwd=project_dir) - - log.debug("Building test projects.") - for project in os.listdir(TEST_PROJECTS): - log.debug("Building test project: {}".format(project)) - project_dir = os.path.join(TEST_PROJECTS, project) - out = os.path.join(output, project) - command = base_cmd + [out, ] - subprocess.check_call(command, cwd=project_dir) - - log.debug("Theme and integration builds are in {}".format(output)) - - -if __name__ == '__main__': - main() diff --git a/mkdocs/tests/integration/complicated_config/documentation/custom.html b/mkdocs/tests/integration/complicated_config/documentation/custom.html deleted file mode 100644 index edef317c2..000000000 --- a/mkdocs/tests/integration/complicated_config/documentation/custom.html +++ /dev/null @@ -1,9 +0,0 @@ - - - -Content
") - - self.assertEqual(stripper.data, ["Testing", "Content"]) - - def test_content_parser(self): - - parser = search_index.ContentParser() - - parser.feed('Content 1
-Content 2
-Content 3
- """ - - cfg = load_config() - pages = [ - Page('Home', File('index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']), cfg), - Page('About', File('about.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']), cfg) - ] - - md = dedent(""" - # Heading 1 - ## Heading 2 - ### Heading 3 - """) - toc = get_toc(get_markdown_toc(md)) - - full_content = ''.join("""Heading{0}Content{0}""".format(i) for i in range(1, 4)) - - for page in pages: - # Fake page.read_source() and page.render() - page.markdown = md - page.toc = toc - page.content = html_content - - index = search_index.SearchIndex() - index.add_entry_from_context(page) - - self.assertEqual(len(index._entries), 4) - - loc = page.url - - self.assertEqual(index._entries[0]['title'], page.title) - self.assertEqual(strip_whitespace(index._entries[0]['text']), full_content) - self.assertEqual(index._entries[0]['location'], loc) - - self.assertEqual(index._entries[1]['title'], "Heading 1") - self.assertEqual(index._entries[1]['text'], "Content 1") - self.assertEqual(index._entries[1]['location'], "{}#heading-1".format(loc)) - - self.assertEqual(index._entries[2]['title'], "Heading 2") - self.assertEqual(strip_whitespace(index._entries[2]['text']), "Content2") - self.assertEqual(index._entries[2]['location'], "{}#heading-2".format(loc)) - - self.assertEqual(index._entries[3]['title'], "Heading 3") - self.assertEqual(strip_whitespace(index._entries[3]['text']), "Content3") - self.assertEqual(index._entries[3]['location'], "{}#heading-3".format(loc)) - - @mock.patch('subprocess.Popen', autospec=True) - def test_prebuild_index(self, mock_popen): - # See https://stackoverflow.com/a/36501078/866026 - mock_popen.return_value = mock.Mock() - mock_popen_obj = mock_popen.return_value - mock_popen_obj.communicate.return_value = ('{"mock": "index"}', None) - mock_popen_obj.returncode = 0 - - index = search_index.SearchIndex(prebuild_index=True) - expected = { - 'docs': [], - 'config': {'prebuild_index': True}, - 'index': {'mock': 'index'} - } - result = json.loads(index.generate_search_index()) - self.assertEqual(mock_popen.call_count, 1) - self.assertEqual(mock_popen_obj.communicate.call_count, 1) - self.assertEqual(result, expected) - - @mock.patch('subprocess.Popen', autospec=True) - def test_prebuild_index_returns_error(self, mock_popen): - # See https://stackoverflow.com/a/36501078/866026 - mock_popen.return_value = mock.Mock() - mock_popen_obj = mock_popen.return_value - mock_popen_obj.communicate.return_value = ('', 'Some Error') - mock_popen_obj.returncode = 0 - - index = search_index.SearchIndex(prebuild_index=True) - expected = { - 'docs': [], - 'config': {'prebuild_index': True} - } - result = json.loads(index.generate_search_index()) - self.assertEqual(mock_popen.call_count, 1) - self.assertEqual(mock_popen_obj.communicate.call_count, 1) - self.assertEqual(result, expected) - - @mock.patch('subprocess.Popen', autospec=True) - def test_prebuild_index_raises_ioerror(self, mock_popen): - # See https://stackoverflow.com/a/36501078/866026 - mock_popen.return_value = mock.Mock() - mock_popen_obj = mock_popen.return_value - mock_popen_obj.communicate.side_effect = OSError - mock_popen_obj.returncode = 1 - - index = search_index.SearchIndex(prebuild_index=True) - expected = { - 'docs': [], - 'config': {'prebuild_index': True} - } - result = json.loads(index.generate_search_index()) - self.assertEqual(mock_popen.call_count, 1) - self.assertEqual(mock_popen_obj.communicate.call_count, 1) - self.assertEqual(result, expected) - - @mock.patch('subprocess.Popen', autospec=True, side_effect=OSError) - def test_prebuild_index_raises_oserror(self, mock_popen): - # See https://stackoverflow.com/a/36501078/866026 - mock_popen.return_value = mock.Mock() - mock_popen_obj = mock_popen.return_value - mock_popen_obj.communicate.return_value = ('', '') - mock_popen_obj.returncode = 0 - - index = search_index.SearchIndex(prebuild_index=True) - expected = { - 'docs': [], - 'config': {'prebuild_index': True} - } - result = json.loads(index.generate_search_index()) - self.assertEqual(mock_popen.call_count, 1) - self.assertEqual(mock_popen_obj.communicate.call_count, 0) - self.assertEqual(result, expected) - - @mock.patch('subprocess.Popen', autospec=True) - def test_prebuild_index_false(self, mock_popen): - # See https://stackoverflow.com/a/36501078/866026 - mock_popen.return_value = mock.Mock() - mock_popen_obj = mock_popen.return_value - mock_popen_obj.communicate.return_value = ('', '') - mock_popen_obj.returncode = 0 - - index = search_index.SearchIndex(prebuild_index=False) - expected = { - 'docs': [], - 'config': {'prebuild_index': False} - } - result = json.loads(index.generate_search_index()) - self.assertEqual(mock_popen.call_count, 0) - self.assertEqual(mock_popen_obj.communicate.call_count, 0) - self.assertEqual(result, expected) - - @mock.patch('mkdocs.contrib.search.search_index.lunr', autospec=True) - def test_prebuild_index_python(self, mock_lunr): - mock_lunr.return_value.serialize.return_value = {'mock': 'index'} - index = search_index.SearchIndex(prebuild_index='python', lang='en') - expected = { - 'docs': [], - 'config': {'prebuild_index': 'python', 'lang': 'en'}, - 'index': {'mock': 'index'} - } - result = json.loads(index.generate_search_index()) - self.assertEqual(mock_lunr.call_count, 1) - self.assertEqual(result, expected) - - @mock.patch('subprocess.Popen', autospec=True) - def test_prebuild_index_node(self, mock_popen): - # See https://stackoverflow.com/a/36501078/866026 - mock_popen.return_value = mock.Mock() - mock_popen_obj = mock_popen.return_value - mock_popen_obj.communicate.return_value = ('{"mock": "index"}', None) - mock_popen_obj.returncode = 0 - - index = search_index.SearchIndex(prebuild_index='node') - expected = { - 'docs': [], - 'config': {'prebuild_index': 'node'}, - 'index': {'mock': 'index'} - } - result = json.loads(index.generate_search_index()) - self.assertEqual(mock_popen.call_count, 1) - self.assertEqual(mock_popen_obj.communicate.call_count, 1) - self.assertEqual(result, expected) diff --git a/mkdocs/tests/structure/__init__.py b/mkdocs/tests/structure/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/mkdocs/tests/structure/file_tests.py b/mkdocs/tests/structure/file_tests.py deleted file mode 100644 index 5712ed668..000000000 --- a/mkdocs/tests/structure/file_tests.py +++ /dev/null @@ -1,648 +0,0 @@ -import unittest -import os -from unittest import mock - -from mkdocs.structure.files import Files, File, get_files, _sort_files, _filter_paths -from mkdocs.tests.base import load_config, tempdir, PathAssertionMixin - - -class TestFiles(PathAssertionMixin, unittest.TestCase): - - def test_file_eq(self): - file = File('a.md', '/path/to/docs', '/path/to/site', use_directory_urls=False) - self.assertTrue(file == File('a.md', '/path/to/docs', '/path/to/site', use_directory_urls=False)) - - def test_file_ne(self): - file = File('a.md', '/path/to/docs', '/path/to/site', use_directory_urls=False) - # Different filename - self.assertTrue(file != File('b.md', '/path/to/docs', '/path/to/site', use_directory_urls=False)) - # Different src_path - self.assertTrue(file != File('a.md', '/path/to/other', '/path/to/site', use_directory_urls=False)) - # Different URL - self.assertTrue(file != File('a.md', '/path/to/docs', '/path/to/site', use_directory_urls=True)) - - def test_sort_files(self): - self.assertEqual( - _sort_files(['b.md', 'bb.md', 'a.md', 'index.md', 'aa.md']), - ['index.md', 'a.md', 'aa.md', 'b.md', 'bb.md'] - ) - - self.assertEqual( - _sort_files(['b.md', 'index.html', 'a.md', 'index.md']), - ['index.html', 'index.md', 'a.md', 'b.md'] - ) - - self.assertEqual( - _sort_files(['a.md', 'index.md', 'b.md', 'index.html']), - ['index.md', 'index.html', 'a.md', 'b.md'] - ) - - self.assertEqual( - _sort_files(['.md', '_.md', 'a.md', 'index.md', '1.md']), - ['index.md', '.md', '1.md', '_.md', 'a.md'] - ) - - self.assertEqual( - _sort_files(['a.md', 'b.md', 'a.md']), - ['a.md', 'a.md', 'b.md'] - ) - - self.assertEqual( - _sort_files(['A.md', 'B.md', 'README.md']), - ['README.md', 'A.md', 'B.md'] - ) - - def test_md_file(self): - f = File('foo.md', '/path/to/docs', '/path/to/site', use_directory_urls=False) - self.assertPathsEqual(f.src_path, 'foo.md') - self.assertPathsEqual(f.abs_src_path, '/path/to/docs/foo.md') - self.assertPathsEqual(f.dest_path, 'foo.html') - self.assertPathsEqual(f.abs_dest_path, '/path/to/site/foo.html') - self.assertEqual(f.url, 'foo.html') - self.assertEqual(f.name, 'foo') - self.assertTrue(f.is_documentation_page()) - self.assertFalse(f.is_static_page()) - self.assertFalse(f.is_media_file()) - self.assertFalse(f.is_javascript()) - self.assertFalse(f.is_css()) - - def test_md_file_use_directory_urls(self): - f = File('foo.md', '/path/to/docs', '/path/to/site', use_directory_urls=True) - self.assertPathsEqual(f.src_path, 'foo.md') - self.assertPathsEqual(f.abs_src_path, '/path/to/docs/foo.md') - self.assertPathsEqual(f.dest_path, 'foo/index.html') - self.assertPathsEqual(f.abs_dest_path, '/path/to/site/foo/index.html') - self.assertEqual(f.url, 'foo/') - self.assertEqual(f.name, 'foo') - self.assertTrue(f.is_documentation_page()) - self.assertFalse(f.is_static_page()) - self.assertFalse(f.is_media_file()) - self.assertFalse(f.is_javascript()) - self.assertFalse(f.is_css()) - - def test_md_file_nested(self): - f = File('foo/bar.md', '/path/to/docs', '/path/to/site', use_directory_urls=False) - self.assertPathsEqual(f.src_path, 'foo/bar.md') - self.assertPathsEqual(f.abs_src_path, '/path/to/docs/foo/bar.md') - self.assertPathsEqual(f.dest_path, 'foo/bar.html') - self.assertPathsEqual(f.abs_dest_path, '/path/to/site/foo/bar.html') - self.assertEqual(f.url, 'foo/bar.html') - self.assertEqual(f.name, 'bar') - self.assertTrue(f.is_documentation_page()) - self.assertFalse(f.is_static_page()) - self.assertFalse(f.is_media_file()) - self.assertFalse(f.is_javascript()) - self.assertFalse(f.is_css()) - - def test_md_file_nested_use_directory_urls(self): - f = File('foo/bar.md', '/path/to/docs', '/path/to/site', use_directory_urls=True) - self.assertPathsEqual(f.src_path, 'foo/bar.md') - self.assertPathsEqual(f.abs_src_path, '/path/to/docs/foo/bar.md') - self.assertPathsEqual(f.dest_path, 'foo/bar/index.html') - self.assertPathsEqual(f.abs_dest_path, '/path/to/site/foo/bar/index.html') - self.assertEqual(f.url, 'foo/bar/') - self.assertEqual(f.name, 'bar') - self.assertTrue(f.is_documentation_page()) - self.assertFalse(f.is_static_page()) - self.assertFalse(f.is_media_file()) - self.assertFalse(f.is_javascript()) - self.assertFalse(f.is_css()) - - def test_md_index_file(self): - f = File('index.md', '/path/to/docs', '/path/to/site', use_directory_urls=False) - self.assertPathsEqual(f.src_path, 'index.md') - self.assertPathsEqual(f.abs_src_path, '/path/to/docs/index.md') - self.assertPathsEqual(f.dest_path, 'index.html') - self.assertPathsEqual(f.abs_dest_path, '/path/to/site/index.html') - self.assertEqual(f.url, 'index.html') - self.assertEqual(f.name, 'index') - self.assertTrue(f.is_documentation_page()) - self.assertFalse(f.is_static_page()) - self.assertFalse(f.is_media_file()) - self.assertFalse(f.is_javascript()) - self.assertFalse(f.is_css()) - - def test_md_index_file_use_directory_urls(self): - f = File('index.md', '/path/to/docs', '/path/to/site', use_directory_urls=True) - self.assertPathsEqual(f.src_path, 'index.md') - self.assertPathsEqual(f.abs_src_path, '/path/to/docs/index.md') - self.assertPathsEqual(f.dest_path, 'index.html') - self.assertPathsEqual(f.abs_dest_path, '/path/to/site/index.html') - self.assertEqual(f.url, '.') - self.assertEqual(f.name, 'index') - self.assertTrue(f.is_documentation_page()) - self.assertFalse(f.is_static_page()) - self.assertFalse(f.is_media_file()) - self.assertFalse(f.is_javascript()) - self.assertFalse(f.is_css()) - - def test_md_index_file_nested(self): - f = File('foo/index.md', '/path/to/docs', '/path/to/site', use_directory_urls=False) - self.assertPathsEqual(f.src_path, 'foo/index.md') - self.assertPathsEqual(f.abs_src_path, '/path/to/docs/foo/index.md') - self.assertPathsEqual(f.dest_path, 'foo/index.html') - self.assertPathsEqual(f.abs_dest_path, '/path/to/site/foo/index.html') - self.assertEqual(f.url, 'foo/index.html') - self.assertEqual(f.name, 'index') - self.assertTrue(f.is_documentation_page()) - self.assertFalse(f.is_static_page()) - self.assertFalse(f.is_media_file()) - self.assertFalse(f.is_javascript()) - self.assertFalse(f.is_css()) - - def test_md_index_file_nested_use_directory_urls(self): - f = File('foo/index.md', '/path/to/docs', '/path/to/site', use_directory_urls=True) - self.assertPathsEqual(f.src_path, 'foo/index.md') - self.assertPathsEqual(f.abs_src_path, '/path/to/docs/foo/index.md') - self.assertPathsEqual(f.dest_path, 'foo/index.html') - self.assertPathsEqual(f.abs_dest_path, '/path/to/site/foo/index.html') - self.assertEqual(f.url, 'foo/') - self.assertEqual(f.name, 'index') - self.assertTrue(f.is_documentation_page()) - self.assertFalse(f.is_static_page()) - self.assertFalse(f.is_media_file()) - self.assertFalse(f.is_javascript()) - self.assertFalse(f.is_css()) - - def test_static_file(self): - f = File('foo/bar.html', '/path/to/docs', '/path/to/site', use_directory_urls=False) - self.assertPathsEqual(f.src_path, 'foo/bar.html') - self.assertPathsEqual(f.abs_src_path, '/path/to/docs/foo/bar.html') - self.assertPathsEqual(f.dest_path, 'foo/bar.html') - self.assertPathsEqual(f.abs_dest_path, '/path/to/site/foo/bar.html') - self.assertEqual(f.url, 'foo/bar.html') - self.assertEqual(f.name, 'bar') - self.assertFalse(f.is_documentation_page()) - self.assertTrue(f.is_static_page()) - self.assertFalse(f.is_media_file()) - self.assertFalse(f.is_javascript()) - self.assertFalse(f.is_css()) - - def test_static_file_use_directory_urls(self): - f = File('foo/bar.html', '/path/to/docs', '/path/to/site', use_directory_urls=True) - self.assertPathsEqual(f.src_path, 'foo/bar.html') - self.assertPathsEqual(f.abs_src_path, '/path/to/docs/foo/bar.html') - self.assertPathsEqual(f.dest_path, 'foo/bar.html') - self.assertPathsEqual(f.abs_dest_path, '/path/to/site/foo/bar.html') - self.assertEqual(f.url, 'foo/bar.html') - self.assertEqual(f.name, 'bar') - self.assertFalse(f.is_documentation_page()) - self.assertTrue(f.is_static_page()) - self.assertFalse(f.is_media_file()) - self.assertFalse(f.is_javascript()) - self.assertFalse(f.is_css()) - - def test_media_file(self): - f = File('foo/bar.jpg', '/path/to/docs', '/path/to/site', use_directory_urls=False) - self.assertPathsEqual(f.src_path, 'foo/bar.jpg') - self.assertPathsEqual(f.abs_src_path, '/path/to/docs/foo/bar.jpg') - self.assertPathsEqual(f.dest_path, 'foo/bar.jpg') - self.assertPathsEqual(f.abs_dest_path, '/path/to/site/foo/bar.jpg') - self.assertEqual(f.url, 'foo/bar.jpg') - self.assertEqual(f.name, 'bar') - self.assertFalse(f.is_documentation_page()) - self.assertFalse(f.is_static_page()) - self.assertTrue(f.is_media_file()) - self.assertFalse(f.is_javascript()) - self.assertFalse(f.is_css()) - - def test_media_file_use_directory_urls(self): - f = File('foo/bar.jpg', '/path/to/docs', '/path/to/site', use_directory_urls=True) - self.assertPathsEqual(f.src_path, 'foo/bar.jpg') - self.assertPathsEqual(f.abs_src_path, '/path/to/docs/foo/bar.jpg') - self.assertPathsEqual(f.dest_path, 'foo/bar.jpg') - self.assertPathsEqual(f.abs_dest_path, '/path/to/site/foo/bar.jpg') - self.assertEqual(f.url, 'foo/bar.jpg') - self.assertEqual(f.name, 'bar') - self.assertFalse(f.is_documentation_page()) - self.assertFalse(f.is_static_page()) - self.assertTrue(f.is_media_file()) - self.assertFalse(f.is_javascript()) - self.assertFalse(f.is_css()) - - def test_javascript_file(self): - f = File('foo/bar.js', '/path/to/docs', '/path/to/site', use_directory_urls=False) - self.assertPathsEqual(f.src_path, 'foo/bar.js') - self.assertPathsEqual(f.abs_src_path, '/path/to/docs/foo/bar.js') - self.assertPathsEqual(f.dest_path, 'foo/bar.js') - self.assertPathsEqual(f.abs_dest_path, '/path/to/site/foo/bar.js') - self.assertEqual(f.url, 'foo/bar.js') - self.assertEqual(f.name, 'bar') - self.assertFalse(f.is_documentation_page()) - self.assertFalse(f.is_static_page()) - self.assertTrue(f.is_media_file()) - self.assertTrue(f.is_javascript()) - self.assertFalse(f.is_css()) - - def test_javascript_file_use_directory_urls(self): - f = File('foo/bar.js', '/path/to/docs', '/path/to/site', use_directory_urls=True) - self.assertPathsEqual(f.src_path, 'foo/bar.js') - self.assertPathsEqual(f.abs_src_path, '/path/to/docs/foo/bar.js') - self.assertPathsEqual(f.dest_path, 'foo/bar.js') - self.assertPathsEqual(f.abs_dest_path, '/path/to/site/foo/bar.js') - self.assertEqual(f.url, 'foo/bar.js') - self.assertEqual(f.name, 'bar') - self.assertFalse(f.is_documentation_page()) - self.assertFalse(f.is_static_page()) - self.assertTrue(f.is_media_file()) - self.assertTrue(f.is_javascript()) - self.assertFalse(f.is_css()) - - def test_css_file(self): - f = File('foo/bar.css', '/path/to/docs', '/path/to/site', use_directory_urls=False) - self.assertPathsEqual(f.src_path, 'foo/bar.css') - self.assertPathsEqual(f.abs_src_path, '/path/to/docs/foo/bar.css') - self.assertPathsEqual(f.dest_path, 'foo/bar.css') - self.assertPathsEqual(f.abs_dest_path, '/path/to/site/foo/bar.css') - self.assertEqual(f.url, 'foo/bar.css') - self.assertEqual(f.name, 'bar') - self.assertFalse(f.is_documentation_page()) - self.assertFalse(f.is_static_page()) - self.assertTrue(f.is_media_file()) - self.assertFalse(f.is_javascript()) - self.assertTrue(f.is_css()) - - def test_css_file_use_directory_urls(self): - f = File('foo/bar.css', '/path/to/docs', '/path/to/site', use_directory_urls=True) - self.assertPathsEqual(f.src_path, 'foo/bar.css') - self.assertPathsEqual(f.abs_src_path, '/path/to/docs/foo/bar.css') - self.assertPathsEqual(f.dest_path, 'foo/bar.css') - self.assertPathsEqual(f.abs_dest_path, '/path/to/site/foo/bar.css') - self.assertEqual(f.url, 'foo/bar.css') - self.assertEqual(f.name, 'bar') - self.assertFalse(f.is_documentation_page()) - self.assertFalse(f.is_static_page()) - self.assertTrue(f.is_media_file()) - self.assertFalse(f.is_javascript()) - self.assertTrue(f.is_css()) - - def test_file_name_with_space(self): - f = File('foo bar.md', '/path/to/docs', '/path/to/site', use_directory_urls=False) - self.assertPathsEqual(f.src_path, 'foo bar.md') - self.assertPathsEqual(f.abs_src_path, '/path/to/docs/foo bar.md') - self.assertPathsEqual(f.dest_path, 'foo bar.html') - self.assertPathsEqual(f.abs_dest_path, '/path/to/site/foo bar.html') - self.assertEqual(f.url, 'foo%20bar.html') - self.assertEqual(f.name, 'foo bar') - - def test_files(self): - fs = [ - File('index.md', '/path/to/docs', '/path/to/site', use_directory_urls=True), - File('foo/bar.md', '/path/to/docs', '/path/to/site', use_directory_urls=True), - File('foo/bar.html', '/path/to/docs', '/path/to/site', use_directory_urls=True), - File('foo/bar.jpg', '/path/to/docs', '/path/to/site', use_directory_urls=True), - File('foo/bar.js', '/path/to/docs', '/path/to/site', use_directory_urls=True), - File('foo/bar.css', '/path/to/docs', '/path/to/site', use_directory_urls=True) - ] - files = Files(fs) - self.assertEqual([f for f in files], fs) - self.assertEqual(len(files), 6) - self.assertEqual(files.documentation_pages(), [fs[0], fs[1]]) - self.assertEqual(files.static_pages(), [fs[2]]) - self.assertEqual(files.media_files(), [fs[3], fs[4], fs[5]]) - self.assertEqual(files.javascript_files(), [fs[4]]) - self.assertEqual(files.css_files(), [fs[5]]) - self.assertEqual(files.get_file_from_path('foo/bar.jpg'), fs[3]) - self.assertEqual(files.get_file_from_path('foo/bar.jpg'), fs[3]) - self.assertEqual(files.get_file_from_path('missing.jpg'), None) - self.assertTrue(fs[2].src_path in files) - self.assertTrue(fs[2].src_path in files) - extra_file = File('extra.md', '/path/to/docs', '/path/to/site', use_directory_urls=True) - self.assertFalse(extra_file.src_path in files) - files.append(extra_file) - self.assertEqual(len(files), 7) - self.assertTrue(extra_file.src_path in files) - self.assertEqual(files.documentation_pages(), [fs[0], fs[1], extra_file]) - - @tempdir(files=[ - 'favicon.ico', - 'index.md' - ]) - @tempdir(files=[ - 'base.html', - 'favicon.ico', - 'style.css', - 'foo.md', - 'README', - '.ignore.txt', - '.ignore/file.txt', - 'foo/.ignore.txt', - 'foo/.ignore/file.txt' - ]) - def test_add_files_from_theme(self, tdir, ddir): - config = load_config(docs_dir=ddir, theme={'name': None, 'custom_dir': tdir}) - env = config['theme'].get_env() - files = get_files(config) - self.assertEqual( - [file.src_path for file in files], - ['index.md', 'favicon.ico'] - ) - files.add_files_from_theme(env, config) - self.assertEqual( - [file.src_path for file in files], - ['index.md', 'favicon.ico', 'style.css'] - ) - # Ensure theme file does not override docs_dir file - self.assertEqual( - files.get_file_from_path('favicon.ico').abs_src_path, - os.path.normpath(os.path.join(ddir, 'favicon.ico')) - ) - - def test_filter_paths(self): - # Root level file - self.assertFalse(_filter_paths('foo.md', 'foo.md', False, ['bar.md'])) - self.assertTrue(_filter_paths('foo.md', 'foo.md', False, ['foo.md'])) - - # Nested file - self.assertFalse(_filter_paths('foo.md', 'baz/foo.md', False, ['bar.md'])) - self.assertTrue(_filter_paths('foo.md', 'baz/foo.md', False, ['foo.md'])) - - # Wildcard - self.assertFalse(_filter_paths('foo.md', 'foo.md', False, ['*.txt'])) - self.assertTrue(_filter_paths('foo.md', 'foo.md', False, ['*.md'])) - - # Root level dir - self.assertFalse(_filter_paths('bar', 'bar', True, ['/baz'])) - self.assertFalse(_filter_paths('bar', 'bar', True, ['/baz/'])) - self.assertTrue(_filter_paths('bar', 'bar', True, ['/bar'])) - self.assertTrue(_filter_paths('bar', 'bar', True, ['/bar/'])) - - # Nested dir - self.assertFalse(_filter_paths('bar', 'foo/bar', True, ['/bar'])) - self.assertFalse(_filter_paths('bar', 'foo/bar', True, ['/bar/'])) - self.assertTrue(_filter_paths('bar', 'foo/bar', True, ['bar/'])) - - # Files that look like dirs (no extension). Note that `is_dir` is `False`. - self.assertFalse(_filter_paths('bar', 'bar', False, ['bar/'])) - self.assertFalse(_filter_paths('bar', 'foo/bar', False, ['bar/'])) - - def test_get_relative_url_use_directory_urls(self): - to_files = [ - 'index.md', - 'foo/index.md', - 'foo/bar/index.md', - 'foo/bar/baz/index.md', - 'foo.md', - 'foo/bar.md', - 'foo/bar/baz.md' - ] - - to_file_urls = [ - '.', - 'foo/', - 'foo/bar/', - 'foo/bar/baz/', - 'foo/', - 'foo/bar/', - 'foo/bar/baz/' - ] - - from_file = File('img.jpg', '/path/to/docs', '/path/to/site', use_directory_urls=True) - expected = [ - 'img.jpg', # img.jpg relative to . - '../img.jpg', # img.jpg relative to foo/ - '../../img.jpg', # img.jpg relative to foo/bar/ - '../../../img.jpg', # img.jpg relative to foo/bar/baz/ - '../img.jpg', # img.jpg relative to foo - '../../img.jpg', # img.jpg relative to foo/bar - '../../../img.jpg' # img.jpg relative to foo/bar/baz - ] - - for i, filename in enumerate(to_files): - file = File(filename, '/path/to/docs', '/path/to/site', use_directory_urls=True) - self.assertEqual(from_file.url, 'img.jpg') - self.assertEqual(file.url, to_file_urls[i]) - self.assertEqual(from_file.url_relative_to(file.url), expected[i]) - self.assertEqual(from_file.url_relative_to(file), expected[i]) - - from_file = File('foo/img.jpg', '/path/to/docs', '/path/to/site', use_directory_urls=True) - expected = [ - 'foo/img.jpg', # foo/img.jpg relative to . - 'img.jpg', # foo/img.jpg relative to foo/ - '../img.jpg', # foo/img.jpg relative to foo/bar/ - '../../img.jpg', # foo/img.jpg relative to foo/bar/baz/ - 'img.jpg', # foo/img.jpg relative to foo - '../img.jpg', # foo/img.jpg relative to foo/bar - '../../img.jpg' # foo/img.jpg relative to foo/bar/baz - ] - - for i, filename in enumerate(to_files): - file = File(filename, '/path/to/docs', '/path/to/site', use_directory_urls=True) - self.assertEqual(from_file.url, 'foo/img.jpg') - self.assertEqual(file.url, to_file_urls[i]) - self.assertEqual(from_file.url_relative_to(file.url), expected[i]) - self.assertEqual(from_file.url_relative_to(file), expected[i]) - - from_file = File('index.html', '/path/to/docs', '/path/to/site', use_directory_urls=True) - expected = [ - '.', # . relative to . - '..', # . relative to foo/ - '../..', # . relative to foo/bar/ - '../../..', # . relative to foo/bar/baz/ - '..', # . relative to foo - '../..', # . relative to foo/bar - '../../..' # . relative to foo/bar/baz - ] - - for i, filename in enumerate(to_files): - file = File(filename, '/path/to/docs', '/path/to/site', use_directory_urls=True) - self.assertEqual(from_file.url, '.') - self.assertEqual(file.url, to_file_urls[i]) - self.assertEqual(from_file.url_relative_to(file.url), expected[i]) - self.assertEqual(from_file.url_relative_to(file), expected[i]) - - from_file = File('file.md', '/path/to/docs', '/path/to/site', use_directory_urls=True) - expected = [ - 'file/', # file relative to . - '../file/', # file relative to foo/ - '../../file/', # file relative to foo/bar/ - '../../../file/', # file relative to foo/bar/baz/ - '../file/', # file relative to foo - '../../file/', # file relative to foo/bar - '../../../file/' # file relative to foo/bar/baz - ] - - for i, filename in enumerate(to_files): - file = File(filename, '/path/to/docs', '/path/to/site', use_directory_urls=True) - self.assertEqual(from_file.url, 'file/') - self.assertEqual(file.url, to_file_urls[i]) - self.assertEqual(from_file.url_relative_to(file.url), expected[i]) - self.assertEqual(from_file.url_relative_to(file), expected[i]) - - def test_get_relative_url(self): - to_files = [ - 'index.md', - 'foo/index.md', - 'foo/bar/index.md', - 'foo/bar/baz/index.md', - 'foo.md', - 'foo/bar.md', - 'foo/bar/baz.md' - ] - - to_file_urls = [ - 'index.html', - 'foo/index.html', - 'foo/bar/index.html', - 'foo/bar/baz/index.html', - 'foo.html', - 'foo/bar.html', - 'foo/bar/baz.html' - ] - - from_file = File('img.jpg', '/path/to/docs', '/path/to/site', use_directory_urls=False) - expected = [ - 'img.jpg', # img.jpg relative to . - '../img.jpg', # img.jpg relative to foo/ - '../../img.jpg', # img.jpg relative to foo/bar/ - '../../../img.jpg', # img.jpg relative to foo/bar/baz/ - 'img.jpg', # img.jpg relative to foo.html - '../img.jpg', # img.jpg relative to foo/bar.html - '../../img.jpg' # img.jpg relative to foo/bar/baz.html - ] - - for i, filename in enumerate(to_files): - file = File(filename, '/path/to/docs', '/path/to/site', use_directory_urls=False) - self.assertEqual(from_file.url, 'img.jpg') - self.assertEqual(file.url, to_file_urls[i]) - self.assertEqual(from_file.url_relative_to(file.url), expected[i]) - self.assertEqual(from_file.url_relative_to(file), expected[i]) - - from_file = File('foo/img.jpg', '/path/to/docs', '/path/to/site', use_directory_urls=False) - expected = [ - 'foo/img.jpg', # foo/img.jpg relative to . - 'img.jpg', # foo/img.jpg relative to foo/ - '../img.jpg', # foo/img.jpg relative to foo/bar/ - '../../img.jpg', # foo/img.jpg relative to foo/bar/baz/ - 'foo/img.jpg', # foo/img.jpg relative to foo.html - 'img.jpg', # foo/img.jpg relative to foo/bar.html - '../img.jpg' # foo/img.jpg relative to foo/bar/baz.html - ] - - for i, filename in enumerate(to_files): - file = File(filename, '/path/to/docs', '/path/to/site', use_directory_urls=False) - self.assertEqual(from_file.url, 'foo/img.jpg') - self.assertEqual(file.url, to_file_urls[i]) - self.assertEqual(from_file.url_relative_to(file.url), expected[i]) - self.assertEqual(from_file.url_relative_to(file), expected[i]) - - from_file = File('index.html', '/path/to/docs', '/path/to/site', use_directory_urls=False) - expected = [ - 'index.html', # index.html relative to . - '../index.html', # index.html relative to foo/ - '../../index.html', # index.html relative to foo/bar/ - '../../../index.html', # index.html relative to foo/bar/baz/ - 'index.html', # index.html relative to foo.html - '../index.html', # index.html relative to foo/bar.html - '../../index.html' # index.html relative to foo/bar/baz.html - ] - - for i, filename in enumerate(to_files): - file = File(filename, '/path/to/docs', '/path/to/site', use_directory_urls=False) - self.assertEqual(from_file.url, 'index.html') - self.assertEqual(file.url, to_file_urls[i]) - self.assertEqual(from_file.url_relative_to(file.url), expected[i]) - self.assertEqual(from_file.url_relative_to(file), expected[i]) - - from_file = File('file.html', '/path/to/docs', '/path/to/site', use_directory_urls=False) - expected = [ - 'file.html', # file.html relative to . - '../file.html', # file.html relative to foo/ - '../../file.html', # file.html relative to foo/bar/ - '../../../file.html', # file.html relative to foo/bar/baz/ - 'file.html', # file.html relative to foo.html - '../file.html', # file.html relative to foo/bar.html - '../../file.html' # file.html relative to foo/bar/baz.html - ] - - for i, filename in enumerate(to_files): - file = File(filename, '/path/to/docs', '/path/to/site', use_directory_urls=False) - self.assertEqual(from_file.url, 'file.html') - self.assertEqual(file.url, to_file_urls[i]) - self.assertEqual(from_file.url_relative_to(file.url), expected[i]) - self.assertEqual(from_file.url_relative_to(file), expected[i]) - - @tempdir(files=[ - 'index.md', - 'bar.css', - 'bar.html', - 'bar.jpg', - 'bar.js', - 'bar.md', - '.dotfile', - 'templates/foo.html' - ]) - def test_get_files(self, tdir): - config = load_config(docs_dir=tdir, extra_css=['bar.css'], extra_javascript=['bar.js']) - files = get_files(config) - expected = ['index.md', 'bar.css', 'bar.html', 'bar.jpg', 'bar.js', 'bar.md'] - self.assertIsInstance(files, Files) - self.assertEqual(len(files), len(expected)) - self.assertEqual([f.src_path for f in files], expected) - - @tempdir(files=[ - 'README.md', - 'foo.md' - ]) - def test_get_files_include_readme_without_index(self, tdir): - config = load_config(docs_dir=tdir) - files = get_files(config) - expected = ['README.md', 'foo.md'] - self.assertIsInstance(files, Files) - self.assertEqual(len(files), len(expected)) - self.assertEqual([f.src_path for f in files], expected) - - @tempdir(files=[ - 'index.md', - 'README.md', - 'foo.md' - ]) - def test_get_files_exclude_readme_with_index(self, tdir): - config = load_config(docs_dir=tdir) - files = get_files(config) - expected = ['index.md', 'foo.md'] - self.assertIsInstance(files, Files) - self.assertEqual(len(files), len(expected)) - self.assertEqual([f.src_path for f in files], expected) - - @tempdir() - @tempdir(files={'test.txt': 'source content'}) - def test_copy_file(self, src_dir, dest_dir): - file = File('test.txt', src_dir, dest_dir, use_directory_urls=False) - dest_path = os.path.join(dest_dir, 'test.txt') - self.assertPathNotExists(dest_path) - file.copy_file() - self.assertPathIsFile(dest_path) - - @tempdir(files={'test.txt': 'destination content'}) - @tempdir(files={'test.txt': 'source content'}) - def test_copy_file_clean_modified(self, src_dir, dest_dir): - file = File('test.txt', src_dir, dest_dir, use_directory_urls=False) - file.is_modified = mock.Mock(return_value=True) - dest_path = os.path.join(dest_dir, 'test.txt') - file.copy_file(dirty=False) - self.assertPathIsFile(dest_path) - with open(dest_path, 'r', encoding='utf-8') as f: - self.assertEqual(f.read(), 'source content') - - @tempdir(files={'test.txt': 'destination content'}) - @tempdir(files={'test.txt': 'source content'}) - def test_copy_file_dirty_modified(self, src_dir, dest_dir): - file = File('test.txt', src_dir, dest_dir, use_directory_urls=False) - file.is_modified = mock.Mock(return_value=True) - dest_path = os.path.join(dest_dir, 'test.txt') - file.copy_file(dirty=True) - self.assertPathIsFile(dest_path) - with open(dest_path, 'r', encoding='utf-8') as f: - self.assertEqual(f.read(), 'source content') - - @tempdir(files={'test.txt': 'destination content'}) - @tempdir(files={'test.txt': 'source content'}) - def test_copy_file_dirty_not_modified(self, src_dir, dest_dir): - file = File('test.txt', src_dir, dest_dir, use_directory_urls=False) - file.is_modified = mock.Mock(return_value=False) - dest_path = os.path.join(dest_dir, 'test.txt') - file.copy_file(dirty=True) - self.assertPathIsFile(dest_path) - with open(dest_path, 'r', encoding='utf-8') as f: - self.assertEqual(f.read(), 'destination content') diff --git a/mkdocs/tests/structure/nav_tests.py b/mkdocs/tests/structure/nav_tests.py deleted file mode 100644 index ae130fcf4..000000000 --- a/mkdocs/tests/structure/nav_tests.py +++ /dev/null @@ -1,379 +0,0 @@ -#!/usr/bin/env python - -import sys -import unittest - -from mkdocs.structure.nav import get_navigation -from mkdocs.structure.files import File, Files -from mkdocs.structure.pages import Page -from mkdocs.tests.base import dedent, load_config - - -class SiteNavigationTests(unittest.TestCase): - - maxDiff = None - - def test_simple_nav(self): - nav_cfg = [ - {'Home': 'index.md'}, - {'About': 'about.md'} - ] - expected = dedent(""" - Page(title='Home', url='/') - Page(title='About', url='/about/') - """) - cfg = load_config(nav=nav_cfg, site_url='http://example.com/') - files = Files( - [File(list(item.values())[0], cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) - for item in nav_cfg] - ) - site_navigation = get_navigation(files, cfg) - self.assertEqual(str(site_navigation).strip(), expected) - self.assertEqual(len(site_navigation.items), 2) - self.assertEqual(len(site_navigation.pages), 2) - self.assertEqual(repr(site_navigation.homepage), "Page(title='Home', url='/')") - - def test_nav_no_directory_urls(self): - nav_cfg = [ - {'Home': 'index.md'}, - {'About': 'about.md'} - ] - expected = dedent(""" - Page(title='Home', url='/index.html') - Page(title='About', url='/about.html') - """) - cfg = load_config(nav=nav_cfg, use_directory_urls=False, site_url='http://example.com/') - files = Files( - [File(list(item.values())[0], cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) - for item in nav_cfg] - ) - site_navigation = get_navigation(files, cfg) - self.assertEqual(str(site_navigation).strip(), expected) - self.assertEqual(len(site_navigation.items), 2) - self.assertEqual(len(site_navigation.pages), 2) - - def test_nav_missing_page(self): - nav_cfg = [ - {'Home': 'index.md'} - ] - expected = dedent(""" - Page(title='Home', url='/') - """) - cfg = load_config(nav=nav_cfg, site_url='http://example.com/') - files = Files([ - File('index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']), - File('page_not_in_nav.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) - ]) - site_navigation = get_navigation(files, cfg) - self.assertEqual(str(site_navigation).strip(), expected) - self.assertEqual(len(site_navigation.items), 1) - self.assertEqual(len(site_navigation.pages), 1) - for file in files: - self.assertIsInstance(file.page, Page) - - def test_nav_no_title(self): - nav_cfg = [ - 'index.md', - {'About': 'about.md'} - ] - expected = dedent(""" - Page(title=[blank], url='/') - Page(title='About', url='/about/') - """) - cfg = load_config(nav=nav_cfg, site_url='http://example.com/') - files = Files([ - File(nav_cfg[0], cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']), - File(nav_cfg[1]['About'], cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) - ]) - site_navigation = get_navigation(files, cfg) - self.assertEqual(str(site_navigation).strip(), expected) - self.assertEqual(len(site_navigation.items), 2) - self.assertEqual(len(site_navigation.pages), 2) - - def test_nav_external_links(self): - nav_cfg = [ - {'Home': 'index.md'}, - {'Local': '/local.html'}, - {'External': 'http://example.com/external.html'} - ] - expected = dedent(""" - Page(title='Home', url='/') - Link(title='Local', url='/local.html') - Link(title='External', url='http://example.com/external.html') - """) - cfg = load_config(nav=nav_cfg, site_url='http://example.com/') - files = Files([File('index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls'])]) - with self.assertLogs('mkdocs', level='DEBUG') as cm: - site_navigation = get_navigation(files, cfg) - self.assertEqual( - cm.output, - [ - "DEBUG:mkdocs.structure.nav:An absolute path to '/local.html' is included in the " - "'nav' configuration, which presumably points to an external resource.", - "DEBUG:mkdocs.structure.nav:An external link to 'http://example.com/external.html' " - "is included in the 'nav' configuration." - ] - ) - self.assertEqual(str(site_navigation).strip(), expected) - self.assertEqual(len(site_navigation.items), 3) - self.assertEqual(len(site_navigation.pages), 1) - - def test_nav_bad_links(self): - nav_cfg = [ - {'Home': 'index.md'}, - {'Missing': 'missing.html'}, - {'Bad External': 'example.com'} - ] - expected = dedent(""" - Page(title='Home', url='/') - Link(title='Missing', url='missing.html') - Link(title='Bad External', url='example.com') - """) - cfg = load_config(nav=nav_cfg, site_url='http://example.com/') - files = Files([File('index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls'])]) - with self.assertLogs('mkdocs', level='WARNING') as cm: - site_navigation = get_navigation(files, cfg) - self.assertEqual( - cm.output, - [ - "WARNING:mkdocs.structure.nav:A relative path to 'missing.html' is included " - "in the 'nav' configuration, which is not found in the documentation files", - "WARNING:mkdocs.structure.nav:A relative path to 'example.com' is included " - "in the 'nav' configuration, which is not found in the documentation files" - ] - ) - self.assertEqual(str(site_navigation).strip(), expected) - self.assertEqual(len(site_navigation.items), 3) - self.assertEqual(len(site_navigation.pages), 1) - - def test_indented_nav(self): - nav_cfg = [ - {'Home': 'index.md'}, - {'API Guide': [ - {'Running': 'api-guide/running.md'}, - {'Testing': 'api-guide/testing.md'}, - {'Debugging': 'api-guide/debugging.md'}, - {'Advanced': [ - {'Part 1': 'api-guide/advanced/part-1.md'}, - ]}, - ]}, - {'About': [ - {'Release notes': 'about/release-notes.md'}, - {'License': '/license.html'} - ]}, - {'External': 'https://example.com/'} - ] - expected = dedent(""" - Page(title='Home', url='/') - Section(title='API Guide') - Page(title='Running', url='/api-guide/running/') - Page(title='Testing', url='/api-guide/testing/') - Page(title='Debugging', url='/api-guide/debugging/') - Section(title='Advanced') - Page(title='Part 1', url='/api-guide/advanced/part-1/') - Section(title='About') - Page(title='Release notes', url='/about/release-notes/') - Link(title='License', url='/license.html') - Link(title='External', url='https://example.com/') - """) - cfg = load_config(nav=nav_cfg, site_url='http://example.com/') - files = Files([ - File('index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']), - File('api-guide/running.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']), - File('api-guide/testing.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']), - File('api-guide/debugging.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']), - File('api-guide/advanced/part-1.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']), - File('about/release-notes.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']), - ]) - site_navigation = get_navigation(files, cfg) - self.assertEqual(str(site_navigation).strip(), expected) - self.assertEqual(len(site_navigation.items), 4) - self.assertEqual(len(site_navigation.pages), 6) - self.assertEqual(repr(site_navigation.homepage), "Page(title='Home', url='/')") - self.assertIsNone(site_navigation.items[0].parent) - self.assertEqual(site_navigation.items[0].ancestors, []) - self.assertIsNone(site_navigation.items[1].parent) - self.assertEqual(site_navigation.items[1].ancestors, []) - self.assertEqual(len(site_navigation.items[1].children), 4) - self.assertEqual(repr(site_navigation.items[1].children[0].parent), "Section(title='API Guide')") - self.assertEqual(site_navigation.items[1].children[0].ancestors, [site_navigation.items[1]]) - self.assertEqual(repr(site_navigation.items[1].children[1].parent), "Section(title='API Guide')") - self.assertEqual(site_navigation.items[1].children[1].ancestors, [site_navigation.items[1]]) - self.assertEqual(repr(site_navigation.items[1].children[2].parent), "Section(title='API Guide')") - self.assertEqual(site_navigation.items[1].children[2].ancestors, [site_navigation.items[1]]) - self.assertEqual(repr(site_navigation.items[1].children[3].parent), "Section(title='API Guide')") - self.assertEqual(site_navigation.items[1].children[3].ancestors, [site_navigation.items[1]]) - self.assertEqual(len(site_navigation.items[1].children[3].children), 1) - self.assertEqual(repr(site_navigation.items[1].children[3].children[0].parent), "Section(title='Advanced')") - self.assertEqual(site_navigation.items[1].children[3].children[0].ancestors, - [site_navigation.items[1].children[3], site_navigation.items[1]]) - self.assertIsNone(site_navigation.items[2].parent) - self.assertEqual(len(site_navigation.items[2].children), 2) - self.assertEqual(repr(site_navigation.items[2].children[0].parent), "Section(title='About')") - self.assertEqual(site_navigation.items[2].children[0].ancestors, [site_navigation.items[2]]) - self.assertEqual(repr(site_navigation.items[2].children[1].parent), "Section(title='About')") - self.assertEqual(site_navigation.items[2].children[1].ancestors, [site_navigation.items[2]]) - self.assertIsNone(site_navigation.items[3].parent) - self.assertEqual(site_navigation.items[3].ancestors, []) - self.assertIsNone(site_navigation.items[3].children) - - def test_nested_ungrouped_nav(self): - nav_cfg = [ - {'Home': 'index.md'}, - {'Contact': 'about/contact.md'}, - {'License Title': 'about/sub/license.md'}, - ] - expected = dedent(""" - Page(title='Home', url='/') - Page(title='Contact', url='/about/contact/') - Page(title='License Title', url='/about/sub/license/') - """) - cfg = load_config(nav=nav_cfg, site_url='http://example.com/') - files = Files( - [File(list(item.values())[0], cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) - for item in nav_cfg] - ) - site_navigation = get_navigation(files, cfg) - self.assertEqual(str(site_navigation).strip(), expected) - self.assertEqual(len(site_navigation.items), 3) - self.assertEqual(len(site_navigation.pages), 3) - - def test_nested_ungrouped_nav_no_titles(self): - nav_cfg = [ - 'index.md', - 'about/contact.md', - 'about/sub/license.md' - ] - expected = dedent(""" - Page(title=[blank], url='/') - Page(title=[blank], url='/about/contact/') - Page(title=[blank], url='/about/sub/license/') - """) - - cfg = load_config(nav=nav_cfg, site_url='http://example.com/') - files = Files( - [File(item, cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) for item in nav_cfg] - ) - site_navigation = get_navigation(files, cfg) - self.assertEqual(str(site_navigation).strip(), expected) - self.assertEqual(len(site_navigation.items), 3) - self.assertEqual(len(site_navigation.pages), 3) - self.assertEqual(repr(site_navigation.homepage), "Page(title=[blank], url='/')") - - @unittest.skipUnless(sys.platform.startswith("win"), "requires Windows") - def test_nested_ungrouped_no_titles_windows(self): - nav_cfg = [ - 'index.md', - 'about\\contact.md', - 'about\\sub\\license.md', - ] - expected = dedent(""" - Page(title=[blank], url='/') - Page(title=[blank], url='/about/contact/') - Page(title=[blank], url='/about/sub/license/') - """) - - cfg = load_config(nav=nav_cfg, site_url='http://example.com/') - files = Files( - [File(item, cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) for item in nav_cfg] - ) - site_navigation = get_navigation(files, cfg) - self.assertEqual(str(site_navigation).strip(), expected) - self.assertEqual(len(site_navigation.items), 3) - self.assertEqual(len(site_navigation.pages), 3) - - def test_nav_from_files(self): - expected = dedent(""" - Page(title=[blank], url='/') - Page(title=[blank], url='/about/') - """) - cfg = load_config(site_url='http://example.com/') - files = Files([ - File('index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']), - File('about.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) - ]) - site_navigation = get_navigation(files, cfg) - self.assertEqual(str(site_navigation).strip(), expected) - self.assertEqual(len(site_navigation.items), 2) - self.assertEqual(len(site_navigation.pages), 2) - self.assertEqual(repr(site_navigation.homepage), "Page(title=[blank], url='/')") - - def test_nav_from_nested_files(self): - expected = dedent(""" - Page(title=[blank], url='/') - Section(title='About') - Page(title=[blank], url='/about/license/') - Page(title=[blank], url='/about/release-notes/') - Section(title='Api guide') - Page(title=[blank], url='/api-guide/debugging/') - Page(title=[blank], url='/api-guide/running/') - Page(title=[blank], url='/api-guide/testing/') - Section(title='Advanced') - Page(title=[blank], url='/api-guide/advanced/part-1/') - """) - cfg = load_config(site_url='http://example.com/') - files = Files([ - File('index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']), - File('about/license.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']), - File('about/release-notes.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']), - File('api-guide/debugging.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']), - File('api-guide/running.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']), - File('api-guide/testing.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']), - File('api-guide/advanced/part-1.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']), - ]) - site_navigation = get_navigation(files, cfg) - self.assertEqual(str(site_navigation).strip(), expected) - self.assertEqual(len(site_navigation.items), 3) - self.assertEqual(len(site_navigation.pages), 7) - self.assertEqual(repr(site_navigation.homepage), "Page(title=[blank], url='/')") - - def test_active(self): - nav_cfg = [ - {'Home': 'index.md'}, - {'API Guide': [ - {'Running': 'api-guide/running.md'}, - {'Testing': 'api-guide/testing.md'}, - {'Debugging': 'api-guide/debugging.md'}, - {'Advanced': [ - {'Part 1': 'api-guide/advanced/part-1.md'}, - ]}, - ]}, - {'About': [ - {'Release notes': 'about/release-notes.md'}, - {'License': 'about/license.md'} - ]} - ] - cfg = load_config(nav=nav_cfg, site_url='http://example.com/') - files = Files([ - File('index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']), - File('api-guide/running.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']), - File('api-guide/testing.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']), - File('api-guide/debugging.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']), - File('api-guide/advanced/part-1.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']), - File('about/release-notes.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']), - File('about/license.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']), - ]) - site_navigation = get_navigation(files, cfg) - # Confirm nothing is active - self.assertTrue(all(page.active is False for page in site_navigation.pages)) - self.assertTrue(all(item.active is False for item in site_navigation.items)) - # Activate - site_navigation.items[1].children[3].children[0].active = True - # Confirm ancestors are activated - self.assertTrue(site_navigation.items[1].children[3].children[0].active) - self.assertTrue(site_navigation.items[1].children[3].active) - self.assertTrue(site_navigation.items[1].active) - # Confirm non-ancestors are not activated - self.assertFalse(site_navigation.items[0].active) - self.assertFalse(site_navigation.items[1].children[0].active) - self.assertFalse(site_navigation.items[1].children[1].active) - self.assertFalse(site_navigation.items[1].children[2].active) - self.assertFalse(site_navigation.items[2].active) - self.assertFalse(site_navigation.items[2].children[0].active) - self.assertFalse(site_navigation.items[2].children[1].active) - # Deactivate - site_navigation.items[1].children[3].children[0].active = False - # Confirm ancestors are deactivated - self.assertFalse(site_navigation.items[1].children[3].children[0].active) - self.assertFalse(site_navigation.items[1].children[3].active) - self.assertFalse(site_navigation.items[1].active) diff --git a/mkdocs/tests/structure/page_tests.py b/mkdocs/tests/structure/page_tests.py deleted file mode 100644 index db8fd9fa1..000000000 --- a/mkdocs/tests/structure/page_tests.py +++ /dev/null @@ -1,845 +0,0 @@ -import unittest -import os -import sys -from unittest import mock -from tempfile import TemporaryDirectory - -from mkdocs.structure.pages import Page -from mkdocs.structure.files import File, Files -from mkdocs.tests.base import load_config, dedent - - -class PageTests(unittest.TestCase): - - DOCS_DIR = os.path.join(os.path.abspath(os.path.dirname(__file__)), '../integration/subpages/docs') - - def test_homepage(self): - cfg = load_config(docs_dir=self.DOCS_DIR) - fl = File('index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) - self.assertIsNone(fl.page) - pg = Page('Foo', fl, cfg) - self.assertEqual(fl.page, pg) - self.assertEqual(pg.url, '') - self.assertEqual(pg.abs_url, None) - self.assertEqual(pg.canonical_url, None) - self.assertEqual(pg.edit_url, None) - self.assertEqual(pg.file, fl) - self.assertEqual(pg.content, None) - self.assertTrue(pg.is_homepage) - self.assertTrue(pg.is_index) - self.assertTrue(pg.is_page) - self.assertFalse(pg.is_section) - self.assertTrue(pg.is_top_level) - self.assertEqual(pg.markdown, None) - self.assertEqual(pg.meta, {}) - self.assertEqual(pg.next_page, None) - self.assertEqual(pg.parent, None) - self.assertEqual(pg.previous_page, None) - self.assertEqual(pg.title, 'Foo') - self.assertEqual(pg.toc, []) - - def test_nested_index_page(self): - cfg = load_config(docs_dir=self.DOCS_DIR) - fl = File('sub1/index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) - pg = Page('Foo', fl, cfg) - pg.parent = 'foo' - self.assertEqual(pg.url, 'sub1/') - self.assertEqual(pg.abs_url, None) - self.assertEqual(pg.canonical_url, None) - self.assertEqual(pg.edit_url, None) - self.assertEqual(pg.file, fl) - self.assertEqual(pg.content, None) - self.assertFalse(pg.is_homepage) - self.assertTrue(pg.is_index) - self.assertTrue(pg.is_page) - self.assertFalse(pg.is_section) - self.assertFalse(pg.is_top_level) - self.assertEqual(pg.markdown, None) - self.assertEqual(pg.meta, {}) - self.assertEqual(pg.next_page, None) - self.assertEqual(pg.parent, 'foo') - self.assertEqual(pg.previous_page, None) - self.assertEqual(pg.title, 'Foo') - self.assertEqual(pg.toc, []) - - def test_nested_index_page_no_parent(self): - cfg = load_config(docs_dir=self.DOCS_DIR) - fl = File('sub1/index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) - pg = Page('Foo', fl, cfg) - pg.parent = None # non-homepage at nav root level; see #1919. - self.assertEqual(pg.url, 'sub1/') - self.assertEqual(pg.abs_url, None) - self.assertEqual(pg.canonical_url, None) - self.assertEqual(pg.edit_url, None) - self.assertEqual(pg.file, fl) - self.assertEqual(pg.content, None) - self.assertFalse(pg.is_homepage) - self.assertTrue(pg.is_index) - self.assertTrue(pg.is_page) - self.assertFalse(pg.is_section) - self.assertTrue(pg.is_top_level) - self.assertEqual(pg.markdown, None) - self.assertEqual(pg.meta, {}) - self.assertEqual(pg.next_page, None) - self.assertEqual(pg.parent, None) - self.assertEqual(pg.previous_page, None) - self.assertEqual(pg.title, 'Foo') - self.assertEqual(pg.toc, []) - - def test_nested_index_page_no_parent_no_directory_urls(self): - cfg = load_config(docs_dir=self.DOCS_DIR, use_directory_urls=False) - fl = File('sub1/index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) - pg = Page('Foo', fl, cfg) - pg.parent = None # non-homepage at nav root level; see #1919. - self.assertEqual(pg.url, 'sub1/index.html') - self.assertEqual(pg.abs_url, None) - self.assertEqual(pg.canonical_url, None) - self.assertEqual(pg.edit_url, None) - self.assertEqual(pg.file, fl) - self.assertEqual(pg.content, None) - self.assertFalse(pg.is_homepage) - self.assertTrue(pg.is_index) - self.assertTrue(pg.is_page) - self.assertFalse(pg.is_section) - self.assertTrue(pg.is_top_level) - self.assertEqual(pg.markdown, None) - self.assertEqual(pg.meta, {}) - self.assertEqual(pg.next_page, None) - self.assertEqual(pg.parent, None) - self.assertEqual(pg.previous_page, None) - self.assertEqual(pg.title, 'Foo') - self.assertEqual(pg.toc, []) - - def test_nested_nonindex_page(self): - cfg = load_config(docs_dir=self.DOCS_DIR) - fl = File('sub1/non-index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) - pg = Page('Foo', fl, cfg) - pg.parent = 'foo' - self.assertEqual(pg.url, 'sub1/non-index/') - self.assertEqual(pg.abs_url, None) - self.assertEqual(pg.canonical_url, None) - self.assertEqual(pg.edit_url, None) - self.assertEqual(pg.file, fl) - self.assertEqual(pg.content, None) - self.assertFalse(pg.is_homepage) - self.assertFalse(pg.is_index) - self.assertTrue(pg.is_page) - self.assertFalse(pg.is_section) - self.assertFalse(pg.is_top_level) - self.assertEqual(pg.markdown, None) - self.assertEqual(pg.meta, {}) - self.assertEqual(pg.next_page, None) - self.assertEqual(pg.parent, 'foo') - self.assertEqual(pg.previous_page, None) - self.assertEqual(pg.title, 'Foo') - self.assertEqual(pg.toc, []) - - def test_page_defaults(self): - cfg = load_config() - fl = File('testing.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) - pg = Page('Foo', fl, cfg) - self.assertRegex(pg.update_date, r'\d{4}-\d{2}-\d{2}') - self.assertEqual(pg.url, 'testing/') - self.assertEqual(pg.abs_url, None) - self.assertEqual(pg.canonical_url, None) - self.assertEqual(pg.edit_url, None) - self.assertEqual(pg.file, fl) - self.assertEqual(pg.content, None) - self.assertFalse(pg.is_homepage) - self.assertFalse(pg.is_index) - self.assertTrue(pg.is_page) - self.assertFalse(pg.is_section) - self.assertTrue(pg.is_top_level) - self.assertEqual(pg.markdown, None) - self.assertEqual(pg.meta, {}) - self.assertEqual(pg.next_page, None) - self.assertEqual(pg.parent, None) - self.assertEqual(pg.previous_page, None) - self.assertEqual(pg.title, 'Foo') - self.assertEqual(pg.toc, []) - - def test_page_no_directory_url(self): - cfg = load_config(use_directory_urls=False) - fl = File('testing.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) - pg = Page('Foo', fl, cfg) - self.assertEqual(pg.url, 'testing.html') - self.assertEqual(pg.abs_url, None) - self.assertEqual(pg.canonical_url, None) - self.assertEqual(pg.edit_url, None) - self.assertEqual(pg.file, fl) - self.assertEqual(pg.content, None) - self.assertFalse(pg.is_homepage) - self.assertFalse(pg.is_index) - self.assertTrue(pg.is_page) - self.assertFalse(pg.is_section) - self.assertTrue(pg.is_top_level) - self.assertEqual(pg.markdown, None) - self.assertEqual(pg.meta, {}) - self.assertEqual(pg.next_page, None) - self.assertEqual(pg.parent, None) - self.assertEqual(pg.previous_page, None) - self.assertEqual(pg.title, 'Foo') - self.assertEqual(pg.toc, []) - - def test_page_canonical_url(self): - cfg = load_config(site_url='http://example.com') - fl = File('testing.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) - pg = Page('Foo', fl, cfg) - self.assertEqual(pg.url, 'testing/') - self.assertEqual(pg.abs_url, '/testing/') - self.assertEqual(pg.canonical_url, 'http://example.com/testing/') - self.assertEqual(pg.edit_url, None) - self.assertEqual(pg.file, fl) - self.assertEqual(pg.content, None) - self.assertFalse(pg.is_homepage) - self.assertFalse(pg.is_index) - self.assertTrue(pg.is_page) - self.assertFalse(pg.is_section) - self.assertTrue(pg.is_top_level) - self.assertEqual(pg.markdown, None) - self.assertEqual(pg.meta, {}) - self.assertEqual(pg.next_page, None) - self.assertEqual(pg.parent, None) - self.assertEqual(pg.previous_page, None) - self.assertEqual(pg.title, 'Foo') - self.assertEqual(pg.toc, []) - - def test_page_canonical_url_nested(self): - cfg = load_config(site_url='http://example.com/foo/') - fl = File('testing.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) - pg = Page('Foo', fl, cfg) - self.assertEqual(pg.url, 'testing/') - self.assertEqual(pg.abs_url, '/foo/testing/') - self.assertEqual(pg.canonical_url, 'http://example.com/foo/testing/') - self.assertEqual(pg.edit_url, None) - self.assertEqual(pg.file, fl) - self.assertEqual(pg.content, None) - self.assertFalse(pg.is_homepage) - self.assertFalse(pg.is_index) - self.assertTrue(pg.is_page) - self.assertFalse(pg.is_section) - self.assertTrue(pg.is_top_level) - self.assertEqual(pg.markdown, None) - self.assertEqual(pg.meta, {}) - self.assertEqual(pg.next_page, None) - self.assertEqual(pg.parent, None) - self.assertEqual(pg.previous_page, None) - self.assertEqual(pg.title, 'Foo') - self.assertEqual(pg.toc, []) - - def test_page_canonical_url_nested_no_slash(self): - cfg = load_config(site_url='http://example.com/foo') - fl = File('testing.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) - pg = Page('Foo', fl, cfg) - self.assertEqual(pg.url, 'testing/') - self.assertEqual(pg.abs_url, '/foo/testing/') - self.assertEqual(pg.canonical_url, 'http://example.com/foo/testing/') - self.assertEqual(pg.edit_url, None) - self.assertEqual(pg.file, fl) - self.assertEqual(pg.content, None) - self.assertFalse(pg.is_homepage) - self.assertFalse(pg.is_index) - self.assertTrue(pg.is_page) - self.assertFalse(pg.is_section) - self.assertTrue(pg.is_top_level) - self.assertEqual(pg.markdown, None) - self.assertEqual(pg.meta, {}) - self.assertEqual(pg.next_page, None) - self.assertEqual(pg.parent, None) - self.assertEqual(pg.previous_page, None) - self.assertEqual(pg.title, 'Foo') - self.assertEqual(pg.toc, []) - - def test_predefined_page_title(self): - cfg = load_config() - fl = File('testing.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) - pg = Page('Page Title', fl, cfg) - pg.read_source(cfg) - self.assertEqual(pg.url, 'testing/') - self.assertEqual(pg.abs_url, None) - self.assertEqual(pg.canonical_url, None) - self.assertEqual(pg.edit_url, None) - self.assertEqual(pg.file, fl) - self.assertEqual(pg.content, None) - self.assertFalse(pg.is_homepage) - self.assertFalse(pg.is_index) - self.assertTrue(pg.is_page) - self.assertFalse(pg.is_section) - self.assertTrue(pg.is_top_level) - self.assertTrue(pg.markdown.startswith('# Welcome to MkDocs\n')) - self.assertEqual(pg.meta, {}) - self.assertEqual(pg.next_page, None) - self.assertEqual(pg.parent, None) - self.assertEqual(pg.previous_page, None) - self.assertEqual(pg.title, 'Page Title') - self.assertEqual(pg.toc, []) - - def test_page_title_from_markdown(self): - cfg = load_config() - fl = File('testing.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) - pg = Page(None, fl, cfg) - pg.read_source(cfg) - self.assertEqual(pg.url, 'testing/') - self.assertEqual(pg.abs_url, None) - self.assertEqual(pg.canonical_url, None) - self.assertEqual(pg.edit_url, None) - self.assertEqual(pg.file, fl) - self.assertEqual(pg.content, None) - self.assertFalse(pg.is_homepage) - self.assertFalse(pg.is_index) - self.assertTrue(pg.is_page) - self.assertFalse(pg.is_section) - self.assertTrue(pg.is_top_level) - self.assertTrue(pg.markdown.startswith('# Welcome to MkDocs\n')) - self.assertEqual(pg.meta, {}) - self.assertEqual(pg.next_page, None) - self.assertEqual(pg.parent, None) - self.assertEqual(pg.previous_page, None) - self.assertEqual(pg.title, 'Welcome to MkDocs') - self.assertEqual(pg.toc, []) - - def test_page_title_from_meta(self): - cfg = load_config(docs_dir=self.DOCS_DIR) - fl = File('metadata.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) - pg = Page(None, fl, cfg) - pg.read_source(cfg) - self.assertEqual(pg.url, 'metadata/') - self.assertEqual(pg.abs_url, None) - self.assertEqual(pg.canonical_url, None) - self.assertEqual(pg.edit_url, None) - self.assertEqual(pg.file, fl) - self.assertEqual(pg.content, None) - self.assertFalse(pg.is_homepage) - self.assertFalse(pg.is_index) - self.assertTrue(pg.is_page) - self.assertFalse(pg.is_section) - self.assertTrue(pg.is_top_level) - self.assertTrue(pg.markdown.startswith('# Welcome to MkDocs\n')) - self.assertEqual(pg.meta, {'title': 'A Page Title'}) - self.assertEqual(pg.next_page, None) - self.assertEqual(pg.parent, None) - self.assertEqual(pg.previous_page, None) - self.assertEqual(pg.title, 'A Page Title') - self.assertEqual(pg.toc, []) - - def test_page_title_from_filename(self): - cfg = load_config(docs_dir=self.DOCS_DIR) - fl = File('page-title.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) - pg = Page(None, fl, cfg) - pg.read_source(cfg) - self.assertEqual(pg.url, 'page-title/') - self.assertEqual(pg.abs_url, None) - self.assertEqual(pg.canonical_url, None) - self.assertEqual(pg.edit_url, None) - self.assertEqual(pg.file, fl) - self.assertEqual(pg.content, None) - self.assertFalse(pg.is_homepage) - self.assertFalse(pg.is_index) - self.assertTrue(pg.is_page) - self.assertFalse(pg.is_section) - self.assertTrue(pg.is_top_level) - self.assertTrue(pg.markdown.startswith('Page content.\n')) - self.assertEqual(pg.meta, {}) - self.assertEqual(pg.next_page, None) - self.assertEqual(pg.parent, None) - self.assertEqual(pg.previous_page, None) - self.assertEqual(pg.title, 'Page title') - self.assertEqual(pg.toc, []) - - def test_page_title_from_capitalized_filename(self): - cfg = load_config(docs_dir=self.DOCS_DIR) - fl = File('pageTitle.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) - pg = Page(None, fl, cfg) - pg.read_source(cfg) - self.assertEqual(pg.url, 'pageTitle/') - self.assertEqual(pg.abs_url, None) - self.assertEqual(pg.canonical_url, None) - self.assertEqual(pg.edit_url, None) - self.assertEqual(pg.file, fl) - self.assertEqual(pg.content, None) - self.assertFalse(pg.is_homepage) - self.assertFalse(pg.is_index) - self.assertTrue(pg.is_page) - self.assertFalse(pg.is_section) - self.assertTrue(pg.is_top_level) - self.assertTrue(pg.markdown.startswith('Page content.\n')) - self.assertEqual(pg.meta, {}) - self.assertEqual(pg.next_page, None) - self.assertEqual(pg.parent, None) - self.assertEqual(pg.previous_page, None) - self.assertEqual(pg.title, 'pageTitle') - self.assertEqual(pg.toc, []) - - def test_page_title_from_homepage_filename(self): - cfg = load_config(docs_dir=self.DOCS_DIR) - fl = File('index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) - pg = Page(None, fl, cfg) - pg.read_source(cfg) - self.assertEqual(pg.url, '') - self.assertEqual(pg.abs_url, None) - self.assertEqual(pg.canonical_url, None) - self.assertEqual(pg.edit_url, None) - self.assertEqual(pg.file, fl) - self.assertEqual(pg.content, None) - self.assertTrue(pg.is_homepage) - self.assertTrue(pg.is_index) - self.assertTrue(pg.is_page) - self.assertFalse(pg.is_section) - self.assertTrue(pg.is_top_level) - self.assertTrue(pg.markdown.startswith('## Test')) - self.assertEqual(pg.meta, {}) - self.assertEqual(pg.next_page, None) - self.assertEqual(pg.parent, None) - self.assertEqual(pg.previous_page, None) - self.assertEqual(pg.title, 'Home') - self.assertEqual(pg.toc, []) - - def test_page_eq(self): - cfg = load_config() - fl = File('testing.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) - pg = Page('Foo', fl, cfg) - self.assertTrue(pg == Page('Foo', fl, cfg)) - - def test_page_ne(self): - cfg = load_config() - f1 = File('testing.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) - f2 = File('index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) - pg = Page('Foo', f1, cfg) - # Different Title - self.assertTrue(pg != Page('Bar', f1, cfg)) - # Different File - self.assertTrue(pg != Page('Foo', f2, cfg)) - - def test_BOM(self): - md_src = '# An UTF-8 encoded file with a BOM' - with TemporaryDirectory() as docs_dir: - # We don't use mkdocs.tests.base.tempdir decorator here due to uniqueness of this test. - cfg = load_config(docs_dir=docs_dir) - fl = File('index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) - pg = Page(None, fl, cfg) - # Create an UTF-8 Encoded file with BOM (as Micorsoft editors do). See #1186 - with open(fl.abs_src_path, 'w', encoding='utf-8-sig') as f: - f.write(md_src) - # Now read the file. - pg.read_source(cfg) - # Ensure the BOM (`\ufeff`) is removed - self.assertNotIn('\ufeff', pg.markdown) - self.assertEqual(pg.markdown, md_src) - self.assertEqual(pg.meta, {}) - - def test_page_edit_url(self): - configs = [ - { - 'repo_url': 'http://github.com/mkdocs/mkdocs' - }, - { - 'repo_url': 'https://github.com/mkdocs/mkdocs/' - }, { - 'repo_url': 'http://example.com' - }, { - 'repo_url': 'http://example.com', - 'edit_uri': 'edit/master' - }, { - 'repo_url': 'http://example.com', - 'edit_uri': '/edit/master' - }, { - 'repo_url': 'http://example.com/foo/', - 'edit_uri': '/edit/master/' - }, { - 'repo_url': 'http://example.com/foo', - 'edit_uri': '/edit/master/' - }, { - 'repo_url': 'http://example.com/foo/', - 'edit_uri': '/edit/master' - }, { - 'repo_url': 'http://example.com/foo/', - 'edit_uri': 'edit/master/' - }, { - 'repo_url': 'http://example.com/foo', - 'edit_uri': 'edit/master/' - }, { - 'repo_url': 'http://example.com', - 'edit_uri': '?query=edit/master' - }, { - 'repo_url': 'http://example.com/', - 'edit_uri': '?query=edit/master/' - }, { - 'repo_url': 'http://example.com', - 'edit_uri': '#edit/master' - }, { - 'repo_url': 'http://example.com/', - 'edit_uri': '#edit/master/' - }, { - 'repo_url': 'http://example.com', - 'edit_uri': '' # Set to blank value - }, { - # Nothing defined - } - ] - - expected = [ - 'http://github.com/mkdocs/mkdocs/edit/master/docs/testing.md', - 'https://github.com/mkdocs/mkdocs/edit/master/docs/testing.md', - None, - 'http://example.com/edit/master/testing.md', - 'http://example.com/edit/master/testing.md', - 'http://example.com/edit/master/testing.md', - 'http://example.com/edit/master/testing.md', - 'http://example.com/edit/master/testing.md', - 'http://example.com/foo/edit/master/testing.md', - 'http://example.com/foo/edit/master/testing.md', - 'http://example.com?query=edit/master/testing.md', - 'http://example.com/?query=edit/master/testing.md', - 'http://example.com#edit/master/testing.md', - 'http://example.com/#edit/master/testing.md', - None, - None - ] - - for i, c in enumerate(configs): - cfg = load_config(**c) - fl = File('testing.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) - pg = Page('Foo', fl, cfg) - self.assertEqual(pg.url, 'testing/') - self.assertEqual(pg.edit_url, expected[i]) - - def test_nested_page_edit_url(self): - configs = [ - { - 'repo_url': 'http://github.com/mkdocs/mkdocs' - }, - { - 'repo_url': 'https://github.com/mkdocs/mkdocs/' - }, { - 'repo_url': 'http://example.com' - }, { - 'repo_url': 'http://example.com', - 'edit_uri': 'edit/master' - }, { - 'repo_url': 'http://example.com', - 'edit_uri': '/edit/master' - }, { - 'repo_url': 'http://example.com/foo/', - 'edit_uri': '/edit/master/' - }, { - 'repo_url': 'http://example.com/foo', - 'edit_uri': '/edit/master/' - }, { - 'repo_url': 'http://example.com/foo/', - 'edit_uri': '/edit/master' - }, { - 'repo_url': 'http://example.com/foo/', - 'edit_uri': 'edit/master/' - }, { - 'repo_url': 'http://example.com/foo', - 'edit_uri': 'edit/master/' - }, { - 'repo_url': 'http://example.com', - 'edit_uri': '?query=edit/master' - }, { - 'repo_url': 'http://example.com/', - 'edit_uri': '?query=edit/master/' - }, { - 'repo_url': 'http://example.com', - 'edit_uri': '#edit/master' - }, { - 'repo_url': 'http://example.com/', - 'edit_uri': '#edit/master/' - } - ] - - expected = [ - 'http://github.com/mkdocs/mkdocs/edit/master/docs/sub1/non-index.md', - 'https://github.com/mkdocs/mkdocs/edit/master/docs/sub1/non-index.md', - None, - 'http://example.com/edit/master/sub1/non-index.md', - 'http://example.com/edit/master/sub1/non-index.md', - 'http://example.com/edit/master/sub1/non-index.md', - 'http://example.com/edit/master/sub1/non-index.md', - 'http://example.com/edit/master/sub1/non-index.md', - 'http://example.com/foo/edit/master/sub1/non-index.md', - 'http://example.com/foo/edit/master/sub1/non-index.md', - 'http://example.com?query=edit/master/sub1/non-index.md', - 'http://example.com/?query=edit/master/sub1/non-index.md', - 'http://example.com#edit/master/sub1/non-index.md', - 'http://example.com/#edit/master/sub1/non-index.md' - ] - - for i, c in enumerate(configs): - c['docs_dir'] = self.DOCS_DIR - cfg = load_config(**c) - fl = File('sub1/non-index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) - pg = Page('Foo', fl, cfg) - self.assertEqual(pg.url, 'sub1/non-index/') - self.assertEqual(pg.edit_url, expected[i]) - - @unittest.skipUnless(sys.platform.startswith("win"), "requires Windows") - def test_nested_page_edit_url_windows(self): - configs = [ - { - 'repo_url': 'http://github.com/mkdocs/mkdocs' - }, - { - 'repo_url': 'https://github.com/mkdocs/mkdocs/' - }, { - 'repo_url': 'http://example.com' - }, { - 'repo_url': 'http://example.com', - 'edit_uri': 'edit/master' - }, { - 'repo_url': 'http://example.com', - 'edit_uri': '/edit/master' - }, { - 'repo_url': 'http://example.com/foo/', - 'edit_uri': '/edit/master/' - }, { - 'repo_url': 'http://example.com/foo', - 'edit_uri': '/edit/master/' - }, { - 'repo_url': 'http://example.com/foo/', - 'edit_uri': '/edit/master' - }, { - 'repo_url': 'http://example.com/foo/', - 'edit_uri': 'edit/master/' - }, { - 'repo_url': 'http://example.com/foo', - 'edit_uri': 'edit/master/' - }, { - 'repo_url': 'http://example.com', - 'edit_uri': '?query=edit/master' - }, { - 'repo_url': 'http://example.com/', - 'edit_uri': '?query=edit/master/' - }, { - 'repo_url': 'http://example.com', - 'edit_uri': '#edit/master' - }, { - 'repo_url': 'http://example.com/', - 'edit_uri': '#edit/master/' - } - ] - - expected = [ - 'http://github.com/mkdocs/mkdocs/edit/master/docs/sub1/non-index.md', - 'https://github.com/mkdocs/mkdocs/edit/master/docs/sub1/non-index.md', - None, - 'http://example.com/edit/master/sub1/non-index.md', - 'http://example.com/edit/master/sub1/non-index.md', - 'http://example.com/edit/master/sub1/non-index.md', - 'http://example.com/edit/master/sub1/non-index.md', - 'http://example.com/edit/master/sub1/non-index.md', - 'http://example.com/foo/edit/master/sub1/non-index.md', - 'http://example.com/foo/edit/master/sub1/non-index.md', - 'http://example.com?query=edit/master/sub1/non-index.md', - 'http://example.com/?query=edit/master/sub1/non-index.md', - 'http://example.com#edit/master/sub1/non-index.md', - 'http://example.com/#edit/master/sub1/non-index.md' - ] - - for i, c in enumerate(configs): - c['docs_dir'] = self.DOCS_DIR - cfg = load_config(**c) - fl = File('sub1\\non-index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) - pg = Page('Foo', fl, cfg) - self.assertEqual(pg.url, 'sub1/non-index/') - self.assertEqual(pg.edit_url, expected[i]) - - def test_page_render(self): - cfg = load_config() - fl = File('testing.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) - pg = Page('Foo', fl, cfg) - pg.read_source(cfg) - self.assertEqual(pg.content, None) - self.assertEqual(pg.toc, []) - pg.render(cfg, [fl]) - self.assertTrue(pg.content.startswith( - 'not a link.
' - ) - - @mock.patch('mkdocs.structure.pages.open', mock.mock_open(read_data='[link](non-existant.md)')) - def test_bad_relative_html_link(self): - with self.assertLogs('mkdocs', level='WARNING') as cm: - self.assertEqual( - self.get_rendered_result(['index.md']), - '' - ) - self.assertEqual( - cm.output, - ["WARNING:mkdocs.structure.pages:Documentation file 'index.md' contains a link " - "to 'non-existant.md' which is not found in the documentation files."] - ) - - @mock.patch('mkdocs.structure.pages.open', mock.mock_open(read_data='[external](http://example.com/index.md)')) - def test_external_link(self): - self.assertEqual( - self.get_rendered_result(['index.md']), - '' - ) - - @mock.patch('mkdocs.structure.pages.open', mock.mock_open(read_data='[absolute link](/path/to/file.md)')) - def test_absolute_link(self): - self.assertEqual( - self.get_rendered_result(['index.md']), - '' - ) - - @mock.patch('mkdocs.structure.pages.open', mock.mock_open(read_data='Heading
2
- ## Heading 3
- """)
- expected = dedent("""
- Heading 1 - #heading-1
- Heading 2 - #heading-2
- Heading 3 - #heading-3
- """)
- toc = get_toc(get_markdown_toc(md))
- self.assertEqual(str(toc).strip(), expected)
- self.assertEqual(len(toc), 1)
-
- def test_flat_toc(self):
- md = dedent("""
- # Heading 1
- # Heading 2
- # Heading 3
- """)
- expected = dedent("""
- Heading 1 - #heading-1
- Heading 2 - #heading-2
- Heading 3 - #heading-3
- """)
- toc = get_toc(get_markdown_toc(md))
- self.assertEqual(str(toc).strip(), expected)
- self.assertEqual(len(toc), 3)
-
- def test_flat_h2_toc(self):
- md = dedent("""
- ## Heading 1
- ## Heading 2
- ## Heading 3
- """)
- expected = dedent("""
- Heading 1 - #heading-1
- Heading 2 - #heading-2
- Heading 3 - #heading-3
- """)
- toc = get_toc(get_markdown_toc(md))
- self.assertEqual(str(toc).strip(), expected)
- self.assertEqual(len(toc), 3)
-
- def test_mixed_toc(self):
- md = dedent("""
- # Heading 1
- ## Heading 2
- # Heading 3
- ### Heading 4
- ### Heading 5
- """)
- expected = dedent("""
- Heading 1 - #heading-1
- Heading 2 - #heading-2
- Heading 3 - #heading-3
- Heading 4 - #heading-4
- Heading 5 - #heading-5
- """)
- toc = get_toc(get_markdown_toc(md))
- self.assertEqual(str(toc).strip(), expected)
- self.assertEqual(len(toc), 2)
-
- def test_mixed_html(self):
- md = dedent("""
- # Heading 1
- ## Heading 2
- # Heading 3
- ### Heading 4
- ### Heading 5
- """)
- expected = dedent("""
- Heading 1 - #heading-1
- Heading 2 - #heading-2
- Heading 3 - #heading-3
- Heading 4 - #heading-4
- Heading 5 - #heading-5
- """)
- toc = get_toc(get_markdown_toc(md))
- self.assertEqual(str(toc).strip(), expected)
- self.assertEqual(len(toc), 2)
-
- def test_nested_anchor(self):
- md = dedent("""
- # Heading 1
- ## Heading 2
- # Heading 3
- ### Heading 4
- ### Heading 5
- """)
- expected = dedent("""
- Heading 1 - #heading-1
- Heading 2 - #heading-2
- Heading 3 - #heading-3
- Heading 4 - #heading-4
- Heading 5 - #heading-5
- """)
- toc = get_toc(get_markdown_toc(md))
- self.assertEqual(str(toc).strip(), expected)
- self.assertEqual(len(toc), 2)
-
- def test_entityref(self):
- md = dedent("""
- # Heading & 1
- ## Heading > 2
- ### Heading < 3
- """)
- expected = dedent("""
- Heading & 1 - #heading-1
- Heading > 2 - #heading-2
- Heading < 3 - #heading-3
- """)
- toc = get_toc(get_markdown_toc(md))
- self.assertEqual(str(toc).strip(), expected)
- self.assertEqual(len(toc), 1)
-
- def test_charref(self):
- md = '# @Header'
- expected = '@Header - #header'
- toc = get_toc(get_markdown_toc(md))
- self.assertEqual(str(toc).strip(), expected)
- self.assertEqual(len(toc), 1)
-
- def test_level(self):
- md = dedent("""
- # Heading 1
- ## Heading 1.1
- ### Heading 1.1.1
- ### Heading 1.1.2
- ## Heading 1.2
- """)
- toc = get_toc(get_markdown_toc(md))
-
- def get_level_sequence(items):
- for item in items:
- yield item.level
- yield from get_level_sequence(item.children)
-
- self.assertEqual(tuple(get_level_sequence(toc)), (1, 2, 3, 3, 2))
diff --git a/mkdocs/tests/theme_tests.py b/mkdocs/tests/theme_tests.py
deleted file mode 100644
index 24e0ebb40..000000000
--- a/mkdocs/tests/theme_tests.py
+++ /dev/null
@@ -1,101 +0,0 @@
-import os
-import tempfile
-import unittest
-from unittest import mock
-
-import mkdocs
-from mkdocs.theme import Theme
-
-abs_path = os.path.abspath(os.path.dirname(__file__))
-mkdocs_dir = os.path.abspath(os.path.dirname(mkdocs.__file__))
-mkdocs_templates_dir = os.path.join(mkdocs_dir, 'templates')
-theme_dir = os.path.abspath(os.path.join(mkdocs_dir, 'themes'))
-
-
-def get_vars(theme):
- """ Return dict of theme vars. """
- return {k: theme[k] for k in iter(theme)}
-
-
-class ThemeTests(unittest.TestCase):
-
- def test_simple_theme(self):
- theme = Theme(name='mkdocs')
- self.assertEqual(
- theme.dirs,
- [os.path.join(theme_dir, 'mkdocs'), mkdocs_templates_dir]
- )
- self.assertEqual(theme.static_templates, {'404.html', 'sitemap.xml'})
- self.assertEqual(get_vars(theme), {
- 'include_search_page': False,
- 'search_index_only': False,
- 'highlightjs': True,
- 'hljs_style': 'github',
- 'hljs_languages': [],
- 'navigation_depth': 2,
- 'nav_style': 'primary',
- 'shortcuts': {'help': 191, 'next': 78, 'previous': 80, 'search': 83}
- })
-
- def test_custom_dir(self):
- custom = tempfile.mkdtemp()
- theme = Theme(name='mkdocs', custom_dir=custom)
- self.assertEqual(
- theme.dirs,
- [
- custom,
- os.path.join(theme_dir, 'mkdocs'),
- mkdocs_templates_dir
- ]
- )
-
- def test_custom_dir_only(self):
- custom = tempfile.mkdtemp()
- theme = Theme(name=None, custom_dir=custom)
- self.assertEqual(
- theme.dirs,
- [custom, mkdocs_templates_dir]
- )
-
- def static_templates(self):
- theme = Theme(name='mkdocs', static_templates='foo.html')
- self.assertEqual(
- theme.static_templates,
- {'404.html', 'sitemap.xml', 'foo.html'}
- )
-
- def test_vars(self):
- theme = Theme(name='mkdocs', foo='bar', baz=True)
- self.assertEqual(theme['foo'], 'bar')
- self.assertEqual(theme['baz'], True)
- self.assertTrue('new' not in theme)
- self.assertRaises(KeyError, lambda t, k: t[k], theme, 'new')
- theme['new'] = 42
- self.assertTrue('new' in theme)
- self.assertEqual(theme['new'], 42)
-
- @mock.patch('mkdocs.utils.yaml_load', return_value=None)
- def test_no_theme_config(self, m):
- theme = Theme(name='mkdocs')
- self.assertEqual(m.call_count, 1)
- self.assertEqual(theme.static_templates, {'sitemap.xml'})
-
- def test_inherited_theme(self):
- m = mock.Mock(side_effect=[
- {'extends': 'readthedocs', 'static_templates': ['child.html']},
- {'static_templates': ['parent.html']}
- ])
- with mock.patch('mkdocs.utils.yaml_load', m) as m:
- theme = Theme(name='mkdocs')
- self.assertEqual(m.call_count, 2)
- self.assertEqual(
- theme.dirs,
- [
- os.path.join(theme_dir, 'mkdocs'),
- os.path.join(theme_dir, 'readthedocs'),
- mkdocs_templates_dir
- ]
- )
- self.assertEqual(
- theme.static_templates, {'sitemap.xml', 'child.html', 'parent.html'}
- )
diff --git a/mkdocs/tests/utils/__init__.py b/mkdocs/tests/utils/__init__.py
deleted file mode 100644
index e69de29bb..000000000
diff --git a/mkdocs/tests/utils/ghp_import_tests.py b/mkdocs/tests/utils/ghp_import_tests.py
deleted file mode 100644
index 84b4451f3..000000000
--- a/mkdocs/tests/utils/ghp_import_tests.py
+++ /dev/null
@@ -1,134 +0,0 @@
-#!/usr/bin/env python
-
-
-from unittest import mock
-import os
-import subprocess
-import tempfile
-import unittest
-import shutil
-
-from mkdocs.utils import ghp_import
-
-
-class UtilsTests(unittest.TestCase):
-
- @mock.patch('subprocess.call', auto_spec=True)
- @mock.patch('subprocess.Popen', auto_spec=True)
- def test_try_rebase(self, mock_popen, mock_call):
-
- popen = mock.Mock()
- mock_popen.return_value = popen
- popen.communicate.return_value = (
- '4c82346e4b1b816be89dd709d35a6b169aa3df61\n', '')
- popen.wait.return_value = 0
-
- ghp_import.try_rebase('origin', 'gh-pages')
-
- mock_popen.assert_called_once_with(
- ['git', 'rev-list', '--max-count=1', 'origin/gh-pages'],
- stdin=subprocess.PIPE, stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
- mock_call.assert_called_once_with(
- ['git', 'update-ref', 'refs/heads/gh-pages',
- '4c82346e4b1b816be89dd709d35a6b169aa3df61'])
-
- @mock.patch('subprocess.Popen', auto_spec=True)
- def test_get_prev_commit(self, mock_popen):
-
- popen = mock.Mock()
- mock_popen.return_value = popen
- popen.communicate.return_value = (
- b'4c82346e4b1b816be89dd709d35a6b169aa3df61\n', '')
- popen.wait.return_value = 0
-
- result = ghp_import.get_prev_commit('test-branch')
-
- self.assertEqual(result, '4c82346e4b1b816be89dd709d35a6b169aa3df61')
- mock_popen.assert_called_once_with(
- ['git', 'rev-list', '--max-count=1', 'test-branch', '--'],
- stdin=subprocess.PIPE, stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
-
- @mock.patch('subprocess.Popen', auto_spec=True)
- def test_get_config(self, mock_popen):
-
- popen = mock.Mock()
- mock_popen.return_value = popen
- popen.communicate.return_value = (
- b'Dougal Matthews\n', '')
-
- result = ghp_import.get_config('user.name')
-
- self.assertEqual(result, 'Dougal Matthews')
- mock_popen.assert_called_once_with(
- ['git', 'config', 'user.name'],
- stdout=subprocess.PIPE, stdin=subprocess.PIPE)
-
- @mock.patch('mkdocs.utils.ghp_import.get_prev_commit')
- @mock.patch('mkdocs.utils.ghp_import.get_config')
- def test_start_commit(self, mock_get_config, mock_get_prev_commit):
-
- pipe = mock.Mock()
- mock_get_config.side_effect = ['username', 'email']
- mock_get_prev_commit.return_value = 'SHA'
-
- ghp_import.start_commit(pipe, 'test-branch', 'test-message')
-
- mock_get_prev_commit.assert_called_once_with('test-branch')
- self.assertEqual(pipe.stdin.write.call_count, 5)
-
- @mock.patch('mkdocs.utils.ghp_import.try_rebase', return_value=True)
- @mock.patch('mkdocs.utils.ghp_import.get_prev_commit', return_value='sha')
- @mock.patch('mkdocs.utils.ghp_import.get_config', return_value='config')
- @mock.patch('subprocess.call', auto_spec=True)
- @mock.patch('subprocess.Popen', auto_spec=True)
- def test_ghp_import(self, mock_popen, mock_call, mock_get_config,
- mock_get_prev_commit, mock_try_rebase):
-
- directory = tempfile.mkdtemp()
- open(os.path.join(directory, 'file'), 'a').close()
-
- try:
- popen = mock.Mock()
- mock_popen.return_value = popen
- popen.communicate.return_value = ('', '')
- popen.wait.return_value = 0
-
- ghp_import.ghp_import(directory, "test message",
- remote='fake-remote-name',
- branch='fake-branch-name')
-
- self.assertEqual(mock_popen.call_count, 2)
- self.assertEqual(mock_call.call_count, 0)
- finally:
- shutil.rmtree(directory)
-
- @mock.patch('mkdocs.utils.ghp_import.try_rebase', return_value=True)
- @mock.patch('mkdocs.utils.ghp_import.get_prev_commit', return_value='sha')
- @mock.patch('mkdocs.utils.ghp_import.get_config', return_value='config')
- @mock.patch('mkdocs.utils.ghp_import.run_import')
- @mock.patch('subprocess.call', auto_spec=True)
- @mock.patch('subprocess.Popen', auto_spec=True)
- def test_ghp_import_error(self, mock_popen, mock_call, mock_get_config,
- mock_run_import, mock_get_prev_commit, mock_try_rebase):
-
- directory = tempfile.mkdtemp()
- open(os.path.join(directory, 'file'), 'a').close()
-
- try:
- popen = mock.Mock()
- mock_popen.return_value = popen
-
- error_string = 'TestError123'
- popen.communicate.return_value = ('', error_string)
- popen.wait.return_value = 1
-
- result, ghp_error = ghp_import.ghp_import(directory, "test message",
- remote='fake-remote-name',
- branch='fake-branch-name')
-
- self.assertEqual(result, False)
- self.assertEqual(ghp_error, error_string)
- finally:
- shutil.rmtree(directory)
diff --git a/mkdocs/tests/utils/utils_tests.py b/mkdocs/tests/utils/utils_tests.py
deleted file mode 100644
index 609b9f00a..000000000
--- a/mkdocs/tests/utils/utils_tests.py
+++ /dev/null
@@ -1,462 +0,0 @@
-#!/usr/bin/env python
-
-
-from unittest import mock
-import os
-import unittest
-import tempfile
-import shutil
-import stat
-import datetime
-
-from mkdocs import utils, exceptions
-from mkdocs.structure.files import File
-from mkdocs.structure.pages import Page
-from mkdocs.tests.base import dedent, load_config
-
-
-class UtilsTests(unittest.TestCase):
- def test_html_path(self):
- expected_results = {
- 'index.md': 'index.html',
- 'api-guide.md': 'api-guide/index.html',
- 'api-guide/index.md': 'api-guide/index.html',
- 'api-guide/testing.md': 'api-guide/testing/index.html',
- }
- for file_path, expected_html_path in expected_results.items():
- html_path = utils.get_html_path(file_path)
- self.assertEqual(html_path, expected_html_path)
-
- def test_url_path(self):
- expected_results = {
- 'index.md': '/',
- 'api-guide.md': '/api-guide/',
- 'api-guide/index.md': '/api-guide/',
- 'api-guide/testing.md': '/api-guide/testing/',
- }
- for file_path, expected_html_path in expected_results.items():
- html_path = utils.get_url_path(file_path)
- self.assertEqual(html_path, expected_html_path)
-
- def test_is_markdown_file(self):
- expected_results = {
- 'index.md': True,
- 'index.MARKDOWN': True,
- 'index.txt': False,
- 'indexmd': False
- }
- for path, expected_result in expected_results.items():
- is_markdown = utils.is_markdown_file(path)
- self.assertEqual(is_markdown, expected_result)
-
- def test_is_html_file(self):
- expected_results = {
- 'index.htm': True,
- 'index.HTML': True,
- 'index.txt': False,
- 'indexhtml': False
- }
- for path, expected_result in expected_results.items():
- is_html = utils.is_html_file(path)
- self.assertEqual(is_html, expected_result)
-
- def test_create_media_urls(self):
-
- expected_results = {
- 'https://media.cdn.org/jq.js': [
- 'https://media.cdn.org/jq.js',
- 'https://media.cdn.org/jq.js',
- 'https://media.cdn.org/jq.js'
- ],
- 'http://media.cdn.org/jquery.js': [
- 'http://media.cdn.org/jquery.js',
- 'http://media.cdn.org/jquery.js',
- 'http://media.cdn.org/jquery.js'
- ],
- '//media.cdn.org/jquery.js': [
- '//media.cdn.org/jquery.js',
- '//media.cdn.org/jquery.js',
- '//media.cdn.org/jquery.js'
- ],
- 'media.cdn.org/jquery.js': [
- 'media.cdn.org/jquery.js',
- 'media.cdn.org/jquery.js',
- '../media.cdn.org/jquery.js'
- ],
- 'local/file/jquery.js': [
- 'local/file/jquery.js',
- 'local/file/jquery.js',
- '../local/file/jquery.js'
- ],
- 'local\\windows\\file\\jquery.js': [
- 'local/windows/file/jquery.js',
- 'local/windows/file/jquery.js',
- '../local/windows/file/jquery.js'
- ],
- 'image.png': [
- 'image.png',
- 'image.png',
- '../image.png'
- ],
- 'style.css?v=20180308c': [
- 'style.css?v=20180308c',
- 'style.css?v=20180308c',
- '../style.css?v=20180308c'
- ],
- '#some_id': [
- '#some_id',
- '#some_id',
- '#some_id'
- ]
- }
-
- cfg = load_config(use_directory_urls=False)
- pages = [
- Page('Home', File('index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']), cfg),
- Page('About', File('about.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']), cfg),
- Page('FooBar', File('foo/bar.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']), cfg)
- ]
-
- for i, page in enumerate(pages):
- urls = utils.create_media_urls(expected_results.keys(), page)
- self.assertEqual([v[i] for v in expected_results.values()], urls)
-
- def test_create_media_urls_use_directory_urls(self):
-
- expected_results = {
- 'https://media.cdn.org/jq.js': [
- 'https://media.cdn.org/jq.js',
- 'https://media.cdn.org/jq.js',
- 'https://media.cdn.org/jq.js'
- ],
- 'http://media.cdn.org/jquery.js': [
- 'http://media.cdn.org/jquery.js',
- 'http://media.cdn.org/jquery.js',
- 'http://media.cdn.org/jquery.js'
- ],
- '//media.cdn.org/jquery.js': [
- '//media.cdn.org/jquery.js',
- '//media.cdn.org/jquery.js',
- '//media.cdn.org/jquery.js'
- ],
- 'media.cdn.org/jquery.js': [
- 'media.cdn.org/jquery.js',
- '../media.cdn.org/jquery.js',
- '../../media.cdn.org/jquery.js'
- ],
- 'local/file/jquery.js': [
- 'local/file/jquery.js',
- '../local/file/jquery.js',
- '../../local/file/jquery.js'
- ],
- 'local\\windows\\file\\jquery.js': [
- 'local/windows/file/jquery.js',
- '../local/windows/file/jquery.js',
- '../../local/windows/file/jquery.js'
- ],
- 'image.png': [
- 'image.png',
- '../image.png',
- '../../image.png'
- ],
- 'style.css?v=20180308c': [
- 'style.css?v=20180308c',
- '../style.css?v=20180308c',
- '../../style.css?v=20180308c'
- ],
- '#some_id': [
- '#some_id',
- '#some_id',
- '#some_id'
- ]
- }
-
- cfg = load_config(use_directory_urls=True)
- pages = [
- Page('Home', File('index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']), cfg),
- Page('About', File('about.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']), cfg),
- Page('FooBar', File('foo/bar.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']), cfg)
- ]
-
- for i, page in enumerate(pages):
- urls = utils.create_media_urls(expected_results.keys(), page)
- self.assertEqual([v[i] for v in expected_results.values()], urls)
-
- def test_reduce_list(self):
- self.assertEqual(
- utils.reduce_list([1, 2, 3, 4, 5, 5, 2, 4, 6, 7, 8]),
- [1, 2, 3, 4, 5, 6, 7, 8]
- )
-
- def test_get_themes(self):
-
- self.assertEqual(
- sorted(utils.get_theme_names()),
- ['mkdocs', 'readthedocs'])
-
- @mock.patch('pkg_resources.iter_entry_points', autospec=True)
- def test_get_theme_dir(self, mock_iter):
-
- path = 'some/path'
-
- theme = mock.Mock()
- theme.name = 'mkdocs2'
- theme.dist.key = 'mkdocs2'
- theme.load().__file__ = os.path.join(path, '__init__.py')
-
- mock_iter.return_value = iter([theme])
-
- self.assertEqual(utils.get_theme_dir(theme.name), os.path.abspath(path))
-
- def test_get_theme_dir_keyerror(self):
-
- self.assertRaises(KeyError, utils.get_theme_dir, 'nonexistanttheme')
-
- @mock.patch('pkg_resources.iter_entry_points', autospec=True)
- def test_get_theme_dir_importerror(self, mock_iter):
-
- theme = mock.Mock()
- theme.name = 'mkdocs2'
- theme.dist.key = 'mkdocs2'
- theme.load.side_effect = ImportError()
-
- mock_iter.return_value = iter([theme])
-
- self.assertRaises(ImportError, utils.get_theme_dir, theme.name)
-
- @mock.patch('pkg_resources.iter_entry_points', autospec=True)
- def test_get_themes_warning(self, mock_iter):
-
- theme1 = mock.Mock()
- theme1.name = 'mkdocs2'
- theme1.dist.key = 'mkdocs2'
- theme1.load().__file__ = "some/path1"
-
- theme2 = mock.Mock()
- theme2.name = 'mkdocs2'
- theme2.dist.key = 'mkdocs3'
- theme2.load().__file__ = "some/path2"
-
- mock_iter.return_value = iter([theme1, theme2])
-
- self.assertEqual(
- sorted(utils.get_theme_names()),
- sorted(['mkdocs2', ]))
-
- @mock.patch('pkg_resources.iter_entry_points', autospec=True)
- @mock.patch('pkg_resources.get_entry_map', autospec=True)
- def test_get_themes_error(self, mock_get, mock_iter):
-
- theme1 = mock.Mock()
- theme1.name = 'mkdocs'
- theme1.dist.key = 'mkdocs'
- theme1.load().__file__ = "some/path1"
-
- theme2 = mock.Mock()
- theme2.name = 'mkdocs'
- theme2.dist.key = 'mkdocs2'
- theme2.load().__file__ = "some/path2"
-
- mock_iter.return_value = iter([theme1, theme2])
- mock_get.return_value = {'mkdocs': theme1, }
-
- self.assertRaises(exceptions.ConfigurationError, utils.get_theme_names)
-
- def test_nest_paths(self):
-
- j = os.path.join
-
- result = utils.nest_paths([
- 'index.md',
- j('user-guide', 'configuration.md'),
- j('user-guide', 'styling-your-docs.md'),
- j('user-guide', 'writing-your-docs.md'),
- j('about', 'contributing.md'),
- j('about', 'license.md'),
- j('about', 'release-notes.md'),
- ])
-
- self.assertEqual(
- result,
- [
- 'index.md',
- {'User guide': [
- j('user-guide', 'configuration.md'),
- j('user-guide', 'styling-your-docs.md'),
- j('user-guide', 'writing-your-docs.md')]},
- {'About': [
- j('about', 'contributing.md'),
- j('about', 'license.md'),
- j('about', 'release-notes.md')]}
- ]
- )
-
- def test_unicode_yaml(self):
-
- yaml_src = dedent(
- '''
- key: value
- key2:
- - value
- '''
- )
-
- config = utils.yaml_load(yaml_src)
- self.assertTrue(isinstance(config['key'], str))
- self.assertTrue(isinstance(config['key2'][0], str))
-
- def test_copy_files(self):
- src_paths = [
- 'foo.txt',
- 'bar.txt',
- 'baz.txt',
- ]
- dst_paths = [
- 'foo.txt',
- 'foo/', # ensure src filename is appended
- 'foo/bar/baz.txt' # ensure missing dirs are created
- ]
- expected = [
- 'foo.txt',
- 'foo/bar.txt',
- 'foo/bar/baz.txt',
- ]
-
- src_dir = tempfile.mkdtemp()
- dst_dir = tempfile.mkdtemp()
-
- try:
- for i, src in enumerate(src_paths):
- src = os.path.join(src_dir, src)
- with open(src, 'w') as f:
- f.write('content')
- dst = os.path.join(dst_dir, dst_paths[i])
- utils.copy_file(src, dst)
- self.assertTrue(os.path.isfile(os.path.join(dst_dir, expected[i])))
- finally:
- shutil.rmtree(src_dir)
- shutil.rmtree(dst_dir)
-
- def test_copy_files_without_permissions(self):
- src_paths = [
- 'foo.txt',
- 'bar.txt',
- 'baz.txt',
- ]
- expected = [
- 'foo.txt',
- 'bar.txt',
- 'baz.txt',
- ]
-
- src_dir = tempfile.mkdtemp()
- dst_dir = tempfile.mkdtemp()
-
- try:
- for i, src in enumerate(src_paths):
- src = os.path.join(src_dir, src)
- with open(src, 'w') as f:
- f.write('content')
- # Set src file to read-only
- os.chmod(src, stat.S_IRUSR)
- utils.copy_file(src, dst_dir)
- self.assertTrue(os.path.isfile(os.path.join(dst_dir, expected[i])))
- self.assertNotEqual(os.stat(src).st_mode, os.stat(os.path.join(dst_dir, expected[i])).st_mode)
- # While src was read-only, dst must remain writable
- self.assertTrue(os.access(os.path.join(dst_dir, expected[i]), os.W_OK))
- finally:
- for src in src_paths:
- # Undo read-only so we can delete temp files
- src = os.path.join(src_dir, src)
- if os.path.exists(src):
- os.chmod(src, stat.S_IRUSR | stat.S_IWUSR)
- shutil.rmtree(src_dir)
- shutil.rmtree(dst_dir)
-
- def test_mm_meta_data(self):
- doc = dedent(
- """
- Title: Foo Bar
- Date: 2018-07-10
- Summary: Line one
- Line two
- Tags: foo
- Tags: bar
-
- Doc body
- """
- )
- self.assertEqual(
- utils.meta.get_data(doc),
- (
- "Doc body",
- {
- 'title': 'Foo Bar',
- 'date': '2018-07-10',
- 'summary': 'Line one Line two',
- 'tags': 'foo bar'
- }
- )
- )
-
- def test_mm_meta_data_blank_first_line(self):
- doc = '\nfoo: bar\nDoc body'
- self.assertEqual(utils.meta.get_data(doc), (doc.lstrip(), {}))
-
- def test_yaml_meta_data(self):
- doc = dedent(
- """
- ---
- Title: Foo Bar
- Date: 2018-07-10
- Summary: Line one
- Line two
- Tags:
- - foo
- - bar
- ---
- Doc body
- """
- )
- self.assertEqual(
- utils.meta.get_data(doc),
- (
- "Doc body",
- {
- 'Title': 'Foo Bar',
- 'Date': datetime.date(2018, 7, 10),
- 'Summary': 'Line one Line two',
- 'Tags': ['foo', 'bar']
- }
- )
- )
-
- def test_yaml_meta_data_not_dict(self):
- doc = dedent(
- """
- ---
- - List item
- ---
- Doc body
- """
- )
- self.assertEqual(utils.meta.get_data(doc), (doc, {}))
-
- def test_yaml_meta_data_invalid(self):
- doc = dedent(
- """
- ---
- foo: bar: baz
- ---
- Doc body
- """
- )
- self.assertEqual(utils.meta.get_data(doc), (doc, {}))
-
- def test_no_meta_data(self):
- doc = dedent(
- """
- Doc body
- """
- )
- self.assertEqual(utils.meta.get_data(doc), (doc, {}))
diff --git a/mkdocs/theme.py b/mkdocs/theme.py
deleted file mode 100644
index 3f1c38032..000000000
--- a/mkdocs/theme.py
+++ /dev/null
@@ -1,113 +0,0 @@
-import os
-import jinja2
-import logging
-
-from mkdocs import utils
-from mkdocs.utils import filters
-from mkdocs.config.base import ValidationError
-
-log = logging.getLogger(__name__)
-log.addFilter(utils.warning_filter)
-
-
-class Theme:
- """
- A Theme object.
-
- Keywords:
-
- name: The name of the theme as defined by its entrypoint.
-
- custom_dir: User defined directory for custom templates.
-
- static_templates: A list of templates to render as static pages.
-
- All other keywords are passed as-is and made available as a key/value mapping.
-
- """
-
- def __init__(self, name=None, **user_config):
- self.name = name
- self._vars = {}
-
- # MkDocs provided static templates are always included
- package_dir = os.path.abspath(os.path.dirname(__file__))
- mkdocs_templates = os.path.join(package_dir, 'templates')
- self.static_templates = set(os.listdir(mkdocs_templates))
-
- # Build self.dirs from various sources in order of precedence
- self.dirs = []
-
- if 'custom_dir' in user_config:
- self.dirs.append(user_config.pop('custom_dir'))
-
- if self.name:
- self._load_theme_config(name)
-
- # Include templates provided directly by MkDocs (outside any theme)
- self.dirs.append(mkdocs_templates)
-
- # Handle remaining user configs. Override theme configs (if set)
- self.static_templates.update(user_config.pop('static_templates', []))
- self._vars.update(user_config)
-
- def __repr__(self):
- return "{}(name='{}', dirs={}, static_templates={}, {})".format(
- self.__class__.__name__, self.name, self.dirs, list(self.static_templates),
- ', '.join('{}={}'.format(k, repr(v)) for k, v in self._vars.items())
- )
-
- def __getitem__(self, key):
- return self._vars[key]
-
- def __setitem__(self, key, value):
- self._vars[key] = value
-
- def __contains__(self, item):
- return item in self._vars
-
- def __iter__(self):
- return iter(self._vars)
-
- def _load_theme_config(self, name):
- """ Recursively load theme and any parent themes. """
-
- theme_dir = utils.get_theme_dir(name)
- self.dirs.append(theme_dir)
-
- try:
- file_path = os.path.join(theme_dir, 'mkdocs_theme.yml')
- with open(file_path, 'rb') as f:
- theme_config = utils.yaml_load(f)
- if theme_config is None:
- theme_config = {}
- except OSError as e:
- log.debug(e)
- raise ValidationError(
- "The theme '{}' does not appear to have a configuration file. "
- "Please upgrade to a current version of the theme.".format(name)
- )
-
- log.debug("Loaded theme configuration for '%s' from '%s': %s", name, file_path, theme_config)
-
- parent_theme = theme_config.pop('extends', None)
- if parent_theme:
- themes = utils.get_theme_names()
- if parent_theme not in themes:
- raise ValidationError(
- "The theme '{}' inherits from '{}', which does not appear to be installed. "
- "The available installed themes are: {}".format(name, parent_theme, ', '.join(themes))
- )
- self._load_theme_config(parent_theme)
-
- self.static_templates.update(theme_config.pop('static_templates', []))
- self._vars.update(theme_config)
-
- def get_env(self):
- """ Return a Jinja environment for the theme. """
-
- loader = jinja2.FileSystemLoader(self.dirs)
- env = jinja2.Environment(loader=loader)
- env.filters['tojson'] = filters.tojson
- env.filters['url'] = filters.url_filter
- return env
diff --git a/mkdocs/themes/__init__.py b/mkdocs/themes/__init__.py
deleted file mode 100644
index e69de29bb..000000000
diff --git a/mkdocs/themes/mkdocs/404.html b/mkdocs/themes/mkdocs/404.html
deleted file mode 100644
index c45fda8b9..000000000
--- a/mkdocs/themes/mkdocs/404.html
+++ /dev/null
@@ -1,12 +0,0 @@
-{% extends "base.html" %}
-
-{% block content %}
-
- Page not found
-t |