From c1aea6e40ad2932d395bb568532edde05dbfcfc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Fri, 28 Apr 2023 13:46:54 +0200 Subject: [PATCH 01/13] chore: Template upgrade --- .copier-answers.yml | 6 +- .github/FUNDING.yml | 4 +- .github/workflows/ci.yml | 3 + .github/workflows/dists.yml | 29 +++++ .gitignore | 4 +- CONTRIBUTING.md | 2 +- Makefile | 7 +- config/ruff.toml | 1 + docs/.overrides/main.html | 18 +++ docs/css/insiders.css | 98 +++++++++++++++ docs/insiders/changelog.md | 3 + docs/insiders/goals.yml | 1 + docs/insiders/index.md | 222 ++++++++++++++++++++++++++++++++++ docs/insiders/installation.md | 190 +++++++++++++++++++++++++++++ duties.py | 89 ++++++++++---- mkdocs.insiders.yml | 4 + mkdocs.yml | 61 ++++++++-- pyproject.toml | 19 +-- scripts/gen_credits.py | 2 +- scripts/insiders.py | 201 ++++++++++++++++++++++++++++++ scripts/setup.sh | 4 +- 21 files changed, 906 insertions(+), 62 deletions(-) create mode 100644 .github/workflows/dists.yml create mode 100644 docs/.overrides/main.html create mode 100644 docs/css/insiders.css create mode 100644 docs/insiders/changelog.md create mode 100644 docs/insiders/goals.yml create mode 100644 docs/insiders/index.md create mode 100644 docs/insiders/installation.md create mode 100644 mkdocs.insiders.yml create mode 100644 scripts/insiders.py diff --git a/.copier-answers.yml b/.copier-answers.yml index e3b6e940..2d479ffb 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -1,5 +1,5 @@ # Changes here will be overwritten by Copier -_commit: 0.11.3 +_commit: 0.15.0 _src_path: gh:pawamoy/copier-pdm.git author_email: pawamoy@pm.me author_fullname: Timothée Mazzucotelli @@ -8,13 +8,13 @@ copyright_date: '2019' copyright_holder: Timothée Mazzucotelli copyright_holder_email: pawamoy@pm.me copyright_license: ISC License +insiders: true project_description: Automatic documentation from sources, for MkDocs. project_name: mkdocstrings -python_package_command_line_name: mkdocstrings +python_package_command_line_name: '' python_package_distribution_name: mkdocstrings python_package_import_name: mkdocstrings repository_name: mkdocstrings repository_namespace: mkdocstrings repository_provider: github.com -use_precommit: false diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index cf5764f4..01e293ac 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,4 +1,4 @@ -github: -- pawamoy +github: pawamoy +ko_fi: pawamoy custom: - https://www.paypal.me/pawamoy diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ef882e3c..acc26f2f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,6 +49,9 @@ jobs: - name: Check for vulnerabilities in dependencies run: pdm run duty check-dependencies + - name: Check for breaking changes in the API + run: pdm run duty check-api + tests: strategy: diff --git a/.github/workflows/dists.yml b/.github/workflows/dists.yml new file mode 100644 index 00000000..ae5dd197 --- /dev/null +++ b/.github/workflows/dists.yml @@ -0,0 +1,29 @@ +name: dists + +on: push +permissions: + contents: write + +jobs: + build: + name: Build dists + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Setup Python + uses: actions/setup-python@v3 + - name: Install build + run: python -m pip install build + - name: Build dists + run: python -m build + - name: Upload dists artifact + uses: actions/upload-artifact@v3 + with: + name: mkdocstrings-insiders + path: ./dist/* + - name: Create release and upload assets + uses: softprops/action-gh-release@v1 + if: startsWith(github.ref, 'refs/tags/') + with: + files: ./dist/* diff --git a/.gitignore b/.gitignore index f6a13b06..c08a8296 100644 --- a/.gitignore +++ b/.gitignore @@ -11,7 +11,9 @@ pip-wheel-metadata/ .python-version site/ pdm.lock -.pdm.toml +pdm.toml +.pdm-python __pypackages__/ .mypy_cache/ .venv/ +.cache/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d9f8e89f..4ecc0fa0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -61,7 +61,7 @@ As usual: 1. run `make check` to check everything (fix any warning) 1. run `make test` to run the tests (fix any issue) 1. if you updated the documentation or the project dependencies: - 1. run `make docs-serve` + 1. run `make docs` 1. go to http://localhost:8000 and check that everything looks good 1. follow our [commit message convention](#commit-message-convention) diff --git a/Makefile b/Makefile index b034ffff..68fea02f 100644 --- a/Makefile +++ b/Makefile @@ -5,19 +5,18 @@ DUTY = $(shell [ -n "${VIRTUAL_ENV}" ] || echo pdm run) duty args = $(foreach a,$($(subst -,_,$1)_args),$(if $(value $a),$a="$($a)")) check_quality_args = files -docs_serve_args = host port +docs_args = host port release_args = version test_args = match BASIC_DUTIES = \ changelog \ + check-api \ check-dependencies \ clean \ coverage \ docs \ docs-deploy \ - docs-regen \ - docs-serve \ format \ release @@ -42,7 +41,7 @@ setup: .PHONY: check check: @pdm multirun duty check-quality check-types check-docs - @$(DUTY) check-dependencies + @$(DUTY) check-dependencies check-api .PHONY: $(BASIC_DUTIES) $(BASIC_DUTIES): diff --git a/config/ruff.toml b/config/ruff.toml index 62f45f64..53824875 100644 --- a/config/ruff.toml +++ b/config/ruff.toml @@ -63,6 +63,7 @@ ignore = [ "D105", # Missing docstring in magic method "D417", # Missing argument description in the docstring "E501", # Line too long + "ERA001", # Commented out code "G004", # Logging statement uses f-string "INP001", # File is part of an implicit namespace package "PLR0911", # Too many return statements diff --git a/docs/.overrides/main.html b/docs/.overrides/main.html new file mode 100644 index 00000000..cb5234e5 --- /dev/null +++ b/docs/.overrides/main.html @@ -0,0 +1,18 @@ +{% extends "base.html" %} + +{% block announce %} + + Sponsorship + is now available! + + {% include ".icons/octicons/heart-fill-16.svg" %} + + + — For updates follow @pawamoy on + + + {% include ".icons/fontawesome/brands/mastodon.svg" %} + + Fosstodon + +{% endblock %} diff --git a/docs/css/insiders.css b/docs/css/insiders.css new file mode 100644 index 00000000..81dbd756 --- /dev/null +++ b/docs/css/insiders.css @@ -0,0 +1,98 @@ +@keyframes heart { + + 0%, + 40%, + 80%, + 100% { + transform: scale(1); + } + + 20%, + 60% { + transform: scale(1.15); + } +} + +@keyframes vibrate { + 0%, 2%, 4%, 6%, 8%, 10%, 12%, 14%, 16%, 18% { + -webkit-transform: translate3d(-2px, 0, 0); + transform: translate3d(-2px, 0, 0); + } + 1%, 3%, 5%, 7%, 9%, 11%, 13%, 15%, 17%, 19% { + -webkit-transform: translate3d(2px, 0, 0); + transform: translate3d(2px, 0, 0); + } + 20%, 100% { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +.heart { + color: #e91e63; +} + +.pulse { + animation: heart 1000ms infinite; +} + +.vibrate { + animation: vibrate 2000ms infinite; +} + +.new-feature svg { + fill: var(--md-accent-fg-color) !important; +} + +a.insiders { + color: #e91e63; +} + +.sponsorship-list { + width: 100%; +} + +.sponsorship-item { + float: left; + border-radius: 100%; + display: block; + height: 1.6rem; + margin: .2rem; + overflow: hidden; + width: 1.6rem; +} + +.sponsorship-item:focus, .sponsorship-item:hover { + transform: scale(1.1); +} + +.sponsorship-item img { + filter: grayscale(100%) opacity(75%); + height: auto; + width: 100%; +} + +.sponsorship-item:focus img, .sponsorship-item:hover img { + filter: grayscale(0); +} + +.sponsorship-item.private { + background: var(--md-default-fg-color--lightest); + color: var(--md-default-fg-color); + font-size: .6rem; + font-weight: 700; + line-height: 1.6rem; + text-align: center; +} + +.mastodon { + color: #897ff8; + border-radius: 100%; + box-shadow: inset 0 0 0 .05rem currentcolor; + display: inline-block; + height: 1.2rem !important; + padding: .25rem; + transition: all .25s; + vertical-align: bottom !important; + width: 1.2rem; +} \ No newline at end of file diff --git a/docs/insiders/changelog.md b/docs/insiders/changelog.md new file mode 100644 index 00000000..0f438566 --- /dev/null +++ b/docs/insiders/changelog.md @@ -0,0 +1,3 @@ +# Changelog + +## mkdocstrings Insiders diff --git a/docs/insiders/goals.yml b/docs/insiders/goals.yml new file mode 100644 index 00000000..896b9240 --- /dev/null +++ b/docs/insiders/goals.yml @@ -0,0 +1 @@ +goals: {} diff --git a/docs/insiders/index.md b/docs/insiders/index.md new file mode 100644 index 00000000..13e7ccbe --- /dev/null +++ b/docs/insiders/index.md @@ -0,0 +1,222 @@ +# Insiders + +*mkdocstrings* follows the **sponsorware** release strategy, which means +that new features are first exclusively released to sponsors as part of +[Insiders][insiders]. Read on to learn [what sponsorships achieve][sponsorship], +[how to become a sponsor][sponsors] to get access to Insiders, +and [what's in it for you][features]! + +## What is Insiders? + +*mkdocstrings Insiders* is a private fork of *mkdocstrings*, hosted as +a private GitHub repository. Almost[^1] [all new features][features] +are developed as part of this fork, which means that they are immediately +available to all eligible sponsors, as they are made collaborators of this +repository. + + [^1]: + In general, every new feature is first exclusively released to sponsors, but + sometimes upstream dependencies enhance + existing features that must be supported by *mkdocstrings*. + +Every feature is tied to a [funding goal][funding] in monthly subscriptions. When a +funding goal is hit, the features that are tied to it are merged back into +*mkdocstrings* and released for general availability, making them available +to all users. Bugfixes are always released in tandem. + +Sponsorships start as low as [**$10 a month**][sponsors].[^2] + + [^2]: + Note that $10 a month is the minimum amount to become eligible for + Insiders. While GitHub Sponsors also allows to sponsor lower amounts or + one-time amounts, those can't be granted access to Insiders due to + technical reasons. Such contributions are still very much welcome as + they help ensuring the project's sustainability. + + +## What sponsorships achieve + +Sponsorships make this project sustainable, as they buy the maintainers of this +project time – a very scarce resource – which is spent on the development of new +features, bug fixing, stability improvement, issue triage and general support. +The biggest bottleneck in Open Source is time.[^3] + + [^3]: + Making an Open Source project sustainable is exceptionally hard: maintainers + burn out, projects are abandoned. That's not great and very unpredictable. + The sponsorware model ensures that if you decide to use *mkdocstrings*, + you can be sure that bugs are fixed quickly and new features are added + regularly. + +If you're unsure if you should sponsor this project, check out the list of +[completed funding goals][goals completed] to learn whether you're already using features that +were developed with the help of sponsorships. You're most likely using at least +a handful of them, [thanks to our awesome sponsors][sponsors]! + +## What's in it for me? + +```python exec="1" session="insiders" +data_source = "docs/insiders/goals.yml" +``` + +```python exec="1" session="insiders" +--8<-- "scripts/insiders.py" + +print(f"""The moment you become a sponsor, you'll get **immediate +access to {len(completed_features)} additional features** that you can start using right away, and +which are currently exclusively available to sponsors:\n""") + +for feature in completed_features: + feature.render(badge=True) +``` + +## How to become a sponsor + +Thanks for your interest in sponsoring! In order to become an eligible sponsor +with your GitHub account, visit [pawamoy's sponsor profile][github sponsor profile], +and complete a sponsorship of **$10 a month or more**. +You can use your individual or organization GitHub account for sponsoring. + +**Important**: If you're sponsoring **[@pawamoy][github sponsor profile]** +through a GitHub organization, please send a short email +to pawamoy@pm.me with the name of your +organization and the GitHub account of the individual +that should be added as a collaborator.[^4] + +You can cancel your sponsorship anytime.[^5] + + [^4]: + It's currently not possible to grant access to each member of an + organization, as GitHub only allows for adding users. Thus, after + sponsoring, please send an email to pawamoy@pm.me, stating which + account should become a collaborator of the Insiders repository. We're + working on a solution which will make access to organizations much simpler. + To ensure that access is not tied to a particular individual GitHub account, + create a bot account (i.e. a GitHub account that is not tied to a specific + individual), and use this account for the sponsoring. After being added to + the list of collaborators, the bot account can create a private fork of the + private Insiders GitHub repository, and grant access to all members of the + organizations. + + [^5]: + If you cancel your sponsorship, GitHub schedules a cancellation request + which will become effective at the end of the billing cycle. This means + that even though you cancel your sponsorship, you will keep your access to + Insiders as long as your cancellation isn't effective. All charges are + processed by GitHub through Stripe. As we don't receive any information + regarding your payment, and GitHub doesn't offer refunds, sponsorships are + non-refundable. + + +```python exec="1" session="insiders" +print_join_sponsors_button() +``` + + + +
+ +```python exec="1" session="insiders" +print_sponsors() +``` + +

