├── .github └── workflows │ ├── on-merge-to-main.yml │ ├── on-pull-request.yml │ ├── on-release-main.yml │ ├── run-checks │ └── action.yml │ └── setup-poetry-env │ └── action.yml ├── .gitignore ├── CONTRIBUTING.rst ├── LICENSE ├── Makefile ├── README.md ├── docs ├── commands.md ├── demo │ ├── dark │ │ ├── homepage.html │ │ └── static │ │ │ ├── favicon.svg │ │ │ ├── homepage.js │ │ │ ├── images │ │ │ └── placeholder.png │ │ │ └── stylesheet.css │ └── light │ │ ├── homepage.html │ │ └── static │ │ ├── favicon.svg │ │ ├── homepage.js │ │ ├── images │ │ └── placeholder.png │ │ └── stylesheet.css ├── getting_started.md ├── index.md └── static │ ├── screenshot-dark.png │ └── screenshot-light.png ├── mkdocs.yml ├── poetry.lock ├── poetry.toml ├── pyproject.toml ├── simple_homepage ├── __init__.py ├── cli.py ├── directory_builder.py ├── files │ ├── settings.yaml │ └── template │ │ ├── _homepage.html │ │ └── static │ │ ├── _stylesheet.css │ │ ├── favicon.svg │ │ ├── homepage.js │ │ └── images │ │ ├── placeholder_black.png │ │ └── placeholder_white.png └── homepage_generator.py ├── static ├── screenshot-dark.png └── screenshot-light.png ├── tests └── test_simple_homepage.py └── tox.ini /.github/workflows/on-merge-to-main.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | tags-ignore: 6 | - '**' 7 | 8 | name: merge-to-main 9 | 10 | jobs: 11 | quality: 12 | runs-on: ubuntu-latest 13 | steps: 14 | 15 | - name: Check out 16 | uses: actions/checkout@v2 17 | 18 | - name: Set up the environment 19 | uses: ./.github/workflows/setup-poetry-env 20 | 21 | - name: Run checks 22 | uses: ./.github/workflows/run-checks 23 | 24 | - name: Documentation Test 25 | run: | 26 | source .venv/bin/activate 27 | make docs-test 28 | 29 | tox: 30 | runs-on: ubuntu-latest 31 | needs: quality 32 | strategy: 33 | matrix: 34 | python-version: ['3.8', '3.9', '3.10'] 35 | steps: 36 | - name: Check out 37 | uses: actions/checkout@v2 38 | 39 | - name: Set up the environment 40 | uses: ./.github/workflows/setup-poetry-env 41 | with: 42 | python-version: ${{ matrix.python-version }} 43 | 44 | - name: Test with tox 45 | run: | 46 | source .venv/bin/activate 47 | poetry add tox-gh-actions 48 | tox -------------------------------------------------------------------------------- /.github/workflows/on-pull-request.yml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: 3 | types: [opened, synchronize, reopened] 4 | 5 | name: on-pull-request 6 | 7 | jobs: 8 | quality: 9 | runs-on: ubuntu-latest 10 | steps: 11 | 12 | - name: Check out 13 | uses: actions/checkout@v2 14 | 15 | - name: Set up the environment 16 | uses: ./.github/workflows/setup-poetry-env 17 | 18 | - name: Run checks 19 | uses: ./.github/workflows/run-checks 20 | 21 | - name: Documentation Test 22 | run: | 23 | source .venv/bin/activate 24 | make docs-test 25 | 26 | tox: 27 | runs-on: ubuntu-latest 28 | needs: quality 29 | strategy: 30 | matrix: 31 | python-version: ['3.8', '3.9', '3.10'] 32 | steps: 33 | - name: Check out 34 | uses: actions/checkout@v2 35 | 36 | - name: Set up the environment 37 | uses: ./.github/workflows/setup-poetry-env 38 | with: 39 | python-version: ${{ matrix.python-version }} 40 | 41 | - name: Test with tox 42 | run: | 43 | source .venv/bin/activate 44 | poetry add tox-gh-actions 45 | tox 46 | -------------------------------------------------------------------------------- /.github/workflows/on-release-main.yml: -------------------------------------------------------------------------------- 1 | on: 2 | release: 3 | types: [published] 4 | branches: [main] 5 | 6 | name: release-main 7 | 8 | jobs: 9 | 10 | quality: 11 | runs-on: ubuntu-latest 12 | steps: 13 | 14 | - name: Check out 15 | uses: actions/checkout@v2 16 | 17 | - name: Set up the environment 18 | uses: ./.github/workflows/setup-poetry-env 19 | 20 | - name: Run checks 21 | uses: ./.github/workflows/run-checks 22 | 23 | - name: Documentation Test 24 | run: | 25 | source .venv/bin/activate 26 | make docs-test 27 | 28 | tox: 29 | needs: quality 30 | runs-on: ubuntu-latest 31 | strategy: 32 | matrix: 33 | python-version: ['3.8', '3.9', '3.10'] 34 | steps: 35 | - name: Check out 36 | uses: actions/checkout@v2 37 | 38 | - name: Set up the environment 39 | uses: ./.github/workflows/setup-poetry-env 40 | with: 41 | python-version: ${{ matrix.python-version }} 42 | 43 | - name: Test with tox 44 | run: | 45 | source .venv/bin/activate 46 | poetry add tox-gh-actions 47 | tox 48 | 49 | publish: 50 | runs-on: ubuntu-latest 51 | needs: tox 52 | steps: 53 | 54 | - name: Check out 55 | uses: actions/checkout@v2 56 | 57 | - name: Set up the environment 58 | uses: ./.github/workflows/setup-poetry-env 59 | 60 | - name: Set output 61 | id: vars 62 | run: echo ::set-output name=tag::${GITHUB_REF#refs/*/} 63 | 64 | - name: Build and publish 65 | run: | 66 | source .venv/bin/activate 67 | poetry version $RELEASE_VERSION 68 | make build-and-publish 69 | env: 70 | PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} 71 | RELEASE_VERSION: ${{ steps.vars.outputs.tag }} 72 | 73 | documentation: 74 | runs-on: ubuntu-latest 75 | needs: publish 76 | steps: 77 | 78 | - name: Check out 79 | uses: actions/checkout@v2 80 | 81 | - name: Set up the environment 82 | uses: ./.github/workflows/setup-poetry-env 83 | 84 | - name: Generate documentation 85 | run: | 86 | source .venv/bin/activate 87 | mkdocs gh-deploy --force -------------------------------------------------------------------------------- /.github/workflows/run-checks/action.yml: -------------------------------------------------------------------------------- 1 | # checkout-and-yarn/action.yml 2 | 3 | name: 'run-checks' 4 | description: 'Composite action to run checks for code formatting and to run the unittests.' 5 | runs: 6 | using: 'composite' 7 | steps: 8 | 9 | - name: Formatting check 10 | run: | 11 | source .venv/bin/activate 12 | make check 13 | shell: bash 14 | 15 | - name: Test with pytest 16 | run: | 17 | source .venv/bin/activate 18 | make test 19 | shell: bash 20 | -------------------------------------------------------------------------------- /.github/workflows/setup-poetry-env/action.yml: -------------------------------------------------------------------------------- 1 | # checkout-and-yarn/action.yml 2 | 3 | name: "setup-poetry-env" 4 | description: "Composite action to setup the Python and poetry environment." 5 | inputs: 6 | python-version: 7 | required: false 8 | description: "The python version to use" 9 | default: 3.9.7 10 | runs: 11 | using: "composite" 12 | steps: 13 | #---------------------------------------------- 14 | # from: https://github.com/snok/install-poetry 15 | # check-out repo and set-up python 16 | #---------------------------------------------- 17 | - name: Check out repository 18 | uses: actions/checkout@v2 19 | - name: Set up python 20 | uses: actions/setup-python@v2 21 | with: 22 | python-version: ${{ inputs.python-version }} 23 | #---------------------------------------------- 24 | # ----- install & configure poetry ----- 25 | #---------------------------------------------- 26 | - name: Install Poetry 27 | uses: snok/install-poetry@v1 28 | with: 29 | virtualenvs-create: true 30 | virtualenvs-in-project: true 31 | installer-parallel: true 32 | 33 | #---------------------------------------------- 34 | # load cached venv if cache exists 35 | #---------------------------------------------- 36 | - name: Load cached venv 37 | id: cached-poetry-dependencies 38 | uses: actions/cache@v2 39 | with: 40 | path: .venv 41 | key: venv-${{ runner.os }}-${{ hashFiles('**/poetry.lock') }} 42 | 43 | #---------------------------------------------- 44 | # install dependencies if cache does not exist 45 | #---------------------------------------------- 46 | - name: Install dependencies 47 | if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' 48 | run: poetry install --no-interaction --no-root 49 | shell: bash 50 | #---------------------------------------------- 51 | # install your root project, if required 52 | #---------------------------------------------- 53 | - name: Install library 54 | run: poetry install --no-interaction 55 | shell: bash 56 | - name: Activate environment 57 | run: source .venv/bin/activate 58 | shell: bash -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | docs/source 2 | .vscode/ 3 | /public/ 4 | /settings.yaml 5 | /template/ 6 | sandbox 7 | .DS_Store 8 | 9 | # From https://raw.githubusercontent.com/github/gitignore/main/Python.gitignore 10 | 11 | Byte-compiled / optimized / DLL files 12 | __pycache__/ 13 | *.py[cod] 14 | *$py.class 15 | 16 | # C extensions 17 | *.so 18 | 19 | # Distribution / packaging 20 | .Python 21 | build/ 22 | develop-eggs/ 23 | dist/ 24 | downloads/ 25 | eggs/ 26 | .eggs/ 27 | lib/ 28 | lib64/ 29 | parts/ 30 | sdist/ 31 | var/ 32 | wheels/ 33 | share/python-wheels/ 34 | *.egg-info/ 35 | .installed.cfg 36 | *.egg 37 | MANIFEST 38 | 39 | # PyInstaller 40 | # Usually these files are written by a python script from a template 41 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 42 | *.manifest 43 | *.spec 44 | 45 | # Installer logs 46 | pip-log.txt 47 | pip-delete-this-directory.txt 48 | 49 | # Unit test / coverage reports 50 | htmlcov/ 51 | .tox/ 52 | .nox/ 53 | .coverage 54 | .coverage.* 55 | .cache 56 | nosetests.xml 57 | coverage.xml 58 | *.cover 59 | *.py,cover 60 | .hypothesis/ 61 | .pytest_cache/ 62 | cover/ 63 | 64 | # Translations 65 | *.mo 66 | *.pot 67 | 68 | # Django stuff: 69 | *.log 70 | local_settings.py 71 | db.sqlite3 72 | db.sqlite3-journal 73 | 74 | # Flask stuff: 75 | instance/ 76 | .webassets-cache 77 | 78 | # Scrapy stuff: 79 | .scrapy 80 | 81 | # Sphinx documentation 82 | docs/_build/ 83 | 84 | # PyBuilder 85 | .pybuilder/ 86 | target/ 87 | 88 | # Jupyter Notebook 89 | .ipynb_checkpoints 90 | 91 | # IPython 92 | profile_default/ 93 | ipython_config.py 94 | 95 | # pyenv 96 | # For a library or package, you might want to ignore these files since the code is 97 | # intended to run in multiple environments; otherwise, check them in: 98 | # .python-version 99 | 100 | # pipenv 101 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 102 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 103 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 104 | # install all needed dependencies. 105 | #Pipfile.lock 106 | 107 | # poetry 108 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 109 | # This is especially recommended for binary packages to ensure reproducibility, and is more 110 | # commonly ignored for libraries. 111 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 112 | #poetry.lock 113 | 114 | # pdm 115 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 116 | #pdm.lock 117 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 118 | # in version control. 119 | # https://pdm.fming.dev/#use-with-ide 120 | .pdm.toml 121 | 122 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 123 | __pypackages__/ 124 | 125 | # Celery stuff 126 | celerybeat-schedule 127 | celerybeat.pid 128 | 129 | # SageMath parsed files 130 | *.sage.py 131 | 132 | # Environments 133 | .env 134 | .venv 135 | env/ 136 | venv/ 137 | ENV/ 138 | env.bak/ 139 | venv.bak/ 140 | 141 | # Spyder project settings 142 | .spyderproject 143 | .spyproject 144 | 145 | # Rope project settings 146 | .ropeproject 147 | 148 | # mkdocs documentation 149 | /site 150 | 151 | # mypy 152 | .mypy_cache/ 153 | .dmypy.json 154 | dmypy.json 155 | 156 | # Pyre type checker 157 | .pyre/ 158 | 159 | # pytype static type analyzer 160 | .pytype/ 161 | 162 | # Cython debug symbols 163 | cython_debug/ 164 | 165 | # PyCharm 166 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 167 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 168 | # and can be added to the global gitignore or merged into this file. For a more nuclear 169 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 170 | #.idea/ -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Contributing 3 | ============ 4 | 5 | Contributions are welcome, and they are greatly appreciated! Every little bit 6 | helps, and credit will always be given. 7 | 8 | You can contribute in many ways: 9 | 10 | Types of Contributions 11 | ---------------------- 12 | 13 | Report Bugs 14 | ~~~~~~~~~~~ 15 | 16 | Report bugs at https://github.com/fpgmaas/simple-homepage/issues 17 | 18 | If you are reporting a bug, please include: 19 | 20 | * Your operating system name and version. 21 | * Any details about your local setup that might be helpful in troubleshooting. 22 | * Detailed steps to reproduce the bug. 23 | 24 | Fix Bugs 25 | ~~~~~~~~ 26 | 27 | Look through the GitHub issues for bugs. Anything tagged with "bug" 28 | and "help wanted" is open to whoever wants to implement a fix for it. 29 | 30 | Implement Features 31 | ~~~~~~~~~~~~~~~~~~ 32 | 33 | Look through the GitHub issues for features. Anything tagged with "enhancement" 34 | and "help wanted" is open to whoever wants to implement it. 35 | 36 | Write Documentation 37 | ~~~~~~~~~~~~~~~~~~~ 38 | 39 | Cookiecutter PyPackage could always use more documentation, whether as part of 40 | the official docs, in docstrings, or even on the web in blog posts, articles, 41 | and such. 42 | 43 | Submit Feedback 44 | ~~~~~~~~~~~~~~~ 45 | 46 | The best way to send feedback is to file an issue at 47 | https://github.com/fpgmaas/simple-homepage/issues. 48 | 49 | If you are proposing a new feature: 50 | 51 | * Explain in detail how it would work. 52 | * Keep the scope as narrow as possible, to make it easier to implement. 53 | * Remember that this is a volunteer-driven project, and that contributions 54 | are welcome :) 55 | 56 | Get Started! 57 | ------------ 58 | 59 | Ready to contribute? Here's how to set up `simple-homepage` for local 60 | development. Please note this documentation assumes you already have 61 | `poetry` and `Git` installed and ready to go. 62 | 63 | | 1. Fork the `simple-homepage` repo on GitHub. 64 | 65 | | 2. Clone your fork locally: 66 | 67 | .. code-block:: bash 68 | 69 | cd 70 | git clone git@github.com:YOUR_NAME/simple-homepage.git 71 | 72 | 73 | | 3. Now we need to install the environment. Navigate into the directory 74 | 75 | .. code-block:: bash 76 | 77 | cd simple-homepage 78 | 79 | If you are using ``pyenv``, select a version to use locally. (See installed versions with ``pyenv versions``) 80 | 81 | .. code-block:: bash 82 | 83 | pyenv local 84 | 85 | Then, install and activate the environment with: 86 | 87 | .. code-block:: bash 88 | 89 | poetry install 90 | poetry shell 91 | 92 | | 4. Create a branch for local development: 93 | 94 | .. code-block:: bash 95 | 96 | git checkout -b name-of-your-bugfix-or-feature 97 | 98 | Now you can make your changes locally. 99 | 100 | 101 | | 5. Don't forget to add test cases for your added functionality to the ``tests`` directory. 102 | 103 | | 6. When you're done making changes, check that your changes pass the formatting tests. 104 | 105 | .. code-block:: bash 106 | 107 | make lint 108 | 109 | | 7. Now, validate that all unit tests are passing: 110 | 111 | .. code-block:: bash 112 | 113 | make test 114 | 115 | | 8. Before raising a pull request you should also run tox. This will run the 116 | tests across different versions of Python: 117 | 118 | .. code-block:: bash 119 | 120 | tox 121 | 122 | This requires you to have multiple versions of python installed. 123 | This step is also triggered in the CI/CD pipeline, so you could also choose to skip this 124 | step locally. 125 | 126 | | 9. Commit your changes and push your branch to GitHub: 127 | 128 | .. code-block:: bash 129 | 130 | git add . 131 | git commit -m "Your detailed description of your changes." 132 | git push origin name-of-your-bugfix-or-feature 133 | 134 | | 10. Submit a pull request through the GitHub website. 135 | 136 | Pull Request Guidelines 137 | --------------------------- 138 | 139 | Before you submit a pull request, check that it meets these guidelines: 140 | 141 | 1. The pull request should include tests. 142 | 143 | 2. If the pull request adds functionality, the docs should be updated. Put your 144 | new functionality into a function with a docstring, and add the feature to 145 | the list in README.rst. 146 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022, Florian Maas 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | install: ## Install the poetry environment 2 | @echo "🚀 Creating virtual environment using pyenv and poetry" 3 | @poetry install 4 | @poetry shell 5 | 6 | format: ## Format code using isort and black. 7 | @echo "🚀 Formatting code: Running isort and black" 8 | @isort . 9 | @black . 10 | 11 | check: ## Check code formatting using isort, black, flake8 and mypy. 12 | @echo "🚀 Checking code formatting: Running isort" 13 | @isort --check-only --diff . 14 | @echo "🚀 Checking code formatting: Running black" 15 | @black --check . 16 | @echo "🚀 Checking code formatting: Running flake8" 17 | @flake8 . 18 | @echo "🚀 Checking code formatting: Running mypy" 19 | @mypy . 20 | 21 | test: ## Test the code with pytest 22 | @echo "🚀 Testing code: Running pytest" 23 | @pytest --doctest-modules 24 | 25 | build: clean-build ## Build wheel file using poetry 26 | @echo "🚀 Creating wheel file" 27 | @poetry build 28 | 29 | clean-build: ## clean build artifacts 30 | @rm -rf dist 31 | 32 | publish: ## publish a release to pypi. 33 | @echo "🚀 Publishing: Dry run." 34 | @poetry config pypi-token.pypi $(PYPI_TOKEN) 35 | @poetry publish --dry-run 36 | @echo "🚀 Publishing." 37 | @poetry publish 38 | 39 | build-and-publish: build publish ## Build and publish. 40 | 41 | docs-test: ## Test if documentation can be built without warnings or errors 42 | @mkdocs build -s 43 | 44 | docs: ## Build and serve the documentation 45 | @mkdocs serve 46 | 47 | .PHONY: docs 48 | 49 | .PHONY: help 50 | 51 | help: 52 | @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' 53 | 54 | .DEFAULT_GOAL := help -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # simple-homepage 2 | 3 | [![Release](https://img.shields.io/github/v/release/fpgmaas/simple-homepage)](https://img.shields.io/github/v/release/fpgmaas/simple-homepage) 4 | [![Build status](https://img.shields.io/github/workflow/status/fpgmaas/simple-homepage/merge-to-main)](https://img.shields.io/github/workflow/status/fpgmaas/simple-homepage/merge-to-main) 5 | [![Docs](https://img.shields.io/badge/docs-gh--pages-blue)](https://fpgmaas.github.io/simple-homepage/) 6 | [![Code style with black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) 7 | [![Imports with isort](https://img.shields.io/badge/%20imports-isort-%231674b1)](https://pycqa.github.io/isort/) 8 | [![License](https://img.shields.io/github/license/fpgmaas/simple-homepage)](https://img.shields.io/github/license/fpgmaas/simple-homepage) 9 | 10 | `simple-homepage` is a command line utility that helps you create a simple static homepage for your browser. The documentation can be found [here](https://fpgmaas.github.io/simple-homepage/). 11 | 12 | ### Light ([Link to demo](https://fpgmaas.github.io/simple-homepage/demo/light/homepage.html)) 13 | 14 | 15 | Example light homepage 16 | 17 | ### Dark ([Link to demo](https://fpgmaas.github.io/simple-homepage/demo/dark/homepage.html)) 18 | 19 | Example dark homepage 20 | 21 | ## Quick start 22 | 23 | To get started, first install the package: 24 | 25 | ``` 26 | pip install simple-homepage 27 | ``` 28 | 29 | Then, navigate to a directory in which you want to create your homepage, and run 30 | 31 | ``` 32 | homepage init 33 | ``` 34 | 35 | or, for the dark version of the homepage: 36 | 37 | ``` 38 | homepage init --dark 39 | ``` 40 | 41 | Then, modify `settings.yaml` to your liking, and run 42 | 43 | ``` 44 | homepage build 45 | ``` 46 | 47 | Your custom homepage is now available under `public/homepage.html`. 48 | 49 | ## Acknowledgements 50 | 51 | Inspiration for this project comes from [this](https://www.reddit.com/r/startpages/comments/hca1dj/simple_light_startpage/) post on Reddit by [/u/akauro](https://www.reddit.com/user/akauro/). 52 | 53 | --- 54 | 55 | Repository initiated with [fpgmaas/cookiecutter-poetry](https://github.com/fpgmaas/cookiecutter-poetry). 56 | -------------------------------------------------------------------------------- /docs/commands.md: -------------------------------------------------------------------------------- 1 | # Commands 2 | 3 | ## `homepage init` 4 | 5 | This creates the following directory structure with template files: 6 | 7 | ``` 8 | . 9 | ├── settings.yaml 10 | └── template 11 | ├── _homepage.html 12 | └── static 13 | ├── _stylesheet.css 14 | ├── favicon.svg 15 | ├── homepage.js 16 | └── images 17 | └── placeholder.png 18 | ``` 19 | 20 | ### Arguments 21 | 22 | - `--dark`: Optional. Initialize the template files for the dark version of the homepage. This modifies the default colors in `settings.yaml` and 23 | change the `placeholder.png` from black to white. 24 | - `--dir `: Optional. Name of a directory (relative to the current directory) to create and place the templates in. 25 | If not specified, the template files will be placed in the current directory. 26 | - `--overwrite`: Optional. Ignore errors and overwrite existing files if they are found. 27 | 28 | ## `homepage build` 29 | 30 | Build the custom homepage from template files and write the output to a directory named `public`, so the resulting homepage is found in `public/homepage.html` This command should be run within the directory that contains the template files created with `homepage init`. 31 | 32 | ### Arguments 33 | 34 | - `--output-dir `: Optional. Name of a directory (relative to the current directory) to create and place the resulting page in. Defaults to `public` 35 | - `--output-file `: Optional. Name of the resulting html file. Defaults to homepage.html. -------------------------------------------------------------------------------- /docs/demo/dark/homepage.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Flo's Homepage 11 | 12 | 13 | 14 |
15 |
16 | 17 |
18 |
20 | 22 |
23 | 63 |
64 | 65 | 66 | -------------------------------------------------------------------------------- /docs/demo/dark/static/favicon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/demo/dark/static/homepage.js: -------------------------------------------------------------------------------- 1 | var images = new Array("static/images/placeholder.png"); 2 | window.onload = function(){ 3 | choosePic(); 4 | } 5 | 6 | function choosePic() { 7 | var randomNum = Math.floor(Math.random() * images.length); 8 | document.getElementById("center-image").src = images[randomNum]; 9 | } 10 | -------------------------------------------------------------------------------- /docs/demo/dark/static/images/placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fpgmaas/simple-homepage/5017d870cd7dcdca6486b64747414338d1c4e40e/docs/demo/dark/static/images/placeholder.png -------------------------------------------------------------------------------- /docs/demo/dark/static/stylesheet.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --background-color: #26263C; 3 | --text-color: #dedeee; 4 | --placeholder-color: #dedeee; 5 | --header-color: white; 6 | --accent-color: orange; 7 | } 8 | 9 | body { 10 | min-height: 100vh; 11 | min-width: 100vw; 12 | display: flex; 13 | justify-content: center; 14 | align-items: center; 15 | font-family: 'Pangolin', cursive; 16 | color: var(--header-color); 17 | letter-spacing: 1px; 18 | margin: 0; 19 | padding: 0; 20 | background-color: var(--background-color); 21 | } 22 | 23 | 24 | h2 { 25 | font-size: 1.1rem; 26 | margin: 0 0 .5rem 0; 27 | letter-spacing: 2px; 28 | } 29 | 30 | .center-container { 31 | width: 90%; 32 | max-width: 1000px; 33 | display: flex; 34 | flex-direction: column; 35 | justify-content: center; 36 | align-items: center; 37 | } 38 | 39 | .image-container { 40 | width: 80%; 41 | max-width: 350px; 42 | margin-bottom: 3rem; 43 | } 44 | 45 | .image { 46 | width: 100%; 47 | border-radius: 50%; 48 | aspect-ratio: 1; 49 | object-fit: cover; 50 | } 51 | 52 | #search-form { 53 | width: 100%; 54 | max-width: 350px; 55 | margin-bottom: 2rem; 56 | } 57 | 58 | #search-input { 59 | width: 100%; 60 | border: 0; 61 | font-size: .9rem; 62 | border-bottom: 1px solid grey; 63 | text-align: center; 64 | font-family: 'Pangolin', cursive; 65 | outline: none; 66 | background-color: var(--background-color); 67 | color: var(--text-color); 68 | } 69 | 70 | #search-input::placeholder { 71 | color: var(--placeholder-color); 72 | } 73 | 74 | .links-container { 75 | width: 100%; 76 | display: flex; 77 | flex-direction: row; 78 | justify-content: center; 79 | align-items: top; 80 | flex-wrap: wrap; 81 | } 82 | 83 | .links-group { 84 | display: flex; 85 | flex-direction: column; 86 | justify-content: top; 87 | align-items: center; 88 | flex: 1 1 0px; 89 | margin: 1em; 90 | max-width: 180px; 91 | } 92 | 93 | .links-list { 94 | text-align: center; 95 | list-style-type: none; /* Remove bullets */ 96 | padding: 0; /* Remove padding */ 97 | margin: 0; /* Remove margins */ 98 | } 99 | 100 | .links-list a { 101 | text-decoration: none; 102 | color: var(--text-color); 103 | transition: ease all .3s; 104 | } 105 | 106 | .links-list a:hover{ 107 | color: var(--accent-color); 108 | } 109 | 110 | .links-list li { 111 | margin: 0.2rem 0; 112 | } -------------------------------------------------------------------------------- /docs/demo/light/homepage.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Flo's Homepage 11 | 12 | 13 | 14 |
15 |
16 | 17 |
18 |
20 | 22 |
23 | 63 |
64 | 65 | 66 | -------------------------------------------------------------------------------- /docs/demo/light/static/favicon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/demo/light/static/homepage.js: -------------------------------------------------------------------------------- 1 | var images = new Array("static/images/placeholder.png"); 2 | window.onload = function(){ 3 | choosePic(); 4 | } 5 | 6 | function choosePic() { 7 | var randomNum = Math.floor(Math.random() * images.length); 8 | document.getElementById("center-image").src = images[randomNum]; 9 | } 10 | -------------------------------------------------------------------------------- /docs/demo/light/static/images/placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fpgmaas/simple-homepage/5017d870cd7dcdca6486b64747414338d1c4e40e/docs/demo/light/static/images/placeholder.png -------------------------------------------------------------------------------- /docs/demo/light/static/stylesheet.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --background-color: white; 3 | --text-color: #555555; 4 | --placeholder-color: #999999; 5 | --header-color: #111111; 6 | --accent-color: orange; 7 | } 8 | 9 | body { 10 | min-height: 100vh; 11 | min-width: 100vw; 12 | display: flex; 13 | justify-content: center; 14 | align-items: center; 15 | font-family: 'Pangolin', cursive; 16 | color: var(--header-color); 17 | letter-spacing: 1px; 18 | margin: 0; 19 | padding: 0; 20 | background-color: var(--background-color); 21 | } 22 | 23 | 24 | h2 { 25 | font-size: 1.1rem; 26 | margin: 0 0 .5rem 0; 27 | letter-spacing: 2px; 28 | } 29 | 30 | .center-container { 31 | width: 90%; 32 | max-width: 1000px; 33 | display: flex; 34 | flex-direction: column; 35 | justify-content: center; 36 | align-items: center; 37 | } 38 | 39 | .image-container { 40 | width: 80%; 41 | max-width: 350px; 42 | margin-bottom: 3rem; 43 | } 44 | 45 | .image { 46 | width: 100%; 47 | border-radius: 50%; 48 | aspect-ratio: 1; 49 | object-fit: cover; 50 | } 51 | 52 | #search-form { 53 | width: 100%; 54 | max-width: 350px; 55 | margin-bottom: 2rem; 56 | } 57 | 58 | #search-input { 59 | width: 100%; 60 | border: 0; 61 | font-size: .9rem; 62 | border-bottom: 1px solid grey; 63 | text-align: center; 64 | font-family: 'Pangolin', cursive; 65 | outline: none; 66 | background-color: var(--background-color); 67 | color: var(--text-color); 68 | } 69 | 70 | #search-input::placeholder { 71 | color: var(--placeholder-color); 72 | } 73 | 74 | .links-container { 75 | width: 100%; 76 | display: flex; 77 | flex-direction: row; 78 | justify-content: center; 79 | align-items: top; 80 | flex-wrap: wrap; 81 | } 82 | 83 | .links-group { 84 | display: flex; 85 | flex-direction: column; 86 | justify-content: top; 87 | align-items: center; 88 | flex: 1 1 0px; 89 | margin: 1em; 90 | max-width: 180px; 91 | } 92 | 93 | .links-list { 94 | text-align: center; 95 | list-style-type: none; /* Remove bullets */ 96 | padding: 0; /* Remove padding */ 97 | margin: 0; /* Remove margins */ 98 | } 99 | 100 | .links-list a { 101 | text-decoration: none; 102 | color: var(--text-color); 103 | transition: ease all .3s; 104 | } 105 | 106 | .links-list a:hover{ 107 | color: var(--accent-color); 108 | } 109 | 110 | .links-list li { 111 | margin: 0.2rem 0; 112 | } -------------------------------------------------------------------------------- /docs/getting_started.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | To get started, first install the package: 4 | 5 | ``` 6 | pip install homepage 7 | ``` 8 | 9 | ## 1. Creating the template 10 | 11 | Navigate to a directory in which you want to create your homepage, and run 12 | 13 | ``` 14 | homepage init 15 | ``` 16 | 17 | or, for the dark version of the homepage: 18 | 19 | ``` 20 | homepage init --dark 21 | ``` 22 | 23 | This creates the necessary template files to generate your custom homepage. 24 | 25 | ## 2. Modifying the template 26 | 27 | To personalize the homepage, open `settings.yaml` and modify it to your liking. The number of sections under `urls` is three by default, but you can add or remove sections simply by adding new entries to the list. 28 | 29 | Next to that, you can add your own images to the `template/static/images` directory, and optionally remove `placeholder.png`. When there are multiple images in this directory, the homepage will randomly display one of them each time the page is openend or refreshed. 30 | 31 | ## 3. Building the homepage 32 | 33 | ``` 34 | homepage build 35 | ``` 36 | 37 | Your custom homepage is now available under `public/homepage.html`. 38 | 39 | ## 3. Setting the HTML file as your homepage 40 | 41 | Finally, to use the page as your homepage, modify your browser to open the `HTML` file whenever you open a new tab. The simplest way to get the path to the file is by opening the file in a browser with `open public/homepage.html`. Some browsers require extensions to modify the new-tab page. For example, for Chrome one could use [New Tab Redirect](https://chrome.google.com/webstore/detail/new-tab-redirect/icpgjfneehieebagbmdbhnlpiopdcmna) or for Mozilla one could use [New Tab Override](https://addons.mozilla.org/en-US/firefox/addon/new-tab-override/). -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # simple-homepage 2 | 3 | [![Release](https://img.shields.io/github/v/release/fpgmaas/simple-homepage)](https://img.shields.io/github/v/release/fpgmaas/simple-homepage) 4 | [![Build status](https://img.shields.io/github/workflow/status/fpgmaas/simple-homepage/merge-to-main)](https://img.shields.io/github/workflow/status/fpgmaas/simple-homepage/merge-to-main) 5 | [![Commit activity](https://img.shields.io/github/commit-activity/m/fpgmaas/simple-homepage)](https://img.shields.io/github/commit-activity/m/fpgmaas/simple-homepage) 6 | [![License](https://img.shields.io/github/license/fpgmaas/simple-homepage)](https://img.shields.io/github/license/fpgmaas/simple-homepage) 7 | 8 | 9 | `simple-homepage` is a command line utility that helps you create a simple static homepage for your browser. For instructions on creating your own homepage, please refer to the `Getting Started` section in the sidebar. 10 | 11 | ### Light ([Link to demo](demo/light/homepage.html)) 12 | 13 | 14 | Example light homepage 15 | 16 | ### Dark ([Link to demo](demo/dark/homepage.html)) 17 | 18 | Example dark homepage 19 | 20 | ## Acknowledgements 21 | 22 | Inspiration for this project comes from [this](https://www.reddit.com/r/startpages/comments/hca1dj/simple_light_startpage/) post on Reddit by [/u/akauro](https://www.reddit.com/user/akauro/). 23 | 24 | --- -------------------------------------------------------------------------------- /docs/static/screenshot-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fpgmaas/simple-homepage/5017d870cd7dcdca6486b64747414338d1c4e40e/docs/static/screenshot-dark.png -------------------------------------------------------------------------------- /docs/static/screenshot-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fpgmaas/simple-homepage/5017d870cd7dcdca6486b64747414338d1c4e40e/docs/static/screenshot-light.png -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: simple-homepage 2 | repo_url: https://github.com/fpgmaas/simple-homepage 3 | site_url: https://fpgmaas.github.io/simple-homepage 4 | site_description: Create a simple homepage 5 | site_author: Florian Maas 6 | 7 | nav: 8 | - Home: index.md 9 | - Getting Started: getting_started.md 10 | - Commands: commands.md 11 | - Demo: 12 | - Light: demo/light/homepage.html 13 | - Dark: demo/dark/homepage.html 14 | plugins: 15 | - search 16 | - mkdocstrings: 17 | handlers: 18 | python: 19 | setup_commands: 20 | - import sys 21 | - sys.path.append('../') 22 | copyright: Maintained by Florian Maas. 23 | theme: 24 | name: material 25 | feature: 26 | tabs: true 27 | palette: 28 | primary: white 29 | accent: blue 30 | markdown_extensions: 31 | - toc: 32 | permalink: true 33 | - pymdownx.arithmatex: 34 | generic: true -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "astunparse" 3 | version = "1.6.3" 4 | description = "An AST unparser for Python" 5 | category = "dev" 6 | optional = false 7 | python-versions = "*" 8 | 9 | [package.dependencies] 10 | six = ">=1.6.1,<2.0" 11 | 12 | [[package]] 13 | name = "atomicwrites" 14 | version = "1.4.0" 15 | description = "Atomic file writes." 16 | category = "dev" 17 | optional = false 18 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 19 | 20 | [[package]] 21 | name = "attrs" 22 | version = "21.4.0" 23 | description = "Classes Without Boilerplate" 24 | category = "dev" 25 | optional = false 26 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 27 | 28 | [package.extras] 29 | dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"] 30 | docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] 31 | tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] 32 | tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"] 33 | 34 | [[package]] 35 | name = "black" 36 | version = "22.3.0" 37 | description = "The uncompromising code formatter." 38 | category = "dev" 39 | optional = false 40 | python-versions = ">=3.6.2" 41 | 42 | [package.dependencies] 43 | click = ">=8.0.0" 44 | mypy-extensions = ">=0.4.3" 45 | pathspec = ">=0.9.0" 46 | platformdirs = ">=2" 47 | tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} 48 | typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} 49 | 50 | [package.extras] 51 | colorama = ["colorama (>=0.4.3)"] 52 | d = ["aiohttp (>=3.7.4)"] 53 | jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] 54 | uvloop = ["uvloop (>=0.15.2)"] 55 | 56 | [[package]] 57 | name = "click" 58 | version = "8.1.3" 59 | description = "Composable command line interface toolkit" 60 | category = "dev" 61 | optional = false 62 | python-versions = ">=3.7" 63 | 64 | [package.dependencies] 65 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 66 | 67 | [[package]] 68 | name = "colorama" 69 | version = "0.4.4" 70 | description = "Cross-platform colored terminal text." 71 | category = "dev" 72 | optional = false 73 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 74 | 75 | [[package]] 76 | name = "flake8" 77 | version = "4.0.1" 78 | description = "the modular source code checker: pep8 pyflakes and co" 79 | category = "dev" 80 | optional = false 81 | python-versions = ">=3.6" 82 | 83 | [package.dependencies] 84 | mccabe = ">=0.6.0,<0.7.0" 85 | pycodestyle = ">=2.8.0,<2.9.0" 86 | pyflakes = ">=2.4.0,<2.5.0" 87 | 88 | [[package]] 89 | name = "ghp-import" 90 | version = "2.1.0" 91 | description = "Copy your docs directly to the gh-pages branch." 92 | category = "dev" 93 | optional = false 94 | python-versions = "*" 95 | 96 | [package.dependencies] 97 | python-dateutil = ">=2.8.1" 98 | 99 | [package.extras] 100 | dev = ["twine", "markdown", "flake8", "wheel"] 101 | 102 | [[package]] 103 | name = "importlib-metadata" 104 | version = "4.11.3" 105 | description = "Read metadata from Python packages" 106 | category = "dev" 107 | optional = false 108 | python-versions = ">=3.7" 109 | 110 | [package.dependencies] 111 | zipp = ">=0.5" 112 | 113 | [package.extras] 114 | docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] 115 | perf = ["ipython"] 116 | testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "importlib-resources (>=1.3)"] 117 | 118 | [[package]] 119 | name = "iniconfig" 120 | version = "1.1.1" 121 | description = "iniconfig: brain-dead simple config-ini parsing" 122 | category = "dev" 123 | optional = false 124 | python-versions = "*" 125 | 126 | [[package]] 127 | name = "isort" 128 | version = "5.10.1" 129 | description = "A Python utility / library to sort Python imports." 130 | category = "dev" 131 | optional = false 132 | python-versions = ">=3.6.1,<4.0" 133 | 134 | [package.extras] 135 | pipfile_deprecated_finder = ["pipreqs", "requirementslib"] 136 | requirements_deprecated_finder = ["pipreqs", "pip-api"] 137 | colors = ["colorama (>=0.4.3,<0.5.0)"] 138 | plugins = ["setuptools"] 139 | 140 | [[package]] 141 | name = "jinja2" 142 | version = "3.1.2" 143 | description = "A very fast and expressive template engine." 144 | category = "main" 145 | optional = false 146 | python-versions = ">=3.7" 147 | 148 | [package.dependencies] 149 | MarkupSafe = ">=2.0" 150 | 151 | [package.extras] 152 | i18n = ["Babel (>=2.7)"] 153 | 154 | [[package]] 155 | name = "markdown" 156 | version = "3.3.7" 157 | description = "Python implementation of Markdown." 158 | category = "dev" 159 | optional = false 160 | python-versions = ">=3.6" 161 | 162 | [package.dependencies] 163 | importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} 164 | 165 | [package.extras] 166 | testing = ["coverage", "pyyaml"] 167 | 168 | [[package]] 169 | name = "markupsafe" 170 | version = "2.1.1" 171 | description = "Safely add untrusted strings to HTML/XML markup." 172 | category = "main" 173 | optional = false 174 | python-versions = ">=3.7" 175 | 176 | [[package]] 177 | name = "mccabe" 178 | version = "0.6.1" 179 | description = "McCabe checker, plugin for flake8" 180 | category = "dev" 181 | optional = false 182 | python-versions = "*" 183 | 184 | [[package]] 185 | name = "mergedeep" 186 | version = "1.3.4" 187 | description = "A deep merge function for 🐍." 188 | category = "dev" 189 | optional = false 190 | python-versions = ">=3.6" 191 | 192 | [[package]] 193 | name = "mkdocs" 194 | version = "1.3.0" 195 | description = "Project documentation with Markdown." 196 | category = "dev" 197 | optional = false 198 | python-versions = ">=3.6" 199 | 200 | [package.dependencies] 201 | click = ">=3.3" 202 | ghp-import = ">=1.0" 203 | importlib-metadata = ">=4.3" 204 | Jinja2 = ">=2.10.2" 205 | Markdown = ">=3.2.1" 206 | mergedeep = ">=1.3.4" 207 | packaging = ">=20.5" 208 | PyYAML = ">=3.10" 209 | pyyaml-env-tag = ">=0.1" 210 | watchdog = ">=2.0" 211 | 212 | [package.extras] 213 | i18n = ["babel (>=2.9.0)"] 214 | 215 | [[package]] 216 | name = "mkdocs-autorefs" 217 | version = "0.4.1" 218 | description = "Automatically link across pages in MkDocs." 219 | category = "dev" 220 | optional = false 221 | python-versions = ">=3.7" 222 | 223 | [package.dependencies] 224 | Markdown = ">=3.3" 225 | mkdocs = ">=1.1" 226 | 227 | [[package]] 228 | name = "mkdocs-material" 229 | version = "8.2.13" 230 | description = "Documentation that simply works" 231 | category = "dev" 232 | optional = false 233 | python-versions = ">=3.7" 234 | 235 | [package.dependencies] 236 | jinja2 = ">=2.11.1" 237 | markdown = ">=3.2" 238 | mkdocs = ">=1.3.0" 239 | mkdocs-material-extensions = ">=1.0.3" 240 | pygments = ">=2.12" 241 | pymdown-extensions = ">=9.4" 242 | 243 | [[package]] 244 | name = "mkdocs-material-extensions" 245 | version = "1.0.3" 246 | description = "Extension pack for Python Markdown." 247 | category = "dev" 248 | optional = false 249 | python-versions = ">=3.6" 250 | 251 | [[package]] 252 | name = "mkdocstrings" 253 | version = "0.18.1" 254 | description = "Automatic documentation from sources, for MkDocs." 255 | category = "dev" 256 | optional = false 257 | python-versions = ">=3.7" 258 | 259 | [package.dependencies] 260 | Jinja2 = ">=2.11.1" 261 | Markdown = ">=3.3" 262 | MarkupSafe = ">=1.1" 263 | mkdocs = ">=1.2" 264 | mkdocs-autorefs = ">=0.3.1" 265 | mkdocstrings-python-legacy = ">=0.2" 266 | pymdown-extensions = ">=6.3" 267 | 268 | [package.extras] 269 | crystal = ["mkdocstrings-crystal (>=0.3.4)"] 270 | python = ["mkdocstrings-python (>=0.5.2)"] 271 | python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"] 272 | 273 | [[package]] 274 | name = "mkdocstrings-python-legacy" 275 | version = "0.2.2" 276 | description = "A legacy Python handler for mkdocstrings." 277 | category = "dev" 278 | optional = false 279 | python-versions = ">=3.7" 280 | 281 | [package.dependencies] 282 | mkdocstrings = ">=0.18" 283 | pytkdocs = ">=0.14" 284 | 285 | [[package]] 286 | name = "mypy" 287 | version = "0.942" 288 | description = "Optional static typing for Python" 289 | category = "dev" 290 | optional = false 291 | python-versions = ">=3.6" 292 | 293 | [package.dependencies] 294 | mypy-extensions = ">=0.4.3" 295 | tomli = ">=1.1.0" 296 | typing-extensions = ">=3.10" 297 | 298 | [package.extras] 299 | dmypy = ["psutil (>=4.0)"] 300 | python2 = ["typed-ast (>=1.4.0,<2)"] 301 | reports = ["lxml"] 302 | 303 | [[package]] 304 | name = "mypy-extensions" 305 | version = "0.4.3" 306 | description = "Experimental type system extensions for programs checked with the mypy typechecker." 307 | category = "dev" 308 | optional = false 309 | python-versions = "*" 310 | 311 | [[package]] 312 | name = "oyaml" 313 | version = "1.0" 314 | description = "Ordered YAML: drop-in replacement for PyYAML which preserves dict ordering" 315 | category = "main" 316 | optional = false 317 | python-versions = "*" 318 | 319 | [package.dependencies] 320 | pyyaml = "*" 321 | 322 | [[package]] 323 | name = "packaging" 324 | version = "21.3" 325 | description = "Core utilities for Python packages" 326 | category = "dev" 327 | optional = false 328 | python-versions = ">=3.6" 329 | 330 | [package.dependencies] 331 | pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" 332 | 333 | [[package]] 334 | name = "pathspec" 335 | version = "0.9.0" 336 | description = "Utility library for gitignore style pattern matching of file paths." 337 | category = "dev" 338 | optional = false 339 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 340 | 341 | [[package]] 342 | name = "platformdirs" 343 | version = "2.5.2" 344 | description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." 345 | category = "dev" 346 | optional = false 347 | python-versions = ">=3.7" 348 | 349 | [package.extras] 350 | docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)", "sphinx (>=4)"] 351 | test = ["appdirs (==1.4.4)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)", "pytest (>=6)"] 352 | 353 | [[package]] 354 | name = "pluggy" 355 | version = "1.0.0" 356 | description = "plugin and hook calling mechanisms for python" 357 | category = "dev" 358 | optional = false 359 | python-versions = ">=3.6" 360 | 361 | [package.extras] 362 | dev = ["pre-commit", "tox"] 363 | testing = ["pytest", "pytest-benchmark"] 364 | 365 | [[package]] 366 | name = "py" 367 | version = "1.11.0" 368 | description = "library with cross-python path, ini-parsing, io, code, log facilities" 369 | category = "dev" 370 | optional = false 371 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 372 | 373 | [[package]] 374 | name = "pycodestyle" 375 | version = "2.8.0" 376 | description = "Python style guide checker" 377 | category = "dev" 378 | optional = false 379 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 380 | 381 | [[package]] 382 | name = "pyflakes" 383 | version = "2.4.0" 384 | description = "passive checker of Python programs" 385 | category = "dev" 386 | optional = false 387 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 388 | 389 | [[package]] 390 | name = "pygments" 391 | version = "2.12.0" 392 | description = "Pygments is a syntax highlighting package written in Python." 393 | category = "dev" 394 | optional = false 395 | python-versions = ">=3.6" 396 | 397 | [[package]] 398 | name = "pymdown-extensions" 399 | version = "9.4" 400 | description = "Extension pack for Python Markdown." 401 | category = "dev" 402 | optional = false 403 | python-versions = ">=3.7" 404 | 405 | [package.dependencies] 406 | markdown = ">=3.2" 407 | 408 | [[package]] 409 | name = "pyparsing" 410 | version = "3.0.8" 411 | description = "pyparsing module - Classes and methods to define and execute parsing grammars" 412 | category = "dev" 413 | optional = false 414 | python-versions = ">=3.6.8" 415 | 416 | [package.extras] 417 | diagrams = ["railroad-diagrams", "jinja2"] 418 | 419 | [[package]] 420 | name = "pytest" 421 | version = "7.1.2" 422 | description = "pytest: simple powerful testing with Python" 423 | category = "dev" 424 | optional = false 425 | python-versions = ">=3.7" 426 | 427 | [package.dependencies] 428 | atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} 429 | attrs = ">=19.2.0" 430 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 431 | iniconfig = "*" 432 | packaging = "*" 433 | pluggy = ">=0.12,<2.0" 434 | py = ">=1.8.2" 435 | tomli = ">=1.0.0" 436 | 437 | [package.extras] 438 | testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] 439 | 440 | [[package]] 441 | name = "python-dateutil" 442 | version = "2.8.2" 443 | description = "Extensions to the standard Python datetime module" 444 | category = "dev" 445 | optional = false 446 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" 447 | 448 | [package.dependencies] 449 | six = ">=1.5" 450 | 451 | [[package]] 452 | name = "pytkdocs" 453 | version = "0.16.1" 454 | description = "Load Python objects documentation." 455 | category = "dev" 456 | optional = false 457 | python-versions = ">=3.7" 458 | 459 | [package.dependencies] 460 | astunparse = {version = ">=1.6", markers = "python_version < \"3.9\""} 461 | 462 | [package.extras] 463 | numpy-style = ["docstring_parser (>=0.7)"] 464 | 465 | [[package]] 466 | name = "pyyaml" 467 | version = "6.0" 468 | description = "YAML parser and emitter for Python" 469 | category = "main" 470 | optional = false 471 | python-versions = ">=3.6" 472 | 473 | [[package]] 474 | name = "pyyaml-env-tag" 475 | version = "0.1" 476 | description = "A custom YAML tag for referencing environment variables in YAML files. " 477 | category = "dev" 478 | optional = false 479 | python-versions = ">=3.6" 480 | 481 | [package.dependencies] 482 | pyyaml = "*" 483 | 484 | [[package]] 485 | name = "six" 486 | version = "1.16.0" 487 | description = "Python 2 and 3 compatibility utilities" 488 | category = "dev" 489 | optional = false 490 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 491 | 492 | [[package]] 493 | name = "tomli" 494 | version = "2.0.1" 495 | description = "A lil' TOML parser" 496 | category = "dev" 497 | optional = false 498 | python-versions = ">=3.7" 499 | 500 | [[package]] 501 | name = "typing-extensions" 502 | version = "4.2.0" 503 | description = "Backported and Experimental Type Hints for Python 3.7+" 504 | category = "dev" 505 | optional = false 506 | python-versions = ">=3.7" 507 | 508 | [[package]] 509 | name = "watchdog" 510 | version = "2.1.7" 511 | description = "Filesystem events monitoring" 512 | category = "dev" 513 | optional = false 514 | python-versions = ">=3.6" 515 | 516 | [package.extras] 517 | watchmedo = ["PyYAML (>=3.10)"] 518 | 519 | [[package]] 520 | name = "zipp" 521 | version = "3.8.0" 522 | description = "Backport of pathlib-compatible object wrapper for zip files" 523 | category = "dev" 524 | optional = false 525 | python-versions = ">=3.7" 526 | 527 | [package.extras] 528 | docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] 529 | testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"] 530 | 531 | [metadata] 532 | lock-version = "1.1" 533 | python-versions = ">=3.8,<3.11" 534 | content-hash = "741267e70c76b4dc6ab3e56ac338a0b5e460973c740ce811aefb73199045f036" 535 | 536 | [metadata.files] 537 | astunparse = [ 538 | {file = "astunparse-1.6.3-py2.py3-none-any.whl", hash = "sha256:c2652417f2c8b5bb325c885ae329bdf3f86424075c4fd1a128674bc6fba4b8e8"}, 539 | {file = "astunparse-1.6.3.tar.gz", hash = "sha256:5ad93a8456f0d084c3456d059fd9a92cce667963232cbf763eac3bc5b7940872"}, 540 | ] 541 | atomicwrites = [ 542 | {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, 543 | {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, 544 | ] 545 | attrs = [ 546 | {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, 547 | {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, 548 | ] 549 | black = [ 550 | {file = "black-22.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2497f9c2386572e28921fa8bec7be3e51de6801f7459dffd6e62492531c47e09"}, 551 | {file = "black-22.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5795a0375eb87bfe902e80e0c8cfaedf8af4d49694d69161e5bd3206c18618bb"}, 552 | {file = "black-22.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e3556168e2e5c49629f7b0f377070240bd5511e45e25a4497bb0073d9dda776a"}, 553 | {file = "black-22.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67c8301ec94e3bcc8906740fe071391bce40a862b7be0b86fb5382beefecd968"}, 554 | {file = "black-22.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:fd57160949179ec517d32ac2ac898b5f20d68ed1a9c977346efbac9c2f1e779d"}, 555 | {file = "black-22.3.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cc1e1de68c8e5444e8f94c3670bb48a2beef0e91dddfd4fcc29595ebd90bb9ce"}, 556 | {file = "black-22.3.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d2fc92002d44746d3e7db7cf9313cf4452f43e9ea77a2c939defce3b10b5c82"}, 557 | {file = "black-22.3.0-cp36-cp36m-win_amd64.whl", hash = "sha256:a6342964b43a99dbc72f72812bf88cad8f0217ae9acb47c0d4f141a6416d2d7b"}, 558 | {file = "black-22.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:328efc0cc70ccb23429d6be184a15ce613f676bdfc85e5fe8ea2a9354b4e9015"}, 559 | {file = "black-22.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06f9d8846f2340dfac80ceb20200ea5d1b3f181dd0556b47af4e8e0b24fa0a6b"}, 560 | {file = "black-22.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4efa5fad66b903b4a5f96d91461d90b9507a812b3c5de657d544215bb7877a"}, 561 | {file = "black-22.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8477ec6bbfe0312c128e74644ac8a02ca06bcdb8982d4ee06f209be28cdf163"}, 562 | {file = "black-22.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:637a4014c63fbf42a692d22b55d8ad6968a946b4a6ebc385c5505d9625b6a464"}, 563 | {file = "black-22.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:863714200ada56cbc366dc9ae5291ceb936573155f8bf8e9de92aef51f3ad0f0"}, 564 | {file = "black-22.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10dbe6e6d2988049b4655b2b739f98785a884d4d6b85bc35133a8fb9a2233176"}, 565 | {file = "black-22.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:cee3e11161dde1b2a33a904b850b0899e0424cc331b7295f2a9698e79f9a69a0"}, 566 | {file = "black-22.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5891ef8abc06576985de8fa88e95ab70641de6c1fca97e2a15820a9b69e51b20"}, 567 | {file = "black-22.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:30d78ba6bf080eeaf0b7b875d924b15cd46fec5fd044ddfbad38c8ea9171043a"}, 568 | {file = "black-22.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ee8f1f7228cce7dffc2b464f07ce769f478968bfb3dd1254a4c2eeed84928aad"}, 569 | {file = "black-22.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ee227b696ca60dd1c507be80a6bc849a5a6ab57ac7352aad1ffec9e8b805f21"}, 570 | {file = "black-22.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:9b542ced1ec0ceeff5b37d69838106a6348e60db7b8fdd245294dc1d26136265"}, 571 | {file = "black-22.3.0-py3-none-any.whl", hash = "sha256:bc58025940a896d7e5356952228b68f793cf5fcb342be703c3a2669a1488cb72"}, 572 | {file = "black-22.3.0.tar.gz", hash = "sha256:35020b8886c022ced9282b51b5a875b6d1ab0c387b31a065b84db7c33085ca79"}, 573 | ] 574 | click = [ 575 | {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, 576 | {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, 577 | ] 578 | colorama = [ 579 | {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, 580 | {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, 581 | ] 582 | flake8 = [ 583 | {file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"}, 584 | {file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"}, 585 | ] 586 | ghp-import = [ 587 | {file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"}, 588 | {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"}, 589 | ] 590 | importlib-metadata = [ 591 | {file = "importlib_metadata-4.11.3-py3-none-any.whl", hash = "sha256:1208431ca90a8cca1a6b8af391bb53c1a2db74e5d1cef6ddced95d4b2062edc6"}, 592 | {file = "importlib_metadata-4.11.3.tar.gz", hash = "sha256:ea4c597ebf37142f827b8f39299579e31685c31d3a438b59f469406afd0f2539"}, 593 | ] 594 | iniconfig = [ 595 | {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, 596 | {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, 597 | ] 598 | isort = [ 599 | {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"}, 600 | {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"}, 601 | ] 602 | jinja2 = [ 603 | {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, 604 | {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, 605 | ] 606 | markdown = [ 607 | {file = "Markdown-3.3.7-py3-none-any.whl", hash = "sha256:f5da449a6e1c989a4cea2631aa8ee67caa5a2ef855d551c88f9e309f4634c621"}, 608 | {file = "Markdown-3.3.7.tar.gz", hash = "sha256:cbb516f16218e643d8e0a95b309f77eb118cb138d39a4f27851e6a63581db874"}, 609 | ] 610 | markupsafe = [ 611 | {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"}, 612 | {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"}, 613 | {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"}, 614 | {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"}, 615 | {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"}, 616 | {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"}, 617 | {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"}, 618 | {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"}, 619 | {file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"}, 620 | {file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"}, 621 | {file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"}, 622 | {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"}, 623 | {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"}, 624 | {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"}, 625 | {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"}, 626 | {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"}, 627 | {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"}, 628 | {file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"}, 629 | {file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"}, 630 | {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"}, 631 | {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"}, 632 | {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"}, 633 | {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"}, 634 | {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"}, 635 | {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"}, 636 | {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"}, 637 | {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"}, 638 | {file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"}, 639 | {file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"}, 640 | {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"}, 641 | {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"}, 642 | {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"}, 643 | {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"}, 644 | {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"}, 645 | {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"}, 646 | {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"}, 647 | {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"}, 648 | {file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"}, 649 | {file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"}, 650 | {file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"}, 651 | ] 652 | mccabe = [ 653 | {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, 654 | {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, 655 | ] 656 | mergedeep = [ 657 | {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"}, 658 | {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"}, 659 | ] 660 | mkdocs = [ 661 | {file = "mkdocs-1.3.0-py3-none-any.whl", hash = "sha256:26bd2b03d739ac57a3e6eed0b7bcc86168703b719c27b99ad6ca91dc439aacde"}, 662 | {file = "mkdocs-1.3.0.tar.gz", hash = "sha256:b504405b04da38795fec9b2e5e28f6aa3a73bb0960cb6d5d27ead28952bd35ea"}, 663 | ] 664 | mkdocs-autorefs = [ 665 | {file = "mkdocs-autorefs-0.4.1.tar.gz", hash = "sha256:70748a7bd025f9ecd6d6feeba8ba63f8e891a1af55f48e366d6d6e78493aba84"}, 666 | {file = "mkdocs_autorefs-0.4.1-py3-none-any.whl", hash = "sha256:a2248a9501b29dc0cc8ba4c09f4f47ff121945f6ce33d760f145d6f89d313f5b"}, 667 | ] 668 | mkdocs-material = [ 669 | {file = "mkdocs-material-8.2.13.tar.gz", hash = "sha256:505408fe001d668543236f5db5a88771460ad83ef7b58826630cc1f8b7e63099"}, 670 | {file = "mkdocs_material-8.2.13-py2.py3-none-any.whl", hash = "sha256:2666f1d7d6a8dc28dda1e777f77add12799e66bd00250de99914a33525763816"}, 671 | ] 672 | mkdocs-material-extensions = [ 673 | {file = "mkdocs-material-extensions-1.0.3.tar.gz", hash = "sha256:bfd24dfdef7b41c312ede42648f9eb83476ea168ec163b613f9abd12bbfddba2"}, 674 | {file = "mkdocs_material_extensions-1.0.3-py3-none-any.whl", hash = "sha256:a82b70e533ce060b2a5d9eb2bc2e1be201cf61f901f93704b4acf6e3d5983a44"}, 675 | ] 676 | mkdocstrings = [ 677 | {file = "mkdocstrings-0.18.1-py3-none-any.whl", hash = "sha256:4053929356df8cd69ed32eef71d8f676a472ef72980c9ffd4f933ead1debcdad"}, 678 | {file = "mkdocstrings-0.18.1.tar.gz", hash = "sha256:fb7c91ce7e3ab70488d3fa6c073a4f827cdc319042f682ef8ea95459790d64fc"}, 679 | ] 680 | mkdocstrings-python-legacy = [ 681 | {file = "mkdocstrings-python-legacy-0.2.2.tar.gz", hash = "sha256:f0e7ec6a19750581b752acb38f6b32fcd1efe006f14f6703125d2c2c9a5c6f02"}, 682 | {file = "mkdocstrings_python_legacy-0.2.2-py3-none-any.whl", hash = "sha256:379107a3a5b8db9b462efc4493c122efe21e825e3702425dbd404621302a563a"}, 683 | ] 684 | mypy = [ 685 | {file = "mypy-0.942-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5bf44840fb43ac4074636fd47ee476d73f0039f4f54e86d7265077dc199be24d"}, 686 | {file = "mypy-0.942-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dcd955f36e0180258a96f880348fbca54ce092b40fbb4b37372ae3b25a0b0a46"}, 687 | {file = "mypy-0.942-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6776e5fa22381cc761df53e7496a805801c1a751b27b99a9ff2f0ca848c7eca0"}, 688 | {file = "mypy-0.942-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:edf7237137a1a9330046dbb14796963d734dd740a98d5e144a3eb1d267f5f9ee"}, 689 | {file = "mypy-0.942-cp310-cp310-win_amd64.whl", hash = "sha256:64235137edc16bee6f095aba73be5334677d6f6bdb7fa03cfab90164fa294a17"}, 690 | {file = "mypy-0.942-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b840cfe89c4ab6386c40300689cd8645fc8d2d5f20101c7f8bd23d15fca14904"}, 691 | {file = "mypy-0.942-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2b184db8c618c43c3a31b32ff00cd28195d39e9c24e7c3b401f3db7f6e5767f5"}, 692 | {file = "mypy-0.942-cp36-cp36m-win_amd64.whl", hash = "sha256:1a0459c333f00e6a11cbf6b468b870c2b99a906cb72d6eadf3d1d95d38c9352c"}, 693 | {file = "mypy-0.942-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4c3e497588afccfa4334a9986b56f703e75793133c4be3a02d06a3df16b67a58"}, 694 | {file = "mypy-0.942-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6f6ad963172152e112b87cc7ec103ba0f2db2f1cd8997237827c052a3903eaa6"}, 695 | {file = "mypy-0.942-cp37-cp37m-win_amd64.whl", hash = "sha256:0e2dd88410937423fba18e57147dd07cd8381291b93d5b1984626f173a26543e"}, 696 | {file = "mypy-0.942-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:246e1aa127d5b78488a4a0594bd95f6d6fb9d63cf08a66dafbff8595d8891f67"}, 697 | {file = "mypy-0.942-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d8d3ba77e56b84cd47a8ee45b62c84b6d80d32383928fe2548c9a124ea0a725c"}, 698 | {file = "mypy-0.942-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2bc249409a7168d37c658e062e1ab5173300984a2dada2589638568ddc1db02b"}, 699 | {file = "mypy-0.942-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9521c1265ccaaa1791d2c13582f06facf815f426cd8b07c3a485f486a8ffc1f3"}, 700 | {file = "mypy-0.942-cp38-cp38-win_amd64.whl", hash = "sha256:e865fec858d75b78b4d63266c9aff770ecb6a39dfb6d6b56c47f7f8aba6baba8"}, 701 | {file = "mypy-0.942-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6ce34a118d1a898f47def970a2042b8af6bdcc01546454726c7dd2171aa6dfca"}, 702 | {file = "mypy-0.942-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:10daab80bc40f84e3f087d896cdb53dc811a9f04eae4b3f95779c26edee89d16"}, 703 | {file = "mypy-0.942-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3841b5433ff936bff2f4dc8d54cf2cdbfea5d8e88cedfac45c161368e5770ba6"}, 704 | {file = "mypy-0.942-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6f7106cbf9cc2f403693bf50ed7c9fa5bb3dfa9007b240db3c910929abe2a322"}, 705 | {file = "mypy-0.942-cp39-cp39-win_amd64.whl", hash = "sha256:7742d2c4e46bb5017b51c810283a6a389296cda03df805a4f7869a6f41246534"}, 706 | {file = "mypy-0.942-py3-none-any.whl", hash = "sha256:a1b383fe99678d7402754fe90448d4037f9512ce70c21f8aee3b8bf48ffc51db"}, 707 | {file = "mypy-0.942.tar.gz", hash = "sha256:17e44649fec92e9f82102b48a3bf7b4a5510ad0cd22fa21a104826b5db4903e2"}, 708 | ] 709 | mypy-extensions = [ 710 | {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, 711 | {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, 712 | ] 713 | oyaml = [ 714 | {file = "oyaml-1.0-py2.py3-none-any.whl", hash = "sha256:3a378747b7fb2425533d1ce41962d6921cda075d46bb480a158d45242d156323"}, 715 | {file = "oyaml-1.0.tar.gz", hash = "sha256:ed8fc096811f4763e1907dce29c35895d6d5936c4d0400fe843a91133d4744ed"}, 716 | ] 717 | packaging = [ 718 | {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, 719 | {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, 720 | ] 721 | pathspec = [ 722 | {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, 723 | {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, 724 | ] 725 | platformdirs = [ 726 | {file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"}, 727 | {file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"}, 728 | ] 729 | pluggy = [ 730 | {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, 731 | {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, 732 | ] 733 | py = [ 734 | {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, 735 | {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, 736 | ] 737 | pycodestyle = [ 738 | {file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"}, 739 | {file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"}, 740 | ] 741 | pyflakes = [ 742 | {file = "pyflakes-2.4.0-py2.py3-none-any.whl", hash = "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e"}, 743 | {file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"}, 744 | ] 745 | pygments = [ 746 | {file = "Pygments-2.12.0-py3-none-any.whl", hash = "sha256:dc9c10fb40944260f6ed4c688ece0cd2048414940f1cea51b8b226318411c519"}, 747 | {file = "Pygments-2.12.0.tar.gz", hash = "sha256:5eb116118f9612ff1ee89ac96437bb6b49e8f04d8a13b514ba26f620208e26eb"}, 748 | ] 749 | pymdown-extensions = [ 750 | {file = "pymdown_extensions-9.4-py3-none-any.whl", hash = "sha256:5b7432456bf555ce2b0ab3c2439401084cda8110f24f6b3ecef952b8313dfa1b"}, 751 | {file = "pymdown_extensions-9.4.tar.gz", hash = "sha256:1baa22a60550f731630474cad28feb0405c8101f1a7ddc3ec0ed86ee510bcc43"}, 752 | ] 753 | pyparsing = [ 754 | {file = "pyparsing-3.0.8-py3-none-any.whl", hash = "sha256:ef7b523f6356f763771559412c0d7134753f037822dad1b16945b7b846f7ad06"}, 755 | {file = "pyparsing-3.0.8.tar.gz", hash = "sha256:7bf433498c016c4314268d95df76c81b842a4cb2b276fa3312cfb1e1d85f6954"}, 756 | ] 757 | pytest = [ 758 | {file = "pytest-7.1.2-py3-none-any.whl", hash = "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c"}, 759 | {file = "pytest-7.1.2.tar.gz", hash = "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45"}, 760 | ] 761 | python-dateutil = [ 762 | {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, 763 | {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, 764 | ] 765 | pytkdocs = [ 766 | {file = "pytkdocs-0.16.1-py3-none-any.whl", hash = "sha256:a8c3f46ecef0b92864cc598e9101e9c4cf832ebbf228f50c84aa5dd850aac379"}, 767 | {file = "pytkdocs-0.16.1.tar.gz", hash = "sha256:e2ccf6dfe9dbbceb09818673f040f1a7c32ed0bffb2d709b06be6453c4026045"}, 768 | ] 769 | pyyaml = [ 770 | {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, 771 | {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, 772 | {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, 773 | {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, 774 | {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, 775 | {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, 776 | {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, 777 | {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, 778 | {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, 779 | {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, 780 | {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, 781 | {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, 782 | {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, 783 | {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, 784 | {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, 785 | {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, 786 | {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, 787 | {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, 788 | {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, 789 | {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, 790 | {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, 791 | {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, 792 | {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, 793 | {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, 794 | {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, 795 | {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, 796 | {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, 797 | {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, 798 | {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, 799 | {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, 800 | {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, 801 | {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, 802 | {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, 803 | ] 804 | pyyaml-env-tag = [ 805 | {file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"}, 806 | {file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"}, 807 | ] 808 | six = [ 809 | {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, 810 | {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, 811 | ] 812 | tomli = [ 813 | {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, 814 | {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, 815 | ] 816 | typing-extensions = [ 817 | {file = "typing_extensions-4.2.0-py3-none-any.whl", hash = "sha256:6657594ee297170d19f67d55c05852a874e7eb634f4f753dbd667855e07c1708"}, 818 | {file = "typing_extensions-4.2.0.tar.gz", hash = "sha256:f1c24655a0da0d1b67f07e17a5e6b2a105894e6824b92096378bb3668ef02376"}, 819 | ] 820 | watchdog = [ 821 | {file = "watchdog-2.1.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:177bae28ca723bc00846466016d34f8c1d6a621383b6caca86745918d55c7383"}, 822 | {file = "watchdog-2.1.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1d1cf7dfd747dec519486a98ef16097e6c480934ef115b16f18adb341df747a4"}, 823 | {file = "watchdog-2.1.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7f14ce6adea2af1bba495acdde0e510aecaeb13b33f7bd2f6324e551b26688ca"}, 824 | {file = "watchdog-2.1.7-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:4d0e98ac2e8dd803a56f4e10438b33a2d40390a72750cff4939b4b274e7906fa"}, 825 | {file = "watchdog-2.1.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:81982c7884aac75017a6ecc72f1a4fedbae04181a8665a34afce9539fc1b3fab"}, 826 | {file = "watchdog-2.1.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0b4a1fe6201c6e5a1926f5767b8664b45f0fcb429b62564a41f490ff1ce1dc7a"}, 827 | {file = "watchdog-2.1.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6e6ae29b72977f2e1ee3d0b760d7ee47896cb53e831cbeede3e64485e5633cc8"}, 828 | {file = "watchdog-2.1.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b9777664848160449e5b4260e0b7bc1ae0f6f4992a8b285db4ec1ef119ffa0e2"}, 829 | {file = "watchdog-2.1.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:19b36d436578eb437e029c6b838e732ed08054956366f6dd11875434a62d2b99"}, 830 | {file = "watchdog-2.1.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b61acffaf5cd5d664af555c0850f9747cc5f2baf71e54bbac164c58398d6ca7b"}, 831 | {file = "watchdog-2.1.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1e877c70245424b06c41ac258023ea4bd0c8e4ff15d7c1368f17cd0ae6e351dd"}, 832 | {file = "watchdog-2.1.7-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d802d65262a560278cf1a65ef7cae4e2bc7ecfe19e5451349e4c67e23c9dc420"}, 833 | {file = "watchdog-2.1.7-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b3750ee5399e6e9c69eae8b125092b871ee9e2fcbd657a92747aea28f9056a5c"}, 834 | {file = "watchdog-2.1.7-py3-none-manylinux2014_aarch64.whl", hash = "sha256:ed6d9aad09a2a948572224663ab00f8975fae242aa540509737bb4507133fa2d"}, 835 | {file = "watchdog-2.1.7-py3-none-manylinux2014_armv7l.whl", hash = "sha256:b26e13e8008dcaea6a909e91d39b629a39635d1a8a7239dd35327c74f4388601"}, 836 | {file = "watchdog-2.1.7-py3-none-manylinux2014_i686.whl", hash = "sha256:0908bb50f6f7de54d5d31ec3da1654cb7287c6b87bce371954561e6de379d690"}, 837 | {file = "watchdog-2.1.7-py3-none-manylinux2014_ppc64.whl", hash = "sha256:bdcbf75580bf4b960fb659bbccd00123d83119619195f42d721e002c1621602f"}, 838 | {file = "watchdog-2.1.7-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:81a5861d0158a7e55fe149335fb2bbfa6f48cbcbd149b52dbe2cd9a544034bbd"}, 839 | {file = "watchdog-2.1.7-py3-none-manylinux2014_s390x.whl", hash = "sha256:03b43d583df0f18782a0431b6e9e9965c5b3f7cf8ec36a00b930def67942c385"}, 840 | {file = "watchdog-2.1.7-py3-none-manylinux2014_x86_64.whl", hash = "sha256:ae934e34c11aa8296c18f70bf66ed60e9870fcdb4cc19129a04ca83ab23e7055"}, 841 | {file = "watchdog-2.1.7-py3-none-win32.whl", hash = "sha256:49639865e3db4be032a96695c98ac09eed39bbb43fe876bb217da8f8101689a6"}, 842 | {file = "watchdog-2.1.7-py3-none-win_amd64.whl", hash = "sha256:340b875aecf4b0e6672076a6f05cfce6686935559bb6d34cebedee04126a9566"}, 843 | {file = "watchdog-2.1.7-py3-none-win_ia64.whl", hash = "sha256:351e09b6d9374d5bcb947e6ac47a608ec25b9d70583e9db00b2fcdb97b00b572"}, 844 | {file = "watchdog-2.1.7.tar.gz", hash = "sha256:3fd47815353be9c44eebc94cc28fe26b2b0c5bd889dafc4a5a7cbdf924143480"}, 845 | ] 846 | zipp = [ 847 | {file = "zipp-3.8.0-py3-none-any.whl", hash = "sha256:c4f6e5bbf48e74f7a38e7cc5b0480ff42b0ae5178957d564d18932525d5cf099"}, 848 | {file = "zipp-3.8.0.tar.gz", hash = "sha256:56bf8aadb83c24db6c4b577e13de374ccfb67da2078beba1d037c17980bf43ad"}, 849 | ] 850 | -------------------------------------------------------------------------------- /poetry.toml: -------------------------------------------------------------------------------- 1 | [virtualenvs] 2 | in-project = true 3 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "simple_homepage" 3 | version = "0.0.5" 4 | description = "Command line utility that helps you create a simple static homepage for your browser" 5 | authors = ["Florian Maas "] 6 | repository = "https://github.com/fpgmaas/simple-homepage" 7 | documentation = "https://fpgmaas.github.io/simple-homepage/" 8 | readme = "README.md" 9 | packages = [ 10 | {include = "simple_homepage"} 11 | ] 12 | include = ['simple_homepage/files/*'] 13 | 14 | [tool.poetry.dependencies] 15 | python = ">=3.8,<3.11" 16 | Jinja2 = "^3.1.2" 17 | PyYAML = "^6.0" 18 | oyaml = "^1.0" 19 | 20 | 21 | [tool.poetry.dev-dependencies] 22 | black = "^22.3.0" 23 | isort = "^5.10.1" 24 | flake8 = "^4.0.1" 25 | pytest = "^7.1.1" 26 | mkdocs = "^1.3.0" 27 | mkdocs-material = "^8.2.9" 28 | mkdocstrings = "^0.18.1" 29 | mypy = "^0.942" 30 | 31 | [build-system] 32 | requires = ["poetry-core>=1.0.0"] 33 | build-backend = "poetry.core.masonry.api" 34 | 35 | [tool.black] 36 | line-length = 120 37 | include = '\.pyi?$' 38 | target-version = ['py39'] 39 | fast = true 40 | exclude = ''' 41 | ( 42 | /( # exclude a few common directories in the 43 | \.git # root of the project 44 | | \.pytest_cache 45 | | python-venv 46 | | \.venv 47 | | build 48 | | dist 49 | | \.tox 50 | )) 51 | ''' 52 | 53 | [tool.isort] 54 | profile = "black" 55 | 56 | [tool.mypy] 57 | disallow_untyped_defs = "True" 58 | disallow_any_unimported = "True" 59 | no_implicit_optional = "True" 60 | check_untyped_defs = "True" 61 | warn_return_any = "True" 62 | warn_unused_ignores = "True" 63 | show_error_codes = "True" 64 | exclude = [ 65 | '\.venv', 66 | 'tests' 67 | ] 68 | 69 | [tool.poetry.scripts] 70 | homepage = "simple_homepage.cli:cli" 71 | -------------------------------------------------------------------------------- /simple_homepage/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fpgmaas/simple-homepage/5017d870cd7dcdca6486b64747414338d1c4e40e/simple_homepage/__init__.py -------------------------------------------------------------------------------- /simple_homepage/cli.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import logging 3 | import sys 4 | 5 | from simple_homepage.directory_builder import DirectoryBuilder 6 | from simple_homepage.homepage_generator import HomepageGenerator 7 | 8 | logging.basicConfig(level=logging.INFO, handlers=[logging.StreamHandler()], format="%(message)s") 9 | 10 | 11 | def cli() -> None: 12 | """ 13 | Wrapper so commands do not not return the object. 14 | """ 15 | CommandLineInterface() 16 | 17 | 18 | class CommandLineInterface: 19 | def __init__(self) -> None: 20 | parser = argparse.ArgumentParser( 21 | description="Generate a simple homepage", 22 | usage="""homepage [] 23 | 24 | The available commands: 25 | init Initialize the file structure for the homepage 26 | build Build the homepage from template files and settings.yaml 27 | """, 28 | ) 29 | 30 | parser.add_argument("command", help="Subcommand to run") 31 | args = parser.parse_args(sys.argv[1:2]) 32 | if not hasattr(self, args.command): 33 | print("Unrecognized command") 34 | parser.print_help() 35 | exit(1) 36 | # use dispatch pattern to invoke method with same name 37 | getattr(self, args.command)() 38 | 39 | def init(self) -> None: 40 | parser = argparse.ArgumentParser(description="Initialize the file structure for the homepage") 41 | parser.add_argument( 42 | "--dark", 43 | dest="dark", 44 | action="store_true", 45 | help="""Optional. By default the template is initialized in light mode. 46 | Add this flag to initialize the page in dark mode.""", 47 | ) 48 | parser.add_argument( 49 | "--dir", 50 | dest="dir", 51 | type=str, 52 | help="""Optional. Name of a directory (relative to the current directory) to create and place the templates in. 53 | If not specified, the template files will be placed in the current directory. """, 54 | ) 55 | parser.add_argument( 56 | "--overwrite", 57 | dest="overwrite", 58 | action="store_true", 59 | help="""Optional. Ignore errors and overwrite existing files if they are found.""", 60 | ) 61 | args = parser.parse_args(sys.argv[2:]) 62 | dict_args = vars(args) 63 | try: 64 | DirectoryBuilder( 65 | dark=dict_args["dark"], directory=dict_args["dir"], overwrite=dict_args["overwrite"] 66 | ).build() 67 | except (FileExistsError) as e: 68 | logging.info(e) 69 | logging.info( 70 | "Run the command with the `--overwrite` flag to ignore these errors and overwrite existing files." 71 | ) 72 | 73 | def build(self) -> None: 74 | parser = argparse.ArgumentParser( 75 | description="Build the homepage from template files and settings.yaml" 76 | ) # noqa: F841 77 | parser.add_argument( 78 | "--output-dir", 79 | dest="dir", 80 | default="public", 81 | type=str, 82 | help="""Optional. Name of a directory (relative to the current directory) to create and place the resulting page in. 83 | Defaults to `public`. """, 84 | ) 85 | parser.add_argument( 86 | "--output-file", 87 | dest="file", 88 | default="homepage.html", 89 | type=str, 90 | help="""Optional. Name of the resulting html file. Defaults to homepage.html.""", 91 | ) 92 | args = parser.parse_args(sys.argv[2:]) 93 | dict_args = vars(args) 94 | HomepageGenerator(output_dir=dict_args["dir"], output_file=dict_args["file"]).build() 95 | -------------------------------------------------------------------------------- /simple_homepage/directory_builder.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | import shutil 4 | from pathlib import Path 5 | from typing import Union 6 | 7 | import oyaml # type: ignore 8 | import pkg_resources # type: ignore 9 | 10 | 11 | class DirectoryBuilder: 12 | """ 13 | Class to populate the directory with the template files required to build a custom homepage. 14 | """ 15 | 16 | def __init__(self, dark: bool = False, directory: Union[str, None] = None, overwrite: bool = False) -> None: 17 | self.dark = dark 18 | self.directory = directory 19 | self.directory_path = Path(os.getcwd()) / directory if directory else Path(os.getcwd()) 20 | self.overwrite = overwrite 21 | 22 | def build(self) -> None: 23 | 24 | if self.directory: 25 | self._create_directory() 26 | if not self.overwrite: 27 | self._verify_that_template_files_dont_exist() 28 | self._copy_files_from_package() 29 | self._rename_placeholder_images() 30 | if self.dark: 31 | self._change_colors_in_settings_to_dark_mode() 32 | 33 | logging.info("Template files and settings.yaml created.") 34 | 35 | def _create_directory(self) -> None: 36 | if not os.path.exists(self.directory): # type: ignore 37 | os.mkdir(self.directory) # type: ignore 38 | else: 39 | if not self.overwrite: 40 | raise OSError(f"A folder '{self.directory}' already exists within the current directory!") 41 | 42 | def _verify_that_template_files_dont_exist(self) -> None: 43 | if os.path.isdir(self.directory_path / "template") or os.path.isfile(self.directory_path / "settings.yaml"): 44 | raise FileExistsError("The directory already contains template files.") 45 | 46 | def _copy_files_from_package(self) -> None: 47 | """ 48 | Copy the files in the 'files' directory from the package contents to the local client. 49 | """ 50 | try: 51 | files_location = pkg_resources.resource_filename(__name__, "files") 52 | shutil.copytree(files_location, str(self.directory_path), dirs_exist_ok=True) 53 | finally: 54 | pkg_resources.cleanup_resources() 55 | 56 | def _rename_placeholder_images(self) -> None: 57 | """ 58 | Keep the correct placeholder image based on if dark mode is selected, and remove the other one. 59 | """ 60 | image_dir_path = self.directory_path / "template" / "static" / "images" 61 | if self.dark: 62 | os.rename(image_dir_path / "placeholder_white.png", image_dir_path / "placeholder.png") 63 | os.remove(image_dir_path / "placeholder_black.png") 64 | else: 65 | os.rename(image_dir_path / "placeholder_black.png", image_dir_path / "placeholder.png") 66 | os.remove(image_dir_path / "placeholder_white.png") 67 | 68 | def _change_colors_in_settings_to_dark_mode(self) -> None: 69 | 70 | with open(self.directory_path / "settings.yaml") as f: 71 | settings = oyaml.safe_load(f) 72 | 73 | settings["colors"]["background"] = "#26263C" 74 | settings["colors"]["text"] = "#dedeee" 75 | settings["colors"]["placeholder"] = "#dedeee" 76 | settings["colors"]["header"] = "white" 77 | settings["colors"]["accent"] = "orange" 78 | 79 | with open(self.directory_path / "settings.yaml", "w") as f: 80 | oyaml.dump(settings, f) 81 | -------------------------------------------------------------------------------- /simple_homepage/files/settings.yaml: -------------------------------------------------------------------------------- 1 | page_title: Flo's Homepage 2 | search_text: Hi Flo! Looking for something? 3 | colors: 4 | background: 'white' 5 | text: '#555555' 6 | header: '#111111' 7 | placeholder: '#999999' 8 | accent: 'orange' 9 | urls: 10 | Personal: 11 | Gmail: https://mail.google.com/ 12 | Calendar: https://calendar.google.com/ 13 | Productivity: 14 | GitHub: https://github.com 15 | StackOverflow: https://Stackoverflow.com 16 | Google Cloud: https://cloud.google.com/ 17 | Entertainment: 18 | Youtube: https://youtube.com 19 | Reddit: https://reddit.com 20 | Netflix: https://netflix.com 21 | 22 | -------------------------------------------------------------------------------- /simple_homepage/files/template/_homepage.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | {{settings.page_title}} 11 | 12 | 13 | 14 |
15 |
16 | 17 |
18 |
20 | 22 |
23 | 35 |
36 | 37 | 38 | -------------------------------------------------------------------------------- /simple_homepage/files/template/static/_stylesheet.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --background-color: {{settings.colors.background}}; 3 | --text-color: {{settings.colors.text}}; 4 | --placeholder-color: {{settings.colors.placeholder}}; 5 | --header-color: {{settings.colors.header}}; 6 | --accent-color: {{settings.colors.accent}}; 7 | } 8 | 9 | body { 10 | min-height: 100vh; 11 | min-width: 100vw; 12 | display: flex; 13 | justify-content: center; 14 | align-items: center; 15 | font-family: 'Pangolin', cursive; 16 | color: var(--header-color); 17 | letter-spacing: 1px; 18 | margin: 0; 19 | padding: 0; 20 | background-color: var(--background-color); 21 | } 22 | 23 | 24 | h2 { 25 | font-size: 1.1rem; 26 | margin: 0 0 .5rem 0; 27 | letter-spacing: 2px; 28 | } 29 | 30 | .center-container { 31 | width: 90%; 32 | max-width: 1000px; 33 | display: flex; 34 | flex-direction: column; 35 | justify-content: center; 36 | align-items: center; 37 | } 38 | 39 | .image-container { 40 | width: 80%; 41 | max-width: 350px; 42 | margin-bottom: 3rem; 43 | } 44 | 45 | .image { 46 | width: 100%; 47 | border-radius: 50%; 48 | aspect-ratio: 1; 49 | object-fit: cover; 50 | -moz-user-select: none; 51 | -webkit-user-select: none; 52 | user-select: none; 53 | } 54 | 55 | #search-form { 56 | width: 100%; 57 | max-width: 350px; 58 | margin-bottom: 2rem; 59 | } 60 | 61 | #search-input { 62 | width: 100%; 63 | border: 0; 64 | font-size: .9rem; 65 | border-bottom: 1px solid grey; 66 | text-align: center; 67 | font-family: 'Pangolin', cursive; 68 | outline: none; 69 | background-color: var(--background-color); 70 | color: var(--text-color); 71 | } 72 | 73 | #search-input::placeholder { 74 | color: var(--placeholder-color); 75 | } 76 | 77 | .links-container { 78 | width: 100%; 79 | display: flex; 80 | flex-direction: row; 81 | justify-content: center; 82 | align-items: top; 83 | flex-wrap: wrap; 84 | } 85 | 86 | .links-group { 87 | display: flex; 88 | flex-direction: column; 89 | justify-content: top; 90 | align-items: center; 91 | flex: 1 1 0px; 92 | margin: 1em; 93 | max-width: 180px; 94 | } 95 | 96 | .links-list { 97 | text-align: center; 98 | list-style-type: none; /* Remove bullets */ 99 | padding: 0; /* Remove padding */ 100 | margin: 0; /* Remove margins */ 101 | } 102 | 103 | .links-list a { 104 | text-decoration: none; 105 | color: var(--text-color); 106 | transition: ease all .3s; 107 | } 108 | 109 | .links-list a:hover{ 110 | color: var(--accent-color); 111 | } 112 | 113 | .links-list li { 114 | margin: 0.2rem 0; 115 | } 116 | -------------------------------------------------------------------------------- /simple_homepage/files/template/static/favicon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /simple_homepage/files/template/static/homepage.js: -------------------------------------------------------------------------------- 1 | window.onload = function(){ 2 | choosePic(); 3 | } 4 | 5 | function choosePic() { 6 | var randomNum = Math.floor(Math.random() * images.length); 7 | document.getElementById("center-image").src = images[randomNum]; 8 | } 9 | -------------------------------------------------------------------------------- /simple_homepage/files/template/static/images/placeholder_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fpgmaas/simple-homepage/5017d870cd7dcdca6486b64747414338d1c4e40e/simple_homepage/files/template/static/images/placeholder_black.png -------------------------------------------------------------------------------- /simple_homepage/files/template/static/images/placeholder_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fpgmaas/simple-homepage/5017d870cd7dcdca6486b64747414338d1c4e40e/simple_homepage/files/template/static/images/placeholder_white.png -------------------------------------------------------------------------------- /simple_homepage/homepage_generator.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | import shutil 4 | 5 | import yaml # type: ignore 6 | from jinja2 import Environment, FileSystemLoader 7 | 8 | 9 | class HomepageGenerator: 10 | """ 11 | Class to generate a custom homepage from template files. The template files can be created 12 | with the `DirectoryBuilder` class. 13 | """ 14 | 15 | def __init__( 16 | self, 17 | template_dir: str = "template", 18 | settings_yaml: "str" = "settings.yaml", 19 | output_dir: str = "public", 20 | output_file: str = "index.html", 21 | ) -> None: 22 | self.settings_yaml = settings_yaml 23 | self.output_dir = output_dir 24 | self.output_file = output_file 25 | self.template_dir = template_dir 26 | self.env = Environment(loader=FileSystemLoader(template_dir)) 27 | 28 | def build(self) -> None: 29 | self._verify_required_files_exist() 30 | self._clear_output_dir_if_exists() 31 | self._create_output_dir() 32 | self._copy_static_dir() 33 | self._render_page() 34 | self._add_images_list_to_js() 35 | logging.info(f"Page built. The resulting homepage can be found at {self.output_dir}/{self.output_file}") 36 | 37 | def _verify_required_files_exist(self) -> None: 38 | expected_files = [ 39 | self.settings_yaml, 40 | f"{self.template_dir}/_homepage.html", 41 | f"{self.template_dir}/static/_stylesheet.css", 42 | f"{self.template_dir}/static/homepage.js", 43 | ] 44 | files_found = [os.path.exists(file) for file in expected_files] 45 | if not all(files_found): 46 | raise ValueError( 47 | "Not all required template files were found! Did you initialize the directory with `homepage init` first?" 48 | ) 49 | 50 | def _clear_output_dir_if_exists(self) -> None: 51 | if os.path.isdir(self.output_dir): 52 | try: 53 | shutil.rmtree(self.output_dir) 54 | except Exception as e: 55 | logging.info(e) 56 | raise ValueError(f"Error encountered while cleaning {self.output_dir} directory.") 57 | 58 | def _create_output_dir(self) -> None: 59 | try: 60 | os.mkdir(self.output_dir) 61 | except Exception as e: 62 | logging.info(e) 63 | raise ValueError(f"Encountered an error while trying to create the '{self.output_dir}' directory") 64 | 65 | def _copy_static_dir(self) -> None: 66 | """ 67 | Copy all files from the static folder in the template to the public folder, except _stylesheet.css, 68 | since stylesheet.css will be built using Jinja2 in the _render_page method. 69 | """ 70 | try: 71 | shutil.copytree(f"{self.template_dir}/static", f"{self.output_dir}/static") 72 | os.remove(f"{self.output_dir}/static/_stylesheet.css") 73 | except Exception as e: 74 | logging.info(e) 75 | raise ValueError("Encountered an error while copying static files.") 76 | 77 | def _render_page(self) -> None: 78 | """ 79 | Render the Jinja2 templates _homepage.html and _stylesheet.css into self.output_file and stylesheet.css. 80 | """ 81 | 82 | with open(self.settings_yaml, "rt") as f: 83 | settings = yaml.safe_load(f.read()) 84 | 85 | template = self.env.get_template("_homepage.html") 86 | with open(f"{self.output_dir}/{self.output_file}", "w+") as file: 87 | html = template.render(settings=settings) 88 | file.write(html) 89 | 90 | template = self.env.get_template("static/_stylesheet.css") 91 | with open(f"{self.output_dir}/static/stylesheet.css", "w+") as file: 92 | html = template.render(settings=settings) 93 | file.write(html) 94 | 95 | def _add_images_list_to_js(self) -> None: 96 | """ 97 | Scan for any images and add this as a list to the homepage.js files, so JavaScript can randomly pick 98 | one to display when the page is opened. 99 | """ 100 | images = [f'"static/images/{image}"' for image in os.listdir(f"{self.output_dir}/static/images")] 101 | 102 | with open(f"{self.output_dir}/static/homepage.js", "r+") as f: 103 | content = f.read() 104 | f.seek(0, 0) 105 | line = f'var images = new Array({",".join(images)});' 106 | f.write(line.rstrip("\r\n") + "\n" + content) 107 | -------------------------------------------------------------------------------- /static/screenshot-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fpgmaas/simple-homepage/5017d870cd7dcdca6486b64747414338d1c4e40e/static/screenshot-dark.png -------------------------------------------------------------------------------- /static/screenshot-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fpgmaas/simple-homepage/5017d870cd7dcdca6486b64747414338d1c4e40e/static/screenshot-light.png -------------------------------------------------------------------------------- /tests/test_simple_homepage.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shlex 3 | import subprocess 4 | from contextlib import contextmanager 5 | 6 | 7 | @contextmanager 8 | def run_within_dir(path: str): 9 | oldpwd = os.getcwd() 10 | os.chdir(path) 11 | try: 12 | yield 13 | finally: 14 | os.chdir(oldpwd) 15 | 16 | 17 | def file_contains_text(file: str, text: str) -> bool: 18 | return open(file, "r").read().find(text) != -1 19 | 20 | 21 | def test_init_command(tmp_path): 22 | with run_within_dir(tmp_path): 23 | 24 | subprocess.check_call(shlex.split("homepage init")) == 0 25 | expected_files = [ 26 | "settings.yaml", 27 | "template/_homepage.html", 28 | "template/static/_stylesheet.css", 29 | "template/static/homepage.js", 30 | "template/static/images/placeholder.png", 31 | ] 32 | for file in expected_files: 33 | assert os.path.isfile(file) 34 | 35 | 36 | def test_init_command_within_dir(tmp_path): 37 | with run_within_dir(tmp_path): 38 | subprocess.check_call(shlex.split("homepage init --dir test-dir")) == 0 39 | expected_files = [ 40 | "test-dir/settings.yaml", 41 | "test-dir/template/_homepage.html", 42 | "test-dir/template/static/_stylesheet.css", 43 | "test-dir/template/static/homepage.js", 44 | "test-dir/template/static/images/placeholder.png", 45 | ] 46 | for file in expected_files: 47 | assert os.path.isfile(file) 48 | 49 | 50 | def test_init_command_dark_mode(tmp_path): 51 | with run_within_dir(tmp_path): 52 | subprocess.check_call(shlex.split("homepage init --dark")) == 0 53 | assert file_contains_text("settings.yaml", "#26263C") 54 | 55 | 56 | def test_build_command(tmp_path): 57 | with run_within_dir(tmp_path): 58 | subprocess.check_call(shlex.split("homepage init")) == 0 59 | subprocess.check_call(shlex.split("homepage build")) == 0 60 | expected_files = [ 61 | "settings.yaml", 62 | "public/homepage.html", 63 | "public/static/stylesheet.css", 64 | "public/static/homepage.js", 65 | "public/static/images/placeholder.png", 66 | ] 67 | for file in expected_files: 68 | assert os.path.isfile(file) 69 | 70 | 71 | def test_build_command_arguments(tmp_path): 72 | with run_within_dir(tmp_path): 73 | subprocess.check_call(shlex.split("homepage init")) == 0 74 | subprocess.check_call(shlex.split("homepage build --output-dir test --output-file test.html")) == 0 75 | expected_files = [ 76 | "settings.yaml", 77 | "test/test.html", 78 | "test/static/stylesheet.css", 79 | "test/static/homepage.js", 80 | "test/static/images/placeholder.png", 81 | ] 82 | for file in expected_files: 83 | assert os.path.isfile(file) 84 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [flake8] 2 | per-file-ignores = 3 | __init__.py:F401 4 | simple_homepage/cli.py:F841 5 | # PEP-8 The following are ignored: 6 | # E731 do not assign a lambda expression, use a def 7 | # E203 whitespace before ':' 8 | # E501 line too long 9 | # W503 line break before binary operator 10 | # W605 invalid escape sequence 11 | ignore = E731, E203, E501, W503, W605 12 | exclude = 13 | .git, 14 | __pycache__, 15 | docs/source/conf.py, 16 | old, 17 | build, 18 | dist, 19 | .venv, 20 | max-complexity = 10 21 | max-line-length = 120 22 | 23 | [tox] 24 | skipsdist = true 25 | envlist = py38, py39, py310 26 | 27 | [gh-actions] 28 | python = 29 | 3.8: py38 30 | 3.9: py39 31 | 3.10: py310 32 | 33 | [testenv] 34 | passenv = PYTHON_VERSION 35 | whitelist_externals = poetry 36 | commands = 37 | poetry install -v 38 | pytest --doctest-modules tests 39 | --------------------------------------------------------------------------------