pax_global_header00006660000000000000000000000064144556014460014523gustar00rootroot0000000000000052 comment=313c7765b470c886c326858d10d901370808ea39 mkdocs-redirects-1.2.1/000077500000000000000000000000001445560144600147665ustar00rootroot00000000000000mkdocs-redirects-1.2.1/.github/000077500000000000000000000000001445560144600163265ustar00rootroot00000000000000mkdocs-redirects-1.2.1/.github/workflows/000077500000000000000000000000001445560144600203635ustar00rootroot00000000000000mkdocs-redirects-1.2.1/.github/workflows/ci.yml000066400000000000000000000023171445560144600215040ustar00rootroot00000000000000name: CI on: push: pull_request: schedule: - cron: '0 6 * * 6' jobs: build: strategy: fail-fast: false matrix: include: - python: '^3.10' os: ubuntu-latest - python: 3.9 os: windows-latest - python: 3.8 os: ubuntu-latest - python: 3.7 os: macos-latest - python: 3.6 os: windows-latest - python: 3.6 os: ubuntu-20.04 versions: minimal runs-on: ${{matrix.os}} steps: - name: Download source uses: actions/checkout@v3 - name: Install Python uses: actions/setup-python@v4 with: python-version: ${{matrix.python}} - name: Pin to lowest versions if: matrix.versions == 'minimal' run: | sed -i -E "s/^ +'(\\w+)>=([0-9])/'\\1==\\2/" setup.py - name: Install packages run: | python -m pip install -U pip'>=19' pip install -U --upgrade-strategy=eager .[test] - name: Test run: | .tools/ci.sh with_groups - name: Check formatting if: matrix.versions == null run: | git diff --exit-code mkdocs-redirects-1.2.1/.gitignore000066400000000000000000000022631445560144600167610ustar00rootroot00000000000000# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover .hypothesis/ .pytest_cache/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # Jupyter Notebook .ipynb_checkpoints # pyenv .python-version # celery beat schedule file celerybeat-schedule # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ mkdocs-redirects-1.2.1/.tools/000077500000000000000000000000001445560144600162045ustar00rootroot00000000000000mkdocs-redirects-1.2.1/.tools/ci.sh000077500000000000000000000005161445560144600171400ustar00rootroot00000000000000#!/bin/sh set -e cd "$(dirname "$0")/.." with_groups() { echo "::group::$@" "$@" && echo "::endgroup::" } srcs='mkdocs_redirects tests setup.py' "$@" pytest -q "$@" autoflake -i -r --remove-all-unused-imports --remove-unused-variables $srcs "$@" isort -q $srcs "$@" black -l100 -tpy36 --skip-string-normalization -q $srcs mkdocs-redirects-1.2.1/LICENSE000066400000000000000000000020571445560144600157770ustar00rootroot00000000000000MIT License Copyright (c) 2019-2022 DataRobot 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. mkdocs-redirects-1.2.1/Makefile000066400000000000000000000007641445560144600164350ustar00rootroot00000000000000clean: ## Clean intermediate build files find . -name '__pycache__' | xargs rm -rf find . -name '*.pyc' | xargs rm -rf rm -rf dist/ .PHONY: clean build: clean dev ## Build the package (source distribution) python setup.py sdist .PHONY: build dev: clean ## Setup development environment pip install .[dev] .PHONY: dev test: dev ## Run tests .tools/ci.sh .PHONY: test release: build ## Release to PyPi twine upload --repository-url https://upload.pypi.org/legacy/ dist/* .PHONY: release mkdocs-redirects-1.2.1/README.md000066400000000000000000000062511445560144600162510ustar00rootroot00000000000000# mkdocs-redirects Plugin for [`mkdocs`](https://www.mkdocs.org/) to create page redirects (e.g. for moved/renamed pages). Initially developed by [DataRobot](https://www.datarobot.com/). ## Installing > **Note:** This package requires MkDocs version 1.0.4 or higher. Install with pip: ```bash pip install mkdocs-redirects ``` ## Using To use this plugin, specify your desired redirects in the plugin's `redirect_maps` setting in your `mkdocs.yml`: ```yaml plugins: - redirects: redirect_maps: 'old.md': 'new.md' 'old/file.md': 'new/file.md' 'some_file.md': 'http://external.url.com/foobar' ``` _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._ The redirects map should take the form of a key/value pair: - 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` 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. 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/`. 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/`. This mimics the behavior of how MkDocs builds the site dir without this plugin. ## Developing ### Setup a virtualenv Create a virtualenv using a method of your choice. ```bash brew install pyenv pyenv-virtualenv pyenv install 2.7.18 pyenv virtualenv 2.7.18 mkdocs-redirects pyenv activate mkdocs-redirects ``` ### Build ```bash make build ``` ### Test ```bash make test ``` ## Releasing ```bash make release ``` It will prompt you for your PyPI user and password. See: - - mkdocs-redirects-1.2.1/mkdocs_redirects/000077500000000000000000000000001445560144600203125ustar00rootroot00000000000000mkdocs-redirects-1.2.1/mkdocs_redirects/__init__.py000066400000000000000000000000001445560144600224110ustar00rootroot00000000000000mkdocs-redirects-1.2.1/mkdocs_redirects/plugin.py000066400000000000000000000112041445560144600221600ustar00rootroot00000000000000""" Copyright 2019-2022 DataRobot, Inc. and its affiliates. All rights reserved. """ import logging import os import posixpath from mkdocs import utils from mkdocs.config import config_options from mkdocs.plugins import BasePlugin from mkdocs.structure.files import File log = logging.getLogger('mkdocs.plugin.redirects') HTML_TEMPLATE = """ Redirecting... Redirecting... """ 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 log.debug("Creating redirect: '%s' -> '%s'", old_path, new_path) content = HTML_TEMPLATE.format(url=new_path) with open(old_path_abs, 'w', encoding='utf-8') as f: f.write(content) def get_relative_html_path(old_page, new_page, use_directory_urls): """Return the relative path from the old html path to the new html path""" old_path = get_html_path(old_page, use_directory_urls) new_path, new_hash_fragment = _split_hash_fragment(new_page) relative_path = posixpath.relpath(new_path, start=posixpath.dirname(old_path)) if use_directory_urls: relative_path = relative_path + '/' return relative_path + new_hash_fragment def get_html_path(path, use_directory_urls): """Return the HTML file path for a given markdown file""" f = File(path, '', '', use_directory_urls) return f.dest_path.replace(os.sep, '/') 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', {}) # Validate user-provided redirect "old files" for page_old in self.redirects.keys(): if not utils.is_markdown_file(page_old): log.warning("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.replace(os.sep, '/')] = page # Create HTML files for redirects after site dir has been built def on_post_build(self, config, **kwargs): # 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(): # Need to remove hash fragment from new page to verify existence page_new_without_hash, hash = _split_hash_fragment(str(page_new)) # External redirect targets are easy, just use it as the target path if page_new.lower().startswith(('http://', 'https://')): dest_path = page_new elif page_new_without_hash in self.doc_pages: file = self.doc_pages[page_new_without_hash] dest_path = get_relative_html_path(page_old, file.url + hash, use_directory_urls) # 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.warning("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, ) def _split_hash_fragment(path): """ Returns (path, hash-fragment) "path/to/file#hash" => ("/path/to/file", "#hash") "path/to/file" => ("/path/to/file", "") """ path, hash, after = path.partition('#') return path, hash + after mkdocs-redirects-1.2.1/pytest.ini000066400000000000000000000002071445560144600170160ustar00rootroot00000000000000[pytest] addopts = --tb=native filterwarnings = ignore::DeprecationWarning:.*: default::DeprecationWarning:mkdocs_redirects.*: mkdocs-redirects-1.2.1/setup.cfg000066400000000000000000000001371445560144600166100ustar00rootroot00000000000000[flake8] max-line-length = 119 extend-ignore = E203 [isort] profile = black line_length = 100 mkdocs-redirects-1.2.1/setup.py000066400000000000000000000033431445560144600165030ustar00rootroot00000000000000""" Copyright 2019-2022 DataRobot, Inc. and its affiliates. All rights reserved. """ import os from setuptools import find_packages, setup def read(fname): return open(os.path.join(os.path.dirname(__file__), fname)).read() test_requirements = [ 'pytest', 'black', 'isort', 'autoflake', ] release_requirements = [ 'twine>=1.13.0', ] setup( name='mkdocs-redirects', version='1.2.1', description='A MkDocs plugin for dynamic page redirects to prevent broken links.', long_description=read('README.md'), long_description_content_type="text/markdown", keywords='mkdocs redirect', url='https://github.com/datarobot/mkdocs-redirects', author='Dustin Burke', author_email='dustin@datarobot.com', license='MIT', license_files=['LICENSE'], python_requires='>=3.6', install_requires=[ 'mkdocs>=1.1.1', ], extras_require={ 'dev': test_requirements + release_requirements, 'test': test_requirements, 'release': release_requirements, }, classifiers=[ 'Development Status :: 4 - Beta', 'Intended Audience :: Developers', 'Intended Audience :: Information Technology', 'License :: OSI Approved :: MIT License', 'Programming Language :: Python', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', ], packages=find_packages(), entry_points={ 'mkdocs.plugins': [ 'redirects = mkdocs_redirects.plugin:RedirectPlugin', ] }, ) mkdocs-redirects-1.2.1/tests/000077500000000000000000000000001445560144600161305ustar00rootroot00000000000000mkdocs-redirects-1.2.1/tests/test_plugin.py000066400000000000000000000115361445560144600210450ustar00rootroot00000000000000""" Copyright 2019-2022 DataRobot, Inc. and its affiliates. All rights reserved. """ import pytest from mkdocs.structure.files import File from mkdocs_redirects import plugin existing_pages = [ "README.md", "foo/README.md", "foo/bar/new.md", "foo/index.md", "foo/new.md", "index.md", "new.md", "new/README.md", "new/index.md", "100%.md", "the/fake.md", ] @pytest.fixture def run_redirect_test(monkeypatch, old_page, new_page, use_directory_urls): wrote = () def write_html(site_dir, old_path, new_path): nonlocal wrote wrote = (old_path, new_path) monkeypatch.setattr(plugin, "write_html", write_html) plg = plugin.RedirectPlugin() plg.redirects = {old_page: new_page} plg.doc_pages = { path: File(path, "docs", "site", use_directory_urls) for path in existing_pages } plg.doc_pages["the/fake.md"].dest_path = "fake/destination/index.html" plg.doc_pages["the/fake.md"].url = plg.doc_pages["the/fake.md"]._get_url(use_directory_urls) plg.on_post_build(dict(use_directory_urls=use_directory_urls, site_dir="site")) return wrote @pytest.fixture def actual_redirect_target(run_redirect_test): assert bool(run_redirect_test) return run_redirect_test[1] @pytest.fixture def actual_written_file(run_redirect_test): assert bool(run_redirect_test) return run_redirect_test[0] # Tuples of: # * Left side of the redirect item # * Right side of the redirect item # * Expected destination URL written into the HTML file, use_directory_urls=False # * Expected destination URL written into the HTML file, use_directory_urls=True testdata = [ ("old.md", "index.md", "index.html", "../"), ("old.md", "README.md", "index.html", "../"), ("old.md", "new.md", "new.html", "../new/"), ("old.md", "new/index.md", "new/index.html", "../new/"), ("old.md", "new/README.md", "new/index.html", "../new/"), ("foo/old.md", "foo/new.md", "new.html", "../new/"), ("foo/fizz/old.md", "foo/bar/new.md", "../bar/new.html", "../../bar/new/"), ("fizz/old.md", "foo/bar/new.md", "../foo/bar/new.html", "../../foo/bar/new/"), ("foo.md", "foo/index.md", "foo/index.html", "./"), ("foo.md", "foo/README.md", "foo/index.html", "./"), ("foo.md", "the/fake.md", "fake/destination/index.html", "../fake/destination/"), ("old.md", "index.md#hash", "index.html#hash", "../#hash"), ("old.md", "README.md#hash", "index.html#hash", "../#hash"), ("old.md", "new.md#hash", "new.html#hash", "../new/#hash"), ("old.md", "new/index.md#hash", "new/index.html#hash", "../new/#hash"), ("old.md", "new/README.md#hash", "new/index.html#hash", "../new/#hash"), ("foo/old.md", "foo/new.md#hash", "new.html#hash", "../new/#hash"), ("foo/fizz/old.md", "foo/bar/new.md#hash", "../bar/new.html#hash", "../../bar/new/#hash"), ("fizz/old.md", "foo/bar/new.md#hash", "../foo/bar/new.html#hash", "../../foo/bar/new/#hash"), ("foo.md", "foo/index.md#hash", "foo/index.html#hash", "./#hash"), ("foo.md", "foo/README.md#hash", "foo/index.html#hash", "./#hash"), ("foo.md", "the/fake.md#hash", "fake/destination/index.html#hash", "../fake/destination/#hash"), ("foo.md", "100%.md", "100%25.html", "../100%25/"), ("foo/fizz/old.md",) + ("https://example.org/old.md",) * 3, ] @pytest.mark.parametrize("use_directory_urls", [False]) @pytest.mark.parametrize(["old_page", "new_page", "expected", "_"], testdata) def test_relative_redirect_no_directory_urls(actual_redirect_target, expected, _): assert actual_redirect_target == expected @pytest.mark.parametrize("use_directory_urls", [True]) @pytest.mark.parametrize(["old_page", "new_page", "_", "expected"], testdata) def test_relative_redirect_directory_urls(actual_redirect_target, _, expected): assert actual_redirect_target == expected # Tuples of: # * Left side of the redirect item # * Expected path of the written HTML file, use_directory_urls=False # * Expected path of the written HTML file, use_directory_urls=True testdata = [ ("old.md", "old.html", "old/index.html"), ("README.md", "index.html", "index.html"), ("100%.md", "100%.html", "100%/index.html"), ("foo/fizz/old.md", "foo/fizz/old.html", "foo/fizz/old/index.html"), ("foo/fizz/index.md", "foo/fizz/index.html", "foo/fizz/index.html"), ] @pytest.mark.parametrize("use_directory_urls", [False]) @pytest.mark.parametrize("new_page", ["new.md"]) @pytest.mark.parametrize(["old_page", "expected", "_"], testdata) def test_page_dest_path_no_directory_urls(actual_written_file, old_page, expected, _): assert actual_written_file == expected @pytest.mark.parametrize("use_directory_urls", [True]) @pytest.mark.parametrize("new_page", ["new.md"]) @pytest.mark.parametrize(["old_page", "_", "expected"], testdata) def test_page_dest_path_directory_urls(actual_written_file, old_page, _, expected): assert actual_written_file == expected