:py:`PAGE_HEADER: str` :abbr:`reST <reStructuredText>` markup to
put at the top of every page. If not set,
nothing is added anywhere. The
- ``{filename}`` placeholder is replaced with
- current file name.
+ ``{url}`` placeholder is replaced with
+ current file URL.
:py:`FINE_PRINT: str` :abbr:`reST <reStructuredText>` markup to
put into the footer. If not set, a default
generic text is used. If empty, no footer
:py:`SEARCH_DISABLED` is not :py:`True`.
:py:`DOCUTILS_SETTINGS: Dict[Any]` Additional docutils settings. Key/value
pairs as described in `the docs <http://docutils.sourceforge.net/docs/user/config.html>`_.
+:py:`URL_FORMATTER: Callable` Function for creating filenames and URLs
+ for modules, classes, pages and index
+ pages. See `Custom URL formatters`_ for
+ more information.
=================================== ===========================================
`Theme selection`_
The :py:`LINKS_NAVBAR1` and :py:`LINKS_NAVBAR2` options define which links are
shown on the top navbar, split into left and right column on small screen
sizes. These options take a list of :py:`(title, path, sub)` tuples ---
-``title`` is the link title, ``path`` is path to a particular page or
-module/class (in the form of ``module.sub.ClassName``, for example) and ``sub``
-is an optional submenu, containing :py:`(title, path)` tuples. The ``path`` can
-be also one of ``pages``, ``modules`` or ``classes``, linking to the page /
-module / class index. When rendering, the path is converted to an actual URL to
-the destination file.
+``title`` is the link title; ``path`` is either one of :py:`'index'`,
+:py:`'pages'`, :py:`'modules'` or :py:`'classes'` (linking to the main page or
+page / module / class index path), a full URL (pasted as-is) or a *path* to a
+particular page or module/class (in the form of
+:py:`['module', 'sub', 'ClassName']` for :py:`module.sub.ClassName`, which then
+gets formatted according to `URL formatting rules <#custom-url-formatters>`_);
+and ``sub`` is an optional submenu, containing :py:`(title, path)` tuples, with
+``path`` being interpreted the same way.
By default the variables are defined like following --- there's just three
items in the left column, with no submenus and the right column is empty:
SEARCH_EXTERNAL_URL = 'https://google.com/search?q=site:doc.magnum.graphics+{query}'
+`Custom URL formatters`_
+------------------------
+
+The :py:`URL_FORMATTER` option allows you to control how *all* filenames and
+generated URLs look like. It takes an entry type and a "path" as a list of
+strings (so for example :py:`my.module.Class` is represented as
+:py:`['my', 'module', 'Class']`), returning a tuple a filename and an URL.
+Those can be the same, but also different (for example a file getting saved
+into ``my/module/Class/index.html`` but the actual URL being
+``https://docs.my.module/Class/``). The default implementation looks like this,
+producing both filenames and URLs in the form of ``my.module.Class.html``:
+
+.. code:: py
+
+ def default_url_formatter(type: EntryType, path: List[str]) -> Tuple[str, str]:
+ url = '.'.join(path) + '.html'
+ return url, url
+
+The ``type`` is an enum, if you don't want to fiddle with imports, compare
+:py:`str(type)` against a string, which is one of :py:`'PAGE'`, :py:`'MODULE'`,
+:py:`'CLASS'` or :py:`'SPECIAL'`. The :py:`'SPECIAL'` is for index pages and in
+that case the ``path`` has always just one item, one of :py:`'pages'`,
+:py:`'modules'` or :py:`'classes'`.
+
`Module inspection`_
====================
======================= =======================================================
Each template gets passed all configuration values from the `Configuration`_
-table as-is, together with a :py:`FILENAME` variable with name of given output
-file. In addition to builtin Jinja2 filters, the ``basename_or_url`` filter
-returns either a basename of file path, if the path is relative; or a full URL,
-if the argument is an absolute URL. It's useful in cases like this:
+table as-is, together with a :py:`URL` variable with URL of given output file.
+In addition to builtin Jinja2 filters, the ``basename_or_url`` filter returns
+either a basename of file path, if the path is relative; or a full URL, if the
+argument is an absolute URL. It's useful in cases like this:
.. code:: html+jinja
Property Description
======================================= =======================================
:py:`page.summary` Doc summary
-:py:`page.url` File URL
+:py:`page.filename` File name [3]_
+:py:`page.url` File URL [3]_
:py:`page.breadcrumb` List of :py:`(title, URL)` tuples for
breadcrumb navigation.
:py:`page.content` Detailed documentation, if any
======================= =======================================================
Each template is passed all configuration values from the `Configuration`_
-table as-is, together with a :py:`FILENAME`, as above. The navigation tree is
+table as-is, together with an :py:`URL`, as above. The navigation tree is
provided in an :py:`index` object, which has the following properties:
.. class:: m-table m-fullwidth
*documented* enum value listing that makes it worth to render the full
description block. If :py:`False`, the member should be included only in
the summary listing on top of the page to avoid unnecessary repetition.
+.. [3] :py:`page.filename` and :py:`page.url` is generated by an URL formatter,
+ see `Custom URL formatters`_ for more information
from enum import Enum
from types import SimpleNamespace as Empty
from importlib.machinery import SourceFileLoader
-from typing import Tuple, Dict, Set, Any, List
+from typing import Tuple, Dict, Set, Any, List, Callable
from urllib.parse import urljoin
from distutils.version import LooseVersion
from docutils.transforms import Transform
default_templates = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'templates/python/')
+special_pages = ['index', 'modules', 'classes', 'pages']
+
class EntryType(Enum):
- PAGE = 0
- MODULE = 1
- CLASS = 2
- ENUM = 3
- ENUM_VALUE = 4
- FUNCTION = 5
- PROPERTY = 6
- DATA = 7
+ SPECIAL = 0 # one of files from special_pages
+ PAGE = 1
+ MODULE = 2
+ CLASS = 3
+ ENUM = 4
+ ENUM_VALUE = 5
+ FUNCTION = 6
+ PROPERTY = 7
+ DATA = 8
+
+def default_url_formatter(type: EntryType, path: List[str]) -> Tuple[str, str]:
+ # TODO: what about nested pages, how to format?
+ url = '.'.join(path) + '.html'
+ assert '/' not in url # TODO
+ return url, url
default_config = {
'PROJECT_TITLE': 'My Python Project',
""",
'SEARCH_BASE_URL': None,
'SEARCH_EXTERNAL_URL': None,
+
+ 'URL_FORMATTER': default_url_formatter
}
class State:
def is_enum(state: State, object) -> bool:
return (inspect.isclass(object) and issubclass(object, enum.Enum)) or (state.config['PYBIND11_COMPATIBILITY'] and hasattr(object, '__members__'))
-def make_url(path: List[str]) -> str:
- return '.'.join(path) + '.html'
-
def object_type(state: State, object) -> EntryType:
if inspect.ismodule(object): return EntryType.MODULE
if inspect.isclass(object):
def render(config, template: str, page, env: jinja2.Environment):
template = env.get_template(template)
- rendered = template.render(page=page, FILENAME=page.url, **config)
- with open(os.path.join(config['OUTPUT'], page.url), 'wb') as f:
+ rendered = template.render(page=page, URL=page.url, **config)
+ with open(os.path.join(config['OUTPUT'], page.filename), 'wb') as f:
f.write(rendered.encode('utf-8'))
# Add back a trailing newline so we don't need to bother with
# patching test files to include a trailing newline to make Git
assert inspect.ismodule(module)
out = Empty()
- out.url = make_url(path)
+ out.url = state.config['URL_FORMATTER'](EntryType.MODULE, path)[1]
out.name = path[-1]
out.summary = extract_summary(state, state.class_docs, path, module.__doc__)
return out
assert inspect.isclass(class_)
out = Empty()
- out.url = make_url(path)
+ out.url = state.config['URL_FORMATTER'](EntryType.CLASS, path)[1]
out.name = path[-1]
out.summary = extract_summary(state, state.class_docs, path, class_.__doc__)
return out
return out
def render_module(state: State, path, module, env):
- logging.debug("generating %s.html", '.'.join(path))
+ # Generate breadcrumb as the first thing as it generates the output
+ # filename as a side effect
+ breadcrumb = []
+ filename: str
+ url: str
+ for i in range(len(path)):
+ filename, url = state.config['URL_FORMATTER'](EntryType.MODULE, path[:i + 1])
+ breadcrumb += [(path[i], url)]
+
+ logging.debug("generating %s", filename)
# Call all registered page begin hooks
for hook in state.hooks_pre_page: hook()
- url_base = ''
- breadcrumb = []
- for i in path:
- url_base += i + '.'
- breadcrumb += [(i, url_base + 'html')]
-
page = Empty()
page.summary = extract_summary(state, state.module_docs, path, module.__doc__)
- page.url = breadcrumb[-1][1]
+ page.filename = filename
+ page.url = url
page.breadcrumb = breadcrumb
page.prefix_wbr = '.<wbr />'.join(path + [''])
page.modules = []
])
def render_class(state: State, path, class_, env):
- logging.debug("generating %s.html", '.'.join(path))
+ # Generate breadcrumb as the first thing as it generates the output
+ # filename as a side effect. It's a bit hairy because we need to figure out
+ # proper entry type for the URL formatter for each part of the breadcrumb.
+ breadcrumb = []
+ filename: str
+ url: str
+ for i in range(len(path)):
+ type = state.name_map['.'.join(path[:i + 1])].type
+ filename, url = state.config['URL_FORMATTER'](type, path[:i + 1])
+ breadcrumb += [(path[i], url)]
+
+ logging.debug("generating %s", filename)
# Call all registered page begin hooks
for hook in state.hooks_pre_page: hook()
- url_base = ''
- breadcrumb = []
- for i in path:
- url_base += i + '.'
- breadcrumb += [(i, url_base + 'html')]
-
page = Empty()
page.summary = extract_summary(state, state.class_docs, path, class_.__doc__)
- page.url = breadcrumb[-1][1]
+ page.filename = filename
+ page.url = url
page.breadcrumb = breadcrumb
page.prefix_wbr = '.<wbr />'.join(path + [''])
page.classes = []
# discard the output afterwards.
with open(filename, 'r') as f: publish_rst(state, f.read())
-def render_page(state: State, path, filename, env):
- logging.debug("generating %s.html", '.'.join(path))
+def render_page(state: State, path, input_filename, env):
+ filename, url = state.config['URL_FORMATTER'](EntryType.PAGE, path)
+
+ logging.debug("generating %s", filename)
# Call all registered page begin hooks
for hook in state.hooks_pre_page: hook()
# Render the file
- with open(filename, 'r') as f: pub = publish_rst(state, f.read(), source_path=filename)
+ with open(input_filename, 'r') as f: pub = publish_rst(state, f.read(), source_path=input_filename)
# Extract metadata from the page
metadata = {}
# Breadcrumb, we don't do page hierarchy yet
assert len(path) == 1
- breadcrumb = [(pub.writer.parts.get('title'), path[0] + '.html')]
+ breadcrumb = [(pub.writer.parts.get('title'), url)]
page = Empty()
- page.url = breadcrumb[-1][1]
+ page.filename = filename
+ page.url = url
page.breadcrumb = breadcrumb
page.prefix_wbr = path[0]
render(state.config, 'page.html', page, env)
def run(basedir, config, templates):
- # Prepare Jinja environment
- env = jinja2.Environment(
- loader=jinja2.FileSystemLoader(templates), trim_blocks=True,
- lstrip_blocks=True, enable_async=True)
- # Filter to return file basename or the full URL, if absolute
- def basename_or_url(path):
- if urllib.parse.urlparse(path).netloc: return path
- return os.path.basename(path)
- # Filter to return URL for given symbol or the full URL, if absolute
- def path_to_url(path):
- if urllib.parse.urlparse(path).netloc: return path
- return path + '.html'
- env.filters['basename_or_url'] = basename_or_url
- env.filters['path_to_url'] = path_to_url
- env.filters['urljoin'] = urljoin
-
# Populate the INPUT, if not specified, make it absolute
if config['INPUT'] is None: config['INPUT'] = basedir
else: config['INPUT'] = os.path.join(basedir, config['INPUT'])
state = State(config)
+ # Prepare Jinja environment
+ env = jinja2.Environment(
+ loader=jinja2.FileSystemLoader(templates), trim_blocks=True,
+ lstrip_blocks=True, enable_async=True)
+ # Filter to return file basename or the full URL, if absolute
+ def basename_or_url(path):
+ if urllib.parse.urlparse(path).netloc: return path
+ return os.path.basename(path)
+ # Filter to return URL for given symbol. If the path is a string, first try
+ # to treat it as an URL. If that fails, turn it into a list and try to look
+ # it up in various dicts.
+ def path_to_url(path):
+ if isinstance(path, str):
+ if urllib.parse.urlparse(path).netloc: return path
+ path = [path]
+ entry = state.name_map['.'.join(path)]
+ return state.config['URL_FORMATTER'](entry.type, entry.path)[1]
+
+ env.filters['basename_or_url'] = basename_or_url
+ env.filters['path_to_url'] = path_to_url
+ env.filters['urljoin'] = urljoin
+
# Set up extra plugin paths. The one for m.css plugins was added above.
for path in config['PLUGIN_PATHS']:
if path not in sys.path: sys.path.append(os.path.join(config['INPUT'], path))
crawl_module(state, [module_name], module)
class_index += [module_name]
+ # Add special pages to the name map. The pages are done after so they can
+ # override these.
+ for page in special_pages:
+ entry = Empty()
+ entry.type = EntryType.SPECIAL
+ entry.path = [page]
+ state.name_map[page] = entry
+
# Do the same for pages
# TODO: turn also into some crawl_page() function? once we have subpages?
page_index = []
index_entry = Empty()
index_entry.kind = 'module' if entry.type == EntryType.MODULE else 'class'
index_entry.name = entry.path[-1]
- index_entry.url = make_url(entry.path)
+ index_entry.url = state.config['URL_FORMATTER'](entry.type, entry.path)[1]
index_entry.summary = entry.summary
index_entry.has_nestable_children = False
index_entry.children = []
index_entry = Empty()
index_entry.kind = 'page'
index_entry.name = entry.name
- index_entry.url = make_url(entry.path)
+ index_entry.url = state.config['URL_FORMATTER'](entry.type, entry.path)[1]
index_entry.summary = entry.summary
index_entry.has_nestable_children = False
index_entry.children = []
index = Empty()
index.classes = class_index
index.pages = page_index
- for file in ['modules.html', 'classes.html', 'pages.html']:
- template = env.get_template(file)
- rendered = template.render(index=index, FILENAME=file, **config)
- with open(os.path.join(config['OUTPUT'], file), 'wb') as f:
+ for file in special_pages[1:]: # exclude index
+ template = env.get_template(file + '.html')
+ filename, url = state.config['URL_FORMATTER'](EntryType.SPECIAL, [file])
+ rendered = template.render(index=index, URL=url, **config)
+ with open(os.path.join(config['OUTPUT'], filename), 'wb') as f:
f.write(rendered.encode('utf-8'))
# Add back a trailing newline so we don't need to bother with
# patching test files to include a trailing newline to make Git
if 'index.rst' not in [os.path.basename(i) for i in config['INPUT_PAGES']]:
logging.debug("writing index.html for an empty main page")
+ filename, url = state.config['URL_FORMATTER'](EntryType.SPECIAL, ['index'])
+
page = Empty()
- page.breadcrumb = [(config['PROJECT_TITLE'], 'index.html')]
- page.url = page.breadcrumb[-1][1]
+ page.filename = filename
+ page.url = url
+ page.breadcrumb = [(config['PROJECT_TITLE'], url)]
render(config, 'page.html', page, env)
# Copy referenced files
<div class="m-row">
{% if MAIN_PROJECT_URL and PROJECT_TITLE %}
<span id="m-navbar-brand" class="m-col-t-8 m-col-m-none m-left-m">
- <a href="{{ MAIN_PROJECT_URL }}">{{ PROJECT_TITLE }}</a> <span class="m-breadcrumb">|</span> <a href="index.html" class="m-thin">{{ PROJECT_SUBTITLE }}</a>
+ <a href="{{ MAIN_PROJECT_URL }}">{{ PROJECT_TITLE }}</a> <span class="m-breadcrumb">|</span> <a href="{{ 'index'|path_to_url }}" class="m-thin">{{ PROJECT_SUBTITLE }}</a>
</span>
{% else %}
- <a href="index.html" id="m-navbar-brand" class="m-col-t-8 m-col-m-none m-left-m">{{ PROJECT_TITLE }}{% if PROJECT_SUBTITLE %} <span class="m-thin">{{ PROJECT_SUBTITLE }}</span>{% endif %}</a>
+ <a href="{{ 'index'|path_to_url }}" id="m-navbar-brand" class="m-col-t-8 m-col-m-none m-left-m">{{ PROJECT_TITLE }}{% if PROJECT_SUBTITLE %} <span class="m-thin">{{ PROJECT_SUBTITLE }}</span>{% endif %}</a>
{% endif %}
{% if LINKS_NAVBAR1 or LINKS_NAVBAR2 or not SEARCH_DISABLED %}
<div class="m-col-t-4 m-hide-m m-text-right m-nopadr">
<div class="m-row">
<div class="m-col-l-10 m-push-l-1">
{% if PAGE_HEADER %}
- {{ PAGE_HEADER|render_rst|replace('{filename}', FILENAME) }}
+ {{ PAGE_HEADER|render_rst|replace('{url}', URL) }}
{% endif %}
{% block main %}
{% endblock %}
--- /dev/null
+.. this is here only to make the LINKS_NAVBAR work
--- /dev/null
+.. this is here only to make the LINKS_NAVBAR work
--- /dev/null
+.. this is here only to make the LINKS_NAVBAR work
--- /dev/null
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8" />
+ <title>link_formatting.Class.Sub | My Python Project</title>
+ <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400i,600,600i%7CSource+Code+Pro:400,400i,600" />
+ <link rel="stylesheet" href="m-dark+documentation.compiled.css" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+</head>
+<body>
+<header><nav id="navigation">
+ <div class="m-container">
+ <div class="m-row">
+ <a href="s.index.html#this-is-an-url" id="m-navbar-brand" class="m-col-t-8 m-col-m-none m-left-m">My Python Project</a>
+ <div class="m-col-t-4 m-hide-m m-text-right m-nopadr">
+ <a id="m-navbar-show" href="#navigation" title="Show navigation"></a>
+ <a id="m-navbar-hide" href="#" title="Hide navigation"></a>
+ </div>
+ <div id="m-navbar-collapse" class="m-col-t-12 m-show-m m-col-m-none m-right-m">
+ <div class="m-row">
+ <ol class="m-col-t-12 m-col-m-none">
+ <li><a href="s.pages.html#this-is-an-url">Pages</a></li>
+ <li><a href="s.modules.html#this-is-an-url">Modules</a></li>
+ <li><a href="s.classes.html#this-is-an-url">Classes</a></li>
+ </ol>
+ <ol class="m-col-t-6 m-col-m-none" start="4">
+ <li><a href="p.page.html#this-is-an-url">A page</a></li>
+ <li><a href="m.link_formatting.html#this-is-an-url">A module</a></li>
+ <li><a href="c.link_formatting.Class.html#this-is-an-url">The class</a></li>
+ </ol>
+ </div>
+ </div>
+ </div>
+ </div>
+</nav></header>
+<main><article>
+ <div class="m-container m-container-inflatable">
+ <div class="m-row">
+ <div class="m-col-l-10 m-push-l-1">
+ <h1>
+ <span class="m-breadcrumb"><a href="m.link_formatting.html#this-is-an-url">link_formatting</a>.<wbr/></span><span class="m-breadcrumb"><a href="c.link_formatting.Class.html#this-is-an-url">Class</a>.<wbr/></span>Sub <span class="m-thin">class</span>
+ </h1>
+ <p>And a nice subclass, oh.</p>
+ </div>
+ </div>
+ </div>
+</article></main>
+</body>
+</html>
--- /dev/null
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8" />
+ <title>link_formatting.Class | My Python Project</title>
+ <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400i,600,600i%7CSource+Code+Pro:400,400i,600" />
+ <link rel="stylesheet" href="m-dark+documentation.compiled.css" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+</head>
+<body>
+<header><nav id="navigation">
+ <div class="m-container">
+ <div class="m-row">
+ <a href="s.index.html#this-is-an-url" id="m-navbar-brand" class="m-col-t-8 m-col-m-none m-left-m">My Python Project</a>
+ <div class="m-col-t-4 m-hide-m m-text-right m-nopadr">
+ <a id="m-navbar-show" href="#navigation" title="Show navigation"></a>
+ <a id="m-navbar-hide" href="#" title="Hide navigation"></a>
+ </div>
+ <div id="m-navbar-collapse" class="m-col-t-12 m-show-m m-col-m-none m-right-m">
+ <div class="m-row">
+ <ol class="m-col-t-12 m-col-m-none">
+ <li><a href="s.pages.html#this-is-an-url">Pages</a></li>
+ <li><a href="s.modules.html#this-is-an-url">Modules</a></li>
+ <li><a href="s.classes.html#this-is-an-url">Classes</a></li>
+ </ol>
+ <ol class="m-col-t-6 m-col-m-none" start="4">
+ <li><a href="p.page.html#this-is-an-url">A page</a></li>
+ <li><a href="m.link_formatting.html#this-is-an-url">A module</a></li>
+ <li><a href="c.link_formatting.Class.html#this-is-an-url">The class</a></li>
+ </ol>
+ </div>
+ </div>
+ </div>
+ </div>
+</nav></header>
+<main><article>
+ <div class="m-container m-container-inflatable">
+ <div class="m-row">
+ <div class="m-col-l-10 m-push-l-1">
+ <h1>
+ <span class="m-breadcrumb"><a href="m.link_formatting.html#this-is-an-url">link_formatting</a>.<wbr/></span>Class <span class="m-thin">class</span>
+ </h1>
+ <p>This is a nice class.</p>
+ <div class="m-block m-default">
+ <h3>Contents</h3>
+ <ul>
+ <li>
+ Reference
+ <ul>
+ <li><a href="#classes">Classes</a></li>
+ </ul>
+ </li>
+ </ul>
+ </div>
+ <section id="classes">
+ <h2><a href="#classes">Classes</a></h2>
+ <dl class="m-doc">
+ <dt>class <a href="c.link_formatting.Class.Sub.html#this-is-an-url" class="m-doc">Sub</a></dt>
+ <dd>And a nice subclass, oh.</dd>
+ </dl>
+ </section>
+ </div>
+ </div>
+ </div>
+</article></main>
+</body>
+</html>
--- /dev/null
+"""This is a module."""
+
+from . import sub
+
+class Class:
+ """This is a nice class."""
+
+ class Sub:
+ """And a nice subclass, oh."""
--- /dev/null
+"""This is a nice submodule."""
--- /dev/null
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8" />
+ <title>link_formatting | My Python Project</title>
+ <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400i,600,600i%7CSource+Code+Pro:400,400i,600" />
+ <link rel="stylesheet" href="m-dark+documentation.compiled.css" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+</head>
+<body>
+<header><nav id="navigation">
+ <div class="m-container">
+ <div class="m-row">
+ <a href="s.index.html#this-is-an-url" id="m-navbar-brand" class="m-col-t-8 m-col-m-none m-left-m">My Python Project</a>
+ <div class="m-col-t-4 m-hide-m m-text-right m-nopadr">
+ <a id="m-navbar-show" href="#navigation" title="Show navigation"></a>
+ <a id="m-navbar-hide" href="#" title="Hide navigation"></a>
+ </div>
+ <div id="m-navbar-collapse" class="m-col-t-12 m-show-m m-col-m-none m-right-m">
+ <div class="m-row">
+ <ol class="m-col-t-12 m-col-m-none">
+ <li><a href="s.pages.html#this-is-an-url">Pages</a></li>
+ <li><a href="s.modules.html#this-is-an-url">Modules</a></li>
+ <li><a href="s.classes.html#this-is-an-url">Classes</a></li>
+ </ol>
+ <ol class="m-col-t-6 m-col-m-none" start="4">
+ <li><a href="p.page.html#this-is-an-url">A page</a></li>
+ <li><a href="m.link_formatting.html#this-is-an-url">A module</a></li>
+ <li><a href="c.link_formatting.Class.html#this-is-an-url">The class</a></li>
+ </ol>
+ </div>
+ </div>
+ </div>
+ </div>
+</nav></header>
+<main><article>
+ <div class="m-container m-container-inflatable">
+ <div class="m-row">
+ <div class="m-col-l-10 m-push-l-1">
+ <h1>
+ link_formatting <span class="m-thin">module</span>
+ </h1>
+ <p>This is a module.</p>
+ <div class="m-block m-default">
+ <h3>Contents</h3>
+ <ul>
+ <li>
+ Reference
+ <ul>
+ <li><a href="#packages">Modules</a></li>
+ <li><a href="#classes">Classes</a></li>
+ </ul>
+ </li>
+ </ul>
+ </div>
+ <section id="namespaces">
+ <h2><a href="#namespaces">Modules</a></h2>
+ <dl class="m-doc">
+ <dt>module <a href="m.link_formatting.sub.html#this-is-an-url" class="m-doc">sub</a></dt>
+ <dd>This is a nice submodule.</dd>
+ </dl>
+ </section>
+ <section id="classes">
+ <h2><a href="#classes">Classes</a></h2>
+ <dl class="m-doc">
+ <dt>class <a href="c.link_formatting.Class.html#this-is-an-url" class="m-doc">Class</a></dt>
+ <dd>This is a nice class.</dd>
+ </dl>
+ </section>
+ </div>
+ </div>
+ </div>
+</article></main>
+</body>
+</html>
--- /dev/null
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8" />
+ <title>link_formatting.sub | My Python Project</title>
+ <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400i,600,600i%7CSource+Code+Pro:400,400i,600" />
+ <link rel="stylesheet" href="m-dark+documentation.compiled.css" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+</head>
+<body>
+<header><nav id="navigation">
+ <div class="m-container">
+ <div class="m-row">
+ <a href="s.index.html#this-is-an-url" id="m-navbar-brand" class="m-col-t-8 m-col-m-none m-left-m">My Python Project</a>
+ <div class="m-col-t-4 m-hide-m m-text-right m-nopadr">
+ <a id="m-navbar-show" href="#navigation" title="Show navigation"></a>
+ <a id="m-navbar-hide" href="#" title="Hide navigation"></a>
+ </div>
+ <div id="m-navbar-collapse" class="m-col-t-12 m-show-m m-col-m-none m-right-m">
+ <div class="m-row">
+ <ol class="m-col-t-12 m-col-m-none">
+ <li><a href="s.pages.html#this-is-an-url">Pages</a></li>
+ <li><a href="s.modules.html#this-is-an-url">Modules</a></li>
+ <li><a href="s.classes.html#this-is-an-url">Classes</a></li>
+ </ol>
+ <ol class="m-col-t-6 m-col-m-none" start="4">
+ <li><a href="p.page.html#this-is-an-url">A page</a></li>
+ <li><a href="m.link_formatting.html#this-is-an-url">A module</a></li>
+ <li><a href="c.link_formatting.Class.html#this-is-an-url">The class</a></li>
+ </ol>
+ </div>
+ </div>
+ </div>
+ </div>
+</nav></header>
+<main><article>
+ <div class="m-container m-container-inflatable">
+ <div class="m-row">
+ <div class="m-col-l-10 m-push-l-1">
+ <h1>
+ <span class="m-breadcrumb"><a href="m.link_formatting.html#this-is-an-url">link_formatting</a>.<wbr/></span>sub <span class="m-thin">module</span>
+ </h1>
+ <p>This is a nice submodule.</p>
+ </div>
+ </div>
+ </div>
+</article></main>
+</body>
+</html>
--- /dev/null
+This is a page
+##############
--- /dev/null
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8" />
+ <title>My Python Project</title>
+ <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400i,600,600i%7CSource+Code+Pro:400,400i,600" />
+ <link rel="stylesheet" href="m-dark+documentation.compiled.css" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+</head>
+<body>
+<header><nav id="navigation">
+ <div class="m-container">
+ <div class="m-row">
+ <a href="s.index.html#this-is-an-url" id="m-navbar-brand" class="m-col-t-8 m-col-m-none m-left-m">My Python Project</a>
+ <div class="m-col-t-4 m-hide-m m-text-right m-nopadr">
+ <a id="m-navbar-show" href="#navigation" title="Show navigation"></a>
+ <a id="m-navbar-hide" href="#" title="Hide navigation"></a>
+ </div>
+ <div id="m-navbar-collapse" class="m-col-t-12 m-show-m m-col-m-none m-right-m">
+ <div class="m-row">
+ <ol class="m-col-t-12 m-col-m-none">
+ <li><a href="s.pages.html#this-is-an-url">Pages</a></li>
+ <li><a href="s.modules.html#this-is-an-url">Modules</a></li>
+ <li><a href="s.classes.html#this-is-an-url" id="m-navbar-current">Classes</a></li>
+ </ol>
+ <ol class="m-col-t-6 m-col-m-none" start="4">
+ <li><a href="p.page.html#this-is-an-url">A page</a></li>
+ <li><a href="m.link_formatting.html#this-is-an-url">A module</a></li>
+ <li><a href="c.link_formatting.Class.html#this-is-an-url">The class</a></li>
+ </ol>
+ </div>
+ </div>
+ </div>
+ </div>
+</nav></header>
+<main><article>
+ <div class="m-container m-container-inflatable">
+ <div class="m-row">
+ <div class="m-col-l-10 m-push-l-1">
+ <h1>Classes</h2>
+ <ul class="m-doc">
+ <li class="m-doc-collapsible">
+ <a href="#" onclick="return toggle(this)">module</a> <a href="m.link_formatting.html#this-is-an-url" class="m-doc">link_formatting</a> <span class="m-doc">This is a module.</span>
+ <ul class="m-doc">
+ <li>module <a href="m.link_formatting.sub.html#this-is-an-url" class="m-doc">sub</a> <span class="m-doc">This is a nice submodule.</span></li>
+ <li class="m-doc-collapsible collapsed">
+ <a href="#" onclick="return toggle(this)">class</a> <a href="c.link_formatting.Class.html#this-is-an-url" class="m-doc">Class</a> <span class="m-doc">This is a nice class.</span>
+ <ul class="m-doc">
+ <li>class <a href="c.link_formatting.Class.Sub.html#this-is-an-url" class="m-doc">Sub</a> <span class="m-doc">And a nice subclass, oh.</span></li>
+ </ul>
+ </li>
+ </ul>
+ </li>
+ </ul>
+ <script>
+ function toggle(e) {
+ e.parentElement.className = e.parentElement.className == 'm-doc-collapsible' ?
+ 'm-doc-expansible' : 'm-doc-collapsible';
+ return false;
+ }
+ /* Collapse all nodes marked as such. Doing it via JS instead of
+ directly in markup so disabling it doesn't harm usability. The list
+ is somehow regenerated on every iteration and shrinks as I change
+ the classes. It's not documented anywhere and I'm not sure if this
+ is the same across browsers, so I am going backwards in that list to
+ be sure. */
+ var collapsed = document.getElementsByClassName("collapsed");
+ for(var i = collapsed.length - 1; i >= 0; --i)
+ collapsed[i].className = 'm-doc-expansible';
+ </script>
+ </div>
+ </div>
+ </div>
+</article></main>
+</body>
+</html>
--- /dev/null
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8" />
+ <title>My Python Project</title>
+ <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400i,600,600i%7CSource+Code+Pro:400,400i,600" />
+ <link rel="stylesheet" href="m-dark+documentation.compiled.css" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+</head>
+<body>
+<header><nav id="navigation">
+ <div class="m-container">
+ <div class="m-row">
+ <a href="s.index.html#this-is-an-url" id="m-navbar-brand" class="m-col-t-8 m-col-m-none m-left-m">My Python Project</a>
+ <div class="m-col-t-4 m-hide-m m-text-right m-nopadr">
+ <a id="m-navbar-show" href="#navigation" title="Show navigation"></a>
+ <a id="m-navbar-hide" href="#" title="Hide navigation"></a>
+ </div>
+ <div id="m-navbar-collapse" class="m-col-t-12 m-show-m m-col-m-none m-right-m">
+ <div class="m-row">
+ <ol class="m-col-t-12 m-col-m-none">
+ <li><a href="s.pages.html#this-is-an-url">Pages</a></li>
+ <li><a href="s.modules.html#this-is-an-url" id="m-navbar-current">Modules</a></li>
+ <li><a href="s.classes.html#this-is-an-url">Classes</a></li>
+ </ol>
+ <ol class="m-col-t-6 m-col-m-none" start="4">
+ <li><a href="p.page.html#this-is-an-url">A page</a></li>
+ <li><a href="m.link_formatting.html#this-is-an-url">A module</a></li>
+ <li><a href="c.link_formatting.Class.html#this-is-an-url">The class</a></li>
+ </ol>
+ </div>
+ </div>
+ </div>
+ </div>
+</nav></header>
+<main><article>
+ <div class="m-container m-container-inflatable">
+ <div class="m-row">
+ <div class="m-col-l-10 m-push-l-1">
+ <h1>Modules</h2>
+ <ul class="m-doc">
+ <li class="m-doc-collapsible">
+ <a href="#" onclick="return toggle(this)">module</a> <a href="m.link_formatting.html#this-is-an-url" class="m-doc">link_formatting</a> <span class="m-doc">This is a module.</span>
+ <ul class="m-doc">
+ <li>module <a href="m.link_formatting.sub.html#this-is-an-url" class="m-doc">sub</a> <span class="m-doc">This is a nice submodule.</span></li>
+ </ul>
+ </li>
+ </ul>
+ <script>
+ function toggle(e) {
+ e.parentElement.className = e.parentElement.className == 'm-doc-collapsible' ?
+ 'm-doc-expansible' : 'm-doc-collapsible';
+ return false;
+ }
+ /* Collapse all nodes marked as such. Doing it via JS instead of
+ directly in markup so disabling it doesn't harm usability. The list
+ is somehow regenerated on every iteration and shrinks as I change
+ the classes. It's not documented anywhere and I'm not sure if this
+ is the same across browsers, so I am going backwards in that list to
+ be sure. */
+ var collapsed = document.getElementsByClassName("collapsed");
+ for(var i = collapsed.length - 1; i >= 0; --i)
+ collapsed[i].className = 'm-doc-expansible';
+ </script>
+ </div>
+ </div>
+ </div>
+</article></main>
+</body>
+</html>
--- /dev/null
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8" />
+ <title>My Python Project</title>
+ <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400i,600,600i%7CSource+Code+Pro:400,400i,600" />
+ <link rel="stylesheet" href="m-dark+documentation.compiled.css" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+</head>
+<body>
+<header><nav id="navigation">
+ <div class="m-container">
+ <div class="m-row">
+ <a href="s.index.html#this-is-an-url" id="m-navbar-brand" class="m-col-t-8 m-col-m-none m-left-m">My Python Project</a>
+ <div class="m-col-t-4 m-hide-m m-text-right m-nopadr">
+ <a id="m-navbar-show" href="#navigation" title="Show navigation"></a>
+ <a id="m-navbar-hide" href="#" title="Hide navigation"></a>
+ </div>
+ <div id="m-navbar-collapse" class="m-col-t-12 m-show-m m-col-m-none m-right-m">
+ <div class="m-row">
+ <ol class="m-col-t-12 m-col-m-none">
+ <li><a href="s.pages.html#this-is-an-url" id="m-navbar-current">Pages</a></li>
+ <li><a href="s.modules.html#this-is-an-url">Modules</a></li>
+ <li><a href="s.classes.html#this-is-an-url">Classes</a></li>
+ </ol>
+ <ol class="m-col-t-6 m-col-m-none" start="4">
+ <li><a href="p.page.html#this-is-an-url">A page</a></li>
+ <li><a href="m.link_formatting.html#this-is-an-url">A module</a></li>
+ <li><a href="c.link_formatting.Class.html#this-is-an-url">The class</a></li>
+ </ol>
+ </div>
+ </div>
+ </div>
+ </div>
+</nav></header>
+<main><article>
+ <div class="m-container m-container-inflatable">
+ <div class="m-row">
+ <div class="m-col-l-10 m-push-l-1">
+ <h1>Pages</h2>
+ <ul class="m-doc">
+ <li><a href="p.page.html#this-is-an-url" class="m-doc">This is a page</a> <span class="m-doc"></span></li>
+ </ul>
+ <script>
+ function toggle(e) {
+ e.parentElement.className = e.parentElement.className == 'm-doc-collapsible' ?
+ 'm-doc-expansible' : 'm-doc-collapsible';
+ return false;
+ }
+ /* Collapse all nodes marked as such. Doing it via JS instead of
+ directly in markup so disabling it doesn't harm usability. The list
+ is somehow regenerated on every iteration and shrinks as I change
+ the classes. It's not documented anywhere and I'm not sure if this
+ is the same across browsers, so I am going backwards in that list to
+ be sure. */
+ var collapsed = document.getElementsByClassName("collapsed");
+ for(var i = collapsed.length - 1; i >= 0; --i)
+ collapsed[i].className = 'm-doc-expansible';
+ </script>
+ </div>
+ </div>
+ </div>
+</article></main>
+</body>
+</html>
'THEME_COLOR': '#00ffff',
'FAVICON': 'favicon-light.png',
- 'PAGE_HEADER': "`A self link <{filename}>`_",
+ 'PAGE_HEADER': "`A self link <{url}>`_",
'FINE_PRINT': "This beautiful thing is done thanks to\n`m.css <https://mcss.mosra.cz>`_.",
+ 'INPUT_PAGES': ['getting-started.rst', 'troubleshooting.rst', 'about.rst'],
'LINKS_NAVBAR1': [
('Pages', 'pages', [
('Getting started', 'getting-started'),
--- /dev/null
+#
+# This file is part of m.css.
+#
+# Copyright © 2017, 2018, 2019 Vladimír Vondruš <mosra@centrum.cz>
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the "Software"),
+# to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense,
+# and/or sell copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+#
+
+import os
+import unittest
+
+from typing import List
+
+from . import BaseInspectTestCase
+from python import EntryType
+
+def custom_url_formatter(type: EntryType, path: List[str]) -> str:
+ if type == EntryType.CLASS:
+ filename = 'c.' + '.'.join(path) + '.html'
+ elif type == EntryType.MODULE:
+ filename = 'm.' + '.'.join(path) + '.html'
+ elif type == EntryType.PAGE:
+ filename = 'p.' + '.'.join(path) + '.html'
+ elif type == EntryType.SPECIAL:
+ filename = 's.' + '.'.join(path) + '.html'
+ else: assert False
+
+ return filename, filename + "#this-is-an-url"
+
+class Test(BaseInspectTestCase):
+ def __init__(self, *args, **kwargs):
+ super().__init__(__file__, '', *args, **kwargs)
+
+ def test(self):
+ self.run_python({
+ 'INPUT_PAGES': ['page.rst'],
+ 'URL_FORMATTER': custom_url_formatter,
+ 'LINKS_NAVBAR1': [
+ ('Pages', 'pages', []),
+ ('Modules', 'modules', []),
+ ('Classes', 'classes', [])],
+ 'LINKS_NAVBAR2': [('A page', 'page', []),
+ ('A module', 'link_formatting', []),
+ ('The class', ['link_formatting', 'Class'], [])]
+ })
+ self.assertEqual(*self.actual_expected_contents('m.link_formatting.html'))
+ self.assertEqual(*self.actual_expected_contents('m.link_formatting.sub.html'))
+ self.assertEqual(*self.actual_expected_contents('c.link_formatting.Class.html'))
+ self.assertEqual(*self.actual_expected_contents('c.link_formatting.Class.Sub.html'))
+
+ # There's nothing inside p.page.html that wouldn't be already covered
+ # by others
+ self.assertTrue(os.path.exists(os.path.join(self.path, 'output/p.page.html')))
+
+ self.assertEqual(*self.actual_expected_contents('s.classes.html'))
+ self.assertEqual(*self.actual_expected_contents('s.modules.html'))
+ self.assertEqual(*self.actual_expected_contents('s.pages.html'))
+
+ # There's nothing inside s.index.html that wouldn't be already covered
+ # by others
+ self.assertTrue(os.path.exists(os.path.join(self.path, 'output/s.index.html')))