├── .github └── workflows │ ├── circular_release.yml │ └── docs.yml ├── .gitignore ├── .vscode ├── launch.json └── settings.json ├── LICENSE ├── README.rst ├── data ├── 20221024_pypi_data.json ├── 20221025_awesome.json └── 20221025_pypi_gh_data.json ├── docs ├── Makefile ├── _awesome_templates │ ├── analysis.rst │ └── data_import.rst ├── _static │ ├── custom.css │ └── custom.js ├── _templates │ ├── layout.html │ └── partials │ │ └── footer.html ├── additions.rst ├── analysis │ ├── active.rst │ ├── famous.rst │ └── healthy.rst ├── categories │ ├── extensions.rst │ ├── others.rst │ └── themes.rst ├── conf.py ├── contribute.rst ├── index.rst ├── license.rst ├── make.bat ├── needs_templates │ └── software.need ├── rating.rst ├── requirements.txt └── tags.rst ├── projects.py └── scripts ├── awesome_config.py ├── github_stats.py ├── needs_json.py ├── pypi_json.py └── requirements.txt /.github/workflows/circular_release.yml: -------------------------------------------------------------------------------- 1 | # From: https://github.com/rkdarst/sphinx-actions-test/blob/master/.github/workflows/sphinx-build.yml 2 | 3 | name: Collect_Build 4 | on: 5 | workflow_dispatch: 6 | inputs: 7 | # amount: 8 | # description: 'Amount of projects to fetch. For production this should be 1000 or higher.' 9 | # required: true 10 | # type: number 11 | # default: 1000 12 | deploy: 13 | description: 'Docs shall be deployed' 14 | required: true 15 | type: boolean 16 | default: false 17 | schedule: 18 | # * is a special character in YAML so you have to quote this string 19 | - cron: '0 6 * * 1' # Every Monday at 6 20 | 21 | env: 22 | DEFAULT_BRANCH: "main" 23 | SPHINXOPTS: "-W --keep-going -T" 24 | # ^-- If these SPHINXOPTS are enabled, then be strict about the builds and fail on any warnings 25 | 26 | jobs: 27 | build-and-deploy_docs: 28 | name: Collect_and_Docs 29 | runs-on: ubuntu-latest 30 | permissions: 31 | contents: write 32 | id-token: 'write' 33 | env: 34 | ON_CI: True 35 | GH_KEY: ${{ secrets.GITHUB_TOKEN }} 36 | AWESOMESPHINX_NEEDS_FILE: "/../awesome.json" 37 | AWESOMESPHINX_AMOUNT: 1000 38 | steps: 39 | - uses: actions/checkout@v3 40 | with: 41 | fetch-depth: 0 42 | - name: Install Python 43 | uses: actions/setup-python@v4 44 | with: 45 | python-version: '3.9' 46 | - name: Install deps 47 | run: | 48 | pip install -r docs/requirements.txt 49 | pip install -r scripts/requirements.txt 50 | - name: Debugging information 51 | run: | 52 | echo "github.ref:" ${{github.ref}} 53 | echo "github.event_name:" ${{github.event_name}} 54 | echo "github.head_ref:" ${{github.head_ref}} 55 | echo "github.base_ref:" ${{github.base_ref}} 56 | set -x 57 | git rev-parse --abbrev-ref HEAD 58 | git branch 59 | git branch -a 60 | git remote -v 61 | python -V 62 | pip list --not-required 63 | pip list 64 | echo "ON_CI = $ON_CI" 65 | # Google auth 66 | - uses: 'google-github-actions/auth@v0' 67 | with: 68 | service_account: '${{secrets.SERVICE_ACCOUNT_EMAIL}}' 69 | workload_identity_provider: 'projects/${{secrets.PROJECT_ID}}/locations/global/workloadIdentityPools/${{secrets.WI_POOL_NAME}}/providers/${{secrets.WI_PROVIDER_NAME}}' 70 | # build 71 | - name: Collect PyPI data 72 | run: python scripts/pypi_json.py 73 | - name: Collect GitHub data 74 | id: gh_data_collect 75 | run: python scripts/github_stats.py 76 | - name: Create Sphinx-Needs file 77 | run: python scripts/needs_json.py 78 | 79 | - uses: ammaraskar/sphinx-problem-matcher@master 80 | - name: Build Sphinx docs 81 | run: | 82 | cd docs 83 | sphinx-build -a -E -b html . _build/html 84 | 85 | - name: Archive docs 86 | uses: actions/upload-artifact@v3 87 | with: 88 | name: awesomeSphinx docs 89 | path: docs/_build/html 90 | # the following steps are only done after pushing to main or merging a PR 91 | # clone and set up the old gh-pages branch 92 | - name: Clone old gh-pages 93 | if: ${{ github.event_name == 'schedule' && github.ref_name == 'main' || github.event.inputs.deploy == 'true' }} 94 | run: | 95 | set -x 96 | git fetch 97 | ( git branch gh-pages remotes/origin/gh-pages && git clone . --branch=gh-pages _gh-pages/ ) || mkdir _gh-pages 98 | rm -rf _gh-pages/.git/ 99 | rm -rf _gh-pages/.doctrees 100 | mkdir -p _gh-pages/ 101 | # if a push and default branch, copy build to _gh-pages/ as the "main" 102 | # deployment. 103 | - name: Copy docs build 104 | if: ${{ github.event_name == 'schedule' && github.ref_name == 'main' || github.event.inputs.deploy == 'true' }} 105 | run: | 106 | set -x 107 | rsync -a docs/_build/html/ _gh-pages/ 108 | 109 | # add the .nojekyll file 110 | - name: nojekyll 111 | if: ${{ github.event_name == 'schedule' && github.ref_name == 'main' || github.event.inputs.deploy == 'true' }} 112 | run: | 113 | touch _gh-pages/.nojekyll 114 | 115 | # deploy 116 | # https://github.com/peaceiris/actions-gh-pages 117 | - name: Deploy 118 | uses: peaceiris/actions-gh-pages@v3 119 | if: ${{ (github.event_name == 'schedule' && github.ref_name == 'main' || github.event.inputs.deploy == 'true') && steps.gh_data_collect.conclusion == 'success' }} 120 | with: 121 | publish_branch: gh-pages 122 | github_token: ${{ secrets.GITHUB_TOKEN }} 123 | publish_dir: _gh-pages/ 124 | force_orphan: true 125 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | # From: https://github.com/rkdarst/sphinx-actions-test/blob/master/.github/workflows/sphinx-build.yml 2 | 3 | name: Docs 4 | on: 5 | push: 6 | branches: 7 | - main 8 | pull_request: 9 | 10 | env: 11 | DEFAULT_BRANCH: "main" 12 | SPHINXOPTS: "-W --keep-going -T" 13 | # ^-- If these SPHINXOPTS are enabled, then be strict about the builds and fail on any warnings 14 | 15 | jobs: 16 | build-and-deploy_docs: 17 | name: Docs 18 | runs-on: ubuntu-latest 19 | env: 20 | ON_CI: True 21 | steps: 22 | # https://github.com/marketplace/actions/checkout 23 | - uses: actions/checkout@v3 24 | with: 25 | fetch-depth: 0 26 | - name: Install Python 27 | uses: actions/setup-python@v4 28 | with: 29 | python-version: '3.9' 30 | - name: Install deps 31 | run: | 32 | pip install -r docs/requirements.txt 33 | - name: Debugging information 34 | run: | 35 | echo "github.ref:" ${{github.ref}} 36 | echo "github.event_name:" ${{github.event_name}} 37 | echo "github.head_ref:" ${{github.head_ref}} 38 | echo "github.base_ref:" ${{github.base_ref}} 39 | set -x 40 | git rev-parse --abbrev-ref HEAD 41 | git branch 42 | git branch -a 43 | git remote -v 44 | python -V 45 | pip list --not-required 46 | pip list 47 | echo "ON_CI = $ON_CI" 48 | 49 | # build 50 | - uses: ammaraskar/sphinx-problem-matcher@master 51 | - name: Build Sphinx docs 52 | run: | 53 | cd docs 54 | sphinx-build -a -E -b html . _build/html 55 | 56 | - name: Archive docs 57 | uses: actions/upload-artifact@v3 58 | with: 59 | name: awesomeSphinx docs 60 | path: docs/_build/html 61 | 62 | # NO DEPLOYMENT FOR PUSHES 63 | # DOCS GET DEPLOYED WEEKLY 64 | 65 | # the following steps are only done after pushing to main or merging a PR 66 | # clone and set up the old gh-pages branch 67 | # - name: Clone old gh-pages 68 | # if: ${{ github.event_name == 'push' }} 69 | # run: | 70 | # set -x 71 | # git fetch 72 | # ( git branch gh-pages remotes/origin/gh-pages && git clone . --branch=gh-pages _gh-pages/ ) || mkdir _gh-pages 73 | # rm -rf _gh-pages/.git/ 74 | # mkdir -p _gh-pages/ 75 | # if a push and default branch, copy build to _gh-pages/ as the "main" 76 | # deployment. 77 | # - name: Copy docs build 78 | # if: ${{ github.event_name == 'push' }} 79 | # run: | 80 | # set -x 81 | # rsync -a docs/_build/html/ _gh-pages/ 82 | 83 | # add the .nojekyll file 84 | # - name: nojekyll 85 | # if: ${{ github.event_name == 'push' }} 86 | # run: | 87 | # touch _gh-pages/.nojekyll 88 | 89 | # deploy 90 | # https://github.com/peaceiris/actions-gh-pages 91 | # - name: Deploy 92 | # uses: peaceiris/actions-gh-pages@v3 93 | # if: ${{ github.event_name == 'push' }} 94 | # with: 95 | # publish_branch: gh-pages 96 | # github_token: ${{ secrets.GITHUB_TOKEN }} 97 | # publish_dir: _gh-pages/ 98 | # force_orphan: true 99 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | .idea/ 113 | 114 | # Spyder project settings 115 | .spyderproject 116 | .spyproject 117 | 118 | # Rope project settings 119 | .ropeproject 120 | 121 | # mkdocs documentation 122 | /site 123 | 124 | # mypy 125 | .mypy_cache/ 126 | .dmypy.json 127 | dmypy.json 128 | 129 | # Pyre type checker 130 | .pyre/ 131 | 132 | 133 | pypi_data.json 134 | awesome.json 135 | pypi_gh_data.json 136 | .envrc 137 | 138 | secrets/* 139 | 140 | docs/_images -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Python: Current File", 9 | "type": "python", 10 | "request": "launch", 11 | "program": "${file}", 12 | "console": "integratedTerminal", 13 | "justMyCode": true, 14 | "env": { 15 | "GOOGLE_APPLICATION_CREDENTIALS": "${workspaceFolder}/secrets/google_cloud.json" 16 | } 17 | }, 18 | { 19 | "name": "1_pypi_json SHORT", 20 | "type": "python", 21 | "request": "launch", 22 | "program": "scripts/pypi_json.py", 23 | "console": "integratedTerminal", 24 | "justMyCode": true, 25 | "env": { 26 | "GOOGLE_APPLICATION_CREDENTIALS": "${workspaceFolder}/secrets/google_cloud.json", 27 | "AWESOMESPHINX_AMOUNT": "3", 28 | "AWESOMESPHINX_DAYS": "1", 29 | } 30 | }, 31 | { 32 | "name": "2_github_stats SHORT", 33 | "type": "python", 34 | "request": "launch", 35 | "program": "scripts/github_stats.py", 36 | "console": "integratedTerminal", 37 | "justMyCode": true, 38 | "env": { 39 | "AWESOMESPHINX_AMOUNT": "3", 40 | } 41 | }, 42 | { 43 | "name": "3_needs.json SHORT", 44 | "type": "python", 45 | "request": "launch", 46 | "program": "scripts/needs_json.py", 47 | "console": "integratedTerminal", 48 | "justMyCode": true, 49 | "env": { 50 | "AWESOMESPHINX_AMOUNT": "3", 51 | } 52 | }, 53 | 54 | { 55 | "name": "Sphinx build CLEAN", 56 | "type": "python", 57 | "request": "launch", 58 | "program": "${workspaceFolder}/.venv/bin/sphinx-build", 59 | "args": ["-v", "-b", "html", ".", "_build/html", "-a", "-E"], 60 | "console": "integratedTerminal", 61 | "cwd": "${workspaceFolder}/docs", 62 | "env": { 63 | "AWESOMESPHINX_NEEDS_FILE": "/../awesome.json", 64 | } 65 | }, 66 | 67 | { 68 | "name": "Sphinx build INC", 69 | "type": "python", 70 | "request": "launch", 71 | "program": "${workspaceFolder}/.venv/bin/sphinx-build", 72 | "args": ["-b", "html", ".", "_build/html"], 73 | "console": "integratedTerminal", 74 | "cwd": "${workspaceFolder}/docs", 75 | "env": { 76 | "AWESOMESPHINX_NEEDS_FILE": "/../awesome.json", 77 | } 78 | } 79 | ] 80 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "esbonio.sphinx.confDir": "${workspaceFolder}/docs", 3 | "grammarly.files.include": ["**/README.md", "**/readme.md", "**/*.txt", "**/*.rst"] 4 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The code and the documentation of AwesomeSphinx itself is available 2 | by the terms of the MIT license. See license text below. 3 | 4 | However, the data of each documented package is available under the license 5 | of the related project only. This may be BSD, MIT, GPL2 or GPL3, or anything 6 | else. 7 | Please take a look into the related project, if you want to reuse or change 8 | its data. 9 | 10 | ---------------------- 11 | 12 | MIT License 13 | 14 | Copyright (c) 2022 useblocks 15 | 16 | Permission is hereby granted, free of charge, to any person obtaining a copy 17 | of this software and associated documentation files (the "Software"), to deal 18 | in the Software without restriction, including without limitation the rights 19 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 20 | copies of the Software, and to permit persons to whom the Software is 21 | furnished to do so, subject to the following conditions: 22 | 23 | The above copyright notice and this permission notice shall be included in all 24 | copies or substantial portions of the Software. 25 | 26 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 27 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 28 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 29 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 30 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 31 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 32 | SOFTWARE. 33 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Awesome Sphinx 2 | ============== 3 | 4 | An automatically collected and manually curated list of `Sphinx `_ 5 | extensions, themes and other Sphinx-related projects. 6 | 7 | **List and Documentation at https://awesomesphinx.useblocks.com/** 8 | 9 | Motivation 10 | ---------- 11 | There is an often-referenced list of Sphinx extensions: 12 | https://github.com/yoloseem/awesome-sphinxdoc 13 | 14 | But this list is unmaintained for ~2 years and covers only a subset of available 15 | extensions. 16 | 17 | Therefore this project was created, which provides an automated way to keep 18 | itself always up to date. 19 | 20 | Contribute 21 | ---------- 22 | 23 | Help is always welcome, especially for page and user interface design. 24 | 25 | More details on our `Contribute `__ page. 26 | 27 | Add/Update extension 28 | -------------------- 29 | All data is automatically collected from `PyPI `__ 30 | and `Github `__. 31 | 32 | So the easiest way to get your extension on this list is by having the right 33 | meta-data in your package on PyPI. 34 | 35 | More details `here `__. 36 | 37 | 38 | ToDo 39 | ---- 40 | 41 | * Adding tutorials to the list 42 | * Better UX -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/_awesome_templates/analysis.rst: -------------------------------------------------------------------------------- 1 | {# 2 | Template for for all category analysis. 3 | 4 | Do not add any category specific configuration here. 5 | Instead make it configurable by variables, like sphinx_type. 6 | #} 7 | 8 | Overview 9 | -------- 10 | 11 | .. needtable:: 12 | :filter: sphinx_type == "{{sphinx_type}}" 13 | :columns: {{table_columns}} 14 | {% if col_width is defined -%} 15 | :colwidths: {{col_width}} 16 | {%- endif %} 17 | 18 | -------------------------------------------------------------------------------- /docs/_awesome_templates/data_import.rst: -------------------------------------------------------------------------------- 1 | {# 2 | Template for all category-specific data imports 3 | 4 | Do not add any category-specific configuration here. 5 | Instead make it configurable by variables, like sphinx_type. 6 | #} 7 | 8 | 9 | Data 10 | ---- 11 | 12 | .. needimport:: {{need_import_file}} 13 | :filter: sphinx_type == "{{sphinx_type}}" 14 | -------------------------------------------------------------------------------- /docs/_static/custom.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Recursive:wght@300;400&display=swap'); 2 | 3 | div.document { 4 | width: 100%; 5 | width: 100vw; 6 | max-width: 1490px; 7 | padding: 10px 5%; 8 | } 9 | 10 | /* 11 | div.body { 12 | width: 100%; 13 | }*/ 14 | 15 | 16 | table.needs_style_awesome_red tr.head, 17 | tr.awesome_red td.needs_sphinx_type { 18 | background-color: #ffe0d9 !important; 19 | color: #000 !important; 20 | } 21 | 22 | table.needs_style_awesome_blue tr.head, 23 | tr.awesome_blue td.needs_sphinx_type { 24 | background-color: #cbd8e9 !important; 25 | color: #000 !important; 26 | } 27 | 28 | table.needs_style_awesome_green tr.head, 29 | tr.awesome_green td.needs_sphinx_type { 30 | background-color: #dcfcdc !important; 31 | color: #000 !important; 32 | } 33 | 34 | td.needs_title { 35 | font-weight: bold; 36 | } 37 | 38 | .md-typeset pre, pre { 39 | overflow-x: auto; 40 | } 41 | 42 | div.wy-table-responsive{ 43 | width: 100% !important; 44 | } 45 | 46 | .underline{ 47 | text-decoration: underline; 48 | } 49 | 50 | /*Remove READTHEDOCS version badge*/ 51 | .injected { 52 | display: none !important; 53 | } 54 | 55 | table.need{ 56 | display: table !important; 57 | width: 100% !important; 58 | } 59 | tbody tr td { 60 | font-size: medium !important; 61 | } 62 | 63 | /* Sphinx-Immaterial */ 64 | 65 | body, 66 | input { 67 | font-family: 'Recursive', sans-serif !important; 68 | font-size: 16px !important; 69 | font-weight: 400; 70 | line-height: 1.5 !important; 71 | } 72 | 73 | .md-typeset, .md-nav, .md-typeset table:not([class]) { 74 | font-size: 16px !important; 75 | line-height: 1.5 !important; 76 | } 77 | 78 | 79 | h1, h2, h3 { 80 | font-family: 'Recursive', sans-serif !important; 81 | font-weight: 500 !important; 82 | line-height: 1.4 !important; 83 | } 84 | 85 | h1:not([class="md-search-result__title"]) { 86 | font-size: 34px !important; 87 | margin-bottom: 20px !important; 88 | } 89 | 90 | h2 { 91 | font-size: 24px !important; 92 | margin-bottom: 10px !important; 93 | } 94 | 95 | h3 { 96 | font-size: 18px !important; 97 | margin-bottom: 10px !important; 98 | } 99 | 100 | .dataTables_filter input { 101 | border: 1px solid var(--md-typeset-color); 102 | margin-bottom: 10px; 103 | background-color: transparent; 104 | } 105 | 106 | .border { 107 | border: 1px solid var(--md-typeset-color); 108 | } 109 | 110 | table.dataTable tbody tr { 111 | background-color: transparent !important; 112 | } 113 | 114 | .dataTables_wrapper .dataTables_length, .dataTables_wrapper .dataTables_filter, .dataTables_wrapper .dataTables_info, .dataTables_wrapper .dataTables_processing, .dataTables_wrapper .dataTables_paginate { 115 | color: var(--md-typeset-color) !important; 116 | } 117 | 118 | .dataTables_wrapper .dataTables_paginate a.paginate_button, .dataTables_wrapper .dataTables_paginate a.paginate_button.current { 119 | color: var(--md-typeset-color) !important; 120 | } 121 | 122 | div.dataTables_info { 123 | margin-top: 0 !important; 124 | } 125 | 126 | table tbody tr:hover { 127 | background-color: rgb(147 147 147 / 10%) !important; 128 | box-shadow: 0 .05rem 0 var(--md-default-bg-color) inset !important; 129 | } 130 | 131 | button.dt-button:hover:not(.disabled), div.dt-button:hover:not(.disabled), a.dt-button:hover:not(.disabled) { 132 | color: #000; 133 | } 134 | 135 | .dataTables_wrapper .dataTables_filter { 136 | float: left !important; 137 | margin-left: 1% !important; 138 | } 139 | 140 | div.dt-buttons { 141 | margin-left: 1% !important; 142 | } 143 | 144 | .dataTables_wrapper .dataTables_filter input { 145 | max-width: 100px !important; 146 | } 147 | @media screen and (max-width: 640px){ 148 | .dataTables_wrapper .dataTables_filter { 149 | float: none !important; 150 | text-align: center !important; 151 | } 152 | 153 | .dataTables_wrapper .dataTables_filter input { 154 | max-width: inherit !important; 155 | } 156 | } 157 | 158 | div.dt-button-collection button.dt-button.active:not(.disabled) { 159 | color: #000 !important; 160 | border: 1px solid #000 !important; 161 | } 162 | 163 | table.NEEDS_DATATABLES, table.NEEDS_TABLE{ 164 | max-width: inherit; 165 | width: inherit; 166 | } 167 | div.md-grid { 168 | max-width: 100rem !important; 169 | } -------------------------------------------------------------------------------- /docs/_static/custom.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | $("table.NEEDS_DATATABLES, table.NEEDS_TABLE").wrap("
"); 3 | }); -------------------------------------------------------------------------------- /docs/_templates/layout.html: -------------------------------------------------------------------------------- 1 | {# Import the theme's layout. #} 2 | {% extends '!layout.html' %} 3 | 4 | {%- block extrahead %} 5 | 6 | {{ super() }} 7 | {%- endblock %} 8 | -------------------------------------------------------------------------------- /docs/_templates/partials/footer.html: -------------------------------------------------------------------------------- 1 | {% extends "!partials/footer.html" %} 2 | {# the '!' in "!partials/footer.html" is important #} 3 | {% block extracopyright %} 4 |

