Skip to content

Commit

Permalink
Commit all the things!
Browse files Browse the repository at this point in the history
Initial public release of the MkDocs Awesome Pages Plugin
  • Loading branch information
lukasgeiter committed Feb 7, 2018
0 parents commit 7409992
Show file tree
Hide file tree
Showing 22 changed files with 1,462 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* text=auto eol=lf
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.idea/
env/
dist/
build/
__pycache__/
*.egg-info/

!.gitkeep
25 changes: 25 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
language: python
python:
- 3.5
- 3.6

branches:
only:
- master
- /^v\d+\.\d+(\.\d+)?(-\S*)?$/ # version tags

script:
- pytest

deploy:
provider: pypi
distributions: sdist bdist_wheel
user: $PYPI_USER
password: $PYPI_PASSWORD
on:
tags: true
python: 3.6

notifications:
email:
on_success: never
51 changes: 51 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Contribution Guidelines

Thank you for considering to contribute to this project. These guidelines will help you get going with development and outline the most important rules to follow when submitting pull requests for this project.

<br/>

## Development

#### Setup

##### Prerequisites

- [Python 3]
- virtualenv

##### Steps

1. Clone the (forked) repository
1. Create a virtualenv with `virtualenv env`
1. Activate virtualenv `source env/Scripts/activate`
1. Run `pip install -r requirements.txt` in the project directory

#### Running Tests

```bash
pytest
```

<br/>


## Submitting Changes

To get changes merged, create a pull request. Here are a few things to pay attention to when doing so:

#### Commit Messages

The summary of a commit should be concise and worded in an imperative mood.
...a *what* mood? This should clear things up: *[How to Write a Git Commit Message][git-commit-message]*

#### Code Style