+ + + If you sponsor publicly, you're automatically added here with a link to + your profile and avatar to show your support for *mkdocstrings*. + Alternatively, if you wish to keep your sponsorship private, you'll be a + silent +1. You can select visibility during checkout and change it + afterwards. + + +## Funding + +```python exec="1" session="insiders" idprefix="" +print(f"Current funding is at **$ {human_readable_amount(current_funding)} a month**.") +``` + +### Goals + +The following section lists all funding goals. Each goal contains a list of +features prefixed with a checkmark symbol, denoting whether a feature is +:octicons-check-circle-fill-24:{ style="color: #00e676" } already available or +:octicons-check-circle-fill-24:{ style="color: var(--md-default-fg-color--lightest)" } planned, +but not yet implemented. When the funding goal is hit, +the features are released for general availability. + +```python exec="1" session="insiders" idprefix="" +for goal in goals.values(): + if not goal.complete: + goal.render() +``` + +### Goals completed + +This section lists all funding goals that were previously completed, which means +that those features were part of Insiders, but are now generally available and +can be used by all users. + +```python exec="1" session="insiders" +for goal in goals.values(): + if goal.complete: + goal.render() +``` + +## Frequently asked questions + +### Compatibility + +> We're building an open source project and want to allow outside collaborators +to use *mkdocstrings* locally without having access to Insiders. +Is this still possible? + +Yes. Insiders is compatible with *mkdocstrings*. Almost all new features +and configuration options are either backward-compatible or implemented behind +feature flags. Most Insiders features enhance the overall experience, +though while these features add value for the users of your project, they +shouldn't be necessary for previewing when making changes to content. + + + +### Payment + +> We don't want to pay for sponsorship every month. Are there any other options? + +Yes. You can sponsor on a yearly basis by [switching your GitHub account to a +yearly billing cycle][billing cycle]. If for some reason you cannot do that, you +could also create a dedicated GitHub account with a yearly billing cycle, which +you only use for sponsoring (some sponsors already do that). + +If you have any problems or further questions, please reach out to pawamoy@pm.me. + +### Terms + +> Are we allowed to use Insiders under the same terms and conditions as +*mkdocstrings*? + +Yes. Whether you're an individual or a company, you may use *mkdocstrings +Insiders* precisely under the same terms as *mkdocstrings*, which are given +by the [ISC License][license]. However, we kindly ask you to respect our +**fair use policy**: + +- Please **don't distribute the source code** of Insiders. You may freely use + it for public, private or commercial projects, privately fork or mirror it, + but please don't make the source code public, as it would counteract the + sponsorware strategy. + +- If you cancel your subscription, you're automatically removed as a + collaborator and will miss out on all future updates of Insiders. However, you + may **use the latest version** that's available to you **as long as you like**. + Just remember that [GitHub deletes private forks][private forks]. + +[insiders]: #what-is-insiders +[sponsorship]: #what-sponsorships-achieve +[sponsors]: #how-to-become-a-sponsor +[features]: #whats-in-it-for-me +[funding]: #funding +[goals completed]: #goals-completed +[github sponsor profile]: https://github.com/sponsors/pawamoy +[billing cycle]: https://docs.github.com/en/github/setting-up-and-managing-billing-and-payments-on-github/changing-the-duration-of-your-billing-cycle +[license]: ../license/ +[private forks]: https://docs.github.com/en/github/setting-up-and-managing-your-github-user-account/removing-a-collaborator-from-a-personal-repository diff --git a/docs/insiders/installation.md b/docs/insiders/installation.md new file mode 100644 index 00000000..9852182b --- /dev/null +++ b/docs/insiders/installation.md @@ -0,0 +1,190 @@ +--- +title: Getting started with Insiders +--- + +# Getting started with Insiders + +*mkdocstrings Insiders* is a compatible drop-in replacement for *mkdocstrings*, +and can be installed similarly using `pip` or `git`. +Note that in order to access the Insiders repository, +you need to [become an eligible sponsor] of @pawamoy on GitHub. + + [become an eligible sponsor]: index.md#how-to-become-a-sponsor + +## Installation + +### with pip (ssh/https) + +*mkdocstrings Insiders* can be installed with `pip` [using SSH][using ssh]: + +```bash +pip install git+ssh://git@github.com/pawamoy-insiders/mkdocstrings.git +``` + + [using ssh]: https://docs.github.com/en/authentication/connecting-to-github-with-ssh + +Or using HTTPS: + +```bash +pip install git+https://${GH_TOKEN}@github.com/pawamoy-insiders/mkdocstrings.git +``` + +>? NOTE: **How to get a GitHub personal access token** +> The `GH_TOKEN` environment variable is a GitHub token. +> It can be obtained by creating a [personal access token] for +> your GitHub account. It will give you access to the Insiders repository, +> programmatically, from the command line or GitHub Actions workflows: +> +> 1. Go to https://github.com/settings/tokens +> 2. Click on [Generate a new token] +> 3. Enter a name and select the [`repo`][scopes] scope +> 4. Generate the token and store it in a safe place +> +> [personal access token]: https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token +> [Generate a new token]: https://github.com/settings/tokens/new +> [scopes]: https://docs.github.com/en/developers/apps/scopes-for-oauth-apps#available-scopes +> +> Note that the personal access +> token must be kept secret at all times, as it allows the owner to access your +> private repositories. + +### with pip (self-hosted) + +Self-hosting the Insiders package makes it possible to depend on *mkdocstrings* normally, +while transparently downloading and installing the Insiders version locally. +It means that you can specify your dependencies normally, and your contributors without access +to Insiders will get the public version, while you get the Insiders version on your machine. + +WARNING: **Limitation** +With this method, there is no way to force the installation of an Insiders version +rather than a public version. If there is a public version that is more recent +than your self-hosted Insiders version, the public version will take precedence. +Remember to regularly update your self-hosted versions by uploading latest distributions. + +You can build the distributions for Insiders yourself, by cloning the repository +and using [build] to build the distributions, +or you can download them from our [GitHub Releases]. +You can upload these distributions to a private PyPI-like registry +([Artifactory], [Google Cloud], [pypiserver], etc.) +with [Twine]: + + [build]: https://pypi.org/project/build/ + [Artifactory]: https://jfrog.com/help/r/jfrog-artifactory-documentation/pypi-repositories + [Google Cloud]: https://cloud.google.com/artifact-registry/docs/python + [pypiserver]: https://pypi.org/project/pypiserver/ + [Github Releases]: https://github.com/pawamoy-insiders/mkdocstrings/releases + [Twine]: https://pypi.org/project/twine/ + +```bash +# download distributions in ~/dists, then upload with: +twine upload --repository-url https://your-private-index.com ~/dists/* +``` + +You might also need to provide a username and password/token to authenticate against the registry. +Please check [Twine's documentation][twine docs]. + + [twine docs]: https://twine.readthedocs.io/en/stable/ + +You can then configure pip (or other tools) to look for packages into your package index. +For example, with pip: + +```bash +pip config set global.extra-index-url https://your-private-index.com/simple +``` + +Note that the URL might differ depending on whether your are uploading a package (with Twine) +or installing a package (with pip), and depending on the registry you are using (Artifactory, Google Cloud, etc.). +Please check the documentation of your registry to learn how to configure your environment. + +**We kindly ask that you do not upload the distributions to public registries, +as it is against our [Terms of use](../#terms).** + +>? TIP: **Full example with `pypiserver`** +> In this example we use [pypiserver] to serve a local PyPI index. +> +> ```bash +> pip install --user pypiserver +> # or pipx install pypiserver +> +> # create a packages directory +> mkdir -p ~/.local/pypiserver/packages +> +> # run the pypi server without authentication +> pypi-server run -p 8080 -a . -P . ~/.local/pypiserver/packages & +> ``` +> +> We can configure the credentials to access the server in [`~/.pypirc`][pypirc]: +> +> [pypirc]: https://packaging.python.org/en/latest/specifications/pypirc/ +> +> ```ini title=".pypirc" +> [distutils] +> index-servers = +> local +> +> [local] +> repository: http://localhost:8080 +> username: +> password: +> ``` +> +> We then clone the Insiders repository, build distributions and upload them to our local server: +> +> ```bash +> # clone the repository +> git clone git@github.com:pawamoy-insiders/mkdocstrings +> cd mkdocstrings +> +> # install build +> pip install --user build +> # or pipx install build +> +> # checkout latest tag +> git checkout $(git describe --tags --abbrev=0) +> +> # build the distributions +> pyproject-build +> +> # upload them to our local server +> twine upload -r local dist/* --skip-existing +> ``` +> +> Finally, we configure pip, and for example [PDM][pdm], to use our local index to find packages: +> +> ```bash +> pip config set global.extra-index-url http://localhost:8080/simple +> pdm config pypi.extra.url http://localhost:8080/simple +> ``` +> +> [pdm]: https://pdm.fming.dev/latest/ +> +> Now when running `pip install mkdocstrings`, +> or resolving dependencies with PDM, +> both tools will look into our local index and find the Insiders version. +> **Remember to update your local index regularly!** + +### with git + +Of course, you can use *mkdocstrings Insiders* directly from `git`: + +``` +git clone git@github.com:pawamoy-insiders/mkdocstrings +``` + +When cloning from `git`, the package must be installed: + +``` +pip install -e mkdocstrings +``` + +## Upgrading + +When upgrading Insiders, you should always check the version of *mkdocstrings* +which makes up the first part of the version qualifier. For example, a version like +`8.x.x.4.x.x` means that Insiders `4.x.x` is currently based on `8.x.x`. + +If the major version increased, it's a good idea to consult the [changelog] +and go through the steps to ensure your configuration is up to date and +all necessary changes have been made. + + [changelog]: ./changelog.md diff --git a/duties.py b/duties.py index 01d632ae..5d4f9602 100644 --- a/duties.py +++ b/duties.py @@ -10,13 +10,18 @@ from duty import duty from duty.callables import black, blacken_docs, coverage, lazy, mkdocs, mypy, pytest, ruff, safety +if sys.version_info < (3, 8): + from importlib_metadata import version as pkgversion +else: + from importlib.metadata import version as pkgversion + + if TYPE_CHECKING: from duty.context import Context PY_SRC_PATHS = (Path(_) for _ in ("src", "tests", "duties.py", "scripts")) PY_SRC_LIST = tuple(str(_) for _ in PY_SRC_PATHS) PY_SRC = " ".join(PY_SRC_LIST) -TESTING = os.environ.get("TESTING", "0") in {"1", "true"} CI = os.environ.get("CI", "0") in {"1", "true", "yes", ""} WINDOWS = os.name == "nt" PTY = not WINDOWS and not CI @@ -30,6 +35,12 @@ def pyprefix(title: str) -> str: # noqa: D103 return title +def mkdocs_config() -> str: # noqa: D103 + if "+insiders" in pkgversion("mkdocs-material"): + return "mkdocs.insiders.yml" + return "mkdocs.yml" + + @duty def changelog(ctx: Context) -> None: """Update the changelog in-place with latest commits. @@ -39,7 +50,7 @@ def changelog(ctx: Context) -> None: """ from git_changelog.cli import build_and_render - git_changelog = lazy("git_changelog")(build_and_render) + git_changelog = lazy(build_and_render, name="git_changelog") ctx.run( git_changelog( repository=".", @@ -48,7 +59,7 @@ def changelog(ctx: Context) -> None: template="keepachangelog", parse_trailers=True, parse_refs=False, - sections=("build", "deps", "feat", "fix", "refactor"), + sections=["build", "deps", "feat", "fix", "refactor"], bump_latest=True, in_place=True, ), @@ -56,7 +67,7 @@ def changelog(ctx: Context) -> None: ) -@duty(pre=["check_quality", "check_types", "check_docs", "check_dependencies"]) +@duty(pre=["check_quality", "check_types", "check_docs", "check_dependencies", "check-api"]) def check(ctx: Context) -> None: # noqa: ARG001 """Check it all! @@ -104,7 +115,7 @@ def check_docs(ctx: Context) -> None: """ Path("htmlcov").mkdir(parents=True, exist_ok=True) Path("htmlcov/index.html").touch(exist_ok=True) - ctx.run(mkdocs.build(strict=True), title=pyprefix("Building documentation")) + ctx.run(mkdocs.build(strict=True, config_file=mkdocs_config()), title=pyprefix("Building documentation")) @duty @@ -121,6 +132,23 @@ def check_types(ctx: Context) -> None: ) +@duty +def check_api(ctx: Context) -> None: + """Check for API breaking changes. + + Parameters: + ctx: The context instance (passed automatically). + """ + from griffe.cli import check as g_check + + griffe_check = lazy(g_check, name="griffe.check") + ctx.run( + griffe_check("mkdocstrings", search_paths=["src"]), + title="Checking for API breaking changes", + nofail=True, + ) + + @duty(silent=True) def clean(ctx: Context) -> None: """Delete temporary files. @@ -142,17 +170,7 @@ def clean(ctx: Context) -> None: @duty -def docs(ctx: Context) -> None: - """Build the documentation locally. - - Parameters: - ctx: The context instance (passed automatically). - """ - ctx.run(mkdocs.build, title="Building documentation") - - -@duty -def docs_serve(ctx: Context, host: str = "127.0.0.1", port: int = 8000) -> None: +def docs(ctx: Context, host: str = "127.0.0.1", port: int = 8000) -> None: """Serve the documentation (localhost:8000). Parameters: @@ -161,7 +179,7 @@ def docs_serve(ctx: Context, host: str = "127.0.0.1", port: int = 8000) -> None: port: The port to serve the docs on. """ ctx.run( - mkdocs.serve(dev_addr=f"{host}:{port}"), + mkdocs.serve(dev_addr=f"{host}:{port}", config_file=mkdocs_config()), title="Serving documentation", capture=False, ) @@ -174,8 +192,23 @@ def docs_deploy(ctx: Context) -> None: Parameters: ctx: The context instance (passed automatically). """ - ctx.run("git remote add org-pages git@github.com:mkdocstrings/mkdocstrings.github.io", silent=True, nofail=True) - ctx.run(mkdocs.gh_deploy(remote_name="org-pages", force=True), title="Deploying documentation") + os.environ["DEPLOY"] = "true" + config_file = mkdocs_config() + if config_file == "mkdocs.yml": + ctx.run(lambda: False, title="Not deploying docs without Material for MkDocs Insiders!") + origin = ctx.run("git config --get remote.origin.url", silent=True) + if "pawamoy-insiders/mkdocstrings" in origin: + ctx.run("git remote add org-pages git@github.com:mkdocstrings/mkdocstrings.github.io", silent=True, nofail=True) + ctx.run( + mkdocs.gh_deploy(config_file=config_file, remote_name="org-pages", force=True), + title="Deploying documentation", + ) + else: + ctx.run( + lambda: False, + title="Not deploying docs from public repository (do that from insiders instead!)", + nofail=True, + ) @duty @@ -197,7 +230,7 @@ def format(ctx: Context) -> None: ) -@duty +@duty(post=["docs-deploy"]) def release(ctx: Context, version: str) -> None: """Release a new Python package. @@ -205,15 +238,19 @@ def release(ctx: Context, version: str) -> None: ctx: The context instance (passed automatically). version: The new version number to use. """ + origin = ctx.run("git config --get remote.origin.url", silent=True) + if "pawamoy-insiders/mkdocstrings" in origin: + ctx.run( + lambda: False, + title="Not releasing from insiders repository (do that from public repo instead!)", + ) ctx.run("git add pyproject.toml CHANGELOG.md", title="Staging files", pty=PTY) ctx.run(["git", "commit", "-m", f"chore: Prepare release {version}"], title="Committing changes", pty=PTY) ctx.run(f"git tag {version}", title="Tagging commit", pty=PTY) - if not TESTING: - ctx.run("git push", title="Pushing commits", pty=False) - ctx.run("git push --tags", title="Pushing tags", pty=False) - ctx.run("pdm build", title="Building dist/wheel", pty=PTY) - ctx.run("twine upload --skip-existing dist/*", title="Publishing version", pty=PTY) - docs_deploy.run() + ctx.run("git push", title="Pushing commits", pty=False) + ctx.run("git push --tags", title="Pushing tags", pty=False) + ctx.run("pdm build", title="Building dist/wheel", pty=PTY) + ctx.run("twine upload --skip-existing dist/*", title="Publishing version", pty=PTY) @duty(silent=True, aliases=["coverage"]) diff --git a/mkdocs.insiders.yml b/mkdocs.insiders.yml new file mode 100644 index 00000000..93e3a93b --- /dev/null +++ b/mkdocs.insiders.yml @@ -0,0 +1,4 @@ +INHERIT: mkdocs.yml + +plugins: + typeset: {} diff --git a/mkdocs.yml b/mkdocs.yml index 191526d4..fc8bbacb 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -5,7 +5,8 @@ repo_url: "https://github.com/mkdocstrings/mkdocstrings" edit_uri: "blob/master/docs/" repo_name: "mkdocstrings/mkdocstrings" site_dir: "site" -watch: [README.md, CONTRIBUTING.md, CHANGELOG.md, src/mkdocstrings] +watch: [mkdocs.yml, README.md, CONTRIBUTING.md, CHANGELOG.md, src/mkdocstrings] +copyright: Copyright © 2019 Timothée Mazzucotelli nav: - Home: @@ -29,19 +30,38 @@ nav: - Contributing: contributing.md - Code of Conduct: code_of_conduct.md - Coverage report: coverage.md +- Insiders: + - insiders/index.md + - Getting started: + - Installation: insiders/installation.md + - Changelog: insiders/changelog.md - Author's website: https://pawamoy.github.io/ theme: name: material logo: logo.svg + custom_dir: docs/.overrides features: + - announce.dismiss + - content.action.edit + - content.action.view - content.code.annotate + - content.code.copy + - content.tooltips + - navigation.footer + - navigation.indexes + - navigation.sections - navigation.tabs - navigation.tabs.sticky - navigation.top - search.highlight - search.suggest + - toc.follow palette: + - media: "(prefers-color-scheme)" + toggle: + icon: material/brightness-auto + name: Switch to light mode - media: "(prefers-color-scheme: light)" scheme: default primary: teal @@ -55,40 +75,46 @@ theme: accent: lime toggle: icon: material/weather-night - name: Switch to light mode + name: Switch to system preference extra_css: - css/style.css - css/material.css - css/mkdocstrings.css +- css/insiders.css markdown_extensions: +- attr_list - admonition - callouts -- pymdownx.details -- pymdownx.emoji +- footnotes +- pymdownx.emoji: + emoji_index: !!python/name:materialx.emoji.twemoji + emoji_generator: !!python/name:materialx.emoji.to_svg - pymdownx.magiclink - pymdownx.snippets: check_paths: true - pymdownx.superfences - pymdownx.tabbed: alternate_style: true -- pymdownx.tasklist + slugify: !!python/object/apply:pymdownx.slugs.slugify + kwds: + case: lower +- pymdownx.tasklist: + custom_checkbox: true - toc: permalink: "¤" plugins: -- search -- markdown-exec -- gen-files: + search: {} + markdown-exec: {} + gen-files: scripts: - scripts/gen_ref_nav.py - - scripts/gen_redirects.py -- literate-nav: + literate-nav: nav_file: SUMMARY.txt -- coverage -- section-index -- mkdocstrings: + coverage: {} + mkdocstrings: handlers: python: import: @@ -100,6 +126,11 @@ plugins: merge_init_into_class: true docstring_options: ignore_init_summary: true + git-committers: + enabled: !ENV [DEPLOY, false] + repository: mkdocstrings/mkdocstrings + minify: + minify_html: !ENV [DEPLOY, false] extra: social: @@ -109,3 +140,7 @@ extra: link: https://fosstodon.org/@pawamoy - icon: fontawesome/brands/twitter link: https://twitter.com/pawamoy + - icon: fontawesome/brands/gitter + link: https://gitter.im/mkdocstrings/community + - icon: fontawesome/brands/python + link: https://pypi.org/project/mkdocstrings/ diff --git a/pyproject.toml b/pyproject.toml index 07665246..9997e45d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,12 +1,12 @@ [build-system] -requires = ["pdm-pep517"] -build-backend = "pdm.pep517.api" +requires = ["pdm-backend"] +build-backend = "pdm.backend" [project] name = "mkdocstrings" description = "Automatic documentation from sources, for MkDocs." authors = [{name = "Timothée Mazzucotelli", email = "pawamoy@pm.me"}] -license = "ISC" +license = {text = "ISC"} readme = "README.md" requires-python = ">=3.7" keywords = ["mkdocs", "mkdocs-plugin", "docstrings", "autodoc", "documentation"] @@ -51,7 +51,7 @@ Repository = "https://github.com/mkdocstrings/mkdocstrings" Issues = "https://github.com/mkdocstrings/mkdocstrings/issues" Discussions = "https://github.com/mkdocstrings/mkdocstrings/discussions" Gitter = "https://gitter.im/mkdocstrings/community" -Funding = "https://github.com/sponsors/mkdocstrings" +Funding = "https://github.com/sponsors/pawamoy" [project.entry-points."mkdocs.plugins"] mkdocstrings = "mkdocstrings.plugin:MkdocstringsPlugin" @@ -65,18 +65,19 @@ includes = ["src/mkdocstrings"] editable-backend = "editables" [tool.pdm.dev-dependencies] -duty = ["duty>=0.8"] +duty = ["duty>=0.10"] docs = [ "black>=23.1", + "markdown-callouts>=0.2", + "markdown-exec>=0.5", "mkdocs>=1.3", "mkdocs-coverage>=0.2", "mkdocs-gen-files>=0.3", + "mkdocs-git-committers-plugin-2>=1.1", "mkdocs-literate-nav>=0.4", "mkdocs-material>=7.3", - "mkdocs-section-index>=0.3", + "mkdocs-minify-plugin>=0.6.4", "mkdocstrings-python>=0.5.1", - "markdown-callouts>=0.2", - "markdown-exec>=0.5", "toml>=0.10", ] maintain = [ @@ -100,7 +101,7 @@ typing = [ "mypy>=0.911", "types-docutils", "types-markdown>=3.3", - "types-pyyaml", + "types-pyyaml>=6.0", "types-toml>=0.10", ] security = ["safety>=2"] diff --git a/scripts/gen_credits.py b/scripts/gen_credits.py index 7f59f8f9..91b356c6 100644 --- a/scripts/gen_credits.py +++ b/scripts/gen_credits.py @@ -87,7 +87,7 @@ def _render_credits() -> str: } template_text = dedent( """ - These projects were used to build `{{ project_name }}`. **Thank you!** + These projects were used to build *{{ project_name }}*. **Thank you!** [`python`](https://www.python.org/) | [`pdm`](https://pdm.fming.dev/) | diff --git a/scripts/insiders.py b/scripts/insiders.py new file mode 100644 index 00000000..add870cb --- /dev/null +++ b/scripts/insiders.py @@ -0,0 +1,201 @@ +"""Functions related to Insiders funding goals.""" + +from __future__ import annotations + +import json +import logging +import posixpath +from dataclasses import dataclass +from datetime import date, datetime, timedelta +from itertools import chain +from pathlib import Path +from textwrap import dedent +from typing import Iterable, cast +from urllib.error import HTTPError +from urllib.parse import urljoin +from urllib.request import urlopen + +import yaml + +logger = logging.getLogger(f"mkdocs.logs.{__name__}") + + +def human_readable_amount(amount: int) -> str: # noqa: D103 + str_amount = str(amount) + if len(str_amount) >= 4: # noqa: PLR2004 + return f"{str_amount[:len(str_amount)-3]},{str_amount[-3:]}" + return str_amount + + +@dataclass +class Project: + """Class representing an Insiders project.""" + + name: str + url: str + + +@dataclass +class Feature: + """Class representing an Insiders feature.""" + + name: str + ref: str + since: date | None + project: Project | None + + def url(self, rel_base: str = "..") -> str: # noqa: D102 + if self.project: + rel_base = self.project.url + return posixpath.join(rel_base, self.ref.lstrip("/")) + + def render(self, rel_base: str = "..", *, badge: bool = False) -> None: # noqa: D102 + new = "" + if badge: + recent = self.since and date.today() - self.since <= timedelta(days=60) # noqa: DTZ011 + if recent: + ft_date = self.since.strftime("%B %d, %Y") # type: ignore[union-attr] + new = f' :material-alert-decagram:{{ .new-feature .vibrate title="Added on {ft_date}" }}' + project = f"[{self.project.name}]({self.project.url}) — " if self.project else "" + print(f"- [{'x' if self.since else ' '}] {project}[{self.name}]({self.url(rel_base)}){new}") + + +@dataclass +class Goal: + """Class representing an Insiders goal.""" + + name: str + amount: int + features: list[Feature] + complete: bool = False + + @property + def human_readable_amount(self) -> str: # noqa: D102 + return human_readable_amount(self.amount) + + def render(self, rel_base: str = "..") -> None: # noqa: D102 + print(f"#### $ {self.human_readable_amount} — {self.name}\n") + for feature in self.features: + feature.render(rel_base) + print("") + + +def load_goals(data: str, funding: int = 0, project: Project | None = None) -> dict[int, Goal]: + """Load goals from JSON data. + + Parameters: + data: The JSON data. + funding: The current total funding, per month. + origin: The origin of the data (URL). + + Returns: + A dictionaries of goals, keys being their target monthly amount. + """ + goals_data = yaml.safe_load(data)["goals"] + return { + amount: Goal( + name=goal_data["name"], + amount=amount, + complete=funding >= amount, + features=[ + Feature( + name=feature_data["name"], + ref=feature_data["ref"], + since=feature_data["since"] + and datetime.strptime(feature_data["since"], "%Y/%m/%d").date(), # noqa: DTZ007 + project=project, + ) + for feature_data in goal_data["features"] + ], + ) + for amount, goal_data in goals_data.items() + } + + +def funding_goals(source: str | list[tuple[str, str, str]], funding: int = 0) -> dict: + """Load funding goals from a given data source. + + Parameters: + source: The data source (local file path or URL). + funding: The current total funding, per month. + + Returns: + A dictionaries of goals, keys being their target monthly amount. + """ + if isinstance(source, str): + try: + data = Path(source).read_text() + except OSError as error: + raise RuntimeError(f"Could not load data from disk: {source}") from error + return load_goals(data, funding) + goals = {} + for project_name, project_url, data_fragment in source: + data_url = urljoin(project_url, data_fragment) + try: + with urlopen(data_url) as response: # noqa: S310 + data = response.read() + except HTTPError as error: + raise RuntimeError(f"Could not load data from network: {data_url}") from error + source_goals = load_goals(data, funding, project=Project(name=project_name, url=project_url)) + for amount, goal in source_goals.items(): + if amount not in goals: + goals[amount] = goal + else: + goals[amount].features.extend(goal.features) + return goals + + +def feature_list(goals: Iterable[Goal]) -> list[Feature]: + """Extract feature list from funding goals. + + Parameters: + goals: A list of funding goals. + + Returns: + A list of features. + """ + return list(chain.from_iterable(goal.features for goal in goals)) + + +def load_json(url: str) -> str | list | dict: # noqa: D103 + with urlopen(url) as response: # noqa: S310 + return json.loads(response.read().decode()) + + +data_source = globals()["data_source"] +sponsor_url = "https://github.com/sponsors/pawamoy" +data_url = "https://raw.githubusercontent.com/pawamoy/sponsors/main" +numbers: dict[str, int] = load_json(f"{data_url}/numbers.json") # type: ignore[assignment] +sponsors: list[dict] = load_json(f"{data_url}/sponsors.json") # type: ignore[assignment] +current_funding = numbers["total"] +sponsors_count = numbers["count"] +goals = funding_goals(data_source, funding=current_funding) +all_features = feature_list(goals.values()) +completed_features = sorted( + (ft for ft in all_features if ft.since), + key=lambda ft: cast(date, ft.since), + reverse=True, +) + + +def print_join_sponsors_button() -> None: # noqa: D103 + btn_classes = "{ .md-button .md-button--primary }" + print( + dedent( + f""" + [:octicons-heart-fill-24:{{ .pulse }} +   Join our {sponsors_count} awesome sponsors]({sponsor_url}){btn_classes} + """, + ), + ) + + +def print_sponsors() -> None: # noqa: D103 + private_sponsors_count = sponsors_count - len(sponsors) + for sponsor in sponsors: + print( + f"""""" + f"""""", + ) + if private_sponsors_count: + print(f"""+{private_sponsors_count}""") diff --git a/scripts/setup.sh b/scripts/setup.sh index 9e7ab1ff..559ae8b1 100755 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -14,7 +14,7 @@ if ! pdm self list 2>/dev/null | grep -q pdm-multirun; then fi if [ -n "${PYTHON_VERSIONS}" ]; then - pdm multirun -vi ${PYTHON_VERSIONS// /,} pdm install + pdm multirun -vi ${PYTHON_VERSIONS// /,} pdm install -G:all else - pdm install + pdm install -G:all fi From d37a137e1642538cd81efc9639b6c7fa1797ed6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Mon, 1 May 2023 16:32:16 +0200 Subject: [PATCH 02/13] docs: Re-organize docs a bit --- docs/troubleshooting.md | 6 +++--- .../overview.md => usage/handlers.md} | 2 +- docs/{usage.md => usage/index.md} | 2 +- docs/{ => usage}/theming.md | 0 mkdocs.insiders.yml | 5 +++-- mkdocs.yml | 20 ++++++++++++------- pyproject.toml | 1 + scripts/gen_redirects.py | 19 ------------------ 8 files changed, 22 insertions(+), 33 deletions(-) rename docs/{handlers/overview.md => usage/handlers.md} (99%) rename docs/{usage.md => usage/index.md} (99%) rename docs/{ => usage}/theming.md (100%) delete mode 100644 scripts/gen_redirects.py diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 1b360ffb..4d2b074e 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -48,7 +48,7 @@ when it should be `[Section][pytkdocs.parsers.docstrings.Section]`. ## Some objects are not rendered (they do not appear in the generated docs) - Make sure the configuration options of the handler are correct. - Check the documentation for [Handlers](handlers/overview.md) to see the available options for each handler. + Check the documentation for [Handlers](usage/handlers.md) to see the available options for each handler. - Also make sure your documentation in your source code is formatted correctly. For Python code, check the [supported docstring styles](https://mkdocstrings.github.io/python/usage/#supported-docstrings-styles) page. - Re-run the Mkdocs command with `-v`, and carefully read any traceback. @@ -116,13 +116,13 @@ use this workaround. Please open an ticket on the [bugtracker][bugtracker] with a detailed explanation and screenshots of the bad-looking parts. -Note that you can always [customize the look](theming.md) of *mkdocstrings* blocks -- through both HTML and CSS. +Note that you can always [customize the look](usage/theming.md) of *mkdocstrings* blocks -- through both HTML and CSS. ## Warning: could not find cross-reference target TIP: **New in version 0.15.** Cross-linking used to include any Markdown heading, but now it's only for *mkdocstrings* identifiers by default. -See [Cross-references to any Markdown heading](usage.md#cross-references-to-any-markdown-heading) to opt back in. +See [Cross-references to any Markdown heading](usage/index.md#cross-references-to-any-markdown-heading) to opt back in. Make sure the referenced object is properly rendered: verify your configuration options. diff --git a/docs/handlers/overview.md b/docs/usage/handlers.md similarity index 99% rename from docs/handlers/overview.md rename to docs/usage/handlers.md index 779f7c81..2083013c 100644 --- a/docs/handlers/overview.md +++ b/docs/usage/handlers.md @@ -5,8 +5,8 @@ A handler is what makes it possible to collect and render documentation for a pa ## Available handlers - Crystal +- Python - Python (Legacy) -- Python (Experimental) ## About the Python handlers diff --git a/docs/usage.md b/docs/usage/index.md similarity index 99% rename from docs/usage.md rename to docs/usage/index.md index 0969164c..3318c053 100644 --- a/docs/usage.md +++ b/docs/usage/index.md @@ -142,7 +142,7 @@ The above is equivalent to: ``` Some handlers accept additional global configuration. -Check the documentation for your handler of interest in [Handlers](handlers/overview.md). +Check the documentation for your handler of interest in [Handlers](handlers.md). ## Cross-references diff --git a/docs/theming.md b/docs/usage/theming.md similarity index 100% rename from docs/theming.md rename to docs/usage/theming.md diff --git a/mkdocs.insiders.yml b/mkdocs.insiders.yml index 93e3a93b..0baa73ce 100644 --- a/mkdocs.insiders.yml +++ b/mkdocs.insiders.yml @@ -1,4 +1,5 @@ INHERIT: mkdocs.yml -plugins: - typeset: {} +# waiting for https://github.com/squidfunk/mkdocs-material/issues/5446 +# plugins: +# typeset: {} diff --git a/mkdocs.yml b/mkdocs.yml index fc8bbacb..fe8e4f1d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -15,15 +15,16 @@ nav: - Credits: credits.md - License: license.md - Usage: - - usage.md - - Theming: theming.md - - Handlers: - - handlers/overview.md + - usage/index.md + - Theming: usage/theming.md + - Handlers: usage/handlers.md + - All handlers: - Crystal: https://mkdocstrings.github.io/crystal/ + - Python: https://mkdocstrings.github.io/python/ - Python (Legacy): https://mkdocstrings.github.io/python-legacy/ - - Python (Experimental): https://mkdocstrings.github.io/python/ - - Recipes: recipes.md - - Troubleshooting: troubleshooting.md + - Guides: + - Recipes: recipes.md + - Troubleshooting: troubleshooting.md # defer to gen-files + literate-nav - Code Reference: reference/ - Development: @@ -91,6 +92,7 @@ markdown_extensions: - pymdownx.emoji: emoji_index: !!python/name:materialx.emoji.twemoji emoji_generator: !!python/name:materialx.emoji.to_svg +- pymdownx.details - pymdownx.magiclink - pymdownx.snippets: check_paths: true @@ -129,6 +131,10 @@ plugins: git-committers: enabled: !ENV [DEPLOY, false] repository: mkdocstrings/mkdocstrings + redirects: + redirect_maps: + theming.md: usage/theming.md + handlers/overview.md: usage/handlers.md minify: minify_html: !ENV [DEPLOY, false] diff --git a/pyproject.toml b/pyproject.toml index 9997e45d..4f9caaa7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -77,6 +77,7 @@ docs = [ "mkdocs-literate-nav>=0.4", "mkdocs-material>=7.3", "mkdocs-minify-plugin>=0.6.4", + "mkdocs-redirects>=1.2.0", "mkdocstrings-python>=0.5.1", "toml>=0.10", ] diff --git a/scripts/gen_redirects.py b/scripts/gen_redirects.py deleted file mode 100644 index f35cce9c..00000000 --- a/scripts/gen_redirects.py +++ /dev/null @@ -1,19 +0,0 @@ -"""Generate redirection pages for autorefs reference.""" - -import mkdocs_gen_files - -redirect_map = { - "reference/autorefs/references.md": "https://mkdocstrings.github.io/autorefs/reference/mkdocs_autorefs/references/", - "reference/autorefs/plugin.md": "https://mkdocstrings.github.io/autorefs/reference/mkdocs_autorefs/plugin/", -} - -redirect_template = """ - -Redirecting... -""" - -for page, link in redirect_map.items(): - with mkdocs_gen_files.open(page, "w") as fd: - print(redirect_template.format(link=link), file=fd) From 6e849e34f6d31c650818021482e8548a59079932 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Mon, 1 May 2023 16:32:55 +0200 Subject: [PATCH 03/13] docs: Comment out not-ready insiders docs --- docs/insiders/index.md | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/docs/insiders/index.md b/docs/insiders/index.md index 13e7ccbe..4f34a7ce 100644 --- a/docs/insiders/index.md +++ b/docs/insiders/index.md @@ -48,10 +48,10 @@ The biggest bottleneck in Open Source is time.[^3] you can be sure that bugs are fixed quickly and new features are added regularly. -If you're unsure if you should sponsor this project, check out the list of + ## What's in it for me? @@ -61,14 +61,22 @@ data_source = "docs/insiders/goals.yml" ```python exec="1" session="insiders" --8<-- "scripts/insiders.py" +``` + + +We currently don't have any features available to sponsors only. +Right now we are putting our efforts into the documentation, +then we will start again implementing features. +You can get updates on *mkdocstrings Insiders* work +by following **@pawamoy** on :material-mastodon:{ .mastodon } [Fosstodon](https://fosstodon.org/@pawamoy). ## How to become a sponsor @@ -107,13 +115,10 @@ You can cancel your sponsorship anytime.[^5] regarding your payment, and GitHub doesn't offer refunds, sponsorships are non-refundable. - ```python exec="1" session="insiders" print_join_sponsors_button() ``` - -
```python exec="1" session="insiders" @@ -132,11 +137,16 @@ print_sponsors() ## Funding -```python exec="1" session="insiders" idprefix="" -print(f"Current funding is at **$ {human_readable_amount(current_funding)} a month**.") +```python exec="1" session="insiders" +print(f""" +Current funding is at **$ {human_readable_amount(current_funding)} a month**. +We do not have any funding goals yet. +Stay updated by following **@pawamoy** +on :material-mastodon:{{ .mastodon }} [Fosstodon](https://fosstodon.org/@pawamoy). +""") ``` -### Goals + ## Frequently asked questions @@ -177,8 +187,6 @@ feature flags. Most Insiders features enhance the overall experience, though while these features add value for the users of your project, they shouldn't be necessary for previewing when making changes to content. - - ### Payment > We don't want to pay for sponsorship every month. Are there any other options? From 4115500ade05562eb61393394dc1cfed9e572a84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Mon, 1 May 2023 16:35:34 +0200 Subject: [PATCH 04/13] chore: Ignore test fixtures for coverage --- config/coverage.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/config/coverage.ini b/config/coverage.ini index fde9d55a..1bcf0935 100644 --- a/config/coverage.ini +++ b/config/coverage.ini @@ -17,6 +17,7 @@ omit = src/*/__init__.py src/*/__main__.py tests/__init__.py + tests/fixtures/*.py exclude_lines = pragma: no cover if TYPE_CHECKING From 286c83c0da2adcd2313bd8ccafedce8f777f9d1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Mon, 1 May 2023 16:53:29 +0200 Subject: [PATCH 05/13] chore: Template upgrade --- .copier-answers.yml | 2 +- .github/workflows/dists.yml | 1 + scripts/gen_credits.py | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.copier-answers.yml b/.copier-answers.yml index 2d479ffb..00c410bb 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -1,5 +1,5 @@ # Changes here will be overwritten by Copier -_commit: 0.15.0 +_commit: 0.15.1 _src_path: gh:pawamoy/copier-pdm.git author_email: pawamoy@pm.me author_fullname: Timothée Mazzucotelli diff --git a/.github/workflows/dists.yml b/.github/workflows/dists.yml index ae5dd197..41833b63 100644 --- a/.github/workflows/dists.yml +++ b/.github/workflows/dists.yml @@ -8,6 +8,7 @@ jobs: build: name: Build dists runs-on: ubuntu-latest + if: github.repository_owner == 'pawamoy-insiders' steps: - name: Checkout uses: actions/checkout@v3 diff --git a/scripts/gen_credits.py b/scripts/gen_credits.py index 91b356c6..aef36b23 100644 --- a/scripts/gen_credits.py +++ b/scripts/gen_credits.py @@ -63,7 +63,7 @@ def _get_deps(base_deps: Mapping[str, Mapping[str, str]]) -> dict[str, dict[str, for pkg_dependency in lock_pkgs[pkg_name].get("dependencies", []): parsed = regex.match(pkg_dependency).groupdict() # type: ignore[union-attr] dep_name = parsed["dist"].lower() - if dep_name not in deps and dep_name != project["name"]: + if dep_name not in deps and dep_name != project["name"] and dep_name in lock_pkgs: deps[dep_name] = {"license": _get_license(dep_name), **parsed, **lock_pkgs[dep_name]} again = True From ca7858857e63014764d922a8140bb2602284b5f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Mon, 1 May 2023 17:23:56 +0200 Subject: [PATCH 06/13] chore: Template upgrade --- .copier-answers.yml | 2 +- scripts/gen_credits.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.copier-answers.yml b/.copier-answers.yml index 00c410bb..41d8c40b 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -1,5 +1,5 @@ # Changes here will be overwritten by Copier -_commit: 0.15.1 +_commit: 0.15.2 _src_path: gh:pawamoy/copier-pdm.git author_email: pawamoy@pm.me author_fullname: Timothée Mazzucotelli diff --git a/scripts/gen_credits.py b/scripts/gen_credits.py index aef36b23..85ac9041 100644 --- a/scripts/gen_credits.py +++ b/scripts/gen_credits.py @@ -53,6 +53,8 @@ def _get_deps(base_deps: Mapping[str, Mapping[str, str]]) -> dict[str, dict[str, for dep in base_deps: parsed = regex.match(dep).groupdict() # type: ignore[union-attr] dep_name = parsed["dist"].lower() + if dep_name not in lock_pkgs: + continue deps[dep_name] = {"license": _get_license(dep_name), **parsed, **lock_pkgs[dep_name]} again = True @@ -63,7 +65,7 @@ def _get_deps(base_deps: Mapping[str, Mapping[str, str]]) -> dict[str, dict[str, for pkg_dependency in lock_pkgs[pkg_name].get("dependencies", []): parsed = regex.match(pkg_dependency).groupdict() # type: ignore[union-attr] dep_name = parsed["dist"].lower() - if dep_name not in deps and dep_name != project["name"] and dep_name in lock_pkgs: + if dep_name in lock_pkgs and dep_name not in deps and dep_name != project["name"]: deps[dep_name] = {"license": _get_license(dep_name), **parsed, **lock_pkgs[dep_name]} again = True From 028a8026dc1398c82e2b2a4360c0623201026bcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Thu, 11 May 2023 16:59:34 +0200 Subject: [PATCH 07/13] chore: Template upgrade --- .copier-answers.yml | 2 +- .github/workflows/ci.yml | 9 ++++---- .gitignore | 1 + Makefile | 2 +- docs/.overrides/main.html | 4 ++-- docs/css/mkdocstrings.css | 25 ++++++++++++++++++++++- duties.py | 24 +++++++++++++++++++++- mkdocs.insiders.yml | 2 +- mkdocs.yml | 18 ++++++++-------- pyproject.toml | 9 +++++++- scripts/insiders.py | 43 ++++++++++++++++++++++++++------------- scripts/setup.sh | 6 +++--- 12 files changed, 107 insertions(+), 38 deletions(-) diff --git a/.copier-answers.yml b/.copier-answers.yml index 41d8c40b..62b9e3c8 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -1,5 +1,5 @@ # Changes here will be overwritten by Copier -_commit: 0.15.2 +_commit: 0.15.7 _src_path: gh:pawamoy/copier-pdm.git author_email: pawamoy@pm.me author_fullname: Timothée Mazzucotelli diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index acc26f2f..2b5ed158 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,10 +32,10 @@ jobs: python-version: "3.8" - name: Resolving dependencies - run: pdm lock -v + run: pdm lock -v --no-cross-platform -G ci-quality - name: Install dependencies - run: pdm install -G duty -G docs -G quality -G typing -G security + run: pdm install -G ci-quality - name: Check if the documentation builds correctly run: pdm run duty check-docs @@ -55,6 +55,7 @@ jobs: tests: strategy: + max-parallel: 4 matrix: os: - ubuntu-latest @@ -79,10 +80,10 @@ jobs: python-version: ${{ matrix.python-version }} - name: Resolving dependencies - run: pdm lock -v + run: pdm lock -v --no-cross-platform -G ci-tests - name: Install dependencies - run: pdm install --no-editable -G duty -G tests -G docs + run: pdm install --no-editable -G ci-tests - name: Run the test suite run: pdm run duty test diff --git a/.gitignore b/.gitignore index c08a8296..97dc958b 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ pip-wheel-metadata/ site/ pdm.lock pdm.toml +.pdm-plugins/ .pdm-python __pypackages__/ .mypy_cache/ diff --git a/Makefile b/Makefile index 68fea02f..b71d86ce 100644 --- a/Makefile +++ b/Makefile @@ -32,7 +32,7 @@ help: .PHONY: lock lock: - @pdm lock + @pdm lock --dev .PHONY: setup setup: diff --git a/docs/.overrides/main.html b/docs/.overrides/main.html index cb5234e5..cf8adeb7 100644 --- a/docs/.overrides/main.html +++ b/docs/.overrides/main.html @@ -6,9 +6,9 @@ is now available! {% include ".icons/octicons/heart-fill-16.svg" %} - + — - — For updates follow @pawamoy on + For updates follow @pawamoy on {% include ".icons/fontawesome/brands/mastodon.svg" %} diff --git a/docs/css/mkdocstrings.css b/docs/css/mkdocstrings.css index f269d975..af758331 100644 --- a/docs/css/mkdocstrings.css +++ b/docs/css/mkdocstrings.css @@ -4,7 +4,30 @@ div.doc-contents:not(.first) { border-left: .05rem solid var(--md-typeset-table-color); } +/* Mark external links as such. */ +a.autorefs-external::after { + /* https://primer.style/octicons/arrow-up-right-24 */ + background-image: url('data:image/svg+xml,'); + content: ' '; + + display: inline-block; + vertical-align: middle; + position: relative; + bottom: 0.1em; + margin-left: 0.2em; + margin-right: 0.1em; + + height: 0.7em; + width: 0.7em; + border-radius: 100%; + background-color: var(--md-typeset-a-color); +} + +a.autorefs-external:hover::after { + background-color: var(--md-accent-fg-color); +} + /* Avoid breaking parameters name, etc. in table cells. */ td code { word-break: normal !important; -} +} \ No newline at end of file diff --git a/duties.py b/duties.py index 5d4f9602..77410349 100644 --- a/duties.py +++ b/duties.py @@ -5,7 +5,7 @@ import os import sys from pathlib import Path -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any from duty import duty from duty.callables import black, blacken_docs, coverage, lazy, mkdocs, mypy, pytest, ruff, safety @@ -35,7 +35,29 @@ def pyprefix(title: str) -> str: # noqa: D103 return title +def merge(d1: Any, d2: Any) -> Any: # noqa: D103 + basic_types = (int, float, str, bool, complex) + if isinstance(d1, dict) and isinstance(d2, dict): + for key, value in d2.items(): + if key in d1: + if isinstance(d1[key], basic_types): + d1[key] = value + else: + d1[key] = merge(d1[key], value) + else: + d1[key] = value + return d1 + if isinstance(d1, list) and isinstance(d2, list): + return d1 + d2 + return d2 + + def mkdocs_config() -> str: # noqa: D103 + from mkdocs import utils + + # patch YAML loader to merge arrays + utils.merge = merge + if "+insiders" in pkgversion("mkdocs-material"): return "mkdocs.insiders.yml" return "mkdocs.yml" diff --git a/mkdocs.insiders.yml b/mkdocs.insiders.yml index 0baa73ce..a93edcc3 100644 --- a/mkdocs.insiders.yml +++ b/mkdocs.insiders.yml @@ -2,4 +2,4 @@ INHERIT: mkdocs.yml # waiting for https://github.com/squidfunk/mkdocs-material/issues/5446 # plugins: -# typeset: {} +# - typeset diff --git a/mkdocs.yml b/mkdocs.yml index fe8e4f1d..f544387c 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -108,15 +108,15 @@ markdown_extensions: permalink: "¤" plugins: - search: {} - markdown-exec: {} - gen-files: +- search +- markdown-exec +- gen-files: scripts: - scripts/gen_ref_nav.py - literate-nav: +- literate-nav: nav_file: SUMMARY.txt - coverage: {} - mkdocstrings: +- coverage +- mkdocstrings: handlers: python: import: @@ -128,14 +128,14 @@ plugins: merge_init_into_class: true docstring_options: ignore_init_summary: true - git-committers: +- git-committers: enabled: !ENV [DEPLOY, false] repository: mkdocstrings/mkdocstrings - redirects: +- redirects: redirect_maps: theming.md: usage/theming.md handlers/overview.md: usage/handlers.md - minify: +- minify: minify_html: !ENV [DEPLOY, false] extra: diff --git a/pyproject.toml b/pyproject.toml index 4f9caaa7..3c28fddf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -58,6 +58,9 @@ mkdocstrings = "mkdocstrings.plugin:MkdocstringsPlugin" [tool.pdm] version = {source = "scm"} +plugins = [ + "pdm-multirun", +] [tool.pdm.build] package-dir = "src" @@ -66,6 +69,8 @@ editable-backend = "editables" [tool.pdm.dev-dependencies] duty = ["duty>=0.10"] +ci-quality = ["mkdocstrings[duty,docs,quality,typing,security]"] +ci-tests = ["mkdocstrings[duty,docs,tests]"] docs = [ "black>=23.1", "markdown-callouts>=0.2", @@ -105,4 +110,6 @@ typing = [ "types-pyyaml>=6.0", "types-toml>=0.10", ] -security = ["safety>=2"] +security = [ + "safety>=2", +] diff --git a/scripts/insiders.py b/scripts/insiders.py index add870cb..0d23a45a 100644 --- a/scripts/insiders.py +++ b/scripts/insiders.py @@ -112,7 +112,32 @@ def load_goals(data: str, funding: int = 0, project: Project | None = None) -> d } -def funding_goals(source: str | list[tuple[str, str, str]], funding: int = 0) -> dict: +def _load_goals_from_disk(path: str, funding: int = 0) -> dict[int, Goal]: + try: + data = Path(path).read_text() + except OSError as error: + raise RuntimeError(f"Could not load data from disk: {path}") from error + return load_goals(data, funding) + + +def _load_goals_from_url(source_data: tuple[str, str, str], funding: int = 0) -> dict[int, Goal]: + project_name, project_url, data_fragment = source_data + data_url = urljoin(project_url, data_fragment) + try: + with urlopen(data_url) as response: # noqa: S310 + data = response.read() + except HTTPError as error: + raise RuntimeError(f"Could not load data from network: {data_url}") from error + return load_goals(data, funding, project=Project(name=project_name, url=project_url)) + + +def _load_goals(source: str | tuple[str, str, str], funding: int = 0) -> dict[int, Goal]: + if isinstance(source, str): + return _load_goals_from_disk(source, funding) + return _load_goals_from_url(source, funding) + + +def funding_goals(source: str | list[str | tuple[str, str, str]], funding: int = 0) -> dict[int, Goal]: """Load funding goals from a given data source. Parameters: @@ -123,20 +148,10 @@ def funding_goals(source: str | list[tuple[str, str, str]], funding: int = 0) -> A dictionaries of goals, keys being their target monthly amount. """ if isinstance(source, str): - try: - data = Path(source).read_text() - except OSError as error: - raise RuntimeError(f"Could not load data from disk: {source}") from error - return load_goals(data, funding) + return _load_goals_from_disk(source, funding) goals = {} - for project_name, project_url, data_fragment in source: - data_url = urljoin(project_url, data_fragment) - try: - with urlopen(data_url) as response: # noqa: S310 - data = response.read() - except HTTPError as error: - raise RuntimeError(f"Could not load data from network: {data_url}") from error - source_goals = load_goals(data, funding, project=Project(name=project_name, url=project_url)) + for src in source: + source_goals = _load_goals(src) for amount, goal in source_goals.items(): if amount not in goals: goals[amount] = goal diff --git a/scripts/setup.sh b/scripts/setup.sh index 559ae8b1..f6c90de5 100755 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -10,11 +10,11 @@ if ! command -v pdm &>/dev/null; then pipx install pdm fi if ! pdm self list 2>/dev/null | grep -q pdm-multirun; then - pipx inject pdm pdm-multirun + pdm install --plugins fi if [ -n "${PYTHON_VERSIONS}" ]; then - pdm multirun -vi ${PYTHON_VERSIONS// /,} pdm install -G:all + pdm multirun -vi ${PYTHON_VERSIONS// /,} pdm install --dev else - pdm install -G:all + pdm install --dev fi From 45f8904f772a3e006b1dab036b861b1e00ba843a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Thu, 11 May 2023 17:56:51 +0200 Subject: [PATCH 08/13] docs: Start showing insiders features --- docs/insiders/index.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/docs/insiders/index.md b/docs/insiders/index.md index 4f34a7ce..2d66028e 100644 --- a/docs/insiders/index.md +++ b/docs/insiders/index.md @@ -56,21 +56,24 @@ a handful of them, [thanks to our awesome sponsors][sponsors]! --> ## What's in it for me? ```python exec="1" session="insiders" -data_source = "docs/insiders/goals.yml" +data_source = [ + "docs/insiders/goals.yml", + ("mkdocstrings-python", "https://mkdocstrings.github.io/python/", "insiders/goals.yml"), +] ``` ```python exec="1" session="insiders" --8<-- "scripts/insiders.py" ``` - +``` We currently don't have any features available to sponsors only. Right now we are putting our efforts into the documentation, @@ -146,7 +149,7 @@ on :material-mastodon:{{ .mastodon }} [Fosstodon](https://fosstodon.org/@pawamoy """) ``` - +## [0.22.0](https://github.com/mkdocstrings/mkdocstrings/releases/tag/0.22.0) - 2023-05-25 + +[Compare with 0.21.2](https://github.com/mkdocstrings/mkdocstrings/compare/0.21.2...0.22.0) + +### Features + +- Allow extensions to add templates ([cf0af05](https://github.com/mkdocstrings/mkdocstrings/commit/cf0af059eb89240eba0437de417c124389e2f20e) by Timothée Mazzucotelli). [PR #569](https://github.com/mkdocstrings/mkdocstrings/pull/569) + +### Code Refactoring + +- Report inventory loading errors ([2c05d78](https://github.com/mkdocstrings/mkdocstrings/commit/2c05d7854b87251e26c1a2e1810b85702ff110f3) by Timothée Mazzucotelli). Co-authored-by: Oleh Prypin + ## [0.21.2](https://github.com/mkdocstrings/mkdocstrings/releases/tag/0.21.2) - 2023-04-06 [Compare with 0.21.1](https://github.com/mkdocstrings/mkdocstrings/compare/0.21.1...0.21.2)