Sponsored and maintained with 💛 by useblocks

5 | {% endblock %} -------------------------------------------------------------------------------- /docs/additions.rst: -------------------------------------------------------------------------------- 1 | :orphan: 2 | 3 | Addtions 4 | ======== 5 | This page does not get included into the documetation. 6 | But it gets rendered and therfor be used to change data elements, e.g. set the category 7 | for selected sphinx-needs objects. 8 | 9 | Documentation of needextend: https://sphinx-needs.readthedocs.io/en/latest/directives/needextend.html 10 | 11 | Specific Need objects 12 | --------------------- 13 | 14 | 15 | .. needextend:: SUNPY-SPHINX-THEME2 16 | :strict: False 17 | :sphinx_type: theme 18 | 19 | .. needextend:: SPHINX-NEEDS 20 | :featured: True 21 | :tags: needs 22 | 23 | .. needextend:: DOXYSPHINX 24 | :featured: True 25 | 26 | .. needextend:: SPHINXCONTRIB-NEEDS 27 | :deprecated: True 28 | :tags: needs 29 | :links: SPHINX-NEEDS 30 | 31 | .. needextend:: SPHINX-TEST-REPORTS 32 | :tags: needs 33 | :links: SPHINX-NEEDS 34 | 35 | .. needextend:: SPHINX-MODELING 36 | :tags: needs 37 | 38 | .. needextend:: SPHINX-SIMPLEPDF 39 | :sphinx_type: extension 40 | :tags: pdf 41 | 42 | .. needextend:: SPHINX-IMMATERIAL 43 | :featured: True 44 | 45 | 46 | .. needextend:: SPHINX_DESIGN 47 | :+tags: layout 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /docs/analysis/active.rst: -------------------------------------------------------------------------------- 1 | Active 2 | ====== 3 | 4 | .. needpie:: Days since last release 5 | :labels: <100, 100-199, 200-399, 400-799, >=800 6 | 7 | int(release_days) < 100 8 | int(release_days) >= 100 and int(release_days) < 200 9 | int(release_days) >= 200 and int(release_days) < 400 10 | int(release_days) >= 400 and int(release_days) < 800 11 | int(release_days) >= 800 12 | 13 | Projects with releases in the last 100 days 14 | 15 | .. needtable:: 16 | :filter: int(release_days) <= 100 17 | :sort: release_days 18 | :columns: id, title, sphinx_type, license, points, code_nice, PyPI_nice, website_nice 19 | -------------------------------------------------------------------------------- /docs/analysis/famous.rst: -------------------------------------------------------------------------------- 1 | Famous 2 | ====== 3 | 4 | .. needpie:: Monthly downloads 5 | :labels: >50000,5000-49999, 1000-4999,100-999,<100 6 | 7 | (int(monthly) if monthly.isdigit() else 0) >= 50000 8 | (int(monthly) if monthly.isdigit() else 0) < 50000 and (int(monthly) if monthly.isdigit() else 0) >= 5000 9 | (int(monthly) if monthly.isdigit() else 0) < 5000 and (int(monthly) if monthly.isdigit() else 0) >= 1000 10 | (int(monthly) if monthly.isdigit() else 0) < 1000 and (int(monthly) if monthly.isdigit() else 0) >= 100 11 | (int(monthly) if monthly.isdigit() else 0) < 100 12 | 13 | 14 | Projects with over 5.000 downloads per month 15 | 16 | .. needtable:: 17 | :filter: (int(monthly) if monthly.isdigit() else 0) >= 5000 18 | :sort: monthly 19 | :columns: id, title, sphinx_type, license, monthly, overall -------------------------------------------------------------------------------- /docs/analysis/healthy.rst: -------------------------------------------------------------------------------- 1 | Healthy 2 | ======= 3 | 4 | 5 | .. needbar:: Health points 6 | :show_top_sum: 7 | :xlabels: 0,1,2,3,4,5,6,7,8,9,10 8 | 9 | int(points) == 0, int(points) == 1,int(points) == 2,int(points) == 3,int(points) == 4,int(points) == 5,int(points) == 6,int(points) == 7,int(points) == 8,int(points) == 9,int(points) == 10 10 | 11 | Projects with 9 points and higher. 12 | 13 | .. needtable:: 14 | :filter: int(points) >= 9 15 | :columns: id, title, sphinx_type as "Type", package_summary as "Summary", points, download_points as "DL points", release_points as "Release points", code_nice as "Code Link", PyPI_nice as "PyPI Link", website_nice as "Website Link" -------------------------------------------------------------------------------- /docs/categories/extensions.rst: -------------------------------------------------------------------------------- 1 | .. _extensions: 2 | 3 | Extensions 4 | ========== 5 | 6 | Collected extensions for Sphinx. 7 | 8 | 9 | .. This loads the analysis and data import for the category 10 | Set sphinx_type and table_columns to configure category specific stuff. 11 | 12 | {% set sphinx_type = "extension" %} 13 | {% set table_columns = 'id, title, package_summary as "summary", license, points, code_nice as "Code", pypi_nice as "PyPI", website_nice as "Website"' %} 14 | {% set col_width = "10, 15, 30, 10,5,10,10,10" %} 15 | 16 | {% include '../_awesome_templates/analysis.rst' %} 17 | 18 | {% include '../_awesome_templates/data_import.rst' %} 19 | 20 | .. Add custom extensions below this line. 21 | 22 | -------------------------------------------------------------------------------- /docs/categories/others.rst: -------------------------------------------------------------------------------- 1 | .. _others: 2 | 3 | Other projects 4 | ============== 5 | 6 | Collected, Sphinx-related projects, which have not yet been classified, the project contains more than Sphinx related parts, or 7 | other reasons. 8 | 9 | 10 | .. This loads the analysis and data import for the category 11 | Set sphinx_type and table_columns to configure category-specific stuff. 12 | 13 | {% set sphinx_type = "other" %} 14 | {% set table_columns = "id, title, license, points, code_nice, pypi_nice, website_nice" %} 15 | 16 | {% include '../_awesome_templates/analysis.rst' %} 17 | 18 | {% include '../_awesome_templates/data_import.rst' %} 19 | 20 | .. Add custom "other" below this line. 21 | 22 | -------------------------------------------------------------------------------- /docs/categories/themes.rst: -------------------------------------------------------------------------------- 1 | .. _themes: 2 | 3 | Themes 4 | ====== 5 | 6 | Collected themes for Sphinx. 7 | 8 | 9 | 10 | .. This loads the analysis and data import for the category 11 | Set sphinx_type and table_columns to configure category specific stuff. 12 | 13 | {% set sphinx_type = "theme" %} 14 | {% set table_columns = "id, title, license, points, code_nice, pypi_nice, website_nice" %} 15 | 16 | {% include '../_awesome_templates/analysis.rst' %} 17 | 18 | {% include '../_awesome_templates/data_import.rst' %} 19 | 20 | .. Add custom themes below this line. 21 | 22 | 23 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # For the full list of built-in configuration values, see the documentation: 4 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 5 | 6 | # -- Project information ----------------------------------------------------- 7 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information 8 | 9 | from ast import arg 10 | from datetime import datetime 11 | import os 12 | import sys 13 | from sphinx_needs.api import add_dynamic_function 14 | 15 | # Make Python aware of the project config file 16 | sys.path.append(os.path.join(os.path.dirname(__file__), '..')) 17 | from projects import PROJECT_TAGS 18 | 19 | project = 'Awesome-Sphinx' 20 | copyright = '2022, team useblocks' 21 | author = 'team useblockjs' 22 | 23 | # -- General configuration --------------------------------------------------- 24 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration 25 | 26 | extensions = [ 27 | 'sphinx_needs', 28 | 'sphinxcontrib.plantuml', 29 | 'sphinx_immaterial', 30 | 'sphinx_design' 31 | ] 32 | 33 | templates_path = ['_templates'] 34 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', '_awesome_templates'] 35 | 36 | # -- Options for HTML output ------------------------------------------------- 37 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output 38 | 39 | html_theme = 'sphinx_immaterial' # 'alabaster' 40 | # html_theme = 'alabaster' 41 | html_static_path = ['_static'] 42 | html_css_files = ['custom.css'] 43 | html_js_files = ['custom.js'] 44 | html_context = { 45 | 'project_tags': PROJECT_TAGS, 46 | 'need_import_file': os.getenv('AWESOMESPHINX_NEEDS_FILE', '/../data/20221025_awesome.json') 47 | } 48 | 49 | html_theme_options = { 50 | # "icon": { 51 | # "repo": "fontawesome/brands/github-square", 52 | # }, 53 | "font": { 54 | "code": "JetBrains Mono" 55 | }, 56 | "site_url": "https://awesomesphinx.useblocks.com/", 57 | "repo_url": "https://github.com/useblocks/awesomesphinx", 58 | "repo_name": "Awesome-Sphinx", 59 | "repo_type": "github", 60 | "edit_uri": "blob/main/docs", 61 | # "google_analytics": ["UA-XXXXX", "auto"], 62 | "globaltoc_collapse": True, 63 | "features": [ 64 | # "navigation.sections", 65 | "navigation.top", 66 | "search.share", 67 | "navigation.tracking", 68 | "toc.follow" 69 | ], 70 | "palette": [ 71 | { 72 | "media": "(prefers-color-scheme: light)", 73 | "scheme": "default", 74 | "primary": "blue", 75 | "accent": "light-blue", 76 | "toggle": { 77 | "icon": "material/weather-night", 78 | "name": "Switch to dark mode", 79 | }, 80 | }, 81 | { 82 | "media": "(prefers-color-scheme: dark)", 83 | "scheme": "slate", 84 | "primary": "blue", 85 | "accent": "yellow", 86 | "toggle": { 87 | "icon": "material/weather-sunny", 88 | "name": "Switch to light mode", 89 | }, 90 | }, 91 | ], 92 | "toc_title_is_page_title": True, 93 | "toc_title": "Awesome-Sphinx", 94 | } 95 | 96 | 97 | # NEED CONFIG 98 | 99 | needs_id_regex = r'.*' 100 | needs_types = [ 101 | dict(directive="sw", title="Software", prefix="S_", color="#BFD8D2", style="card"), 102 | dict(directive="test", title="test", prefix="S_", color="#BFD8D2", style="card") 103 | ] 104 | 105 | 106 | needs_extra_options = [ 107 | 'package_summary', 108 | 'category', 109 | 'sphinx_type', 110 | 'license', 111 | 'monthly', 112 | 'overall', 113 | 'code', 114 | 'pypi', 115 | 'website', 116 | 'release_date', 117 | 'release_name', 118 | 'release_days', 119 | 'download_points' 120 | 'release_points', 121 | 'points', 122 | 'code_nice', 123 | 'pypi_nice', 124 | 'website_nice', 125 | 'featured', 126 | 'color', 127 | 'deprecated', 128 | 'stars', 129 | ] 130 | 131 | 132 | needs_string_links = { 133 | # Links to the related github issue 134 | 'links': { 135 | 'regex': r'^(?P.*)$', 136 | 'link_url': '{{value}}', 137 | 'link_name': '{{value}}', 138 | 'options': ['code', 'pypi', 'website'] 139 | }, 140 | 'links_nice': { 141 | 'regex': r'^(?P.*)$', 142 | 'link_url': '{{value}}', 143 | 'link_name': 'Link', 144 | 'options': ['code_nice', 'pypi_nice', 'website_nice'] 145 | } 146 | } 147 | 148 | # This automatically sets some values for all needs. Mostly dynamic functions 149 | # calls to calucalte some value for needs. 150 | # See https://sphinx-needs.readthedocs.io/en/latest/configuration.html#needs-global-options 151 | needs_global_options = { 152 | 'collapse': 'True', 153 | 'release_days': ("[[days_since_build('release_date')]]", "type == 'sw'"), 154 | 'release_points': ("[[points('release')]]", "type == 'sw'"), 155 | 'download_points': ("[[points('download')]]", "type == 'sw'"), 156 | 'points': ("[[points('all')]]", "type == 'sw'"), 157 | 'code_nice': ("[[copy('code')]]", "type == 'sw'"), 158 | 'pypi_nice': ("[[copy('pypi')]]", "type == 'sw'"), 159 | 'website_nice': ("[[copy('website')]]", "type == 'sw'"), 160 | 'color': [ 161 | ("blue", "sphinx_type=='extension'"), 162 | ("green", "sphinx_type=='theme'"), 163 | ("red", "sphinx_type=='other'"), 164 | ], 165 | 'style': [ 166 | ("awesome_[[copy('color')]]", "type=='sw'"), 167 | ], 168 | 'template': [ 169 | ("software", "type=='sw'"), 170 | ] 171 | } 172 | 173 | needs_variant_options = ["status"] # Not needed, but workarund to avoid a bug and some warnings 174 | 175 | needs_needextend_strict = False 176 | 177 | def days_since_build(app, need, needs, *args, **kwargs): 178 | """ 179 | Calculates the days from now to a given date, which is normally in the past. 180 | 181 | Usage:: 182 | 183 | .. need:: My_need 184 | :date: 2020-09-08T11:05:55.2340Z 185 | :days: [[days_since_build('date')]] 186 | 187 | """ 188 | date_option = args[0] 189 | date = need[date_option] 190 | 191 | date_obj = datetime.fromisoformat(date) 192 | delta = datetime.now() - date_obj 193 | 194 | return str(delta.days) 195 | 196 | def points(app, need, needs, *args, **kwargs): 197 | 198 | categories = ['all', 'download', 'release'] 199 | category = args[0] 200 | if category not in categories: 201 | raise KeyError(f'points-category must be one of {", ".join(categories)}') 202 | 203 | 204 | release_points = 0 205 | if category == 'release' or category == 'all': 206 | if need['release_days'] is not None and need['release_days'].isdigit(): 207 | release_days = int(need['release_days']) 208 | 209 | if release_days < 100: 210 | release_points = 5 211 | elif release_days < 200: 212 | release_points = 4 213 | elif release_days < 400: 214 | release_points = 3 215 | elif release_days < 600: 216 | release_points = 2 217 | else: 218 | release_points = 0 219 | 220 | 221 | download_points = 0 222 | if category == 'download' or category == 'all': 223 | if need['monthly'] is not None and need['monthly'].isdigit(): 224 | monthly = int(need['monthly']) 225 | if monthly > 50000: 226 | download_points = 5 227 | elif monthly > 5000: 228 | download_points = 4 229 | elif monthly > 1000: 230 | download_points = 3 231 | elif monthly > 500: 232 | download_points = 2 233 | elif monthly > 100: 234 | download_points = 1 235 | else: 236 | download_points = 0 237 | 238 | points = release_points + download_points 239 | return str(points) 240 | 241 | def rstjinja(app, docname, source): 242 | """ 243 | Render our pages as a jinja template for fancy templating goodness. 244 | 245 | Source: https://ericholscher.com/blog/2016/jul/25/integrating-jinja-rst-sphinx/ 246 | """ 247 | src = source[0] 248 | rendered = app.builder.templates.render_string( 249 | src, app.config.html_context 250 | ) 251 | source[0] = rendered 252 | 253 | def setup(app): 254 | app.connect("source-read", rstjinja, 50000) 255 | 256 | add_dynamic_function(app, days_since_build) 257 | add_dynamic_function(app, points) -------------------------------------------------------------------------------- /docs/contribute.rst: -------------------------------------------------------------------------------- 1 | .. _contribute: 2 | 3 | Contribute 4 | ========== 5 | 6 | Updating package data 7 | --------------------- 8 | 9 | Changing existing data 10 | ~~~~~~~~~~~~~~~~~~~~~~ 11 | To extend or manipulate the data for a specific extension or theme, please 12 | add an entry to the file ``/docs/additions.rst``. 13 | 14 | We use the `needextend `_ 15 | mechanism of `Sphinx-Needs `_ to add, 16 | change or replace values of imported elements. 17 | 18 | .. code-block:: rst 19 | 20 | .. needextend:: SUNPY-SPHINX-THEME 21 | :sphinx_type: theme 22 | :+tags: theme, sunpy 23 | 24 | The above code is changing data of a need-element with the ID ``SUNPY-SPHINX-THEME``. 25 | It overwrites the ``sphinx_type`` with the value ``theme`` and 26 | also extends the ``tags`` attribute by a new value. 27 | 28 | Changing or better extending data is a common task in the awesomesphinx projects. 29 | It's mostly used to classify an extension or theme, to put it in specific categories for the user. 30 | 31 | 32 | Adding new package 33 | ~~~~~~~~~~~~~~~~~~ 34 | Normally all extensions and themes get imported automatically from PyPI if they have set the correct 35 | classifiers in their packages. 36 | 37 | However, there are projects, which are not released on PyPI or haven't set the right classifiers. 38 | Therefore these packages need to be registered in awesomesphinx by hand. 39 | 40 | 41 | Register a package from PyPI 42 | ++++++++++++++++++++++++++++ 43 | 44 | Updating classifiers 45 | ******************** 46 | The best and most correct way of documenting a package here is to make sure it contains the 47 | correct classifiers. If this is the case, it will be found on the upcoming import run. 48 | 49 | Each Python packaging tool does support setting classifiers. Please take a look at the related documentation to do so. 50 | 51 | The used classifiers for filtering are defined inside ``/projects.py`` file and are currently: 52 | 53 | .. literalinclude:: /../projects.py 54 | :lines: 6-11 55 | 56 | 57 | Not matching classifiers 58 | ************************ 59 | If a package does not match the classifiers, which are used to search for packages on PyPI, it can be added 60 | by hand. However, it still must be available on PyPI! 61 | 62 | Please open the file ``/projects.py`` and add the package-name to 63 | the ``EXTRA_PROJECTS`` list. 64 | 65 | .. code-block:: python 66 | 67 | EXTRA_PROJECTS = [ 68 | 'MY-NEW-PACKAGE' # <-- This is new 69 | 'sphinx-copybutton', 70 | 'doxysphinx' 71 | ] 72 | 73 | Please be sure that the used name can be used to identify the package on PyPI, for instance by copying 74 | the name from the PyPI URL of the package. 75 | 76 | Manually add a new package 77 | ++++++++++++++++++++++++++ 78 | Please open the related category file from the ``/docs/categories`` folder. 79 | 80 | After the ``needimport`` statement you are free to add a new Sphinx-Needs element, which represents 81 | the package. 82 | 83 | .. code-block:: rst 84 | 85 | .. software:: My new theme 86 | :id: MY-NEW-theme 87 | :sphinx_type: theme 88 | :monthly: 0 89 | :release_date: 2022-09-24T21:26:03 90 | :release_name: 2.1.0 91 | :code: https://github.com/me/package 92 | :pypi: https://PyPI.org/package 93 | :website: https://my-new-package.com 94 | 95 | The values for ``points`` and ``release_days`` get calculated automatically. 96 | 97 | .. warning:: 98 | 99 | Please be aware that this is the **worst** way of adding a new extension/theme. 100 | 101 | The values of ``release_date`` and ``monthly`` are not accurate and would need to be 102 | maintained by hand. It is much better to release the package on PyPI with the correct 103 | classifiers. 104 | 105 | Adding tags 106 | +++++++++++ 107 | 108 | Tags are defined inside ``/projectspy``, inside the variable ``PROJECT_TAGS``. 109 | 110 | ``PROJECT_TAGS`` is a dictionary, where the key the is tag name and the value is an rst-based description, which 111 | is taken for the documentation. 112 | 113 | Each tag inside ``PROJECT_TAGS`` gets reported automatically. No further actions are needed. 114 | 115 | If a new tag is needed, simply add it to ``PROJECT_TAGS``, use it for at least one Sphinx element, and create a PR. 116 | 117 | 118 | Developing docs 119 | --------------- 120 | 121 | Run scripts 122 | ~~~~~~~~~~~ 123 | The ``PyPI_json.py`` script is using Google BigQuery to get information about the download numbers of PyPI. 124 | You need a google cloud account and an **authentication-file** to run these queries. 125 | 126 | The installation guide of `PyPInfo `_ has a great 127 | chapter on how to get and configure a google cloud account. 128 | 129 | The **authentication-file** must be set via ENV variable. 130 | If you use our ``.vscode/launch.json`` config, this is set automatically to 131 | ``"GOOGLE_APPLICATION_CREDENTIALS": "${workspaceFolder}/secrets/google_cloud.json"`` 132 | 133 | Technical background 134 | ~~~~~~~~~~~~~~~~~~~~ 135 | 136 | The AwesomeSphinx data workflow is as follows: 137 | 138 | 1. ``/scripts/PyPI_json.py`` gets executed (currently done manually). 139 | 140 | 1. Search for packages on PyPI by classifiers. 141 | 2. Requests package info from PyPI for each package. 142 | 3. Queries PyPI-BigQuery-data for download numbers of the last 30 days. 143 | 4. Stores all data in a ``pypi_data.json`` file. 144 | 145 | 2. ``/scripts/needs_json.py`` gets executed. 146 | 147 | 1. Loads ``pypi_data.json``. 148 | 2. Extracts needed data only. 149 | 3. Constructs need-objects internally. 150 | 4. Creates an ``awesome.json``, which contains the need-objects and is compliant with the Sphinx-Needs ``needs.json`` format. 151 | 152 | 153 | 3. Sphinx build gets started. 154 | 155 | 1. ``needimport`` for ``awesome.json`` is used to import need-object for specific categories. 156 | 2. Jinja templates get rendered and inject data. 157 | 3. Value calculation is done via the ``dynamic functions`` feature of Sphinx-Needs. 158 | 159 | 160 | Own Awesome X list 161 | ------------------ 162 | The used code and documentation configuration are not specific to Sphinx. 163 | 164 | With 1-2 line changes in the file ``/projects.py`` for the used classifiers filters, documentation projects can 165 | be created for other Python-based projects. 166 | 167 | It must be currently Python-based, as the ``pypi_json.py`` script is using PyPI and BigQuery Table from PyPI to get needed data. 168 | If this gets changed as well, also other tools can be documented as well. 169 | 170 | 171 | 172 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Awesome Sphinx 2 | ============== 3 | 4 | An automatically collected and manually curated list of 5 | `Sphinx `__ 6 | extensions, themes and other Sphinx-related projects. 7 | 8 | .. hint:: 9 | 10 | This documentation project is in an early phase and could need some love, smart ideas and beautiful designs from you. 11 | So why not join us on our `GitHub project `_? 12 | More details on the :ref:`contribute` page. 13 | 14 | *Documentation was built on:* |today| 15 | 16 | .. grid:: 2 17 | :gutter: 2 18 | 19 | .. grid-item-card:: Types 20 | :columns: 6 6 6 6 21 | :class-card: border 22 | 23 | | :ref:`Extensions `: :need_count:`sphinx_type == "extension"` 24 | | :ref:`Themes `: :need_count:`sphinx_type == "theme"` 25 | | :ref:`Other projects `: :need_count:`sphinx_type == "other"` 26 | 27 | :ref:`Overall `: :need_count:`type == "sw"` 28 | 29 | 30 | .. grid-item-card:: Tags 31 | :columns: 6 6 6 6 32 | :class-card: border 33 | 34 | {% for tag, desc in project_tags.items() %} 35 | | :ref:`{{tag}} `: :need_count:`"{{tag}}" in tags` 36 | {% endfor %} 37 | 38 | 39 | Featured themes and extensions 40 | ------------------------------ 41 | 42 | .. needextract:: 43 | :filter: featured == "True" 44 | :layout: clean 45 | 46 | 47 | .. _overall: 48 | 49 | All Extensions & Themes 50 | ----------------------- 51 | To search for data in all table columns, simply use the search field on the upper right of the table. 52 | 53 | Use the export buttons to get the current view as a PDF or Excel file. 54 | The export takes filter and sorting into account. 55 | 56 | .. needtable:: 57 | :columns: id, package_summary as "Description", sphinx_type as "Type", tags, website_nice as "Website", license, points, monthly as "Monthly downloads", release_days as "Days since last release", stars as "Stars" 58 | :colwidths: 10,45,5,10,5,5,5,5,5,5 59 | :style_row: awesome_[[copy('color')]] 60 | 61 | 62 | Table of content 63 | ---------------- 64 | 65 | .. toctree:: 66 | :caption: Packages 67 | :maxdepth: 2 68 | 69 | categories/extensions 70 | categories/themes 71 | categories/others 72 | 73 | .. toctree:: 74 | :caption: Tags 75 | :maxdepth: 2 76 | 77 | tags 78 | 79 | .. toctree:: 80 | :caption: Analysis 81 | :maxdepth: 2 82 | 83 | analysis/healthy 84 | analysis/famous 85 | analysis/active 86 | 87 | .. toctree:: 88 | :caption: Collaboration 89 | :maxdepth: 2 90 | 91 | rating 92 | contribute 93 | license 94 | -------------------------------------------------------------------------------- /docs/license.rst: -------------------------------------------------------------------------------- 1 | License 2 | ======= 3 | 4 | .. include:: /../LICENSE -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | %SPHINXBUILD% >NUL 2>NUL 14 | if errorlevel 9009 ( 15 | echo. 16 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 17 | echo.installed, then set the SPHINXBUILD environment variable to point 18 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 19 | echo.may add the Sphinx directory to PATH. 20 | echo. 21 | echo.If you don't have Sphinx installed, grab it from 22 | echo.https://www.sphinx-doc.org/ 23 | exit /b 1 24 | ) 25 | 26 | if "%1" == "" goto help 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/needs_templates/software.need: -------------------------------------------------------------------------------- 1 | {{package_summary}} 2 | 3 | | Links: 4 | {%- if website is not none and website != "" %} `Website <{{website}}>`__ {% endif %} 5 | {%- if pypi is not none and pypi != "" %} | `PyPI <{{pypi}}>`__ {% endif %} 6 | {%- if code is not none and code != "" %} | `Code <{{code}}>`__ {% endif %} 7 | | Points: **{{points}}** ({{release_points}}+{{download_points}}) 8 | -------------------------------------------------------------------------------- /docs/rating.rst: -------------------------------------------------------------------------------- 1 | Rating 2 | ====== 3 | 4 | AwesomeSphinx calculates a value to specify the health status of a project. 5 | This health status is simple called `points`. 6 | 7 | Points are given for downloads in the last month and time passed since the last release. 8 | Both values are then added to an overall result. 9 | 10 | The highest available amount of points is **10**, the lowest **0**. 11 | 12 | Release day rating 13 | ------------------ 14 | 15 | The latest release day if taken from PyPI, by analysing the release day of each release and taking the latest one. 16 | 17 | Points for release days: 18 | 19 | .. list-table:: 20 | 21 | - * Requirement 22 | * Points 23 | - * < 100 24 | * 5 25 | - * < 200 26 | * 4 27 | - * < 400 28 | * 3 29 | - * < 600 30 | * 2 31 | - * >= 600 32 | * 0 33 | 34 | Download rating 35 | --------------- 36 | 37 | The download numbers are taken from the BigQuery Table from PyPI. 38 | The data is stored on Google cloud and querying >1TB per month costs money, these numbers get updated once a week. 39 | 40 | 41 | Points for monthly downloads: 42 | 43 | .. list-table:: 44 | 45 | - * Requirement 46 | * Points 47 | - * > 50.000 48 | * 5 49 | - * > 5.000 50 | * 4 51 | - * > 1.000 52 | * 3 53 | - * > 500 54 | * 2 55 | - * > 100 56 | * 1 57 | - * <= 100 58 | * 0 -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinxcontrib-plantuml 2 | sphinx_design 3 | sphinx-immaterial 4 | git+https://github.com/useblocks/sphinx-needs 5 | -------------------------------------------------------------------------------- /docs/tags.rst: -------------------------------------------------------------------------------- 1 | {% for tag, desc in project_tags.items()|sort() %} 2 | .. _tag_{{tag}}: 3 | 4 | {{tag|capitalize}} 5 | {{"=" * tag|length}} 6 | 7 | {{desc}} 8 | 9 | .. needtable:: 10 | :filter: "{{tag}}" in tags 11 | :columns: id; title; package_summary 12 | :colwidths: 10, 30, 60 13 | 14 | 15 | .. dropdown:: Element details 16 | :animate: fade-in 17 | 18 | .. needextract:: 19 | :filter: "{{tag}}" in tags 20 | 21 | 22 | {% endfor %} -------------------------------------------------------------------------------- /projects.py: -------------------------------------------------------------------------------- 1 | """ 2 | Configuration for projects to collect or ignore 3 | """ 4 | 5 | # List of classifiers, which are used to identify projects on PYPI. 6 | PROJECT_FILTERS = [ 7 | ['Framework :: Sphinx'], 8 | ['Framework :: Sphinx :: Extension'], 9 | ['Framework :: Sphinx :: Theme'] 10 | ] 11 | 12 | 13 | # List of projects, which do not have set the above classifiers but shall be 14 | # documented as well. 15 | EXTRA_PROJECTS = [ 16 | 'sphinx-test-reports', 17 | 'sphinx-collections', 18 | 'sphinx-preview', 19 | 'sphinx-data-viewer', 20 | 'sphinx-copybutton', 21 | 'doxysphinx', 22 | 'breathe' 23 | ] 24 | 25 | # List of of projects, which have set one of the above classifiers, but shall be ignored. 26 | # Mostly because they are not reallyy related to Sphinx or just use Sphinx for documentation. 27 | IGNORE_PROJECTS = [ 28 | "ALVEOLUS" 29 | ] 30 | 31 | # Dict of tags to document. 32 | # Is used to generate tag specific overviews. 33 | # Key is used for filtering, the value as documentation 34 | PROJECT_TAGS = { 35 | 'needs': 'All `Sphinx-Needs `__ related extensions and maybe themes.', 36 | 'layout': 'Extensions which help to structure your dcument data e.g. by grids or dropdowns.', 37 | 'pdf': 'Extensions which are dealing with PDFs.', 38 | 'hacktoberfest': 'Projects, which are part of the `Hacktoberfest `__.', 39 | 'markdown': 'Extensions and tools, which support Markdown in Sphinx projects.', 40 | 'doxygen': 'Doxygen support inside Sphinx.', 41 | 'python': 'Support of the Python programming language', 42 | } 43 | -------------------------------------------------------------------------------- /scripts/awesome_config.py: -------------------------------------------------------------------------------- 1 | """ 2 | Configuration for the awesomesphinx scripts 3 | """ 4 | import sys 5 | import os 6 | 7 | # Make Python aware of the awesom_config file 8 | sys.path.append(os.path.join(os.path.dirname(__file__), '..')) 9 | from projects import PROJECT_FILTERS, EXTRA_PROJECTS, IGNORE_PROJECTS 10 | 11 | # A list of tags, which shall not be set, as they are too commonly used. 12 | PROJECT_IGNORE_TAGS = [ 13 | "sphinxcontrib", 14 | "sphinx", 15 | "documentation", 16 | "sphinx-theme", 17 | "sphinx-extension", 18 | "theme", 19 | "extension", 20 | "documentation-tool", 21 | "documentation-generator", 22 | ] 23 | 24 | ################################################## 25 | # PYPI_JSON.PY configs 26 | ################################################## 27 | JSON_FILE = 'pypi_data.json' 28 | 29 | 30 | # Used during development to reduce amount of data to fetch 31 | # Normally a search contains ~600 findings 32 | MAX_DATA = os.environ.get('AWESOMESPHINX_AMOUNT', -1) 33 | if MAX_DATA == -1: 34 | MAX_DATA = 1000 35 | 36 | if MAX_DATA == '' or MAX_DATA is None: 37 | MAX_DATA == 1000 38 | try: 39 | MAX_DATA = int(MAX_DATA) 40 | except ValueError: 41 | MAX_DATA = 3 42 | 43 | 44 | API_SLEEP = 2 # Wait time for API, if too many requests were made 45 | 46 | # Maximum amount of API sleeps in a row, until the loop gets stopped 47 | MAX_API_SLEEPS = 10 48 | 49 | 50 | # SQL Query for getting downloads numbers 51 | # Add comma separated list of project must be added, e.g. "pytest","sphinx","flask" 52 | # https://packaging.python.org/en/latest/guides/analyzing-pypi-package-downloads/ 53 | BIGQUERY_DL_MONTH = """ 54 | SELECT COUNT(*) AS num_downloads, project 55 | FROM `bigquery-public-data.pypi.file_downloads` 56 | WHERE file.project in ('{}') 57 | -- Only query the last X days of history 58 | AND DATE(timestamp) 59 | BETWEEN DATE_SUB(CURRENT_DATE(), INTERVAL {} DAY) 60 | AND CURRENT_DATE() 61 | GROUP BY `project` 62 | """ 63 | 64 | # Amount of days to fetch data from PyPI 65 | BIGQUERY_DAYS = int(os.environ.get('AWESOMESPHINX_DAYS', 30)) 66 | 67 | ################################################## 68 | # GITHUB_STATS.PY configs 69 | ################################################## 70 | GH_JSON_FILE = 'pypi_gh_data.json' 71 | 72 | GH_RATE_LIMIT_AMOUNT = 6 73 | GH_RATE_LIMIT_WAIT = 600 # in s 74 | 75 | GH_WAIT_COUNTER = 3 # Every x requests we make a break 76 | GH_WAIT_DURATION = 3 # in s 77 | 78 | ################################################## 79 | # NEEDS_JSON.PY configs 80 | ################################################## 81 | PYPI_FILE = os.environ.get('AWESOMESPHINX_PYPI_FILE', GH_JSON_FILE) 82 | if PYPI_FILE is '' or PYPI_FILE is None: 83 | PYPI_FILE = GH_JSON_FILE 84 | 85 | #PYPI_FILE = 'data/20221024_pypi_data.json' 86 | 87 | NEED_FILE = 'awesome.json' 88 | 89 | NEED_TYPE = 'sw' 90 | 91 | MAX_NEEDS = MAX_DATA # Used for development for faster tests 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /scripts/github_stats.py: -------------------------------------------------------------------------------- 1 | """ 2 | Collects GitHub stats from project in pypi_data.json 3 | """ 4 | import json 5 | import sys 6 | import os 7 | from threading import Thread 8 | import time 9 | from github import Github, UnknownObjectException, RateLimitExceededException 10 | 11 | # Make Python aware of the awesom_config file 12 | sys.path.append(os.path.dirname(__file__)) 13 | from awesome_config import * 14 | 15 | 16 | KEY = os.environ['GH_KEY'] 17 | 18 | 19 | def get_gh_topics(name, gh_project, results, counter, rate_limits=0): 20 | awesome_tags = [] 21 | awesome_stars = -1 22 | try: 23 | repo = g.get_repo(gh_project) 24 | awesome_tags = repo.get_topics() 25 | awesome_stars = repo.stargazers_count 26 | print(f'{counter}/{len(pypi_data)} {name}: Tags retrieved: {awesome_tags}') 27 | except UnknownObjectException: 28 | pass 29 | except RateLimitExceededException: 30 | if rate_limits < GH_RATE_LIMIT_AMOUNT: 31 | print(f'{counter} waiting {GH_RATE_LIMIT_WAIT} ({rate_limits+1}/{GH_RATE_LIMIT_AMOUNT})') 32 | time.sleep(GH_RATE_LIMIT_WAIT) 33 | get_gh_topics(name, gh_project, results, counter, rate_limits=rate_limits+1) 34 | 35 | 36 | results[name] = { 37 | 'tags': awesome_tags, 38 | 'stars': awesome_stars 39 | } 40 | 41 | 42 | 43 | print(f'Reading pypi data from {JSON_FILE}') 44 | with open(JSON_FILE, 'r') as f: 45 | pypi_data = json.load(f) 46 | 47 | gh_projects={} 48 | 49 | for name, project in pypi_data.items(): 50 | # Collect possible github repository URLS 51 | from contextlib import suppress 52 | 53 | gh_urls = [] 54 | with suppress(KeyError,TypeError): 55 | gh_urls.append(project['info']['project_urls']['Github']) 56 | with suppress(KeyError,TypeError): 57 | gh_urls.append(project['info']['project_urls']['Code']) 58 | with suppress(KeyError,TypeError): 59 | gh_urls.append(project['info']['project_urls']['Source Code']) 60 | with suppress(KeyError,TypeError): 61 | gh_urls.append(project['info']['project_urls']['Source']) 62 | with suppress(KeyError,TypeError): 63 | gh_urls.append(project['info']['project_urls']['Homepage']) 64 | with suppress(KeyError,TypeError): 65 | gh_urls.append(project['info']['home_page']) 66 | 67 | 68 | gh_urls_clean = [] 69 | for url in gh_urls: 70 | if url is not None and 'github.com' in url and '/issues' not in url: 71 | url_clean = url.replace('https://github.com/', '').replace('https://github.com/', '').strip('/') 72 | gh_urls_clean.append(url_clean) 73 | 74 | if gh_urls_clean: 75 | gh_projects[name] = gh_urls_clean[0] 76 | 77 | print(f'Github projects found: {len(gh_projects)}/{len(pypi_data)}') 78 | 79 | g = Github(KEY) 80 | counter = 0 81 | results = {} 82 | threads = {} 83 | 84 | wait_counter = 0 85 | 86 | for name, project in pypi_data.items(): 87 | 88 | if wait_counter > GH_WAIT_COUNTER: 89 | time.sleep(GH_WAIT_DURATION) 90 | wait_counter = 0 91 | 92 | awesome_tags = [] 93 | if name in gh_projects: 94 | gh_project = gh_projects[name] 95 | threads[name] = Thread(target=get_gh_topics, args=(name, gh_project, results, counter)) 96 | threads[name].start() 97 | counter += 1 98 | 99 | for thre in threads.values(): 100 | thre.join() 101 | 102 | 103 | print(f'Received {len(results)} tag-results from GitHub') 104 | 105 | for name, project in pypi_data.items(): 106 | project['awesome_stats']['tags'] = [] 107 | project['awesome_stats']['stars'] = -1 108 | if name in results: 109 | project['awesome_stats']['tags'] = results[name]['tags'] 110 | project['awesome_stats']['stars'] = results[name]['stars'] 111 | 112 | print(f'Storing data into {GH_JSON_FILE}') 113 | with open(GH_JSON_FILE, 'w') as f: 114 | json.dump(pypi_data, f, sort_keys=True, indent=4) 115 | 116 | print('Done. Exit now!') 117 | 118 | -------------------------------------------------------------------------------- /scripts/needs_json.py: -------------------------------------------------------------------------------- 1 | """ 2 | Creates a Sphinx-Needs compatilbe needs.json file from data collected by pypi_json.py script 3 | """ 4 | 5 | from datetime import datetime 6 | import json 7 | import sys 8 | import os 9 | 10 | 11 | # Make Python aware of the awesom_config file 12 | sys.path.append(os.path.dirname(__file__)) 13 | from awesome_config import * 14 | 15 | 16 | def classifiers_check(value, classifiers): 17 | for cl in classifiers: 18 | if value in cl: 19 | return True 20 | return False 21 | 22 | 23 | print(f'Reading pypi data from {PYPI_FILE}') 24 | with open(PYPI_FILE, 'r') as f: 25 | pypi_data = json.load(f) 26 | 27 | 28 | needs = {} 29 | 30 | print(f'Constructing data for {len(pypi_data)} needs:', end='') 31 | counter = 0 32 | for name, data in pypi_data.items(): 33 | counter += 1 34 | if counter >= MAX_NEEDS: 35 | break 36 | 37 | # check sphinx_type 38 | sphinx_type = 'other' 39 | if classifiers_check('Framework :: Sphinx :: Extension', data['info']['classifiers']): 40 | sphinx_type = 'extension' 41 | if classifiers_check('Framework :: Sphinx :: Theme', data['info']['classifiers']): 42 | sphinx_type = 'theme' 43 | 44 | # Check license 45 | license = data['info']['license'] 46 | if classifiers_check('License :: OSI Approved :: BSD License', data['info']['classifiers']): 47 | license = "BSD" 48 | elif classifiers_check('License :: OSI Approved :: MIT License', data['info']['classifiers']): 49 | license = "MIT" 50 | elif classifiers_check('GNU General Public License v3', data['info']['classifiers']): 51 | license = "GPL3" 52 | elif classifiers_check('GNU General Public License v2', data['info']['classifiers']): 53 | license = "GPL2" 54 | elif classifiers_check('Apache Software License', data['info']['classifiers']): 55 | license = "Apache" 56 | 57 | # If no license was found or it contains a complete license text 58 | if license is None or license == '' or len(license) > 25: 59 | license = 'other' 60 | 61 | 62 | #stats 63 | try: 64 | monthly = data['awesome_stats']['month'] 65 | except KeyError: 66 | monthly = 0 67 | 68 | try: 69 | overall = data['awesome_stats']['overall'] 70 | except KeyError: 71 | overall = 0 72 | 73 | try: 74 | tags = [] 75 | for tag in data['awesome_stats']['tags']: 76 | if tag not in PROJECT_IGNORE_TAGS: 77 | tags.append(tag) 78 | except KeyError: 79 | tags = [] 80 | 81 | try: 82 | stars = data['awesome_stats']['stars'] 83 | except KeyError: 84 | stars = 0 85 | 86 | # urls 87 | code = "" 88 | if data['info']['project_urls']: 89 | code = data['info']['project_urls'].get('Repository', data['info']['project_urls'].get('Code', '')) 90 | 91 | 92 | #last release 93 | release_date = '1970-01-01T00:00:01' 94 | release_name = None 95 | for release_name, release in data['releases'].items(): 96 | try: 97 | if release[0]['upload_time'] > release_date: 98 | release_date = release[0]['upload_time'] 99 | release_name = release_name 100 | except Exception: 101 | pass 102 | 103 | 104 | needs[name] = { 105 | "id": name.upper(), 106 | #"description": data['info']['description'], 107 | "package_summary": data['info']['summary'], 108 | "description": "", 109 | "title": name, 110 | "type": NEED_TYPE, 111 | "sphinx_type": sphinx_type, 112 | "license": license, 113 | "monthly": monthly, 114 | # "overall": overall, # currently not collected 115 | "tags": tags, 116 | "stars": stars, 117 | "pypi": data['info']['package_url'], 118 | "code": code, 119 | "website": data['info']['home_page'], 120 | "release_date": release_date, 121 | "release_name": release_name, 122 | } 123 | 124 | print('.', end='') 125 | print() 126 | 127 | needs_data = { 128 | "created": datetime.now().isoformat(), 129 | "current_version": "1.0", 130 | "project": "needs test docs", 131 | "versions": { 132 | "1.0": { 133 | "created": datetime.now().isoformat(), 134 | "needs": needs 135 | } 136 | } 137 | } 138 | 139 | print(f'Writing need data to {NEED_FILE}') 140 | with open(NEED_FILE, 'w') as f: 141 | json.dump(needs_data, f, sort_keys=True, indent=4) 142 | 143 | with open(NEED_FILE, 'r') as f: 144 | need_test = json.load(f) 145 | 146 | print(f'Reading back {len(need_test["versions"]["1.0"]["needs"])} needs from {NEED_FILE}') 147 | 148 | -------------------------------------------------------------------------------- /scripts/pypi_json.py: -------------------------------------------------------------------------------- 1 | """ 2 | Collects Sphinx pakcage data from pypi and pypistats and stores the data in a json file 3 | """ 4 | 5 | import json 6 | import xmlrpc.client 7 | import requests 8 | import time 9 | import sys 10 | import os 11 | from threading import Thread 12 | from google.cloud import bigquery 13 | 14 | # Make Python aware of the awesom_config file 15 | sys.path.append(os.path.dirname(__file__)) 16 | from awesome_config import * 17 | 18 | 19 | def get_dl_month(name, results, counter): 20 | client = bigquery.Client() 21 | query_string = BIGQUERY_DL_MONTH.format(name, BIGQUERY_DAYS) 22 | query_job = client.query(query_string) 23 | query_results = query_job.result() 24 | for row in query_results: 25 | month_stats = row.num_downloads 26 | project = row.project 27 | results[project] = month_stats 28 | 29 | print(f'{counter} query done') 30 | 31 | def get_package_data(name, results): 32 | r = requests.get(f'https://pypi.org/pypi/{name}/json') 33 | tool_data = r.json() 34 | results[name] = tool_data 35 | print(f'Collected {name}') 36 | 37 | 38 | client = xmlrpc.client.ServerProxy('https://pypi.org/pypi') 39 | 40 | 41 | # Get and store tool 42 | tools = [] 43 | 44 | counter = 0 45 | api_sleeps = 0 46 | 47 | while True: 48 | filter = PROJECT_FILTERS[counter] 49 | filter_tools = [] 50 | try: 51 | package_releases = client.browse(filter) 52 | for release in package_releases: 53 | if release[0] not in filter_tools: 54 | filter_tools.append(release[0]) 55 | print(f'Found {len(filter_tools)} tools for {filter}') 56 | tools = list(set(tools + filter_tools)) 57 | except xmlrpc.client.Fault: 58 | print(f'Sleeping {API_SLEEP}s for API rate refresh') 59 | time.sleep(API_SLEEP) 60 | api_sleeps += 1 61 | if api_sleeps >= MAX_API_SLEEPS: 62 | break 63 | else: 64 | api_sleeps = 0 65 | counter += 1 66 | if counter > len(PROJECT_FILTERS) - 1: 67 | break 68 | 69 | # Get tool specific data 70 | tools_data = {} 71 | 72 | # Add tools, which can not be found by above search 73 | for tool in EXTRA_PROJECTS: 74 | if tool not in tools: 75 | tools.append(tool) 76 | 77 | # Remove not wanted tools 78 | for tool in IGNORE_PROJECTS: 79 | try: 80 | tools.remove(tool) 81 | except ValueError: 82 | pass 83 | 84 | 85 | print(f'Found overall {len(tools)} sphinx tools') 86 | 87 | # Stop data collection, if we only want to get some data and not all 88 | tools = tools[0:MAX_DATA] 89 | 90 | 91 | print('Collection package data') 92 | threads = {} 93 | results = {} 94 | counter = 0 95 | for name in tools: 96 | threads[name] = Thread(target=get_package_data, args=(name, results)) 97 | threads[name].start() 98 | 99 | for thre in threads.values(): 100 | thre.join() 101 | 102 | tools_data = results 103 | 104 | # collect PyPi BigQuery downloads numbers 105 | print(f'Collecting PyPi BigQuery Stats for {len(tools_data)} tools for {BIGQUERY_DAYS} days') 106 | 107 | threads = {} 108 | results = {} 109 | counter = 0 110 | 111 | # Chunks of project-names, to split a big query into smaller ones. 112 | # Currently not needed (+580 projects), but maybe query-string gets to big 113 | # in future 114 | tools_chunks = [tools_data.keys()] 115 | 116 | for chunk in tools_chunks: 117 | name = "','".join(chunk) 118 | threads[name] = Thread(target=get_dl_month, args=(name, results, counter)) 119 | threads[name].start() 120 | counter += 1 121 | 122 | for thre in threads.values(): 123 | thre.join() 124 | 125 | 126 | for name, package in tools_data.items(): 127 | try: 128 | package['awesome_stats'] = { 129 | 'month': results[name], 130 | } 131 | except KeyError: 132 | package['awesome_stats'] = { 133 | 'month': 0, 134 | } 135 | print(f"{name}: {package['awesome_stats']['month']:,}") 136 | 137 | 138 | 139 | # Store tools_data as json 140 | print(f'Storing data into {JSON_FILE}') 141 | with open(JSON_FILE, 'w') as f: 142 | json.dump(tools_data, f, sort_keys=True, indent=4) 143 | 144 | print('Done. Exit now!') 145 | 146 | -------------------------------------------------------------------------------- /scripts/requirements.txt: -------------------------------------------------------------------------------- 1 | google-cloud-bigquery 2 | requests 3 | PyGithub 4 | --------------------------------------------------------------------------------