Make sure your code follows [PEP-8](https://www.python.org/dev/peps/pep-0008/) and keeps things consistent with the rest of the code.

#### Tests

If it makes sense, writing tests for your PRs is always appreciated and will help get them merged.

[Python 3]: https://www.python.org/
[virtualenv]: https://virtualenv.pypa.io/
[git-commit-message]: https://chris.beams.io/posts/git-commit/
21 changes: 21 additions & 0 deletions LICENSE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# MIT License

Copyright (c) 2018 Lukas Geiter

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.
2 changes: 2 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
include README.md
include LICENSE.md
107 changes: 107 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# MkDocs Awesome Pages Plugin [![Build Status][travis-status]][travis-link]

*An MkDocs plugin that simplifies configuring page titles and their order*

The awesome-pages plugin allows you to customize how your pages show up the navigation of your MkDocs without having to configure the full structure in your `mkdocs.yml`. It extracts the title from your markdown files and gives you detailed control using a small configuration file directly placed in the relevant directory of your documentation.

> **Note:** This plugin works best without a `pages` entry in your `mkdocs.yml`. Having a `pages` entry is supported, but you might not get the results you expect, especially if your `pages` structure doesn't match the file structure.
<br/>

## Installation

> **Note:** This package requires MkDown version 0.17 or higher.
Install the package with pip:

```yaml
pip install mkdocs-awesome-pages-plugin
```

Enable the plugin in your `mkdocs.yml`:

```yaml
plugins:
- search
- awesome-pages
```
> **Note:** If you have no `plugins` entry in your config file yet, you'll likely also want to add the `search` plugin. MkDocs enables it by default if there is no `plugins` entry set, but now you have to enable it explicitly.

More information about plugins in the [MkDocs documentation][mkdocs-plugins]

<br/>

## Features

### Extract Page Titles from Markdown

The plugin extracts the H1 title (only `#` syntax is supported) from every page and uses it for the title in the navigation.

### Set Directory Title

Create a YAML file named `.pages` in a directory and set the `title` to override the title of that directory in the navigation:

```yaml
title: Page Title
```

### Arrange Pages

Create a YAML file named `.pages` in a directory and set the `arrange` attribute to change the order of how child pages appear in the navigation. This works for actual pages as well as subdirectories.

```yaml
title: Page Title
arrange:
- page1.md
- page2.md
- subdirectory
```

If you only specify *some* pages, they will be positioned at the beginning, followed by the other pages in their original order.

You may also include a `...` entry at some position to specify where the rest of the pages should be inserted:

```yaml
arrange:
- introduction.md
- ...
- summary.md
```

In this example `introduction.md` is positioned at the beginning, `summary.md` at the end, and any other pages in between.

<br/>

## Options

You may customize the plugin by passing options in `mkdocs.yml`:

```yaml
plugins:
- awesome-pages:
filename: .index
disable_auto_arrange_index: true
```

### `filename`

Name of the file used to configure pages of a directory. Default is `.pages`

### `disable_auto_arrange_index`

Disable the behavior of automatically putting the page with filename `index.*` at the beginning if there is no order specified in `arrange`. Default is `false`

<br/>

## Contributing

From reporting a bug to submitting a pull request: every contribution is appreciated and welcome.
Report bugs, ask questions and request features using [Github issues][github-issues].
If you want to contribute to the code of this project, please read the [Contribution Guidelines][contributing].

[travis-status]: https://travis-ci.org/lukasgeiter/mkdocs-awesome-pages-plugin.svg?branch=master
[travis-link]: https://travis-ci.org/lukasgeiter/mkdocs-awesome-pages-plugin
[mkdocs-plugins]: http://www.mkdocs.org/user-guide/plugins/
[github-issues]: https://github.com/lukasgeiter/mkdocs-awesome-pages-plugin/issues
[contributing]: CONTRIBUTING.md
Empty file.
162 changes: 162 additions & 0 deletions mkdocs_awesome_pages_plugin/factory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import os
import warnings
from functools import reduce
from typing import List, Union, Optional, Dict

from . import markdown
from .page import Page, RootPage
from .pagesfile import PagesFile


class Options:
def __init__(self, *, filename: str, disable_auto_arrange_index: bool):
self.filename = filename
self.disable_auto_arrange_index = disable_auto_arrange_index


class PageNotFoundError(Exception):
def __init__(self, page: str, context: str = None):
message = 'Page "{page}" not found.'
if context:
message += ' [{context}]'

super().__init__(message.format(page=page, context=context))


class TitleInRootPagesFileWarning(Warning):
pass


class Factory:

INDEX_PAGE_NAME = 'index'

def __init__(self, **kwargs):
self.options = Options(**kwargs)

def create(self, config: List) -> RootPage:
""" Creates a root page containing the whole tree of pages from the mkdocs config """
children = self._create_pages(config)

pages_file = self._load_pages_file('')
if pages_file.title is not None:
warnings.warn(
'Using the "title" attribute in the {filename} file of the doc root has no effect'
.format(filename=self.options.filename),
TitleInRootPagesFileWarning)

children = self.arrange_pages(children, pages_file)

return RootPage(children)

def create_page(self, config_entry: Union[Dict, str]) -> Page:
""" Creates a page from an entry in the mkdocs config """
if isinstance(config_entry, str):
return self._create_leaf_page(None, config_entry)

# The config dictionary always contains one entry, retrieve it
[title, value] = next(iter(config_entry.items()))

if isinstance(value, str):
return self._create_leaf_page(title, value)
else:
return self._create_branch_page(title, value)

def _create_pages(self, config: List) -> List[Page]:
""" Calls create_page for every entry in the config list """
return [self.create_page(c) for c in config]

def _create_leaf_page(self, title: Optional[str], path: str) -> Page:
""" Creates a leaf page, a page pointing to an actual markdown file """
try:
with open(path) as file:
title = markdown.extract_h1(file)
except FileNotFoundError:
pass

return Page(title, path)

def _create_branch_page(self, title: str, config_children: list) -> Page:
""" Creates a branch page, a directory page with child pages """
children = self._create_pages(config_children)
path = self.common_dirname(children)

pages_file = self._load_pages_file(path)
title = pages_file.title or title

children = self.arrange_pages(children, pages_file)

return Page(title, path, children)

def _load_pages_file(self, path: str):
""" Loads and parses the pages file for a given path """
try:
if path is None:
raise FileNotFoundError
return PagesFile.load_from(os.path.join(path, self.options.filename))
except FileNotFoundError:
# Default to empty pages file
return PagesFile()

def common_dirname(self, children: List[Page]) -> Optional[str]:
""" Determines the common dirname of all given pages """
if children:
return reduce(self._common_dirname, children, children[0].dirname)

def _common_dirname(self, path: Optional[str], page: Page) -> Optional[str]:
""" Reduces pages to a common dirname """
if page and path is not None and page.dirname == path:
return path

def arrange_pages(self, pages: List[Page], pages_file: PagesFile) -> List[Page]:
""" Sorts the given pages based on the arrange configuration from the pages file """
pages_by_basename = self._group_pages_by_basename(pages)
arranged_pages = set()
rest_index = None
result = []

# Add pages from arrange configuration
for index, path in enumerate(pages_file.arrange):
if path == PagesFile.ARRANGE_REST_TOKEN:
rest_index = index
elif path in pages_by_basename:
matching_pages = pages_by_basename[path]
arranged_pages.update(matching_pages)
result.extend(matching_pages)
else:
raise PageNotFoundError(path, pages_file.path)

if rest_index is None:
# If no rest token is used in the arrange configuration, add remaining pages at the end
rest_index = len(result)

# Add remaining pages
for page in pages:
if page in arranged_pages:
# Skip pages that have already been added
continue

if (
not pages_file.arrange
and not self.options.disable_auto_arrange_index
and page.basename is not None
and os.path.splitext(page.basename)[0] == self.INDEX_PAGE_NAME
):
# Automatically position index file at the beginning
result.insert(0, page)
else:
result.insert(rest_index, page)

rest_index += 1

return result

def _group_pages_by_basename(self, pages: List[Page]) -> Dict[str, List[Page]]:
""" Creates a dictionary with mapping basenames to a list of matching pages """
result = {}
for page in pages:
if page.basename in result:
result[page.basename].append(page)
else:
result[page.basename] = [page]
return result
9 changes: 9 additions & 0 deletions mkdocs_awesome_pages_plugin/markdown.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import re
from typing import TextIO, Optional


def extract_h1(file: TextIO) -> Optional[str]:
for line in file:
match = re.match('#([^#].*)', line)
if match:
return match.group(1).strip()
Loading

0 comments on commit 7409992

Please sign in to comment.