Skip to content

Commit

Permalink
Merge pull request #4 from datarobot/zhimsel/COPS-1524/config_and_beh…
Browse files Browse the repository at this point in the history
…avior_fixes

[COPS-1524] Fix plugin logic and config schema
  • Loading branch information
zhimsel committed Aug 15, 2019
2 parents 9c0c1c1 + 61da76d commit 03dd5fd
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 62 deletions.
2 changes: 2 additions & 0 deletions .flake8
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[flake8]
max-line-length = 120
57 changes: 35 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,53 +1,66 @@
# mkdocs-redirects
Open source plugin for Mkdocs page redirects

Plugin for [`mkdocs`](https://www.mkdocs.org/) to create page redirects (e.g. for moved/renamed pages).

## Installing

> **Note:** This package requires MkDocs version 1.0.4 or higher.
> **Note:** This package requires MkDocs version 1.0.4 or higher.
Install with pip:

```bash
pip install mkdocs-redirects
```

Enable the plugin in your `mkdocs.yml`:
## Using

To use this plugin, specify your desired redirects in the plugin's `redirect_maps` setting in your `mkdocs.yml`:

```yaml
plugins:
- search
- redirects
- redirects:
redirect_maps:
'old.md': 'new.md'
'old/file.md': 'new/file.md'
'some_file.md': 'http://external.url.com/foobar'
```

## Using
_Note: don't forget that specifying the `plugins` setting will override the defaults if you didn't already have it set! See [this page](https://www.mkdocs.org/user-guide/configuration/#plugins) for more information._

In your `mkdocs.yml`, add a `redirects` block that maps the old page location to the new location:
The redirects map should take the form of a key/value pair:

```
redirects:
'old': 'some/new_location'
'something/before': 'another/moved/file'
'external': 'http://google.com'
```
- The key of each redirect is the original _markdown doc_ (relative to the `docs_dir` path).
- This plugin will handle the filename resolution during the `mkdocs build` process.
This should be set to what the original markdown doc's filename was (or what it _would be_ if it existed), not the final HTML file rendered by MkDocs
- The value is the _redirect target_. This can take the following forms:
- Path of the _markdown doc_ you wish to be redirected to (relative to `docs_dir`)
- This plugin will handle the filename resolution during the `mkdocs build` process.
This should be set to what the markdown doc's filename is, not the final HTML file rendered by MkDocs
- External URL (e.g. `http://example.com`)

During the `mkdocs build` process, this plugin will create `.html` files in `site_dir` for each of the "old" file that redirects to the "new" path.
It will produce a warning if any problems are encountered or of the redirect target doesn't actually exist (useful if you have `strict: true` set).

### `use_directory_urls`

Note that the `.html` extension should be omitted (and will be automatically appended).
If you have `use_directory_urls: true` set (which is the default), this plugin will modify the redirect targets to the _directory_ URL, not the _actual_ `index.html` filename.
However, it will create the `index.html` file for each target in the correct place so URL resolution works.

The plugin will dynamically create `old.html`, `something/before.html`, and `external.html` in your configured `site_dir` with
HTML that will include a meta redirect to the new page location.
For example, a redirect map of `'old/dir/README.md': 'new/dir/README.md'` will result in an HTML file created at `$site_dir/old/dir/index.html` which redirects to `/new/dir/.

If the new location does not start with `http` or `HTTP` then it will also be appended with `.html` extension and is assumed to be relative to the root of the site.
Additionally, a redirect map of `'old/dir/doc_name.md': 'new/dir/doc_name.md'` will result in `$site_dir/old/dir/doc_name/index.html` redirecting to `/new/dir/doc_name/`

For nested subfolders, the plugin will automatically create these directories in the `site_dir`.
This mimcs the behavior of how MkDocs builds the site dir without this plugin.

## Contributing

- Pull requests are welcome.
- File bugs and suggestions in the Github Issues tracker.
- Pull Requests are welcome.
- File bugs and suggestions in the [Github Issues tracker](https://github.com/datarobot/mkdocs-redirects/issues).

## Releasing

```
make release
```bash
make release
```

It will prompt you for your PyPI user and password.
Expand Down
152 changes: 113 additions & 39 deletions mkdocs_redirects/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,49 +2,123 @@
import os
import textwrap

from mkdocs import utils as mkdocs_utils
from mkdocs.config import config_options, Config
from mkdocs import utils
from mkdocs.config import config_options
from mkdocs.plugins import BasePlugin
from mkdocs.structure.files import File

log = logging.getLogger(__name__)
log.addFilter(mkdocs_utils.warning_filter)
log = logging.getLogger('mkdocs.plugin.redirects')
log.addFilter(utils.warning_filter)


def write_html(site_dir, old_path, new_path):
""" Write an HTML file in the site_dir with a meta redirect to the new page """
# Determine all relevant paths
old_path_abs = os.path.join(site_dir, old_path)
old_dir = os.path.dirname(old_path)
old_dir_abs = os.path.dirname(old_path_abs)

# Create parent directories if they don't exist
if not os.path.exists(old_dir_abs):
log.debug("Creating directory '%s'", old_dir)
os.makedirs(old_dir_abs)

# Write the HTML redirect file in place of the old file
with open(old_path_abs, 'w') as f:
log.debug("Creating redirect: '%s' -> '%s'",
old_path, new_path)
f.write(textwrap.dedent(
"""
<!doctype html>
<html lang="en" class="no-js">
<head>
<script>var anchor=window.location.hash.substr(1);location.href="{url}"+(anchor?"#"+anchor:"")</script>
<meta http-equiv="refresh" content="0; url={url}">
</head>
<body>
Redirecting...
</body>
</html>
"""
).format(url=new_path))


def get_html_path(path, use_directory_urls):
""" Return the HTML file path for a given markdown file """
parent, filename = os.path.split(path)
name_orig, ext = os.path.splitext(filename)

# Directory URLs require some different logic. This mirrors mkdocs' internal logic.
if use_directory_urls:

# Both `index.md` and `README.md` files are normalized to `index.html` during build
name = 'index' if name_orig.lower() in ('index', 'readme') else name_orig

# If it's name is `index`, then that means it's the "homepage" of a directory, so should get placed in that dir
if name == 'index':
return os.path.join(parent, 'index.html')

# Otherwise, it's a file within that folder, so it should go in its own directory to resolve properly
else:
return os.path.join(parent, name, 'index.html')

# Just use the original name if Directory URLs aren't used
else:
return os.path.join(parent, (name_orig + '.html'))


class RedirectPlugin(BasePlugin):
# Any options that this plugin supplies should go here.
config_scheme = (
('redirect_maps', config_options.Type(dict, default={})), # note the trailing comma
)

# Build a list of redirects on file generation
def on_files(self, files, config, **kwargs):
self.redirects = self.config.get('redirect_maps', {})

# SHIM! Produce a warning if the old root-level 'redirects' config is present
if config.get('redirects'):
log.warn("The root-level 'redirects:' setting is not valid and has been changed in version 1.0! "
"The plugin-level 'redirect-map' must be used instead. See https://git.io/fjdBN")

# Validate user-provided redirect "old files"
for page_old in self.redirects.keys():
if not utils.is_markdown_file(page_old):
log.warn("redirects plugin: '%s' is not a valid markdown file!", page_old)

# Build a dict of known document pages to validate against later
self.doc_pages = {}
for page in files.documentation_pages(): # object type: mkdocs.structure.files.File
self.doc_pages[page.src_path] = page

# Create HTML files for redirects after site dir has been built
def on_post_build(self, config, **kwargs):
redirects = config.get('redirects', {})

for old_page, new_page in redirects.items():
old_page_path = os.path.join(config['site_dir'], '{}.html'.format(old_page))
if not new_page.startswith(('http','HTTP')):
new_page_path = os.path.join(config['site_dir'], '{}.html'.format(new_page))
# check that the page being redirected to actually exists
if not os.path.exists(os.path.dirname(new_page_path)):
msg = 'Redirect does not exist for path: {}'.format(new_page_path)
if config.get('strict', False):
raise Exception(msg)
else:
log.warn(msg)
new_page = '/{}.html'.format(new_page)

# ensure the folder path exists, recursively for nested directories.
if not os.path.exists(os.path.dirname(old_page_path)):
os.makedirs(os.path.dirname(old_page_path))

# write an HTML file in the site_dir with a meta redirect to the new page
# note that it will prefix the path with `/` to be relative to the site root.
with open(old_page_path, 'w') as f:
f.write(textwrap.dedent("""
<!doctype html>
<html lang="en" class="no-js">
<head>
<script>var anchor=window.location.hash.substr(1);location.href="{url}"+(anchor?"#"+anchor:"")</script>
<meta http-equiv="refresh" content="0; url={url}">
</head>
<body>
Redirecting...
</body>
</html>
""").format(url=new_page))

# Determine if 'use_directory_urls' is set
use_directory_urls = config.get('use_directory_urls')

# Walk through the redirect map and write their HTML files
for page_old, page_new in self.redirects.items():

# External redirect targets are easy, just use it as the target path
if page_new.lower().startswith(('http://', 'https://')):
dest_path = page_new

# Internal document targets require a leading '/' to resolve properly.
elif page_new in self.doc_pages:
dest_path = '/' + self.doc_pages[page_new].dest_path

# If use_directory_urls is set, redirect to the directory, not the HTML file
if use_directory_urls:
dest_path = dest_path.split('index.html')[0]

# If the redirect target isn't external or a valid internal page, throw an error
# Note: we use 'warn' here specifically; mkdocs treats warnings specially when in strict mode
else:
log.warn("Redirect target '%s' does not exist!", page_new)
continue

# DO IT!
write_html(config['site_dir'],
get_html_path(page_old, use_directory_urls),
dest_path)
4 changes: 3 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import os
from setuptools import setup, find_packages


def read(fname):
return open(os.path.join(os.path.dirname(__file__), fname)).read()


setup(
name='mkdocs-redirects',
version='0.0.6',
version='1.0.0',
description='A MkDocs plugin for dynamic page redirects to prevent broken links.',
long_description=read('README.md'),
long_description_content_type="text/markdown",
Expand Down

0 comments on commit 03dd5fd

Please sign in to comment.