195 Commits
Author SHA1 Message Date
jakob.scheid b1213e1f24 docs(changelog): add namespace error fix entry
Added a changelog entry for the namespace name error of the Python
project documentation generator.
2026-09-28 22:28:36 +02:00
jakob.scheid d1c9839ec7 fix(python-generator): resolve directory path
The path to the directory is now completely resolved in
PythonDocumentationGenerator._namespace to prevent from empty path names
if the path is '.'. A fallback was also added for the case that the path
is '/'; the fallback value is irrelevant as the root namespace name does
not occur in the output and only the root package path can be '/'.
2026-09-28 22:26:32 +02:00
jcloud-bot Bot eabde41cec chore: update API reference 2026-09-27 23:28:05 +00:00
jakob.scheid cd35457301 Merge pull request 'Fix(ci): API reference configuration error' (#51) from fix/api-reference-workflow-config-error into main
Generate API reference / generate (push) Successful in 21s
Reviewed-on: #51
2026-09-28 01:27:50 +02:00
jakob.scheid 6f2c0871c8 fix(ci): fix API reference configuration error
Fixed a spelling error.
2026-09-28 01:25:16 +02:00
jakob.scheid b2a8e54766 Merge pull request 'Chore: prepare release 0.2.0' (#49) from chore/prepare-0-2-0 into main
Release package / Build package (release) Successful in 24s
Release package / Prepare release asset upload (release) Successful in 14s
Release package / Publish package to package registry (release) Successful in 16s
Release package / Upload release asset (${{ fromJSON(needs.prepare_upload_release_asset.outputs.files) }}) (release) Successful in 23s
Reviewed-on: #49
2026-09-28 00:40:29 +02:00
jakob.scheid 773294f3d9 docs(changelog): move unreleased section to 0.2.0 2026-09-28 00:35:43 +02:00
jakob.scheid 200221e324 chore: update version in pyproject.toml to 0.2.0 2026-09-28 00:34:39 +02:00
jakob.scheid e8bcf4feb1 Merge pull request 'Fix(ci): spelling error in API reference workflow' (#48) from fix/fix-api-reference-spelling into main
Reviewed-on: #48
2026-09-28 00:31:45 +02:00
jakob.scheid ff3bddd366 fix(ci): fix spelling in API reference workflow
Replaced '--chached' with '--cached' and replaced '--quit' with
'--quiet'.
2026-09-28 00:29:11 +02:00
jakob.scheid e94365faf8 Merge pull request 'Chore(ci): adapt API reference workflow to the new API' (#45) from feature/adapt-api-reference-workflow-to-new-cli into main
Reviewed-on: #45
2026-09-28 00:22:39 +02:00
jakob.scheid 95da53898c chore(ci): adapt API reference workflow to new API
Adapted the API reference generation workflow to the new API, which no
longer requires the src/ structure.
2026-09-28 00:18:44 +02:00
jakob.scheid 4d2e63d560 refactor(ci): use force overwriting feature 2026-09-28 00:17:30 +02:00
jakob.scheid 2187590468 Merge pull request 'Refactor: lint code' (#43) from refactor/lint into main
Reviewed-on: #43
2026-09-28 00:08:30 +02:00
jakob.scheid 37308227d9 chore: add noqa comments to wildcard imports
Added noqa comments to suppress linting warnings for wildcard imports.
2026-09-28 00:04:53 +02:00
jakob.scheid f796263c0e fix: fix syntax errors (target version)
Fixed all lines which were syntax errors in the target version (only
multi-line f-strings).
2026-09-28 00:04:53 +02:00
jakob.scheid 896bfcc2dd style: use target version practices 2026-09-28 00:04:53 +02:00
jakob.scheid b2a7c9a0e3 style: fix lines which exceed the line length limit 2026-09-28 00:04:44 +02:00
jakob.scheid ac72eb283c style: organize imports
Organized imports according to Ruff (I001).
2026-09-27 22:23:58 +02:00
jakob.scheid 21c5c20cb5 Merge pull request 'Fix(python-generator): Python namespaces comparision type check' (#41) from fix/python-namespace-comparision-type-check into main
Reviewed-on: #41
2026-09-27 22:17:15 +02:00
jakob.scheid 3865db7919 test(python-generator): add test cases for package namespace comparision
Added test cases with different types for Python package namespace
comparision test.
2026-09-27 22:16:15 +02:00
jakob.scheid ec262ef99e feat(python-generator): check type at package namespace comparision
Added type check to the Python package namespace comparision function.
2026-09-27 22:15:57 +02:00
jakob.scheid f102fa80ce test(python-generator): add test cases for module namespace comparision
Added test cases with different types for Python module namespace
comparision test.
2026-09-27 22:15:55 +02:00
jakob.scheid 6d2b32a736 feat(python-generator): check type at module namespace comparision
Added type check to the Python module namespace comparision function.
2026-09-27 22:01:19 +02:00
jakob.scheid 934624fce3 Merge pull request 'Feat(python-generator): protection against accidental overwriting of the output directory' (#40) from feature/protect-against-accidental-overwriting into main
Reviewed-on: #40
2026-09-27 21:56:40 +02:00
jakob.scheid b043a4ea17 docs(changelog): add accidental overwriting protection entry
Added a changelog entry for the protection against accidental
overwriting of the output directory.
2026-09-27 17:37:51 +02:00
jakob.scheid f2b9f47e09 test(cli): add tests for Python force overwriting
Added a test to the CLI test file for the documentation generation
error.
2026-09-27 17:36:04 +02:00
jakob.scheid f0c271f8fd feat(cli): add Python generator force argument 2026-09-27 15:09:35 +02:00
jakob.scheid d8508e0fa3 test(python-generator): add tests for force overwriting
Added tests to test new output directory deletion behavior:
  - output directory overwriting (not empty; force)
  - error (not empty; not force)
  - nothing (empty; force)
  - nothing (empty; not force)
One test was adapted to output directory emptiness check because the
.gitkeep file which is required for Git to track the directory makes it
non-empty.
2026-09-27 15:09:17 +02:00
jakob.scheid 5791358b41 feat(python-generator): add force argument to generator
Added a force argument to
PythonDocumentationGenerator.generate_documentation to control whether
the content of the output directory should be overwritten or not, if it
is not empty. If the force argument is True, the output directory
content will first be deleted recursively. If it is False and the output
directory is not empty, a DirectoryNotEmptyError will be raised.
2026-09-27 14:59:28 +02:00
jakob.scheid 0261a7b3a6 feat(exceptions): add DirectoryNotEmptyError
Added an OSError subclass for indicating that the directory is not
empty. This exception is explicitly handled by the CLI now.
2026-09-26 16:28:40 +02:00
jakob.scheid 2fe342cbc6 Merge pull request 'Feat(python-generator): remove implicit assumptions of project directory structure' (#39) from feature/no-src-dir-need into main
Reviewed-on: #39
2026-09-22 19:28:50 +02:00
jakob.scheid 53ab0e3829 docs(changelog): add project directory structure entry
Added an entry that the implicit Python project directory
structure assumptions have been removed to the changelog.
2026-09-22 19:28:34 +02:00
jakob.scheid c40e965d8d test(python-generator): update tests to do not assume src/ directories
Updated tests to do not implicitly assume a 'src/' directory structure
and removed all 'src/' subdirectory structures in test project
directories (if possible).
2026-09-22 19:26:55 +02:00
jakob.scheid 0fbfe84657 feat(python-generator)!: do not require specific structure
Removed all implicit assumptions of a project structure:
  - no 'src/' directory is required
  - top-level modules are no longer ignored
2026-09-22 18:28:35 +02:00
jakob.scheid d0ea392f62 Merge pull request 'Feature(python-generator): output directory creation' (#37) from feature/ensure-output-dir-exists into main
Reviewed-on: #37
2026-09-22 17:11:04 +02:00
jakob.scheid 717a8c446e docs(changelog): add docs directory creation entry
Added a changelog entry for the creation of the documentation directory
at the Python documentation generator.
2026-09-22 17:08:15 +02:00
jakob.scheid ec156a538e test(python-generator): add test for directory creation
Added a testing function which tests whether the documentation directory
is created when the documentation is generated.
2026-09-22 17:04:56 +02:00
jakob.scheid e2264696e8 feat(python-generator): create output directory
Now, the output directory and its parents are created when generating
the documentation.
2026-09-22 17:00:34 +02:00
jakob.scheid 6cf3de156d Merge pull request 'Test(python-generator): name testing function correctly' (#36) from test/name-testing-functions-correctly into main
Reviewed-on: #36
2026-09-22 16:49:28 +02:00
jakob.scheid b6d2fa7db8 test(python-generator): name testing function correctly 2026-09-22 16:47:18 +02:00
jakob.scheid 63cef6f641 Merge pull request 'Fix(python-generator): make generated signatures compliant with PEP 8' (#35) from fix/python-signatures-pep-8-compliance into main
Reviewed-on: #35
2026-09-22 16:15:35 +02:00
jakob.scheid 06802c96f6 docs(changelog): add Python signatures PEP 8 compliance entry
Added a changelog entry for the PEP 8 compliance of Python function
signatures default values.
2026-09-22 16:13:43 +02:00
jakob.scheid 17c5e4938f test(python-generator): update tests for PEP 8 compliance
Updated test cases to the new default value PEP 8 compliance and added
new test cases to test the new behavior.
2026-09-22 16:13:23 +02:00
jakob.scheid cc786a6c69 fix(python-generator): make signatures compliant with PEP 8
The '=' character at a parameter in the signature was surrounded with
spaces previously. That is no longer the case, unless there is a type
hint.
2026-09-22 15:55:57 +02:00
jakob.scheid 274ed45167 Merge pull request 'Chore(ci): add workflow to generate API reference' (#29) from chore/api-reference-workflow into main
Reviewed-on: #29
2026-09-22 13:26:43 +02:00
jakob.scheid 62206db574 docs(changelog): add entry for API reference pipeline
Added a changelog entry for the CI pipeline which generated the API
reference.
2026-09-22 13:16:35 +02:00
jakob.scheid 89451c34e3 chore(ci): add workflow to generate API reference
Added a workflow which generates the API reference and commits and
pushes it.
2026-09-22 13:15:47 +02:00
jakob.scheid f6dbefaf6c Merge pull request 'Docs(readme): reference to Gitea Package Registry instead of concrete installation instructions' (#28) from docs/remove-readme-installation-instructions into main
Reviewed-on: #28
2026-09-22 12:45:32 +02:00
jakob.scheid 1a230abf02 docs(changelog): add package registry reference entry
Added an entry for the installation instructions update in README to the
changelog.
2026-09-22 12:43:19 +02:00
jakob.scheid 14d6a8984a docs(readme): update installation instructions
Previously, there were instructions how to install the package (over the
old registry) in README.md. They were replaced with a reference to the
Gitea Package Registry.
2026-09-22 12:37:10 +02:00
jakob.scheid 56f650d0ad Merge pull request 'Chore: conventional commit messages template' (#25) from chore/conventional-commit-msg-template into main
Reviewed-on: #25
2026-09-21 22:31:28 +02:00
jakob.scheid 25a92d710e chore: add setup script
Added a setup script in scripts/ to configure Git to use the commit
message template and added a reference to it to README.md.
2026-09-21 22:27:28 +02:00
jakob.scheid a55d4d4f79 chore: add commit message template
Added a commit message template for conventional commits line
length limits.
2026-09-21 22:21:40 +02:00
jakob.scheid 24673a45a5 Merge pull request 'Refactor(python-generator): rename misnamed testing function' (#23) from refactor/package-namespace-testing-function-name into main
Reviewed-on: #23
2026-09-21 22:14:42 +02:00
jakob.scheid 0e81d0b9ca refactor(python-generator): rename misnamed testing function
Renamed 'test_PythonPackageNamespace___getitem__' to
'test_PythonPackageNamespace_add'.
2026-09-21 22:12:00 +02:00
jakob.scheid b5f67caa69 Merge pull request 'Chore(lint): set up Ruff, lint and format' (#22) from chore/ruff into main
Reviewed-on: #22
2026-09-21 22:06:22 +02:00
jakob.scheid de3eac3bb1 chore: lint source code using Ruff 2026-09-21 20:10:09 +02:00
jakob.scheid aa4a139878 style: format source code using Ruff 2026-09-21 20:10:04 +02:00
jakob.scheid d9887f34c9 chore(ruff): add Ruff configuration 2026-09-18 22:40:39 +02:00
jakob.scheid 178e6b0bd8 chore(deps): add Ruff dependency 2026-09-18 22:31:09 +02:00
jakob.scheid f680110110 Merge pull request 'Style: trailing commas in multi-line statements' (#21) from style/multiline-trailing-commas into main
Reviewed-on: #21
2026-09-18 22:22:15 +02:00
jakob.scheid 475775eb7e style: ensure trailing commas in multi-line statements 2026-09-18 22:18:13 +02:00
jakob.scheid ffcc7404de Merge pull request 'Style: ensure line character limit and remove trailing whitespace' (#19) from style/pip-8-compliance into main
Reviewed-on: #19
2026-09-18 17:12:59 +02:00
jakob.scheid 23e450e9df style: remove trailing whitespace
Removed all trailing whitespace.
2026-09-18 17:02:09 +02:00
jakob.scheid caa8e04047 style: limit line length to 79 characters
Limited the lengths of all lines to 79 characters (if possible).
Additionally, some other lines were breaked for consistence.
2026-09-18 16:57:45 +02:00
jakob.scheid 8930efc7e6 Merge pull request 'Feature(cli): add base command-line interface' (#16) from feature/cli into main
Reviewed-on: #16
2026-09-13 20:11:00 +02:00
jakob.scheid 1ce3d8f9d1 refactor(cli): remove unnecessary, redundant __all__ 2026-09-13 20:10:22 +02:00
jakob.scheid 56588e6c78 fix: jcloud_docsgen.cli instead of jcloud_docsgen.cli.cli in pyproject.toml 2026-09-13 20:09:02 +02:00
jakob.scheid bd7780f112 test(cli): add tests for Python sections list parser 2026-09-13 19:04:55 +02:00
jakob.scheid 5e84156e01 fix(cli): copy VALID_SECTIONS when parsing sections list 2026-09-13 19:04:54 +02:00
jakob.scheid 089cf5f2ff refactor(cli): move sections list parsing to function 2026-09-13 19:04:54 +02:00
jakob.scheid 8644232a00 feat: add __main__ module
Added a __main__ module that calls the CLI main function.
2026-09-13 19:04:54 +02:00
jakob.scheid 2cb99d4ca9 feat(cli): add cli module to cli package __init__ module 2026-09-13 19:04:54 +02:00
jakob.scheid 8f5dc33369 feat(cli): import languages package 2026-09-13 19:04:54 +02:00
jakob.scheid e2e6a59d13 docs(changelog): update CLI entry 2026-09-13 19:04:53 +02:00
jakob.scheid 84fe67230f test(cli): add tests for the CLI 2026-09-13 19:04:41 +02:00
jakob.scheid 348be4f07f refactor(cli): rename 'parsers' package to 'languages' 2026-09-10 15:52:05 +02:00
jakob.scheid db0d36f956 feat(cli): add __all__ to cli.cli module 2026-09-10 13:37:45 +02:00
jakob.scheid 29d1c5bced refactor(cli): move parser creation to a function 2026-09-10 13:36:06 +02:00
jakob.scheid 3ab9884be0 feat(cli): add error handling
The CLI now handles error that may result from user input and prints an
error message. Following error types are handled:
 - file not found
 - not a directory
 - directory instead of file
 - file exists
 - permission error
 - general operating system errors
 - Unicode errors
 - errors parsing project source code

Additionally, KeyboardInterrupt and BrokenPipeError are catched and an
appropriate exit code is returned.
2026-09-10 13:36:03 +02:00
jakob.scheid 60a097f0cc feat(cli): allow to pass command line arguments to function 2026-09-10 09:58:18 +02:00
jakob.scheid a8513a78e2 docs(changelog): add CLI entry to changelog 2026-09-10 09:51:55 +02:00
jakob.scheid 34457f0b9f chore: add project script
Added the script 'jcloud-docsgen' to pyproject.toml so that jCloud Docsgen can
be run directly without using 'python3 -m ...'.
2026-09-10 09:50:19 +02:00
jakob.scheid bce073746e feat(cli): add Python CLI handling
Added a subparser for Python documentation and a function to generate
Python documentation.
2026-09-10 09:46:04 +02:00
jakob.scheid 350f8f9941 refactor(cli)!: make CLI a package 2026-09-05 20:38:59 +02:00
jakob.scheid 2d6de2028f feat(cli): add output directory option 2026-09-05 20:38:58 +02:00
jakob.scheid 436dacf806 feat(cli): make project_directory argument optional 2026-09-05 20:38:19 +02:00
jakob.scheid 3cd374c354 Merge pull request 'Refactor(utils): mark ExistingDirectory and ExistingFile as deprecated' (#14) from refactor/remove-existing-file-class into main
Reviewed-on: #14
2026-09-05 14:44:21 +02:00
jakob.scheid 78e3e020be fix(cli): check whether project directory exists and is a directory 2026-09-05 14:42:19 +02:00
jakob.scheid 36cedb390b refactor: update all instances of ExistingFile and ExistingDirectory in source code 2026-09-05 14:31:00 +02:00
jakob.scheid d54c0fb208 refactor: remove or update all instances of ExistingFile and ExistingDirectory in tests 2026-09-05 14:29:31 +02:00
jakob.scheid 5d8dbd659d chore(utils): mark ExistingDirectory and ExistingFile as deprecated 2026-09-05 14:24:19 +02:00
jakob.scheid 1789352287 Merge pull request 'Chore: Python .gitignore template' (#13) from chore/python-gitignore-template into main
Reviewed-on: #13
2026-09-05 13:49:30 +02:00
jakob.scheid 3daff2ba13 chore: add VS Code directory to gitignore 2026-09-05 13:47:11 +02:00
jakob.scheid 20fe7c5605 chore: use Gitea's python .gitignore template 2026-09-05 13:46:09 +02:00
jakob.scheid f5b2c77427 Merge pull request 'Chore: add .venv to .gitignore' (#11) from chore/add-venv-to-gitignore into main
Reviewed-on: #11
2026-09-05 13:38:18 +02:00
jakob.scheid 75a13527a9 chore: add .venv to .gitignore 2026-09-05 13:37:10 +02:00
jakob.scheid ac8bd2374f Merge pull request 'Chore(deps): add development dependencies' (#9) from chore/dev-deps-group into main
Reviewed-on: #9
2026-09-05 13:35:06 +02:00
jakob.scheid c47aa0738a chore(deps): add used development dependencies 2026-09-04 19:54:46 +02:00
jakob.scheid 260f41b808 Merge pull request 'Chore: migration to Poetry' (#6) from chore/poetry-migration into main
Reviewed-on: #6
2026-09-04 19:35:02 +02:00
jakob.scheid c4212320a2 chore: Add Poetry lock 2026-09-04 19:14:10 +02:00
jakob.scheid 324ffcd4eb chore: update dependency management 2026-09-04 19:13:55 +02:00
jakob.scheid aa702ca9f9 chore: migrate manifest to Poetry 2026-09-04 19:13:32 +02:00
jakob.scheid ef5605ced1 hotfix(cd): fix job label
Release package / Publish package to package registry (release) Successful in 15s
Release package / Upload release asset (${{ fromJSON(needs.prepare_upload_release_asset.outputs.files) }}) (release) Successful in 23s
Release package / Build package (release) Successful in 24s
Release package / Prepare release asset upload (release) Successful in 14s
2026-09-03 19:42:05 +02:00
jakob.scheid eac5fb0ffc hotfix(cd): add build dependency to release asset upload preparation
Release package / Build package (release) Successful in 40s
Release package / Prepare release asset upload (release) Canceled after 0s
Release package / Upload release asset (${{ fromJSON(needs.prepare_upload_release_asset.outputs.files) }}) (release) Canceled after 0s
Release package / Publish package to package registry (release) Canceled after 0s
2026-09-03 19:14:10 +02:00
jakob.scheid 5c036e60a1 hotfix(cd): download artifact before uploading release assets
Release package / Build package (release) Successful in 24s
Release package / Publish package to package registry (release) Skipped
Release package / Prepare release asset upload (release) Successful in 14s
Release package / Upload release asset (${{ fromJSON(needs.prepare_upload_release_asset.outputs.files) }}) (release) Successful in 25s
2026-09-03 19:11:36 +02:00
jakob.scheid 9b49de336b hotfix(cd): fix workflow job label
Release package / Build package (release) Successful in 29s
Release package / Prepare release asset upload (release) Successful in 14s
Release package / Publish package to package registry (release) Skipped
Release package / Upload release asset (${{ fromJSON(needs.prepare_upload_release_asset.outputs.files) }}) (release) Failing after 21s
2026-09-03 18:07:04 +02:00
jakob.scheid 42f8a65933 Merge pull request 'Chore: prepare release 0.1.0' (#4) from chore/prepare-0-1-0 into main
Release package / Build package (release) Failing after 20s
Release package / Publish package to package registry (release) Skipped
Release package / Prepare release asset upload (release) Failing after 13s
Release package / Upload release asset (${{ fromJSON(needs.prepare_upload_release_asset.outputs.files) }}) (release) Skipped
Reviewed-on: #4
2026-09-03 17:18:38 +02:00
jakob.scheid 2ef362b7c0 docs(changelog): mark unreleased section as version 0.1.0 2026-09-01 16:55:44 +02:00
jakob.scheid 3428c54d99 style: ensure trailing newlines 2026-09-01 16:54:03 +02:00
jakob.scheid 3a05718612 docs(changelog): make changelog clearer
Removed internal implementation details from changelog and rephrased and reordered
some entries.
2026-09-01 16:39:42 +02:00
jakob.scheid 44de48ef9d chore: update version information to 0.1.0 2026-09-01 16:33:22 +02:00
jakob.scheid 804368bdd6 Merge pull request 'Feat(cd): add workflow to publish package' (#2) from chore/release-cd-pipeline into main
Release package / Prepare release asset upload (release) Canceled after 0s
Release package / Upload release asset (${{ fromJSON(needs.prepare_upload_release_asset.outputs.files) }}) (release) Canceled after 0s
Release package / Publish package to package registry (release) Canceled after 0s
Release package / Build package (release) Canceled after 16s
Reviewed-on: #2
2026-09-01 16:27:12 +02:00
jakob.scheid c167fcf803 feat(cd): add workflow to publish package 2026-09-01 16:19:51 +02:00
jakob.scheid 44f80dbb6a Update licenses 2026-04-30 19:28:53 +02:00
jakob.scheid 14380b6515 Update licenses 2026-04-30 19:27:32 +02:00
jakob.scheid d3bb51eb61 Rephrase a sentence 2026-04-27 23:01:39 +02:00
jakob.scheid b4f4c4860a Rephrase a sentence 2026-04-27 23:00:54 +02:00
jakob.scheid 86c012ef5c Fatal bug fix: Add quotation mark 2026-04-27 22:47:08 +02:00
jakob.scheid a74a500e7e Prepare first release 2026-04-27 22:45:45 +02:00
jakob.scheid 16ce11b852 Prepare first release 2026-04-27 22:44:30 +02:00
jakob.scheid cf8875463c Add README 2026-04-27 22:43:52 +02:00
jakob.scheid 81b2be7efc Add API reference 2026-04-27 22:43:26 +02:00
jakob.scheid 55c4c74ee8 Remove test dependencies 2026-04-27 22:30:24 +02:00
jakob.scheid 3315448fc5 Add tests for the Python project documentation generator 2026-04-27 22:17:29 +02:00
jakob.scheid ce244df494 Add Python project documentation generator 2026-04-27 22:15:40 +02:00
jakob.scheid d12263f552 Add license for pytest-expectdir 2026-04-27 22:13:22 +02:00
jakob.scheid 22f3233381 Add internal function to generate the documentation of a package namespace 2026-04-26 22:23:18 +02:00
jakob.scheid a17e56fb3d Add Python module documentation generator 2026-04-25 19:34:34 +02:00
jakob.scheid ecd3f7e76b Generalize tests for core.python.PythonDocumentationGenerator exception 2026-04-19 19:51:50 +02:00
jakob.scheid f581774649 Add function to get the path segments of a path relative to another 2026-04-19 19:38:30 +02:00
jakob.scheid 3fd51d126f Add Python definition documentation generator 2026-04-19 19:12:33 +02:00
jakob.scheid 3b45c1a8ed Add function to ensure a non-empty string. 2026-04-19 19:10:45 +02:00
jakob.scheid b71b93000e Add feature to parse Python docstrings 2026-04-13 14:44:04 +02:00
jakob.scheid 16852f3d68 Add class for Python docstrings 2026-04-13 14:15:49 +02:00
jakob.scheid 7f02b5b7ec Add tests for core.python.arguments.PythonFunctionArgument.signature_repr 2026-04-13 12:24:52 +02:00
jakob.scheid a8626418da Add feature to get the signature representation of a Python function argument 2026-04-13 12:19:08 +02:00
jakob.scheid 10707d680d Update licenses 2026-04-13 10:45:05 +02:00
jakob.scheid 42a568223b Update licenses 2026-04-13 09:43:40 +02:00
jakob.scheid 72b1024a16 Bug fix: update NOTICE 2026-04-13 09:33:46 +02:00
jakob.scheid 3bc7953541 Bug fix: update NOTICE 2026-04-13 09:33:23 +02:00
jakob.scheid d5f9368d04 Bug fix: update pyproject.toml 2026-04-13 09:32:51 +02:00
jakob.scheid a79b538de8 Add class for Python modules 2026-04-13 09:24:36 +02:00
jakob.scheid 8c3866a948 Bug fix: remove invalid test cases 2026-04-12 19:40:32 +02:00
jakob.scheid ed3b47bd81 Bug fix: add support for default values in core.python.arguments.PythonASTArgumentsListParser.to_argument_list 2026-04-12 19:30:04 +02:00
jakob.scheid e4fe88b60b Remove core.python.arguments.PythonFunctionArgumentDefault; use strings instead 2026-04-12 19:13:39 +02:00
jakob.scheid 73730520c1 Remove core.python.arguments.PythonFunctionArgumentDefault; use strings instead 2026-04-12 18:54:51 +02:00
jakob.scheid 076e04a67a Add classes for Python definitions 2026-04-12 18:49:32 +02:00
jakob.scheid d6a35ea222 Add classes for Python definitions 2026-04-12 18:49:23 +02:00
jakob.scheid d8fd2b1948 Bug fix: core.python.arguments.PythonASTArgumentsListParser.to_argument_list failed if a type annotation is not a single name 2026-04-11 15:56:14 +02:00
jakob.scheid 29714798f7 Fix spelling mistakes 2026-04-11 15:21:34 +02:00
jakob.scheid 91cecae230 Add classes for Python function arguments 2026-04-11 13:33:42 +02:00
jakob.scheid d0e4ae54c3 Bug fix: remove debug output 2026-04-10 17:19:35 +02:00
jakob.scheid d3e022fb13 Add licenses to the tests 2026-04-10 12:37:59 +02:00
jakob.scheid 3e96072361 Add class for existing files 2026-04-09 19:52:46 +02:00
jakob.scheid a550ba6832 Add class for existing files 2026-04-09 19:51:39 +02:00
jakob.scheid 74cb599ed6 Bug fix: add .gitkeep files in the empty directory in tests/unit/utils/test_directory 2026-04-09 19:45:26 +02:00
jakob.scheid 9d86a64418 Bug fix: add .gitkeep files in every empty directory in tests/unit/core/python/_core/test_project_dirs 2026-04-09 19:40:31 +02:00
jakob.scheid 8f48f591e6 Convert project and docs directory to pathlib.Path objects at the constructor of core.python._core.PythonDocumentationGenerator 2026-04-09 19:39:15 +02:00
jakob.scheid ff45f836b3 Add module cli 2026-04-09 19:21:25 +02:00
jakob.scheid 9e2fe5f2df Change python package namespace sub namespaces to list 2026-04-09 17:32:54 +02:00
jakob.scheid 9e524bec03 Change python package namespace sub namespaces to list 2026-04-09 16:08:07 +02:00
jakob.scheid 512a07a773 Bug fix 2026-04-09 15:59:31 +02:00
jakob.scheid d0e2855b58 Add PythonDocumentationGenerator method to collect all namespaces 2026-04-08 14:24:44 +02:00
jakob.scheid 54e3a30ef0 Add string representation for python namespace objects 2026-04-08 14:13:12 +02:00
jakob.scheid 66f06960a9 Add feature to compare core.python.namespaces.PythonPackageNamespace instances or core.python.namespaces.PythonModuleNamespace instances 2026-04-08 13:33:12 +02:00
jakob.scheid 286ba68c34 Do not permit empty python namespace names 2026-04-08 13:28:48 +02:00
jakob.scheid 2cac1727e1 Add feature to get the names of all sub-namespaces of a core.python.namespaces.PythonPackageNamespace 2026-04-08 13:26:35 +02:00
jakob.scheid 0dcf34c068 Add tests for core.python.namespaces.PythonPackageNamespace 2026-04-07 22:58:29 +02:00
jakob.scheid 6cb5d25f5c Add checking whether sub namespaces are not empty in core.python.namespaces.PythonPackageNamespace.namespace 2026-04-07 22:57:18 +02:00
jakob.scheid fa75b6f036 Update docstring of core.python.namespaces.PythonPackageNamespace.namespace 2026-04-07 16:25:01 +02:00
jakob.scheid 1c0f0ba81a Bug fix: add exceptions for namespaces 2026-04-07 16:06:18 +02:00
jakob.scheid 45b488b8c2 Add tests for core.python.namespaces.PythonModuleNamespace 2026-04-07 16:03:23 +02:00
jakob.scheid f3523549a9 Add tests for core.python.namespaces.PythonNamespace 2026-04-07 16:03:06 +02:00
jakob.scheid e2a4db89ba Add tests for core.python.namespaces.PythonNamespace 2026-04-07 15:58:49 +02:00
jakob.scheid b0a0d3e45b Add type checking for the name attribute at the constructor of core.python.namespaces.PythonNamespace 2026-04-07 14:22:09 +02:00
jakob.scheid 0f817e961c Add python namespaces classes 2026-04-07 14:18:54 +02:00
jakob.scheid e2ebde7cc4 Add __all__ variable in core.python.namespaces 2026-04-07 14:17:54 +02:00
jakob.scheid 57a395727d Add tests for the PythonDocumentationGenerator class constructor type exceptions 2026-04-07 14:14:47 +02:00
jakob.scheid 6119c69a3c Add namespaces classes 2026-04-07 14:13:15 +02:00
jakob.scheid 6f23068e0b Change package structure 2026-04-07 14:11:48 +02:00
jakob.scheid 2f6e373ba7 Change package structure 2026-04-07 14:11:15 +02:00
jakob.scheid 5087645069 Add python project documentation generator class 2026-04-07 12:28:38 +02:00
jakob.scheid 4988491018 Add __init__.py 2026-04-07 12:26:31 +02:00
jakob.scheid 9af9e18304 Rename module _utils to utils 2026-04-07 12:23:30 +02:00
jakob.scheid 7f5430188d Add function to assert that an object is an instance of a specific type 2026-04-07 12:16:07 +02:00
jakob.scheid 47ee0f23b3 Add pyproject.toml 2026-04-07 12:15:14 +02:00
jakob.scheid 7423f19cfd Rename module _utils to utils 2026-04-07 12:14:43 +02:00
jakob.scheid 1a6208888e Add function to assert that an object is an instance of a specific type 2026-04-07 12:14:01 +02:00
jakob.scheid bdf7cb4af7 Add function to format a list human-readable 2026-04-06 19:04:09 +02:00
jakob.scheid 157c8712b2 Rename module _utils to utils 2026-04-06 13:36:25 +02:00
jakob.scheid cc346c983c Rename module _utils to utils 2026-04-06 12:45:33 +02:00
jakob.scheid d79a22e98f Add subclass of pathlib.Path for existing directories 2026-04-05 20:15:33 +02:00
jakob.scheid 00fbd89746 Add licenses 2026-04-05 20:06:05 +02:00
113 changed files with 18211 additions and 4 deletions

No files matched your search

@@ -0,0 +1,57 @@
# Copyright 2026 jCloud
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
name: Generate API reference
on:
push:
branches:
- main
permissions:
contents: write
jobs:
generate:
runs-on: python
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install jcloud-docsgen
run: |
pip install \
--index-url https://gitea.jcloud-services.ddns.net/api/packages/jCloud/pypi/simple/ \
--extra-index-url https://pypi.org/simple \
jcloud-docsgen
- name: Generate documentation
run: |
jcloud-docsgen python -T -f -o docs/reference ./src
- name: Commit and push
run: |
git config user.name "${{ vars.BOT_NAME }}"
git config user.email "${{ vars.BOT_EMAIL }}"
git add docs/reference
if git diff --cached --quiet; then
echo "No changes"
exit 0
fi
git commit -m "chore: update API reference"
git push
+120
View File
@@ -0,0 +1,120 @@
# Copyright 2026 jCloud
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
name: Release package
on:
release:
types:
- published
permissions:
contents: read
jobs:
build:
name: Build package
runs-on: python
steps:
- uses: actions/checkout@v4
- name: Build package
run: |
python3 -m build
- name: Upload artifact
uses: christopherhx/gitea-upload-artifact@v4
with:
name: build-output
path: |
dist/*.tar.gz
dist/*.whl
prepare_upload_release_asset:
name: Prepare release asset upload
runs-on: debian
needs: build
outputs:
files: ${{ steps.files.outputs.files }}
steps:
- name: Download artifact
uses: christopherhx/gitea-download-artifact@v4
with:
name: build-output
path: dist/
- id: files
shell: bash
run: |
files=$(node --input-type=module -e '
import { glob } from "node:fs/promises";
const files = await Array.fromAsync(glob([
"dist/*.tar.gz",
"dist/*.whl"
]));
console.log(JSON.stringify(files));
')
echo "files=$files" >> "$GITHUB_OUTPUT"
upload_release_asset:
name: Upload release asset
runs-on: debian
needs: [build, prepare_upload_release_asset]
strategy:
matrix:
file: ${{ fromJSON(needs.prepare_upload_release_asset.outputs.files) }}
steps:
- name: Download artifact
uses: christopherhx/gitea-download-artifact@v4
with:
name: build-output
path: dist/
- uses: ${{ gitea.server_url }}/jCloud/actions/upload-release-asset@60d1ae076c2e1802e4bc9461e26c42c557652811
with:
file: ${{ matrix.file }}
release: ${{ gitea.event.release.id }}
env:
GITEA_TOKEN: ${{ secrets.RELEASE_ASSET_TOKEN }}
publish:
if: ${{ !github.event.release.prerelease && !github.event.release.draft }}
name: Publish package to package registry
runs-on: python
needs: build
steps:
- name: Download artifact
uses: christopherhx/gitea-download-artifact@v4
with:
name: build-output
path: dist/
- name: Upload package
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.REGISTRY_TOKEN }}
run: |
python3 -m twine upload \
--repository-url ${{ gitea.server_url }}/api/packages/${{ gitea.repository_owner }}/pypi \
--disable-progress-bar \
dist/*
+175 -3
View File
@@ -1,5 +1,177 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
src/*.egg-info/
.vscode/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
__pycache__/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# UV
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
#uv.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
.pdm.toml
.pdm-python
.pdm-build/
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
# Ruff stuff:
.ruff_cache/
# PyPI configuration file
.pypirc
# VS Code
.vscode/
+10
View File
@@ -0,0 +1,10 @@
# ------------------------------------------------ SUBJECT AREA
# ---------------------------------------------------------------------- DETAIL AREA
# Please enter a subject that does not exceed the limit of 50
# characters.
# If you enter a detailed description, no line should
# exceed the limit of 72 characters. Break the line as soon as you
# reach this limit.
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright 2026 jCloud Services GbR
Copyright 2026 jCloud
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
+41
View File
@@ -0,0 +1,41 @@
Third-Party Notices
===================
This software uses the following third-party libraries. Full license texts are available in the 'licenses/' directory.
1. docstring-parser 0.17.0
Author: Marcin Kurczewski
License: MIT License
See licenses/docstring-parser.txt for full license text.
2. iniconfig 2.3.0
Author: Holger Krekel and others
License: MIT License
See licenses/iniconfig.txt for full license text.
3. packaging 26.0
Author: Donald Stufft and individual contributors
License: Apache License 2.0
URL: None
See licenses/packaging.txt for full license text.
4. pluggy 1.6.0
Author: Holger Krekel
License: MIT License
See licenses/pluggy.txt for full license text.
5. pygments 2.18.0
Author: Georg Brandl, Tim Hatch, Armin Ronacher, Others
License: BSD-2-Clause
See licenses/pygments.txt for full license text.
6. pytest 9.0.2
Author: Holger Krekel, Bruno Oliveira, Ronny Pfannschmidt, Floris Bruynooghe, Brianna Laugher, Florian Bruhin, Others
License: MIT License
See licenses/pytest.txt for full license text.
7. pytest-expectdir 1.2.0
Author: Léo Falventin Hauchecorne
License: MIT
URL: https://github.com/hl037/pytest-expectdir
See licenses/pytest-expectdir.txt for full license text.
+22
View File
@@ -0,0 +1,22 @@
# jcloud-docsgen
A tool that generates Markdown documentation from software project source code.
Currently supported programming languages:
- Python
## Installation
You can install the package `jcloud-docsgen` from the [Gitea Package Registry](https://gitea.jcloud-services.ddns.net/jCloud/-/packages/pypi/jcloud-docsgen/).
## Setup
Please execute `scripts/setup.sh` when cloning the repository to configure Git.
## Reference
API reference and generated documentation can be found in [docs/reference](docs/reference).
## Notice
This project is under active development. The documentation will be expanded over time.
+40
View File
@@ -0,0 +1,40 @@
# Changelog
## [Unreleased]
### Fixed
- Internal namespace name error which occured when generating Python documentation with `.` as the project directory
## [0.2.0] - 2026-09-28
### Added
- CLI for `jcloud-docsgen` that can be called without `python3 -m ...`
- CI pipeline to generate API reference on every push or merge on `main`
- Python generator output directory creation
- Protection against accidental overwriting of the output directory
### Changed
- Replaced the installation instructions, which used the old registry, with a reference to the Gitea Package Registry in README
- Made Python function signatures (argument default values) compliant with PEP 8
- Removed implicit Python project directory structure assumptions
### Deprecated
- utility class for existing directories
- utility class for existing files
## [0.1.0] - 2026-09-01
### Added
- utility class for existing directories
- utility class for existing files
- Python project documentation generator
- Python definition documentation generator
- Python module documentation generator
- Python namespaces
- signature representation of a Python function argument
- Python docstrings
@@ -0,0 +1 @@
# Module `jcloud_docsgen.__init__`
@@ -0,0 +1 @@
# Module `jcloud_docsgen.__main__`
@@ -0,0 +1 @@
# Module `jcloud_docsgen.cli.__init__`
+107
View File
@@ -0,0 +1,107 @@
# Module `jcloud_docsgen.cli.cli`
The command line interface
## Function `existing_directory`
### Function signature
```python
existing_directory(path: str) -> pathlib.Path
```
### Returns
Return type: `pathlib.Path`
### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `path` | `str` | normal | -- | -- |
## Function `error`
Shows an error message and returns the exit code.
### Function signature
```python
error(message: str, exit_code: int = 1)
```
### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `message` | `str` | normal | -- | The error message to show. |
| `exit_code` | `int` | normal | `1` | The exit code. |
### Docstring
Shows an error message and returns the exit code.
:param message: The error message to show.
:type message: str
:param exit_code: The exit code.
:type exit_code: int
## Function `create_argument_parser`
Prepares a parser to parse the command-line arguments using
argparse.
### Function signature
```python
create_argument_parser() -> argparse.ArgumentParser
```
### Returns
The parser.
Return type: `argparse.ArgumentParser`
### Docstring
Prepares a parser to parse the command-line arguments using
argparse.
:return: The parser.
:rtype: argparse.ArgumentParser
## Function `main`
### Function signature
```python
main(argv=None) -> int
```
### Returns
Return type: `int`
### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `argv` | -- | normal | `None` | -- |
@@ -0,0 +1 @@
# Module `jcloud_docsgen.cli.languages.__init__`
@@ -0,0 +1,156 @@
# Module `jcloud_docsgen.cli.languages.python`
## Function `parse_sections_list`
Returns a Python definition documentation sections list from the
sections list to be included and the sections list to be excluded.
These lists both consist of the sections as strings which contain
the section name.
### Function signature
```python
parse_sections_list(include_sections: list[str] | None = None, exclude_sections: list[str] = []) -> PythonDefinitionDocumentationIncludeSections
```
### Returns
The Python definition documentation sections object.
Return type: `PythonDefinitionDocumentationIncludeSections`
### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `include_sections` | `list[str] | None` | normal | `None` | The list of sections to be included. |
| `exclude_sections` | `list[str]` | normal | `[]` | The list of sections to be excluded. |
### Docstring
Returns a Python definition documentation sections list from the
sections list to be included and the sections list to be excluded.
These lists both consist of the sections as strings which contain
the section name.
:param include_sections: The list of sections to be included.
:type include_sections: list[str] | None
:param exclude_sections: The list of sections to be excluded.
:type exclude_sections: list[str]
:return: The Python definition documentation sections object.
:rtype: PythonDefinitionDocumentationIncludeSections
## Function `_generate`
Generates the documentation for a Python project.
### Function signature
```python
_generate(project_directory: pathlib.Path, output_directory: pathlib.Path, *, include_sections: PythonDefinitionDocumentationIncludeSections = PythonDefinitionDocumentationIncludeSections(), allow_html: bool = False, allow_tables: bool = False, force: bool = False) -> None
```
### Returns
Return type: `None`
### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `project_directory` | `pathlib.Path` | normal | -- | The project directory. |
| `output_directory` | `pathlib.Path` | normal | -- | The output directory. |
| `include_sections` | `PythonDefinitionDocumentationIncludeSections` | keyword-only | `PythonDefinitionDocumentationIncludeSections()` | The sections to be included in the documentation. |
| `allow_html` | `bool` | keyword-only | `False` | Indicates whether HTML is allowed to appear in the generated documentation. |
| `allow_tables` | `bool` | keyword-only | `False` | Indicates whether Markdown tables are allowed. Warning: Markdown tables are not CommonMark. |
| `force` | `bool` | keyword-only | `False` | Indicates whether the output directory shall be overwritten if it is not empty. |
### Docstring
Generates the documentation for a Python project.
:param project_directory: The project directory.
:type project_directory: pathlib.Path
:param output_directory: The output directory.
:type output_directory: pathlib.Path
:param include_sections: The sections to be included in the
documentation.
:type include_sections: PythonDefinitionDocumentationIncludeSections
:param allow_html: Indicates whether HTML is allowed to appear in
the generated documentation.
:type allow_html: bool
:param allow_tables: Indicates whether Markdown tables are allowed.
Warning: Markdown tables are not CommonMark.
:param force: Indicates whether the output directory shall be
overwritten if it is not empty.
:type force: bool
## Function `generate`
Generates the documentation for a Python project.
### Function signature
```python
generate(project_directory: pathlib.Path, output_directory: pathlib.Path, *, include_sections: list[str] | None = None, exclude_sections: list[str] = [], allow_html: bool = False, allow_tables: bool = False, force: bool = False) -> None
```
### Returns
Return type: `None`
### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `project_directory` | `pathlib.Path` | normal | -- | The project directory. |
| `output_directory` | `pathlib.Path` | normal | -- | The output directory. |
| `include_sections` | `list[str] | None` | keyword-only | `None` | The sections to be included in the documentation. |
| `exclude_sections` | `list[str]` | keyword-only | `[]` | The sections to be excluded in the documentation. |
| `allow_html` | `bool` | keyword-only | `False` | Indicates whether HTML is allowed to appear in the generated documentation. |
| `allow_tables` | `bool` | keyword-only | `False` | Indicates whether Markdown tables are allowed. Warning: Markdown tables are not CommonMark. |
| `force` | `bool` | keyword-only | `False` | Indicates whether the output directory shall be overwritten if it is not empty. |
### Docstring
Generates the documentation for a Python project.
:param project_directory: The project directory.
:type project_directory: pathlib.Path
:param output_directory: The output directory.
:type output_directory: pathlib.Path
:param include_sections: The sections to be included in the
documentation.
:type include_sections: list[str] | None
:param exclude_sections: The sections to be excluded in the
documentation.
:type exclude_sections: list[str]
:param allow_html: Indicates whether HTML is allowed to appear in
the generated documentation.
:type allow_html: bool
:param allow_tables: Indicates whether Markdown tables are allowed.
Warning: Markdown tables are not CommonMark.
:param force: Indicates whether the output directory shall be
overwritten if it is not empty.
:type force: bool
@@ -0,0 +1 @@
# Module `jcloud_docsgen.core.__init__`
@@ -0,0 +1 @@
# Module `jcloud_docsgen.core.python.__init__`
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,205 @@
# Module `jcloud_docsgen.core.python.arguments`
## Class `PythonArgumentKind`
### Base classes
- `Enum`
## Class `PythonFunctionArgument`
Represents an argument of a Python function.
### Body
#### Constructor (method `__init__`)
##### Method signature
```python
__init__(self, name: str, kind: PythonArgumentKind, default: str | None, annotation: str | None) -> None
```
##### Returns
Return type: `None`
##### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `self` | -- | normal | -- | -- |
| `name` | `str` | normal | -- | -- |
| `kind` | `PythonArgumentKind` | normal | -- | -- |
| `default` | `str | None` | normal | -- | -- |
| `annotation` | `str | None` | normal | -- | -- |
#### Method `__eq__`
##### Method signature
```python
__eq__(self, value: PythonFunctionArgument) -> bool
```
##### Returns
Return type: `bool`
##### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `self` | -- | normal | -- | -- |
| `value` | `PythonFunctionArgument` | normal | -- | -- |
#### Method `__repr__`
##### Method signature
```python
__repr__(self) -> str
```
##### Returns
Return type: `str`
##### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `self` | -- | normal | -- | -- |
#### Method `signature_repr`
Returns the function signature representation of the argument.
##### Method signature
```python
signature_repr(self) -> str
```
##### Returns
The string representation of the argument.
Return type: `str`
##### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `self` | -- | normal | -- | -- |
##### Docstring
Returns the function signature representation of the argument.
:return: The string representation of the argument.
:rtype: str
### Docstring
Represents an argument of a Python function.
:param name: The name of the argument.
:type name: str
:param kind: The kind of the argument.
:type kind: ArgumentKind
:param default: The default value of the argument.
:type default: str | None
:param annotation: The type annotation of the argument.
:type annotation: str | None
## Class `PythonASTArgumentsListParser`
A parser for making ``PythonFunctionArgument`` objects from an
``ast.arguments`` arguments list.
### Body
#### Constructor (method `__init__`)
##### Method signature
```python
__init__(self, ast_arguments_list: ast.arguments) -> None
```
##### Returns
Return type: `None`
##### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `self` | -- | normal | -- | -- |
| `ast_arguments_list` | `ast.arguments` | normal | -- | -- |
#### Method `to_argument_list`
Converts the AST arguments list to a list of Python function
argument objects.
##### Method signature
```python
to_argument_list(self) -> list[PythonFunctionArgument]
```
##### Returns
The list of Python function argument objects.
Return type: `list[PythonFunctionArgument]`
##### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `self` | -- | normal | -- | -- |
##### Docstring
Converts the AST arguments list to a list of Python function
argument objects.
:return: The list of Python function argument objects.
:rtype: list[PythonFunctionArgument]
### Docstring
A parser for making ``PythonFunctionArgument`` objects from an
``ast.arguments`` arguments list.
:param ast_arguments_list: The ``ast.arguments`` arguments list.
:type ast_arguments_list: ast.arguments
@@ -0,0 +1,445 @@
# Module `jcloud_docsgen.core.python.definitions`
## Function `_python_docstring`
Returns the PythonDocstring object if the docstring is a string and
not ``None``.
### Function signature
```python
_python_docstring(doc: str | None) -> PythonDocstring | None
```
### Returns
The PythonDocstring object or ``None``.
Return type: `PythonDocstring | None`
### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `doc` | `str | None` | normal | -- | The docstring. |
### Docstring
Returns the PythonDocstring object if the docstring is a string and
not ``None``.
:param doc: The docstring.
:type doc: str | None
:return: The PythonDocstring object or ``None``.
:rtype: PythonDocstring | None
## Class `PythonDefinition`
A base class for Python definition (a class, function or asynchronous
function definition) classes.
### Body
#### Constructor (method `__init__`)
##### Method signature
```python
__init__(self, name: str, doc: PythonDocstring | None, decorators: list[str]) -> None
```
##### Returns
Return type: `None`
##### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `self` | -- | normal | -- | -- |
| `name` | `str` | normal | -- | -- |
| `doc` | `PythonDocstring | None` | normal | -- | -- |
| `decorators` | `list[str]` | normal | -- | -- |
#### Method `_arg_list`
##### Method signature
```python
_arg_list(self) -> tuple
```
##### Returns
Return type: `tuple`
##### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `self` | -- | normal | -- | -- |
#### Method `__repr__`
##### Method signature
```python
__repr__(self) -> str
```
##### Returns
Return type: `str`
##### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `self` | -- | normal | -- | -- |
#### Method `from_node`
From an AST node.
##### Method signature
```python
from_node(cls, node: ast.stmt) -> PythonDefinition
```
##### Returns
Return type: `PythonDefinition`
##### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `cls` | -- | normal | -- | -- |
| `node` | `ast.stmt` | normal | -- | The AST node. |
##### Decorators
```python
classmethod
```
##### Docstring
From an AST node.
:param node: The AST node.
:type node: ast.stmt
#### Method `__eq__`
##### Method signature
```python
__eq__(self, value: object) -> bool
```
##### Returns
Return type: `bool`
##### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `self` | -- | normal | -- | -- |
| `value` | `object` | normal | -- | -- |
### Docstring
A base class for Python definition (a class, function or asynchronous
function definition) classes.
:param name: The name of the definition.
:type name: str
:param doc: The docstring.
:type doc: PythonDocstring | None
:param decorators: The definition decorators.
:type decorators: list[str]
:raises InvalidPythonIdentifierError: If the name is not a valid
Python identifier.
## Class `PythonFunctionDefinition`
A PythonDefinition subclass representing a Python function
definition.
### Base classes
- `PythonDefinition`
### Body
#### Constructor (method `__init__`)
##### Method signature
```python
__init__(self, name: str, args: list[PythonFunctionArgument], returns: str | None, doc: PythonDocstring | None, decorators: list[str]) -> None
```
##### Returns
Return type: `None`
##### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `self` | -- | normal | -- | -- |
| `name` | `str` | normal | -- | -- |
| `args` | `list[PythonFunctionArgument]` | normal | -- | -- |
| `returns` | `str | None` | normal | -- | -- |
| `doc` | `PythonDocstring | None` | normal | -- | -- |
| `decorators` | `list[str]` | normal | -- | -- |
#### Method `_arg_list`
##### Method signature
```python
_arg_list(self) -> tuple
```
##### Returns
Return type: `tuple`
##### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `self` | -- | normal | -- | -- |
#### Method `from_node`
From an AST node.
##### Method signature
```python
from_node(cls, node: ast.stmt) -> PythonFunctionDefinition
```
##### Returns
Return type: `PythonFunctionDefinition`
##### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `cls` | -- | normal | -- | -- |
| `node` | `ast.stmt` | normal | -- | The AST node. |
##### Decorators
```python
classmethod
```
##### Docstring
From an AST node.
:param node: The AST node.
:type node: ast.stmt
### Docstring
A PythonDefinition subclass representing a Python function
definition.
:param name: The name of the definition.
:type name: str
:param args: The arguments.
:type args: list[PythonFunctionArgument]
:param returns: The return type of the function or class.
:type returns: str | None
:param doc: The docstring.
:type doc: PythonDocstring | None
:param decorators: The definition decorators.
:type decorators: list[str]
:raises InvalidPythonIdentifierError: If the name is not a valid
Python identifier.
## Class `PythonAsyncFunctionDefinition`
A PythonDefinition subclass representing an asynchronous Python function
definition.
### Base classes
- `PythonFunctionDefinition`
### Docstring
A PythonDefinition subclass representing an asynchronous Python function
definition.
:param name: The name of the definition.
:type name: str
:param args: The arguments.
:type args: list[PythonFunctionArgument]
:param returns: The return type of the function or class.
:type returns: str | None
:param doc: The docstring.
:type doc: PythonDocstring | None
:param decorators: The definition decorators.
:type decorators: list[str]
:raises InvalidPythonIdentifierError: If the name is not a valid
Python identifier.
## Class `PythonClassDefinition`
A PythonDefinition subclass representing a Python class definition.
### Base classes
- `PythonDefinition`
### Body
#### Constructor (method `__init__`)
##### Method signature
```python
__init__(self, name: str, bases: list[str], doc: PythonDocstring | None, decorators: list[str], body: list[PythonDefinition]) -> None
```
##### Returns
Return type: `None`
##### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `self` | -- | normal | -- | -- |
| `name` | `str` | normal | -- | -- |
| `bases` | `list[str]` | normal | -- | -- |
| `doc` | `PythonDocstring | None` | normal | -- | -- |
| `decorators` | `list[str]` | normal | -- | -- |
| `body` | `list[PythonDefinition]` | normal | -- | -- |
#### Method `_arg_list`
##### Method signature
```python
_arg_list(self) -> tuple
```
##### Returns
Return type: `tuple`
##### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `self` | -- | normal | -- | -- |
#### Method `from_node`
From an AST node.
##### Method signature
```python
from_node(cls, node: ast.stmt) -> PythonClassDefinition
```
##### Returns
Return type: `PythonClassDefinition`
##### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `cls` | -- | normal | -- | -- |
| `node` | `ast.stmt` | normal | -- | The AST node. |
##### Decorators
```python
classmethod
```
##### Docstring
From an AST node.
:param node: The AST node.
:type node: ast.stmt
### Docstring
A PythonDefinition subclass representing a Python class definition.
:param name: The name of the definition.
:type name: str
:param bases: The base classes.
:type bases: list[str]
:param doc: The docstring.
:type doc: PythonDocstring | None
:param decorators: The definition decorators.
:type decorators: list[str]
:param body:
:type body: list[PythonDefinition]
:raises InvalidPythonIdentifierError: If the name is not a valid
Python identifier.
@@ -0,0 +1,113 @@
# Module `jcloud_docsgen.core.python.docstrings`
## Class `PythonDocstring`
Represents a Python docstring.
### Body
#### Constructor (method `__init__`)
##### Method signature
```python
__init__(self, docstring: str) -> None
```
##### Returns
Return type: `None`
##### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `self` | -- | normal | -- | -- |
| `docstring` | `str` | normal | -- | -- |
#### Method `__repr__`
##### Method signature
```python
__repr__(self) -> str
```
##### Returns
Return type: `str`
##### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `self` | -- | normal | -- | -- |
#### Method `__eq__`
##### Method signature
```python
__eq__(self, value) -> bool
```
##### Returns
Return type: `bool`
##### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `self` | -- | normal | -- | -- |
| `value` | -- | normal | -- | -- |
#### Method `parse`
Parses the docstring.
##### Method signature
```python
parse(self) -> docstring_parser.Docstring
```
##### Returns
The parsed docstring.
Return type: `docstring_parser.Docstring`
##### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `self` | -- | normal | -- | -- |
##### Docstring
Parses the docstring.
:return: The parsed docstring.
:rtype: docstring_parser.Docstring
### Docstring
Represents a Python docstring.
:param docstring: The docstring as a string.
:type docstring: str
@@ -0,0 +1,442 @@
# Module `jcloud_docsgen.core.python.namespaces`
## Class `PythonNamespace`
The base class for all Python namespaces such as modules or packages.
### Body
#### Constructor (method `__init__`)
##### Method signature
```python
__init__(self, name: str) -> None
```
##### Returns
Return type: `None`
##### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `self` | -- | normal | -- | -- |
| `name` | `str` | normal | -- | -- |
#### Method `__repr__`
##### Method signature
```python
__repr__(self) -> str
```
##### Returns
Return type: `str`
##### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `self` | -- | normal | -- | -- |
### Docstring
The base class for all Python namespaces such as modules or packages.
:param name: The name of the namespace.
:type name: str
## Class `PythonPackageNamespace`
A subclass of ``PythonNamespace`` for Python package namespaces.
### Base classes
- `PythonNamespace`
### Body
#### Constructor (method `__init__`)
##### Method signature
```python
__init__(self, name: str, sub_namespaces: list[PythonNamespace]) -> None
```
##### Returns
Return type: `None`
##### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `self` | -- | normal | -- | -- |
| `name` | `str` | normal | -- | -- |
| `sub_namespaces` | `list[PythonNamespace]` | normal | -- | -- |
#### Method `namespace`
Returns the namespace object with a specific identifier, such as
``['package', 'subpackage', 'module']``.
##### Method signature
```python
namespace(self, sub_namespaces: list[str]) -> PythonNamespace
```
##### Returns
The namespace.
Return type: `PythonNamespace`
##### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `self` | -- | normal | -- | -- |
| `sub_namespaces` | `list[str]` | normal | -- | The identifier of the namespace. |
##### Exceptions
| Exception | Description |
| --- | --- |
| `InvalidNamespaceError` | If the namespace identifier is invalid. |
| `NamespaceNotFoundError` | If the namespace does not exist. |
##### Docstring
Returns the namespace object with a specific identifier, such as
``['package', 'subpackage', 'module']``.
:param sub_namespaces: The identifier of the namespace.
:type sub_namespaces: list[str]
:raises InvalidNamespaceError: If the namespace identifier is
invalid.
:raises NamespaceNotFoundError: If the namespace does not exist.
:return: The namespace.
:rtype: PythonNamespace
#### Method `_sub_namespaces_names`
Returns the names of all sub-namespaces of the specified package
namespace and keeps the package structure.
##### Method signature
```python
_sub_namespaces_names(self, package: PythonPackageNamespace) -> PythonPackageNamespaceDictType
```
##### Returns
The names if all sub-namespaces.
Return type: `PythonPackageNamespaceDictType`
##### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `self` | -- | normal | -- | -- |
| `package` | `PythonPackageNamespace` | normal | -- | The package. |
##### Docstring
Returns the names of all sub-namespaces of the specified package
namespace and keeps the package structure.
:param package: The package.
:type package: PythonPackageNamespace
:return: The names if all sub-namespaces.
:rtype: PythonPackageNamespaceDictType
#### Method `sub_namespace_names`
Returns the names of all sub-namespaces and keeps the package
structure.
##### Method signature
```python
sub_namespace_names(self) -> PythonPackageNamespaceDictType
```
##### Returns
The names if all sub-namespaces.
Return type: `PythonPackageNamespaceDictType`
##### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `self` | -- | normal | -- | -- |
##### Docstring
Returns the names of all sub-namespaces and keeps the package
structure.
:return: The names if all sub-namespaces.
:rtype: PythonPackageNamespaceDictType
#### Method `__eq__`
##### Method signature
```python
__eq__(self, value: PythonPackageNamespace) -> bool
```
##### Returns
Return type: `bool`
##### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `self` | -- | normal | -- | -- |
| `value` | `PythonPackageNamespace` | normal | -- | -- |
#### Method `__repr__`
##### Method signature
```python
__repr__(self) -> str
```
##### Returns
Return type: `str`
##### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `self` | -- | normal | -- | -- |
#### Method `__getitem__`
Returns a namespace with the specified name.
##### Method signature
```python
__getitem__(self, name: str) -> PythonNamespace
```
##### Returns
The namespace
Return type: `PythonNamespace`
##### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `self` | -- | normal | -- | -- |
| `name` | `str` | normal | -- | The name of the namespace |
##### Exceptions
| Exception | Description |
| --- | --- |
| `NamespaceNotFoundError` | If the namespace does not exist. |
##### Docstring
Returns a namespace with the specified name.
:param name: The name of the namespace
:type name: str
:raises NamespaceNotFoundError: If the namespace does not exist.
:return: The namespace
:rtype: PythonNamespace
#### Method `add`
Adds a namespace.
##### Method signature
```python
add(self, namespace: PythonNamespace) -> None
```
##### Returns
Return type: `None`
##### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `self` | -- | normal | -- | -- |
| `namespace` | `PythonNamespace` | normal | -- | The namespace to add. |
##### Exceptions
| Exception | Description |
| --- | --- |
| `NamespaceExistsError` | If a namespace with the name of the namespace that is attempted to add already exists. |
##### Docstring
Adds a namespace.
:param namespace: The namespace to add.
:type namespace: PythonNamespace
:raises NamespaceExistsError: If a namespace with the name of the
namespace that is attempted to add
already exists.
#### Method `sub_namespaces`
##### Method signature
```python
sub_namespaces(self) -> list[PythonNamespace]
```
##### Returns
Return type: `list[PythonNamespace]`
##### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `self` | -- | normal | -- | -- |
##### Decorators
```python
property
```
### Docstring
A subclass of ``PythonNamespace`` for Python package namespaces.
:param name: The name of the namespace.
:type name: str
:param sub_namespaces: The sub-namespaces.
:type sub_namespaces: list[PythonNamespace]
## Class `PythonModuleNamespace`
A subclass of ``PythonNamespace`` for Python module namespaces.
### Base classes
- `PythonNamespace`
### Body
#### Constructor (method `__init__`)
##### Method signature
```python
__init__(self, name: str) -> None
```
##### Returns
Return type: `None`
##### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `self` | -- | normal | -- | -- |
| `name` | `str` | normal | -- | -- |
#### Method `__eq__`
##### Method signature
```python
__eq__(self, value: PythonModuleNamespace) -> bool
```
##### Returns
Return type: `bool`
##### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `self` | -- | normal | -- | -- |
| `value` | `PythonModuleNamespace` | normal | -- | -- |
### Docstring
A subclass of ``PythonNamespace`` for Python module namespaces.
:param name: The name of the namespace.
:type name: str
+290
View File
@@ -0,0 +1,290 @@
# Module `jcloud_docsgen.exceptions`
## Class `NamespaceError`
Base class for namespace errors.
### Base classes
- `ValueError`
### Body
#### Constructor (method `__init__`)
##### Method signature
```python
__init__(self, *args: object, namespace_identifier: str = '') -> None
```
##### Returns
Return type: `None`
##### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `self` | -- | normal | -- | -- |
| `args` | `object` | vararg | -- | -- |
| `namespace_identifier` | `str` | keyword-only | `''` | -- |
#### Method `__str__`
##### Method signature
```python
__str__(self)
```
##### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `self` | -- | normal | -- | -- |
### Docstring
Base class for namespace errors.
## Class `InvalidNamespaceError`
### Base classes
- `NamespaceError`
## Class `NamespaceNotFoundError`
### Base classes
- `NamespaceError`
## Class `NamespaceExistsError`
### Base classes
- `NamespaceError`
## Class `PythonIdentifierError`
Base class for Python identifier errors.
### Base classes
- `ValueError`
### Body
#### Constructor (method `__init__`)
##### Method signature
```python
__init__(self, *args: object, identifier: str = '') -> None
```
##### Returns
Return type: `None`
##### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `self` | -- | normal | -- | -- |
| `args` | `object` | vararg | -- | -- |
| `identifier` | `str` | keyword-only | `''` | -- |
#### Method `__str__`
##### Method signature
```python
__str__(self)
```
##### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `self` | -- | normal | -- | -- |
### Docstring
Base class for Python identifier errors.
## Class `InvalidPythonIdentifierError`
### Base classes
- `PythonIdentifierError`
## Class `PythonAnnotationError`
Base class for Python annotation errors.
### Base classes
- `ValueError`
### Body
#### Constructor (method `__init__`)
##### Method signature
```python
__init__(self, *args: object, annotation: str = '') -> None
```
##### Returns
Return type: `None`
##### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `self` | -- | normal | -- | -- |
| `args` | `object` | vararg | -- | -- |
| `annotation` | `str` | keyword-only | `''` | -- |
#### Method `__str__`
##### Method signature
```python
__str__(self)
```
##### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `self` | -- | normal | -- | -- |
### Docstring
Base class for Python annotation errors.
## Class `InvalidPythonAnnotationError`
### Base classes
- `PythonAnnotationError`
## Class `PythonDefinitionError`
Base class for Python definition errors.
### Base classes
- `ValueError`
### Body
#### Constructor (method `__init__`)
##### Method signature
```python
__init__(self, *args: object, definition: str = '') -> None
```
##### Returns
Return type: `None`
##### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `self` | -- | normal | -- | -- |
| `args` | `object` | vararg | -- | -- |
| `definition` | `str` | keyword-only | `''` | -- |
#### Method `__str__`
##### Method signature
```python
__str__(self)
```
##### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `self` | -- | normal | -- | -- |
### Docstring
Base class for Python definition errors.
## Class `PythonArgumentStructureError`
### Base classes
- `PythonDefinitionError`
## Class `DirectoryNotEmptyError`
Directory is not empty.
### Base classes
- `OSError`
### Body
#### Constructor (method `__init__`)
##### Method signature
```python
__init__(self, filename: str | None = None) -> None
```
##### Returns
Return type: `None`
##### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `self` | -- | normal | -- | -- |
| `filename` | `str | None` | normal | `None` | -- |
### Docstring
Directory is not empty.
+343
View File
@@ -0,0 +1,343 @@
# Module `jcloud_docsgen.utils`
The utilities for jcloud_docsgen
## Class `ExistingDirectory`
pathlib.Path subclass that is a directory.
ExistingDirectory represents a filesystem path but it also checks
whether the path exists and is not a file but a directory. It
inherits from pathlib.Path.
### Base classes
- `pathlib.Path`
### Body
#### Constructor (method `__init__`)
##### Method signature
```python
__init__(self, path, *args, **kwargs) -> None
```
##### Returns
Return type: `None`
##### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `self` | -- | normal | -- | -- |
| `path` | -- | normal | -- | -- |
| `args` | -- | vararg | -- | -- |
| `kwargs` | -- | kwargs | -- | -- |
### Decorators
```python
deprecated('Use pathlib.Path instead.')
```
### Docstring
pathlib.Path subclass that is a directory.
ExistingDirectory represents a filesystem path but it also checks
whether the path exists and is not a file but a directory. It
inherits from pathlib.Path.
## Class `ExistingFile`
pathlib.Path subclass that is a file.
ExistingFile represents a filesystem path but it also checks whether
the path exists and is not a directory but a file. It inherits from
pathlib.Path.
### Base classes
- `pathlib.Path`
### Body
#### Constructor (method `__init__`)
##### Method signature
```python
__init__(self, path, *args, **kwargs) -> None
```
##### Returns
Return type: `None`
##### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `self` | -- | normal | -- | -- |
| `path` | -- | normal | -- | -- |
| `args` | -- | vararg | -- | -- |
| `kwargs` | -- | kwargs | -- | -- |
### Decorators
```python
deprecated('Use pathlib.Path instead.')
```
### Docstring
pathlib.Path subclass that is a file.
ExistingFile represents a filesystem path but it also checks whether
the path exists and is not a directory but a file. It inherits from
pathlib.Path.
## Function `_quote`
Quotes a string.
### Function signature
```python
_quote(string: str, quotation_mark: str) -> str
```
### Returns
The quoted string
Return type: `str`
### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `string` | `str` | normal | -- | The string to quote |
| `quotation_mark` | `str` | normal | -- | The quotation mark |
### Docstring
Quotes a string.
:param string: The string to quote
:type string: str
:param quotation_mark: The quotation mark
:type quotation_mark: str
:return: The quoted string
:rtype: str
## Function `human_readable_list`
Generates a human-readable list from a list.
### Function signature
```python
human_readable_list(ls: list, final_separator: str = 'and', quotation_mark: str = '') -> str
```
### Returns
The human-readable list.
Return type: `str`
### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `ls` | `list` | normal | -- | The list. |
| `final_separator` | `str` | normal | `'and'` | The seperator between the last and the second-to-last element of the list. Default is 'and'. |
| `quotation_mark` | `str` | normal | `''` | The characters that are inserted before and after an element. Default is ''. |
### Docstring
Generates a human-readable list from a list.
:param ls: The list.
:type ls: list
:param final_separator: The seperator between the last and the
second-to-last element of the list. Default
is 'and'.
:type final_separator: str
:param quotation_mark: The characters that are inserted before and
after an element. Default is ''.
:return: The human-readable list.
:rtype: str
## Function `_list_type_names`
Converts a list of types into a list of their names (``__name__``
attribute)
### Function signature
```python
_list_type_names(types: list[type]) -> list[str]
```
### Returns
The list of the names of the types
Return type: `list[str]`
### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `types` | `list[type]` | normal | -- | The list of types |
### Docstring
Converts a list of types into a list of their names (``__name__``
attribute)
:param types: The list of types
:type types: list[type]
:return: The list of the names of the types
:rtype: list[str]
## Function `assert_that_is_instance`
### Function signature
```python
assert_that_is_instance(obj: object, class_or_tuple: type | types.UnionType | tuple[type, ...]) -> None
```
### Returns
Return type: `None`
### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `obj` | `object` | normal | -- | -- |
| `class_or_tuple` | `type | types.UnionType | tuple[type, ...]` | normal | -- | -- |
## Function `non_empty_str`
Returns the value or ``None``.
Returns the value if it is not empty and not `Ǹone``; otherwise,
returns ``None``.
Please note: values are stripped, i. e. if the value consists only
of spaces, it is also considered empty.
### Function signature
```python
non_empty_str(value: str | None) -> str | None
```
### Returns
The value or ``None``.
Return type: `str | None`
### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `value` | `str | None` | normal | -- | The value. |
### Docstring
Returns the value or ``None``.
Returns the value if it is not empty and not `Ǹone``; otherwise,
returns ``None``.
Please note: values are stripped, i. e. if the value consists only
of spaces, it is also considered empty.
:param value: The value.
:type value: str | None
:return: The value or ``None``.
:rtype: str | None
## Function `get_relative_path_segments`
Returns the path segments relative to ``root``.
### Function signature
```python
get_relative_path_segments(path: pathlib.Path, root: pathlib.Path) -> list[str]
```
### Returns
The path segments relative to ``root``.
Return type: `list[str]`
### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `path` | `pathlib.Path` | normal | -- | The path. |
| `root` | `pathlib.Path` | normal | -- | The root path. |
### Docstring
Returns the path segments relative to ``root``.
:param path: The path.
:type path: pathlib.Path
:param root: The root path.
:type root: pathlib.Path
:return: The path segments relative to ``root``.
:rtype: list[str]
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2018 Marcin Kurczewski
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2010 - 2023 Holger Krekel and others
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+177
View File
@@ -0,0 +1,177 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015 holger krekel (rather uses bitbucket/hpk42)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+25
View File
@@ -0,0 +1,25 @@
Copyright (c) 2006-2022 by the respective authors (see AUTHORS file).
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+19
View File
@@ -0,0 +1,19 @@
This is the MIT license: http://www.opensource.org/licenses/mit-license.php
Copyright (c) 2015-2022 the pytest-datadir authors and contributors <see AUTHORS file>.
Permission is hereby granted, free of charge, to any person obtaining a copy of this
software and associated documentation files (the "Software"), to deal in the Software
without restriction, including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2004 Holger Krekel and others
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Generated
+248
View File
@@ -0,0 +1,248 @@
# This file is automatically @generated by Poetry 2.4.3 and should not be changed by hand.
[[package]]
name = "colorama"
version = "0.4.6"
description = "Cross-platform colored terminal text."
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
groups = ["dev"]
markers = "sys_platform == \"win32\""
files = [
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
]
[[package]]
name = "docstring-parser"
version = "0.18.0"
description = "Parse Python docstrings in reST, Google and Numpydoc format"
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "docstring_parser-0.18.0-py3-none-any.whl", hash = "sha256:b3fcbed555c47d8479be0796ef7e19c2670d428d72e96da63f3a40122860374b"},
{file = "docstring_parser-0.18.0.tar.gz", hash = "sha256:292510982205c12b1248696f44959db3cdd1740237a968ea1e2e7a900eeb2015"},
]
[package.extras]
dev = ["pre-commit (>=2.16.0) ; python_version >= \"3.9\"", "pydoctor (>=25.4.0)", "pytest"]
docs = ["pydoctor (>=25.4.0)"]
test = ["pytest"]
[[package]]
name = "exceptiongroup"
version = "1.3.1"
description = "Backport of PEP 654 (exception groups)"
optional = false
python-versions = ">=3.7"
groups = ["dev"]
markers = "python_version == \"3.10\""
files = [
{file = "exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598"},
{file = "exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219"},
]
[package.dependencies]
typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""}
[package.extras]
test = ["pytest (>=6)"]
[[package]]
name = "iniconfig"
version = "2.3.0"
description = "brain-dead simple config-ini parsing"
optional = false
python-versions = ">=3.10"
groups = ["dev"]
files = [
{file = "iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"},
{file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"},
]
[[package]]
name = "packaging"
version = "26.3"
description = "Core utilities for Python packages"
optional = false
python-versions = ">=3.9"
groups = ["dev"]
files = [
{file = "packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c"},
{file = "packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79"},
]
[[package]]
name = "pluggy"
version = "1.6.0"
description = "plugin and hook calling mechanisms for python"
optional = false
python-versions = ">=3.9"
groups = ["dev"]
files = [
{file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"},
{file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"},
]
[package.extras]
dev = ["pre-commit", "tox"]
testing = ["coverage", "pytest", "pytest-benchmark"]
[[package]]
name = "pygments"
version = "2.21.0"
description = "Pygments is a syntax highlighting package written in Python."
optional = false
python-versions = ">=3.9"
groups = ["dev"]
files = [
{file = "pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9"},
{file = "pygments-2.21.0.tar.gz", hash = "sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c"},
]
[package.extras]
windows-terminal = ["colorama (>=0.4.6)"]
[[package]]
name = "pytest"
version = "9.1.1"
description = "pytest: simple powerful testing with Python"
optional = false
python-versions = ">=3.10"
groups = ["dev"]
files = [
{file = "pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c"},
{file = "pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313"},
]
[package.dependencies]
colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""}
exceptiongroup = {version = ">=1", markers = "python_version < \"3.11\""}
iniconfig = ">=1.0.1"
packaging = ">=22"
pluggy = ">=1.5,<2"
pygments = ">=2.7.2"
tomli = {version = ">=1", markers = "python_version < \"3.11\""}
[package.extras]
dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"]
[[package]]
name = "pytest-expectdir"
version = "1.2.0"
description = "A pytest plugin to provide initial/expected directories, and check a test transforms the initial directory to the expected one"
optional = false
python-versions = ">=3.7"
groups = ["dev"]
files = [
{file = "pytest-expectdir-1.2.0.tar.gz", hash = "sha256:145b7f86d4e8ae7c7cace3007f32d68afbdd4d18c392b3d5792794f8e9d51fae"},
{file = "pytest_expectdir-1.2.0-py3-none-any.whl", hash = "sha256:8fa19e7c0b69d6bfd0e81c6e33c9a8283eb14a7bab8408067e430a827a03f993"},
]
[package.dependencies]
pytest = ">=5.0"
[[package]]
name = "ruff"
version = "0.16.8"
description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false
python-versions = ">=3.7"
groups = ["dev"]
files = [
{file = "ruff-0.16.8-py3-none-linux_armv6l.whl", hash = "sha256:6ffbd6d87383c1edf5f6fa890f10200950240d7c1a16052a19a09d3a2307dd38"},
{file = "ruff-0.16.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:42ed6b878ed61e3acca92f2730a17acff39286944ea82398544696366a6f925e"},
{file = "ruff-0.16.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7ea781c7f2afba8c6a505ea0fb3f994020249e0c450635f5381286fea6b46170"},
{file = "ruff-0.16.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8efeae3bbe414a5efefda11a792dfb51ef90ac48d50c4830de2f644caf3e8659"},
{file = "ruff-0.16.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a79b795469fef7fc6e908b218eed2eb17332afd85031db6480dc864560e69b2"},
{file = "ruff-0.16.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3fdc5563cdc50555e6fba39322850860e9267c1b3d12c26a74729d8604c3c812"},
{file = "ruff-0.16.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:34508983c70665578dab88f5223d8e6228307e1135398ca8bfc8b7e9501e282b"},
{file = "ruff-0.16.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:644bb578569e0ffc575741232bd385dacdd6fbe123f1a729e7a225f54aa3957f"},
{file = "ruff-0.16.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15e7d226246961db9235098333caa13063906d3851136b84c2900b82f5daa1df"},
{file = "ruff-0.16.8-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:a2bf6bc3e9ebdd4449abc6f06cf64b98051a2c61cf94d2fe9596518c881f1a1e"},
{file = "ruff-0.16.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:6ca111ba0849539165e9e59d2b442542f3c1e8060ebbdea82494f1ffbccb1e1f"},
{file = "ruff-0.16.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:359a1e5b495448ee1e91018064382ebc86f90e8aac2fed222c7d0e4e8df85fd2"},
{file = "ruff-0.16.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:59e8f5681349474110b24d62e93cfda6593f5fa3473446ca3705200cac1a08b9"},
{file = "ruff-0.16.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:efa3e7a16d1baaa79957888dfdf8be9ef2e44db81cb032af06d76632ab59e773"},
{file = "ruff-0.16.8-py3-none-win32.whl", hash = "sha256:55793ba85c69921e89be061426d91a78652d6e50317c962240922747a4eb713f"},
{file = "ruff-0.16.8-py3-none-win_amd64.whl", hash = "sha256:a6b85621fd3c81e31fc5f5add09c9c078b430db3595ca632efafdec9e64ebfaa"},
{file = "ruff-0.16.8-py3-none-win_arm64.whl", hash = "sha256:d075e820af612102ce217f07cc93e69f9490b10ec13ea85fa87bd03d996cef8a"},
{file = "ruff-0.16.8.tar.gz", hash = "sha256:9247bf92b5f04d825c8639a4fe423ec2e4222acd9222e58412b0dab7e442798b"},
]
[[package]]
name = "tomli"
version = "2.4.1"
description = "A lil' TOML parser"
optional = false
python-versions = ">=3.8"
groups = ["dev"]
markers = "python_version == \"3.10\""
files = [
{file = "tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30"},
{file = "tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a"},
{file = "tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076"},
{file = "tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9"},
{file = "tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c"},
{file = "tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc"},
{file = "tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049"},
{file = "tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e"},
{file = "tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece"},
{file = "tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a"},
{file = "tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085"},
{file = "tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9"},
{file = "tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5"},
{file = "tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585"},
{file = "tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1"},
{file = "tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917"},
{file = "tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9"},
{file = "tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257"},
{file = "tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54"},
{file = "tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a"},
{file = "tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897"},
{file = "tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f"},
{file = "tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d"},
{file = "tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5"},
{file = "tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd"},
{file = "tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36"},
{file = "tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd"},
{file = "tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf"},
{file = "tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac"},
{file = "tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662"},
{file = "tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853"},
{file = "tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15"},
{file = "tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba"},
{file = "tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6"},
{file = "tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7"},
{file = "tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232"},
{file = "tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4"},
{file = "tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c"},
{file = "tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d"},
{file = "tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41"},
{file = "tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c"},
{file = "tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f"},
{file = "tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8"},
{file = "tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26"},
{file = "tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396"},
{file = "tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe"},
{file = "tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f"},
]
[[package]]
name = "typing-extensions"
version = "4.16.0"
description = "Backported and Experimental Type Hints for Python 3.9+"
optional = false
python-versions = ">=3.9"
groups = ["dev"]
markers = "python_version == \"3.10\""
files = [
{file = "typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8"},
{file = "typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5"},
]
[metadata]
lock-version = "2.1"
python-versions = ">=3.10"
content-hash = "24c9a2d464f6c737eeae0a66f792fa23824c3960b7cc72a22fff1408f21ec812"
+47
View File
@@ -0,0 +1,47 @@
[build-system]
requires = ["poetry-core>=2.0.0"]
build-backend = "poetry.core.masonry.api"
[project]
name = "jcloud-docsgen"
version = "0.2.0"
description = "A tool that generates Markdown documentation for software project source code"
dependencies = ["docstring-parser"]
requires-python = ">=3.10"
license = "Apache-2.0"
[project.scripts]
jcloud-docsgen = "jcloud_docsgen.cli:main"
[tool.poetry]
packages = [
{ include = "jcloud_docsgen", from = "src"}
]
[[tool.poetry.source]]
name = "jcloud"
url = "https://gitea.jcloud-services.ddns.net/api/packages/jCloud/pypi/simple/"
priority = "supplemental"
[dependency-groups]
dev = [
"pytest (>=9.1.1,<10.0.0)",
"pytest-expectdir (>=1.2.0,<2.0.0)",
"ruff (>=0.16.8,<0.17.0)"
]
[tool.ruff]
line-length = 79
target-version = "py311"
[tool.ruff.lint]
select = [
"E",
"F",
"W",
"I",
"UP"
]
[tool.ruff.format]
quote-style = "single"
+15
View File
@@ -0,0 +1,15 @@
# Copyright 2026 jCloud
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
git config --local commit.template .gitmessage
+22
View File
@@ -0,0 +1,22 @@
# Copyright 2026 jCloud
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from . import cli
from .core import * # noqa: F403
from .core import __all__ as _core__all__
__all__ = [
'cli',
*_core__all__,
]
+20
View File
@@ -0,0 +1,20 @@
# Copyright 2026 jCloud
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import sys
from .cli.cli import main
if __name__ == '__main__':
sys.exit(main())
+22
View File
@@ -0,0 +1,22 @@
# Copyright 2026 jCloud
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from . import cli, languages
from .cli import main
__all__ = [
'cli',
'languages',
'main',
]
+190
View File
@@ -0,0 +1,190 @@
# Copyright 2026 jCloud
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
The command line interface
"""
import argparse
import pathlib
import sys
from ..exceptions import DirectoryNotEmptyError
from .languages.python import VALID_SECTIONS as VALID_PYTHON_SECTIONS
__all__ = [
'existing_directory',
'error',
'create_argument_parser',
'main',
]
_LANG_PYTHON = 'python'
_PROGRAM_NAME = 'jcloud-docsgen'
def existing_directory(path: str) -> pathlib.Path:
_path = path
path = pathlib.Path(_path)
if not path.exists():
raise argparse.ArgumentTypeError(f'{_path}: no such file or directory')
if not path.is_dir():
raise argparse.ArgumentTypeError(f'{_path}: not a directory')
return path
def error(message: str, exit_code: int = 1):
"""
Shows an error message and returns the exit code.
:param message: The error message to show.
:type message: str
:param exit_code: The exit code.
:type exit_code: int
"""
print(f'{_PROGRAM_NAME}: error: {message}', file=sys.stderr)
return exit_code
def create_argument_parser() -> argparse.ArgumentParser:
"""
Prepares a parser to parse the command-line arguments using
argparse.
:return: The parser.
:rtype: argparse.ArgumentParser
"""
common_parser = argparse.ArgumentParser(add_help=False)
common_parser.add_argument(
'project_directory',
type=existing_directory,
nargs='?',
default=pathlib.Path('.'),
help='The directory of the project',
)
common_parser.add_argument(
'-o',
'--output-dir',
type=pathlib.Path,
default='docs/reference',
help='the output directory the documentation will be written to',
)
common_parser.add_argument(
'-f',
'--force',
action='store_true',
help=(
'generate documentation even if the output directory is not '
'empty. Warning: data can be lost when setting this flag.'
),
)
argument_parser = argparse.ArgumentParser()
subparsers = argument_parser.add_subparsers(
dest='language',
required=True,
)
python_parser = subparsers.add_parser(
_LANG_PYTHON,
parents=[common_parser],
help='generate documentation for Python project',
)
python_parser.add_argument(
'-s',
'--sections',
nargs='+',
choices=VALID_PYTHON_SECTIONS,
help='the sections to be included in the documentation',
)
python_parser.add_argument(
'-S',
'--sections-exclude',
nargs='+',
default=[],
choices=VALID_PYTHON_SECTIONS,
help=(
'the sections to be excluded in the documentation. '
'If a section shall be included AND excluded, it is excluded.'
),
)
python_parser.add_argument(
'-H',
'--allow-html',
action='store_true',
help=(
'indicates whether HTML is allowed in the documentation. '
'By default, HTML is disabled.'
),
)
python_parser.add_argument(
'-T',
'--allow-tables',
action='store_true',
help=(
'indicates whether Markdown tables are allowed in the '
'documentation. By default, Markdown tables are disabled. '
'Warning: Markdown tables are not CommonMark.'
),
)
return argument_parser
def main(argv=None) -> int:
argument_parser = create_argument_parser()
args = argument_parser.parse_args(argv)
try:
if args.language == _LANG_PYTHON:
from .languages.python import generate
generate(
args.project_directory,
args.output_dir,
include_sections=args.sections,
exclude_sections=args.sections_exclude,
allow_html=args.allow_html,
allow_tables=args.allow_tables,
force=args.force,
)
except FileNotFoundError as exc:
return error(f'{exc.filename}: no such file or directory')
except NotADirectoryError as exc:
return error(f'{exc.filename}: not a directory')
except IsADirectoryError as exc:
return error(f'{exc.filename}: is a directory')
except DirectoryNotEmptyError as exc:
return error(f'{exc.filename}: directory not empty')
except FileExistsError as exc:
return error(f'{exc.filename}: file already exists')
except PermissionError as exc:
return error(f'{exc.filename}: permission denied')
except OSError as exc:
return error(f'{exc.filename}: {exc.args[1]}')
except UnicodeError:
return error('invalid text encoding')
except SyntaxError:
# TODO: add a custom exception class to clearify that a project
# parsing syntax error is meant.
return error('syntax error in project source code')
except KeyboardInterrupt:
return 130
except BrokenPipeError:
return 141
return 0
@@ -0,0 +1,19 @@
# Copyright 2026 jCloud
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from . import python
__all__ = [
'python',
]
+155
View File
@@ -0,0 +1,155 @@
# Copyright 2026 jCloud
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import pathlib
from ...core.python import (
PythonDefinitionDocumentationIncludeSections,
PythonDocumentationGenerator,
)
__all__ = [
'VALID_SECTIONS',
'parse_sections_list',
'generate',
]
VALID_SECTIONS = list(
PythonDefinitionDocumentationIncludeSections()._args.keys()
)
def parse_sections_list(
include_sections: list[str] | None = None,
exclude_sections: list[str] = [],
) -> PythonDefinitionDocumentationIncludeSections:
"""
Returns a Python definition documentation sections list from the
sections list to be included and the sections list to be excluded.
These lists both consist of the sections as strings which contain
the section name.
:param include_sections: The list of sections to be included.
:type include_sections: list[str] | None
:param exclude_sections: The list of sections to be excluded.
:type exclude_sections: list[str]
:return: The Python definition documentation sections object.
:rtype: PythonDefinitionDocumentationIncludeSections
"""
if include_sections is None:
include_sections = VALID_SECTIONS[:]
for section in exclude_sections:
while section in include_sections:
del include_sections[include_sections.index(section)]
return PythonDefinitionDocumentationIncludeSections(
signature='signature' in include_sections,
returns='returns' in include_sections,
params='params' in include_sections,
exceptions='exceptions' in include_sections,
examples='examples' in include_sections,
docstring='docstring' in include_sections,
bases='bases' in include_sections,
decorators='decorators' in include_sections,
class_body='class_body' in include_sections,
)
def _generate(
project_directory: pathlib.Path,
output_directory: pathlib.Path,
*,
include_sections: PythonDefinitionDocumentationIncludeSections = (
PythonDefinitionDocumentationIncludeSections()
),
allow_html: bool = False,
allow_tables: bool = False,
force: bool = False,
) -> None:
"""
Generates the documentation for a Python project.
:param project_directory: The project directory.
:type project_directory: pathlib.Path
:param output_directory: The output directory.
:type output_directory: pathlib.Path
:param include_sections: The sections to be included in the
documentation.
:type include_sections: PythonDefinitionDocumentationIncludeSections
:param allow_html: Indicates whether HTML is allowed to appear in
the generated documentation.
:type allow_html: bool
:param allow_tables: Indicates whether Markdown tables are allowed.
Warning: Markdown tables are not CommonMark.
:param force: Indicates whether the output directory shall be
overwritten if it is not empty.
:type force: bool
"""
docs_generator = PythonDocumentationGenerator(
project_directory,
output_directory,
include_sections=include_sections,
allow_html=allow_html,
allow_tables=allow_tables,
)
docs_generator.generate_documentation(force)
def generate(
project_directory: pathlib.Path,
output_directory: pathlib.Path,
*,
include_sections: list[str] | None = None,
exclude_sections: list[str] = [],
allow_html: bool = False,
allow_tables: bool = False,
force: bool = False,
) -> None:
"""
Generates the documentation for a Python project.
:param project_directory: The project directory.
:type project_directory: pathlib.Path
:param output_directory: The output directory.
:type output_directory: pathlib.Path
:param include_sections: The sections to be included in the
documentation.
:type include_sections: list[str] | None
:param exclude_sections: The sections to be excluded in the
documentation.
:type exclude_sections: list[str]
:param allow_html: Indicates whether HTML is allowed to appear in
the generated documentation.
:type allow_html: bool
:param allow_tables: Indicates whether Markdown tables are allowed.
Warning: Markdown tables are not CommonMark.
:param force: Indicates whether the output directory shall be
overwritten if it is not empty.
:type force: bool
"""
sections = parse_sections_list(include_sections, exclude_sections)
_generate(
project_directory,
output_directory,
include_sections=sections,
allow_html=allow_html,
allow_tables=allow_tables,
force=force,
)
+19
View File
@@ -0,0 +1,19 @@
# Copyright 2026 jCloud
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from . import python
__all__ = [
'python',
]
@@ -0,0 +1,22 @@
# Copyright 2026 jCloud
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from . import namespaces
from ._core import * # noqa: F403
from ._core import __all__ as _core__all__
__all__ = [
*_core__all__,
'namespaces',
]
File diff suppressed because it is too large. Load diff
+295
View File
@@ -0,0 +1,295 @@
# Copyright 2026 jCloud
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
import ast
import keyword
import types
from enum import Enum
from ...exceptions import (
InvalidPythonAnnotationError,
InvalidPythonIdentifierError,
PythonArgumentStructureError,
)
from ...utils import assert_that_is_instance
__all__ = [
'PythonArgumentKind',
'PythonFunctionArgument',
'PythonASTArgumentsListParser',
]
class PythonArgumentKind(Enum):
NORMAL = 0
POSITIONAL_ONLY = 1
KEYWORD_ONLY = 2
VARARG = 3
KWARGS = 4
class PythonFunctionArgument:
"""
Represents an argument of a Python function.
:param name: The name of the argument.
:type name: str
:param kind: The kind of the argument.
:type kind: ArgumentKind
:param default: The default value of the argument.
:type default: str | None
:param annotation: The type annotation of the argument.
:type annotation: str | None
"""
def __init__(
self,
name: str,
kind: PythonArgumentKind,
default: str | None,
annotation: str | None,
) -> None:
assert_that_is_instance(name, str)
assert_that_is_instance(kind, PythonArgumentKind)
assert_that_is_instance(default, (str, types.NoneType))
assert_that_is_instance(annotation, (str, types.NoneType))
# check whether name is a valid identifier
if not name or not name.isidentifier() or keyword.iskeyword(name):
raise InvalidPythonIdentifierError(
'invalid identifier',
identifier=name,
)
# check whether the annotation is a valid annotation
# Currently, it is only checked whether the parameter is not
# empty.
if annotation is not None and not annotation:
raise InvalidPythonAnnotationError(
'invalid annotation',
annotation=annotation,
)
self.name = name
self.kind = kind
self.default = default
self.annotation = annotation
if (
self.kind
in (
PythonArgumentKind.VARARG,
PythonArgumentKind.KWARGS,
)
and self.default is not None
):
raise PythonArgumentStructureError(
'* or ** arguments cannot have default values',
definition=self.signature_repr(),
)
def __eq__(self, value: PythonFunctionArgument) -> bool:
if not isinstance(value, PythonFunctionArgument):
return False
return (
self.name == value.name
and self.kind == value.kind
and self.default == value.default
and self.annotation == value.annotation
)
def __repr__(self) -> str:
return type(self).__name__ + repr(
(
self.name,
self.kind,
self.default,
self.annotation,
)
)
def signature_repr(self) -> str:
"""
Returns the function signature representation of the argument.
:return: The string representation of the argument.
:rtype: str
"""
if self.kind == PythonArgumentKind.KWARGS:
str_repr = '**'
elif self.kind == PythonArgumentKind.VARARG:
str_repr = '*'
else:
str_repr = ''
str_repr += self.name
if self.annotation is not None:
str_repr += ': ' + self.annotation
if self.default is not None:
if self.annotation is None:
str_repr += '=' + self.default
else:
str_repr += ' = ' + self.default
return str_repr
class PythonASTArgumentsListParser:
"""
A parser for making ``PythonFunctionArgument`` objects from an
``ast.arguments`` arguments list.
:param ast_arguments_list: The ``ast.arguments`` arguments list.
:type ast_arguments_list: ast.arguments
"""
def __init__(self, ast_arguments_list: ast.arguments) -> None:
self.ast_arguments_list = ast_arguments_list
def to_argument_list(self) -> list[PythonFunctionArgument]:
"""
Converts the AST arguments list to a list of Python function
argument objects.
:return: The list of Python function argument objects.
:rtype: list[PythonFunctionArgument]
"""
defaults = (
[
None
for _ in range(
len(
self.ast_arguments_list.posonlyargs
+ self.ast_arguments_list.args
+ self.ast_arguments_list.kwonlyargs
)
- len(self.ast_arguments_list.defaults)
- len(self.ast_arguments_list.kw_defaults)
)
]
+ [ast.unparse(d) for d in self.ast_arguments_list.defaults]
+ [
ast.unparse(d) if d is not None else None
for d in self.ast_arguments_list.kw_defaults
]
)
i = 0
arguments = []
for arg in self.ast_arguments_list.posonlyargs:
if arg.annotation is None:
arguments.append(
PythonFunctionArgument(
arg.arg,
PythonArgumentKind.POSITIONAL_ONLY,
defaults[i],
None,
),
)
else:
arguments.append(
PythonFunctionArgument(
arg.arg,
PythonArgumentKind.POSITIONAL_ONLY,
defaults[i],
ast.unparse(arg.annotation),
),
)
i += 1
for arg in self.ast_arguments_list.args:
if arg.annotation is None:
arguments.append(
PythonFunctionArgument(
arg.arg,
PythonArgumentKind.NORMAL,
defaults[i],
None,
),
)
else:
arguments.append(
PythonFunctionArgument(
arg.arg,
PythonArgumentKind.NORMAL,
defaults[i],
ast.unparse(arg.annotation),
),
)
i += 1
if self.ast_arguments_list.vararg is not None:
if self.ast_arguments_list.vararg.annotation is None:
arguments.append(
PythonFunctionArgument(
self.ast_arguments_list.vararg.arg,
PythonArgumentKind.VARARG,
None,
None,
),
)
else:
arguments.append(
PythonFunctionArgument(
self.ast_arguments_list.vararg.arg,
PythonArgumentKind.VARARG,
None,
ast.unparse(self.ast_arguments_list.vararg.annotation),
),
)
for arg in self.ast_arguments_list.kwonlyargs:
if arg.annotation is None:
arguments.append(
PythonFunctionArgument(
arg.arg,
PythonArgumentKind.KEYWORD_ONLY,
defaults[i],
None,
),
)
else:
arguments.append(
PythonFunctionArgument(
arg.arg,
PythonArgumentKind.KEYWORD_ONLY,
defaults[i],
ast.unparse(arg.annotation),
),
)
i += 1
if self.ast_arguments_list.kwarg is not None:
if self.ast_arguments_list.kwarg.annotation is None:
arguments.append(
PythonFunctionArgument(
self.ast_arguments_list.kwarg.arg,
PythonArgumentKind.KWARGS,
None,
None,
),
)
else:
arguments.append(
PythonFunctionArgument(
self.ast_arguments_list.kwarg.arg,
PythonArgumentKind.KWARGS,
None,
ast.unparse(self.ast_arguments_list.kwarg.annotation),
),
)
return arguments
@@ -0,0 +1,259 @@
# Copyright 2026 jCloud
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
import ast
import keyword
import types
from ...exceptions import InvalidPythonIdentifierError
from ...utils import assert_that_is_instance
from .arguments import PythonASTArgumentsListParser, PythonFunctionArgument
from .docstrings import PythonDocstring
__all__ = [
'PythonDefinition',
'PythonFunctionDefinition',
'PythonAsyncFunctionDefinition',
'PythonClassDefinition',
]
def _python_docstring(doc: str | None) -> PythonDocstring | None:
"""
Returns the PythonDocstring object if the docstring is a string and
not ``None``.
:param doc: The docstring.
:type doc: str | None
:return: The PythonDocstring object or ``None``.
:rtype: PythonDocstring | None
"""
return PythonDocstring(doc) if doc is not None else None
class PythonDefinition:
"""
A base class for Python definition (a class, function or asynchronous
function definition) classes.
:param name: The name of the definition.
:type name: str
:param doc: The docstring.
:type doc: PythonDocstring | None
:param decorators: The definition decorators.
:type decorators: list[str]
:raises InvalidPythonIdentifierError: If the name is not a valid
Python identifier.
"""
def __init__(
self,
name: str,
doc: PythonDocstring | None,
decorators: list[str],
) -> None:
if keyword.iskeyword(name) or not name.isidentifier() or not name:
raise InvalidPythonIdentifierError(
'invalid identifier',
identifier=name,
)
assert_that_is_instance(doc, (PythonDocstring, types.NoneType))
self.name = name
self.doc = doc
self.decorators = decorators
def _arg_list(self) -> tuple:
return (
self.name,
self.doc,
self.decorators,
)
def __repr__(self) -> str:
return type(self).__name__ + repr(self._arg_list())
@classmethod
def from_node(cls, node: ast.stmt) -> PythonDefinition:
"""
From an AST node.
:param node: The AST node.
:type node: ast.stmt
"""
return cls(
node.name,
_python_docstring(ast.get_docstring(node)),
[ast.unparse(d) for d in node.decorator_list],
)
def __eq__(self, value: object) -> bool:
if not isinstance(value, PythonDefinition):
return False
return self._arg_list() == value._arg_list()
class PythonFunctionDefinition(PythonDefinition):
"""
A PythonDefinition subclass representing a Python function
definition.
:param name: The name of the definition.
:type name: str
:param args: The arguments.
:type args: list[PythonFunctionArgument]
:param returns: The return type of the function or class.
:type returns: str | None
:param doc: The docstring.
:type doc: PythonDocstring | None
:param decorators: The definition decorators.
:type decorators: list[str]
:raises InvalidPythonIdentifierError: If the name is not a valid
Python identifier.
"""
def __init__(
self,
name: str,
args: list[PythonFunctionArgument],
returns: str | None,
doc: PythonDocstring | None,
decorators: list[str],
) -> None:
super().__init__(name, doc, decorators)
self.args = args
self.returns = returns
def _arg_list(self) -> tuple:
return (
self.name,
self.args,
self.returns,
self.doc,
self.decorators,
)
@classmethod
def from_node(cls, node: ast.stmt) -> PythonFunctionDefinition:
"""
From an AST node.
:param node: The AST node.
:type node: ast.stmt
"""
return cls(
node.name,
PythonASTArgumentsListParser(node.args).to_argument_list(),
ast.unparse(node.returns) if node.returns is not None else None,
_python_docstring(ast.get_docstring(node)),
[ast.unparse(d) for d in node.decorator_list],
)
class PythonAsyncFunctionDefinition(PythonFunctionDefinition):
"""
A PythonDefinition subclass representing an asynchronous Python function
definition.
:param name: The name of the definition.
:type name: str
:param args: The arguments.
:type args: list[PythonFunctionArgument]
:param returns: The return type of the function or class.
:type returns: str | None
:param doc: The docstring.
:type doc: PythonDocstring | None
:param decorators: The definition decorators.
:type decorators: list[str]
:raises InvalidPythonIdentifierError: If the name is not a valid
Python identifier.
"""
class PythonClassDefinition(PythonDefinition):
"""
A PythonDefinition subclass representing a Python class definition.
:param name: The name of the definition.
:type name: str
:param bases: The base classes.
:type bases: list[str]
:param doc: The docstring.
:type doc: PythonDocstring | None
:param decorators: The definition decorators.
:type decorators: list[str]
:param body:
:type body: list[PythonDefinition]
:raises InvalidPythonIdentifierError: If the name is not a valid
Python identifier.
"""
def __init__(
self,
name: str,
bases: list[str],
doc: PythonDocstring | None,
decorators: list[str],
body: list[PythonDefinition],
) -> None:
super().__init__(name, doc, decorators)
self.bases = bases
self.body = body
def _arg_list(self) -> tuple:
return (
self.name,
self.bases,
self.doc,
self.decorators,
self.body,
)
@classmethod
def from_node(cls, node: ast.stmt) -> PythonClassDefinition:
"""
From an AST node.
:param node: The AST node.
:type node: ast.stmt
"""
return cls(
node.name,
[ast.unparse(b) for b in node.bases],
_python_docstring(ast.get_docstring(node)),
[ast.unparse(d) for d in node.decorator_list],
[
PythonFunctionDefinition.from_node(expr)
if isinstance(expr, ast.FunctionDef)
else PythonAsyncFunctionDefinition.from_node(expr)
if isinstance(expr, ast.AsyncFunctionDef)
else PythonClassDefinition.from_node(expr)
for expr in node.body
if isinstance(
expr, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)
)
],
)
@@ -0,0 +1,47 @@
# Copyright 2026 jCloud
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import docstring_parser
__all__ = ['PythonDocstring']
class PythonDocstring:
"""
Represents a Python docstring.
:param docstring: The docstring as a string.
:type docstring: str
"""
def __init__(self, docstring: str) -> None:
self.docstring = docstring
def __repr__(self) -> str:
return type(self).__name__ + '(' + repr(self.docstring) + ')'
def __eq__(self, value) -> bool:
if not isinstance(value, type(self)):
return False
return self.docstring == value.docstring
def parse(self) -> docstring_parser.Docstring:
"""
Parses the docstring.
:return: The parsed docstring.
:rtype: docstring_parser.Docstring
"""
return docstring_parser.parse(self.docstring)
@@ -0,0 +1,226 @@
# Copyright 2026 jCloud
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
from typing import TypeAlias, Union
from ...exceptions import (
InvalidNamespaceError,
NamespaceExistsError,
NamespaceNotFoundError,
)
from ...utils import assert_that_is_instance
__all__ = [
'PythonNamespace',
'PythonPackageNamespace',
'PythonModuleNamespace',
]
class PythonNamespace:
"""
The base class for all Python namespaces such as modules or packages.
:param name: The name of the namespace.
:type name: str
"""
def __init__(self, name: str) -> None:
assert_that_is_instance(name, str)
if not name.strip():
raise InvalidNamespaceError(
'invalid namespace',
namespace_identifier=name,
)
self.name = name
def __repr__(self) -> str:
return f'{type(self).__name__}({self.name!r})'
PythonPackageNamespaceDictType: TypeAlias = dict[
str,
Union[
str,
'PythonPackageNamespaceDictType',
],
]
class PythonPackageNamespace(PythonNamespace):
"""
A subclass of ``PythonNamespace`` for Python package namespaces.
:param name: The name of the namespace.
:type name: str
:param sub_namespaces: The sub-namespaces.
:type sub_namespaces: list[PythonNamespace]
"""
def __init__(
self,
name: str,
sub_namespaces: list[PythonNamespace],
) -> None:
assert_that_is_instance(sub_namespaces, list)
super().__init__(name)
self._sub_namespaces = sub_namespaces
def namespace(self, sub_namespaces: list[str]) -> PythonNamespace:
"""
Returns the namespace object with a specific identifier, such as
``['package', 'subpackage', 'module']``.
:param sub_namespaces: The identifier of the namespace.
:type sub_namespaces: list[str]
:raises InvalidNamespaceError: If the namespace identifier is
invalid.
:raises NamespaceNotFoundError: If the namespace does not exist.
:return: The namespace.
:rtype: PythonNamespace
"""
if not sub_namespaces:
raise InvalidNamespaceError('invalid namespace')
for sn in sub_namespaces:
if not sn:
raise InvalidNamespaceError('invalid namespace')
sub_namespace = self.__getitem__(sub_namespaces[0])
# package namespace
if isinstance(sub_namespace, PythonPackageNamespace):
if len(sub_namespaces) == 1:
return sub_namespace
return sub_namespace.namespace(sub_namespaces[1:])
# module namespace
else:
return sub_namespace
def _sub_namespaces_names(
self,
package: PythonPackageNamespace,
) -> PythonPackageNamespaceDictType:
"""
Returns the names of all sub-namespaces of the specified package
namespace and keeps the package structure.
:param package: The package.
:type package: PythonPackageNamespace
:return: The names if all sub-namespaces.
:rtype: PythonPackageNamespaceDictType
"""
return {
sn.name: (
self._sub_namespaces_names(sn)
if isinstance(sn, PythonPackageNamespace)
else sn.name
)
for sn in package._sub_namespaces
}
def sub_namespace_names(self) -> PythonPackageNamespaceDictType:
"""
Returns the names of all sub-namespaces and keeps the package
structure.
:return: The names if all sub-namespaces.
:rtype: PythonPackageNamespaceDictType
"""
return self._sub_namespaces_names(self)
def __eq__(self, value: PythonPackageNamespace) -> bool:
# For performance reasons, is not called self.sub_namespace_names
# if the name attribute does not match, which can be quickly
# verified
if not (isinstance(value, type(self)) and self.name == value.name):
return False
return value.sub_namespace_names() == self.sub_namespace_names()
def __repr__(self) -> str:
return type(self).__name__ + repr((self.name, self._sub_namespaces))
def __getitem__(self, name: str) -> PythonNamespace:
"""
Returns a namespace with the specified name.
:param name: The name of the namespace
:type name: str
:raises NamespaceNotFoundError: If the namespace does not exist.
:return: The namespace
:rtype: PythonNamespace
"""
for sub in self._sub_namespaces:
if sub.name == name:
return sub
raise NamespaceNotFoundError(
'no such namespace',
namespace_identifier=name,
)
def add(self, namespace: PythonNamespace) -> None:
"""
Adds a namespace.
:param namespace: The namespace to add.
:type namespace: PythonNamespace
:raises NamespaceExistsError: If a namespace with the name of the
namespace that is attempted to add
already exists.
"""
try:
self.__getitem__(namespace.name)
raise NamespaceExistsError(
'namespace already exists',
namespace_identifier=namespace.name,
)
except NamespaceNotFoundError:
self._sub_namespaces.append(namespace)
@property
def sub_namespaces(self) -> list[PythonNamespace]:
return self._sub_namespaces
class PythonModuleNamespace(PythonNamespace):
"""
A subclass of ``PythonNamespace`` for Python module namespaces.
:param name: The name of the namespace.
:type name: str
"""
def __init__(self, name: str) -> None:
super().__init__(name)
def __eq__(self, value: PythonModuleNamespace) -> bool:
return isinstance(value, type(self)) and self.name == value.name
+122
View File
@@ -0,0 +1,122 @@
# Copyright 2026 jCloud
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import errno
class NamespaceError(ValueError):
"""
Base class for namespace errors.
"""
def __init__(self, *args: object, namespace_identifier: str = '') -> None:
super().__init__(*args)
self.namespace_identifier = namespace_identifier
def __str__(self):
if not self.args:
return ''
else:
return (
self.args[0]
+ (': ' if self.namespace_identifier and self.args[0] else '')
+ (self.namespace_identifier if self.args[0] else '')
)
class InvalidNamespaceError(NamespaceError): ...
class NamespaceNotFoundError(NamespaceError): ...
class NamespaceExistsError(NamespaceError): ...
class PythonIdentifierError(ValueError):
"""
Base class for Python identifier errors.
"""
def __init__(self, *args: object, identifier: str = '') -> None:
super().__init__(*args)
self.identifier = identifier
def __str__(self):
if not self.args:
return ''
else:
return (
self.args[0]
+ (': ' if self.identifier and self.args[0] else '')
+ (self.identifier if self.args[0] else '')
)
class InvalidPythonIdentifierError(PythonIdentifierError): ...
class PythonAnnotationError(ValueError):
"""
Base class for Python annotation errors.
"""
def __init__(self, *args: object, annotation: str = '') -> None:
super().__init__(*args)
self.annotation = annotation
def __str__(self):
if not self.args:
return ''
else:
return (
self.args[0]
+ (': ' if self.annotation and self.args[0] else '')
+ (self.annotation if self.args[0] else '')
)
class InvalidPythonAnnotationError(PythonAnnotationError): ...
class PythonDefinitionError(ValueError):
"""
Base class for Python definition errors.
"""
def __init__(self, *args: object, definition: str = '') -> None:
super().__init__(*args)
self.definition = definition
def __str__(self):
if not self.args:
return ''
else:
return (
self.args[0]
+ (': ' if self.definition and self.args[0] else '')
+ (self.definition if self.args[0] else '')
)
class PythonArgumentStructureError(PythonDefinitionError): ...
class DirectoryNotEmptyError(OSError):
"""
Directory is not empty.
"""
def __init__(self, filename: str | None = None) -> None:
super().__init__(errno.ENOTEMPTY, 'Directory not empty', filename)
+229
View File
@@ -0,0 +1,229 @@
# Copyright 2026 jCloud
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
The utilities for jcloud_docsgen
"""
import errno
import os
import pathlib
import types
try:
from warnings import deprecated
except ImportError:
from typing_extensions import deprecated
@deprecated('Use pathlib.Path instead.')
class ExistingDirectory(pathlib.Path):
"""
pathlib.Path subclass that is a directory.
ExistingDirectory represents a filesystem path but it also checks
whether the path exists and is not a file but a directory. It
inherits from pathlib.Path.
"""
def __init__(self, path, *args, **kwargs) -> None:
if not os.path.exists(path):
raise FileNotFoundError(
errno.ENOENT,
'no such file or directory',
path,
)
if not os.path.isdir(path):
raise NotADirectoryError(errno.ENOTDIR, 'not a directory', path)
super().__init__(*(path, *args), **kwargs)
@deprecated('Use pathlib.Path instead.')
class ExistingFile(pathlib.Path):
"""
pathlib.Path subclass that is a file.
ExistingFile represents a filesystem path but it also checks whether
the path exists and is not a directory but a file. It inherits from
pathlib.Path.
"""
def __init__(self, path, *args, **kwargs) -> None:
if not os.path.exists(path):
raise FileNotFoundError(
errno.ENOENT,
'no such file or directory',
path,
)
if not os.path.isfile(path):
raise IsADirectoryError(errno.EISDIR, 'is a directory', path)
super().__init__(*(path, *args), **kwargs)
def _quote(string: str, quotation_mark: str) -> str:
"""
Quotes a string.
:param string: The string to quote
:type string: str
:param quotation_mark: The quotation mark
:type quotation_mark: str
:return: The quoted string
:rtype: str
"""
return quotation_mark + string + quotation_mark
def human_readable_list(
ls: list,
final_separator: str = 'and',
quotation_mark: str = '',
) -> str:
"""
Generates a human-readable list from a list.
:param ls: The list.
:type ls: list
:param final_separator: The seperator between the last and the
second-to-last element of the list. Default
is 'and'.
:type final_separator: str
:param quotation_mark: The characters that are inserted before and
after an element. Default is ''.
:return: The human-readable list.
:rtype: str
"""
# empty list
if not ls:
return ''
# list with one element
if len(ls) == 1:
return _quote(str(ls[0]), quotation_mark)
return (
', '.join(
[
_quote(
str(obj), quotation_mark
) # quote the string representation of the object
for obj in ls[
:-1
] # do not include the last element of the list
],
)
+ ' '
+ final_separator
+ ' '
+ _quote(str(ls[-1]), quotation_mark)
)
def _list_type_names(types: list[type]) -> list[str]:
"""
Converts a list of types into a list of their names (``__name__``
attribute)
:param types: The list of types
:type types: list[type]
:return: The list of the names of the types
:rtype: list[str]
"""
return [tp.__name__ for tp in types]
def assert_that_is_instance(
obj: object,
class_or_tuple: type | types.UnionType | tuple[type, ...],
) -> None:
if not isinstance(class_or_tuple, (type, types.UnionType, tuple)):
raise TypeError(
f"class_or_tuple: expected 'type | types.UnionType, tupl"
f"e[type, ...]', 'got {type(class_or_tuple).__name__}'",
)
if not isinstance(obj, class_or_tuple):
if isinstance(class_or_tuple, (tuple, types.UnionType)):
if isinstance(class_or_tuple, types.UnionType):
class_or_tuple = class_or_tuple.__args__
if len(class_or_tuple) > 1:
exception_message_expected = 'either '
else:
exception_message_expected = ''
exception_message_expected += human_readable_list(
_list_type_names(class_or_tuple),
'or',
"'",
)
else:
exception_message_expected = "'" + class_or_tuple.__name__ + "'"
raise TypeError(
f'expected {exception_message_expected}'
f", got '{type(obj).__name__}'"
)
def non_empty_str(value: str | None) -> str | None:
"""
Returns the value or ``None``.
Returns the value if it is not empty and not `Ǹone``; otherwise,
returns ``None``.
Please note: values are stripped, i. e. if the value consists only
of spaces, it is also considered empty.
:param value: The value.
:type value: str | None
:return: The value or ``None``.
:rtype: str | None
"""
if value is None:
return None
if value.strip() == '':
return None
return value
def get_relative_path_segments(
path: pathlib.Path,
root: pathlib.Path,
) -> list[str]:
"""
Returns the path segments relative to ``root``.
:param path: The path.
:type path: pathlib.Path
:param root: The root path.
:type root: pathlib.Path
:return: The path segments relative to ``root``.
:rtype: list[str]
"""
return list(
path.resolve()
.relative_to(
root.resolve(),
)
.parts,
)
+36
View File
@@ -0,0 +1,36 @@
# Copyright 2026 jCloud
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import subprocess
import sys
def test_cli():
result = subprocess.run(
[
sys.executable,
'-m',
'jcloud_docsgen',
'--help',
],
capture_output=True,
text=True,
)
stdout = result.stdout.lower()
assert (
'usage:' in stdout
and 'positional arguments' in stdout
and 'options' in stdout
and '--help' in stdout
)
@@ -0,0 +1,20 @@
# Module `pkg.module`
## Function `f`
Does something.
### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `a` | `int` | normal | `42` | A parameter. |
<details><summary><h3 style="display:inline">Docstring</h3></summary>Does something.
:param a: A parameter.
:type a: int
:return: A string.
:rtype: str</details>
@@ -0,0 +1,11 @@
def f(a: int = 42) -> str:
"""
Does something.
:param a: A parameter.
:type a: int
:return: A string.
:rtype: str
"""
...
+43
View File
@@ -0,0 +1,43 @@
# Copyright 2026 jCloud
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import pytest
from jcloud_docsgen.cli.cli import create_argument_parser
@pytest.mark.parametrize(
'lang',
[
'javascript',
'Python',
'PYTHON',
],
)
def test_language_error(lang):
parser = create_argument_parser()
with pytest.raises(SystemExit):
parser.parse_args([lang])
@pytest.mark.parametrize(
'lang',
[
'python',
],
)
def test_language_success(lang):
parser = create_argument_parser()
parser.parse_args([lang])
+214
View File
@@ -0,0 +1,214 @@
# Copyright 2026 jCloud
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import pathlib
import tempfile
import pytest
from jcloud_docsgen.cli.cli import create_argument_parser
from jcloud_docsgen.cli.cli import main as cli_main
from jcloud_docsgen.cli.languages.python import parse_sections_list
from jcloud_docsgen.core.python import (
PythonDefinitionDocumentationIncludeSections,
)
@pytest.mark.parametrize(
'include,exclude,expected',
[
(
[],
[],
PythonDefinitionDocumentationIncludeSections(
signature=False,
returns=False,
params=False,
exceptions=False,
examples=False,
docstring=False,
bases=False,
decorators=False,
class_body=False,
),
),
(
[],
['signature'],
PythonDefinitionDocumentationIncludeSections(
signature=False,
returns=False,
params=False,
exceptions=False,
examples=False,
docstring=False,
bases=False,
decorators=False,
class_body=False,
),
),
(
['signature'],
['signature'],
PythonDefinitionDocumentationIncludeSections(
signature=False,
returns=False,
params=False,
exceptions=False,
examples=False,
docstring=False,
bases=False,
decorators=False,
class_body=False,
),
),
(
['signature'],
[],
PythonDefinitionDocumentationIncludeSections(
signature=True,
returns=False,
params=False,
exceptions=False,
examples=False,
docstring=False,
bases=False,
decorators=False,
class_body=False,
),
),
(
None,
[],
PythonDefinitionDocumentationIncludeSections(),
),
(
['signature', 'exceptions', 'bases', 'class_body'],
['signature', 'bases'],
PythonDefinitionDocumentationIncludeSections(
signature=False,
returns=False,
params=False,
exceptions=True,
examples=False,
docstring=False,
bases=False,
decorators=False,
class_body=True,
),
),
(
None,
['params', 'docstring'],
PythonDefinitionDocumentationIncludeSections(
signature=True,
returns=True,
params=False,
exceptions=True,
examples=True,
docstring=False,
bases=True,
decorators=True,
class_body=True,
),
),
],
)
def test_parse_sections_list(include, exclude, expected):
assert parse_sections_list(include, exclude) == expected
def test_sections_error():
parser = create_argument_parser()
with pytest.raises(SystemExit):
parser.parse_args(
[
'python',
'-s',
'param', # correct: `params`
]
)
with pytest.raises(SystemExit):
parser.parse_args(
[
'python',
'-s',
'params',
'docstrng', # correct: `docstring`
]
)
def test_sections_success():
parser = create_argument_parser()
parser.parse_args(
[
'python',
'-s',
'params',
]
)
parser.parse_args(
[
'python',
'-s',
'params',
'docstring',
]
)
def test_docs_generation(expectdir):
with expectdir('python_project') as output_dir:
output_dir: pathlib.Path
(output_dir / 'test_dir').mkdir()
input_dir = pathlib.Path(__file__).parent / 'python_project/project'
assert (
cli_main(
[
'python',
'-s',
'params',
'docstring',
'-HTfo',
str(output_dir),
str(input_dir),
]
)
== 0
)
def test_docs_generation_error():
with tempfile.TemporaryDirectory() as output_dir:
output_dir = pathlib.Path(output_dir)
(output_dir / 'test_dir').mkdir()
input_dir = pathlib.Path(__file__).parent / 'python_project/project'
assert (
cli_main(
[
'python',
'-s',
'params',
'docstring',
'-HTo',
str(output_dir),
str(input_dir),
]
)
== 1
)
Whitespace-only changes.
Whitespace-only changes.
Whitespace-only changes.
Whitespace-only changes.
Whitespace-only changes.
Whitespace-only changes.
Whitespace-only changes.
Whitespace-only changes.
Whitespace-only changes.
Whitespace-only changes.
Whitespace-only changes.
Whitespace-only changes.
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,196 @@
# Copyright 2026 jCloud
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import pathlib
import tempfile
import pytest
from src.jcloud_docsgen.core.python import PythonDocumentationGenerator
from src.jcloud_docsgen.core.python.namespaces import (
PythonModuleNamespace,
PythonPackageNamespace,
)
from src.jcloud_docsgen.exceptions import DirectoryNotEmptyError
@pytest.mark.parametrize(
'python_documentation_generator,expected',
[
(
PythonDocumentationGenerator(
pathlib.Path(
'tests/unit/core/python/_core/test_project_dirs/pdir_1/src'
),
pathlib.Path(
'tests/unit/core/python/_core/test_project_dirs/pdir_1/docs'
),
),
[],
),
(
PythonDocumentationGenerator(
pathlib.Path(
'tests/unit/core/python/_core/test_project_dirs/pdir_2/src'
),
pathlib.Path(
'tests/unit/core/python/_core/test_project_dirs/pdir_2/docs'
),
),
[
PythonPackageNamespace(
'pkg',
[PythonModuleNamespace('module')],
),
],
),
(
PythonDocumentationGenerator(
pathlib.Path(
'tests/unit/core/python/_core/test_project_dirs/pdir_3/src'
),
pathlib.Path(
'tests/unit/core/python/_core/test_project_dirs/pdir_3/docs'
),
),
[
PythonPackageNamespace(
'pkg',
[
PythonModuleNamespace('module'),
PythonPackageNamespace(
'pkg',
[
PythonModuleNamespace('module2'),
],
),
],
),
],
),
],
)
def test_PythonDocumentationGenerator_collect_modules(
python_documentation_generator: PythonDocumentationGenerator,
expected,
):
assert (
python_documentation_generator.namespace().sub_namespaces
) == expected
@pytest.mark.parametrize(
'dir',
[
d.name
for d in pathlib.Path(
(
'tests/unit/core/python/_core/test_PythonDocume'
'ntationGenerator/test_generate_documentation/'
),
).iterdir()
],
)
def test_generate_documentation(dir, expectdir):
with expectdir(
'test_PythonDocumentationGenerator/test_generate_documentation/' + dir
) as d:
PythonDocumentationGenerator(
pathlib.Path(
str(
pathlib.Path(
(
'tests/unit/core/python/_core/test_PythonDocume'
'ntationGenerator/test_generate_documentation/'
),
)
/ dir
/ 'project',
),
),
pathlib.Path(str(d)),
allow_tables=True,
allow_html=False,
).generate_documentation(True) # simplify the tests
def test_creates_directory():
with tempfile.TemporaryDirectory() as output_dir:
output_dir = pathlib.Path(output_dir)
with tempfile.TemporaryDirectory() as project_dir:
project_dir = pathlib.Path(project_dir)
# The directory of the package
package_dir = project_dir / 'pkg'
assert not package_dir.exists()
package_dir.mkdir(parents=True, exist_ok=True)
# The actual output directory directory
docs_dir = output_dir / 'docs' / 'reference'
# It should not exist
assert not docs_dir.exists()
docs_generator = PythonDocumentationGenerator(
project_dir, docs_dir
)
docs_generator.generate_documentation()
assert docs_dir.exists()
class TestForce:
def _prepare(self, directory):
directory = pathlib.Path(directory)
src_directory = directory / 'src'
src_directory.mkdir()
docs_directory = directory / 'docs'
docs_directory.mkdir()
return directory, src_directory, docs_directory
def test_overwrites_output_directory(self):
with tempfile.TemporaryDirectory() as directory:
directory, src_directory, docs_directory = self._prepare(directory)
test_dir = docs_directory / 'test_dir'
test_dir.mkdir()
documentation_generator = PythonDocumentationGenerator(
src_directory, docs_directory
)
documentation_generator.generate_documentation(True)
assert not test_dir.exists()
def test_error(self):
with tempfile.TemporaryDirectory() as directory:
directory, src_directory, docs_directory = self._prepare(directory)
(docs_directory / 'test_dir').mkdir()
documentation_generator = PythonDocumentationGenerator(
src_directory, docs_directory
)
with pytest.raises(DirectoryNotEmptyError) as exc_info:
documentation_generator.generate_documentation()
assert exc_info.value.filename == str(docs_directory)
def test_nothing_force(self):
with tempfile.TemporaryDirectory() as directory:
directory, src_directory, docs_directory = self._prepare(directory)
documentation_generator = PythonDocumentationGenerator(
src_directory, docs_directory
)
documentation_generator.generate_documentation(True)
def test_nothing_not_force(self):
with tempfile.TemporaryDirectory() as directory:
directory, src_directory, docs_directory = self._prepare(directory)
documentation_generator = PythonDocumentationGenerator(
src_directory, docs_directory
)
documentation_generator.generate_documentation(True)
@@ -0,0 +1,39 @@
# Module `pkg.module`
## Function `func`
### Function signature
```python
func()
```
## Function `func2`
### Function signature
```python
func2(a: int, b: int = 42, c=42, d=42)
```
### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `a` | `int` | normal | -- | -- |
| `b` | `int` | normal | `42` | -- |
| `c` | `int` | normal | `42` | -- |
| `d` | -- | normal | `42` | -- |
### Docstring
:param c:
:type c: int
@@ -0,0 +1,8 @@
def func(): ...
def func2(a: int, b: int = 42, c=42, d=42):
"""
:param c:
:type c: int
"""
@@ -0,0 +1,37 @@
# Module `user_management.exceptions`
## Class `UserNotFoundError`
User does not exist.
### Base classes
- `Exception`
### Docstring
User does not exist.
## Class `InvalidEmailError`
Email address is invalid.
### Base classes
- `Exception`
### Docstring
Email address is invalid.
@@ -0,0 +1,21 @@
# Module `user_management.models`
## Class `User`
Represents a user.
### Decorators
```python
dataclass
```
### Docstring
Represents a user.
@@ -0,0 +1,130 @@
# Module `user_management.service`
## Class `UserService`
Central class for the user management.
### Body
#### Constructor (method `__init__`)
##### Method signature
```python
__init__(self, storage: InMemoryUserStorage)
```
##### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `self` | -- | normal | -- | -- |
| `storage` | `InMemoryUserStorage` | normal | -- | -- |
#### Method `create_user`
Creates and stores a new user.
##### Method signature
```python
create_user(self, user_id: str, name: str, email: str, age: int | None = None) -> User
```
##### Returns
Return type: `User`
##### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `self` | -- | normal | -- | -- |
| `user_id` | `str` | normal | -- | -- |
| `name` | `str` | normal | -- | -- |
| `email` | `str` | normal | -- | -- |
| `age` | `int | None` | normal | `None` | -- |
##### Docstring
Creates and stores a new user.
#### Method `get_user`
Returns a user.
##### Method signature
```python
get_user(self, user_id: str) -> User
```
##### Returns
Return type: `User`
##### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `self` | -- | normal | -- | -- |
| `user_id` | `str` | normal | -- | -- |
##### Docstring
Returns a user.
#### Method `delete_user`
Deletes a user.
##### Method signature
```python
delete_user(self, user_id: str) -> None
```
##### Returns
Return type: `None`
##### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `self` | -- | normal | -- | -- |
| `user_id` | `str` | normal | -- | -- |
##### Docstring
Deletes a user.
### Docstring
Central class for the user management.
@@ -0,0 +1,158 @@
# Module `user_management.storage`
## Class `InMemoryUserStorage`
A simple in-memory storage for users.
Intended for testing purposes only.
### Body
#### Constructor (method `__init__`)
##### Method signature
```python
__init__(self)
```
##### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `self` | -- | normal | -- | -- |
#### Method `add`
Adds a user.
##### Method signature
```python
add(self, user: User) -> None
```
##### Returns
Return type: `None`
##### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `self` | -- | normal | -- | -- |
| `user` | `User` | normal | -- | -- |
##### Docstring
Adds a user.
#### Method `get`
Returns a user based on their ID.
##### Method signature
```python
get(self, user_id: str) -> User
```
##### Returns
Return type: `User`
##### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `self` | -- | normal | -- | -- |
| `user_id` | `str` | normal | -- | -- |
##### Docstring
Returns a user based on their ID.
#### Method `delete`
Deletes a user.
##### Method signature
```python
delete(self, user_id: str) -> None
```
##### Returns
Return type: `None`
##### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `self` | -- | normal | -- | -- |
| `user_id` | `str` | normal | -- | -- |
##### Docstring
Deletes a user.
#### Method `list_all`
Returns all users.
##### Method signature
```python
list_all(self) -> list[User]
```
##### Returns
Return type: `list[User]`
##### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `self` | -- | normal | -- | -- |
##### Docstring
Returns all users.
### Docstring
A simple in-memory storage for users.
Intended for testing purposes only.
@@ -0,0 +1,61 @@
# Module `user_management.utils`
## Function `is_valid_email`
Checks whether an email address is valid.
### Function signature
```python
is_valid_email(email: str) -> bool
```
### Returns
Return type: `bool`
### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `email` | `str` | normal | -- | -- |
### Docstring
Checks whether an email address is valid.
## Function `normalize_name`
Removes extra spaces and formats the name.
### Function signature
```python
normalize_name(name: str) -> str
```
### Returns
Return type: `str`
### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `name` | `str` | normal | -- | -- |
### Docstring
Removes extra spaces and formats the name.
@@ -0,0 +1,10 @@
class UserNotFoundError(Exception):
"""User does not exist."""
pass
class InvalidEmailError(Exception):
"""Email address is invalid."""
pass
@@ -0,0 +1,13 @@
from dataclasses import dataclass
@dataclass
class User:
"""
Represents a user.
"""
id: str
name: str
email: str
age: int | None = None
@@ -0,0 +1,47 @@
from .exceptions import InvalidEmailError, UserNotFoundError
from .models import User
from .storage import InMemoryUserStorage
from .utils import is_valid_email, normalize_name
class UserService:
"""
Central class for the user management.
"""
def __init__(self, storage: InMemoryUserStorage):
self.storage = storage
def create_user(
self, user_id: str, name: str, email: str, age: int | None = None
) -> User:
"""
Creates and stores a new user.
"""
if not is_valid_email(email):
raise InvalidEmailError(f'Invalid email: {email}')
user = User(
id=user_id, name=normalize_name(name), email=email, age=age
)
self.storage.add(user)
return user
def get_user(self, user_id: str) -> User:
"""
Returns a user.
"""
try:
return self.storage.get(user_id)
except KeyError:
raise UserNotFoundError(user_id)
def delete_user(self, user_id: str) -> None:
"""
Deletes a user.
"""
try:
self.storage.delete(user_id)
except KeyError:
raise UserNotFoundError(user_id)
@@ -0,0 +1,27 @@
from .models import User
class InMemoryUserStorage:
"""
A simple in-memory storage for users.
Intended for testing purposes only.
"""
def __init__(self):
self._users: dict[str, User] = {}
def add(self, user: User) -> None:
"""Adds a user."""
self._users[user.id] = user
def get(self, user_id: str) -> User:
"""Returns a user based on their ID."""
return self._users[user_id]
def delete(self, user_id: str) -> None:
"""Deletes a user."""
del self._users[user_id]
def list_all(self) -> list[User]:
"""Returns all users."""
return list(self._users.values())
@@ -0,0 +1,16 @@
import re
def is_valid_email(email: str) -> bool:
"""
Checks whether an email address is valid.
"""
pattern = r'^[\w\.-]+@[\w\.-]+\.\w+$'
return re.match(pattern, email) is not None
def normalize_name(name: str) -> str:
"""
Removes extra spaces and formats the name.
"""
return ' '.join(name.strip().split()).title()
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,32 @@
# Copyright 2026 jCloud
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import pytest
from src.jcloud_docsgen.core.python._core import _argument_kind_human_readable
from src.jcloud_docsgen.core.python.arguments import PythonArgumentKind
@pytest.mark.parametrize(
'argument_kind,expected',
[
(PythonArgumentKind.NORMAL, 'normal'),
(PythonArgumentKind.POSITIONAL_ONLY, 'positional-only'),
(PythonArgumentKind.KEYWORD_ONLY, 'keyword-only'),
(PythonArgumentKind.VARARG, 'vararg'),
(PythonArgumentKind.KWARGS, 'kwargs'),
],
)
def test__argument_kind_human_readable(argument_kind, expected):
assert _argument_kind_human_readable(argument_kind) == expected
@@ -0,0 +1,385 @@
# Copyright 2026 jCloud
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import ast
import pytest
from src.jcloud_docsgen.core.python._core import _collect_definitions
from src.jcloud_docsgen.core.python.arguments import (
PythonArgumentKind,
PythonFunctionArgument,
)
from src.jcloud_docsgen.core.python.definitions import (
PythonClassDefinition,
PythonFunctionDefinition,
)
from src.jcloud_docsgen.core.python.docstrings import PythonDocstring
def module(source_code: str) -> ast.Module:
"""
Returns an AST module of the source code.
:param source_code: The module source code.
:type source_code: str
:return: The AST module.
:rtype: ast.Module
"""
return ast.parse(source_code)
@pytest.mark.parametrize(
'module,expected',
[
(
module('def add(a, b):\n\treturn a + b'),
[
PythonFunctionDefinition(
'add',
[
PythonFunctionArgument(
'a',
PythonArgumentKind.NORMAL,
None,
None,
),
PythonFunctionArgument(
'b',
PythonArgumentKind.NORMAL,
None,
None,
),
],
None,
None,
[],
),
],
),
(
module('''def add(a: int | float, b: int | float) -> int | float:
"""Adds two numbers."""
return a + b
import typing
class Something:
"""A class."""
def __init__(self, value: typing.Any = 42) -> None:
self._value = value
@property
def value(self) -> typing.Any:
"""
The value.
"""
return self.get_value()
def get_value(self) -> typing.Any:
"""
Returns the value.
"""
return self._value
'''),
[
PythonFunctionDefinition(
'add',
[
PythonFunctionArgument(
'a',
PythonArgumentKind.NORMAL,
None,
'int | float',
),
PythonFunctionArgument(
'b',
PythonArgumentKind.NORMAL,
None,
'int | float',
),
],
'int | float',
PythonDocstring('Adds two numbers.'),
[],
),
PythonClassDefinition(
'Something',
[],
PythonDocstring('A class.'),
[],
[
PythonFunctionDefinition(
'__init__',
[
PythonFunctionArgument(
'self',
PythonArgumentKind.NORMAL,
None,
None,
),
PythonFunctionArgument(
'value',
PythonArgumentKind.NORMAL,
'42',
'typing.Any',
),
],
'None',
None,
[],
),
PythonFunctionDefinition(
'value',
[
PythonFunctionArgument(
'self',
PythonArgumentKind.NORMAL,
None,
None,
),
],
'typing.Any',
PythonDocstring('The value.'),
['property'],
),
PythonFunctionDefinition(
'get_value',
[
PythonFunctionArgument(
'self',
PythonArgumentKind.NORMAL,
None,
None,
),
],
'typing.Any',
PythonDocstring('Returns the value.'),
[],
),
],
),
],
),
(
module("""from src.jcloud_docsgen.core.python import (
PythonDocumentationGenerator
)
from src.jcloud_docsgen.core.python.namespaces import (
PythonModuleNamespace,
PythonPackageNamespace
)
import pytest
@pytest.mark.parametrize('project_directory,docs_directory', [
(1, 1),
(1, None),
(42, None),
(pathlib.Path('tests'), 1),
(pathlib.Path('tests/'), 1),
(1, pathlib.Path('tests/')),
(1, pathlib.Path('tests')),
(None, pathlib.Path('tests')),
])
def test_PythonDocumentationGenerator_type_exceptions(
project_directory,
docs_directory
):
with pytest.raises(TypeError):
PythonDocumentationGenerator(project_directory, docs_directory)
@pytest.mark.parametrize('python_documentation_generator,expected', [
(PythonDocumentationGenerator(
pathlib.Path(
'tests/unit/core/python/_core/test_project_dirs/pdir_1'
),
pathlib.Path(
'tests/unit/core/python/_core/test_project_dirs/pdir_1/docs'
)
), []),
(PythonDocumentationGenerator(
pathlib.Path(
'tests/unit/core/python/_core/test_project_dirs/pdir_2'
),
pathlib.Path(
'tests/unit/core/python/_core/test_project_dirs/pdir_2/docs'
)
), [PythonPackageNamespace('pkg', [PythonModuleNamespace('module')])]),
(PythonDocumentationGenerator(
pathlib.Path(
'tests/unit/core/python/_core/test_project_dirs/pdir_3'
),
pathlib.Path(
'tests/unit/core/python/_core/test_project_dirs/pdir_3/docs'
)
), [
PythonPackageNamespace(
'pkg',
[
PythonModuleNamespace('module'),
PythonPackageNamespace('pkg',
[PythonModuleNamespace('module2')]
)
]
)
])
])
def test_PythonDocumentationGenerator_collect_modules(
python_documentation_generator: PythonDocumentationGenerator,
expected
):
assert python_documentation_generator.namespace() == expected"""),
[
PythonFunctionDefinition(
'test_PythonDocumentationGenerator_type_exceptions',
[
PythonFunctionArgument(
'project_directory',
PythonArgumentKind.NORMAL,
None,
None,
),
PythonFunctionArgument(
'docs_directory',
PythonArgumentKind.NORMAL,
None,
None,
),
],
None,
None,
[
(
"""pytest.mark.parametrize('project_directo"""
"""ry,docs_directory', [(1, 1), (1, None), """
"""(42, None), (pathlib.Path('tests'), 1), """
"""(pathlib.Path('tests/'), 1), (1, pathlib"""
""".Path('tests/')), (1, pathlib.Path('test"""
"""s')), (None, pathlib.Path('tests'))])"""
),
],
),
PythonFunctionDefinition(
'test_PythonDocumentationGenerator_collect_modules',
[
PythonFunctionArgument(
'python_documentation_generator',
PythonArgumentKind.NORMAL,
None,
'PythonDocumentationGenerator',
),
PythonFunctionArgument(
'expected',
PythonArgumentKind.NORMAL,
None,
None,
),
],
None,
None,
[
(
"pytest.mark.parametrize('python_documentatio"
"n_generator,expected', [(PythonDocumentation"
"Generator(pathlib.Path('tests/unit/core/pyth"
"on/_core/test_project_dirs/pdir_1'), pathlib"
".Path('tests/unit/core/python/_core/test_pro"
"ject_dirs/pdir_1/docs')), []), (PythonDocume"
"ntationGenerator(pathlib.Path('tests/unit/co"
"re/python/_core/test_project_dirs/pdir_2'), "
"pathlib.Path('tests/unit/core/python/_core/t"
"est_project_dirs/pdir_2/docs')), [PythonPack"
"ageNamespace('pkg', [PythonModuleNamespace('"
"module')])]), (PythonDocumentationGenerator("
"pathlib.Path('tests/unit/core/python/_core/t"
"est_project_dirs/pdir_3'), pathlib.Path('tes"
'ts/unit/core/python/_core/test_project_dirs/'
"pdir_3/docs')), [PythonPackageNamespace('pkg"
"', [PythonModuleNamespace('module'), Python"
"PackageNamespace('pkg', [PythonModuleNames"
"pace('module2')])])])])"
)
],
),
],
),
(
module(
"""class C1:
class C2:
class C3:
def function(self, value: bool, /, arg) -> bool:
def should_be_not_in_the_result(arg: int) -> object:
pass
return not value"""
),
[
PythonClassDefinition(
'C1',
[],
None,
[],
[
PythonClassDefinition(
'C2',
[],
None,
[],
[
PythonClassDefinition(
'C3',
[],
None,
[],
[
PythonFunctionDefinition(
'function',
[
PythonFunctionArgument(
'self',
PythonArgumentKind.POSITIONAL_ONLY,
None,
None,
),
PythonFunctionArgument(
'value',
PythonArgumentKind.POSITIONAL_ONLY,
None,
'bool',
),
PythonFunctionArgument(
'arg',
PythonArgumentKind.NORMAL,
None,
None,
),
],
'bool',
None,
[],
),
],
),
],
),
],
),
],
),
],
)
def test___collect_definitions(module, expected):
assert list(_collect_definitions(module)) == expected
@@ -0,0 +1,66 @@
# Copyright 2026 jCloud
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import pytest
from src.jcloud_docsgen.core.python._core import _fit_row_length
@pytest.mark.parametrize(
'expected_length,row,expected',
[
(0, (), ()),
(1, (), (None,)),
(2, (), (None, None)),
(2, (1,), (1, None)),
(0, (1,), ()),
(1, (1, 2), (1,)),
(1, (1, 2), (1,)),
(0, (1, 2), ()),
(0, (1, 2, 3), ()),
(1, (1, 2, 3), (1,)),
(2, (1, 2, 3), (1, 2)),
(1, (1,), (1,)),
(2, (1, 1), (1, 1)),
(2, (1, 2), (1, 2)),
(3, (1, 2, 3), (1, 2, 3)),
],
)
def test__fit_row_length(expected_length, row, expected):
assert _fit_row_length(expected_length, row) == expected
@pytest.mark.parametrize(
'row',
[
(),
(1,),
(1, 2),
(1, 2, 3),
(1, 2, 3, 456),
],
)
@pytest.mark.parametrize(
'expected_length,expected_exception',
[
(-1, ValueError),
(-2, ValueError),
(-3, ValueError),
(-42, ValueError),
],
)
def test__fit_row_length_exceptions(expected_length, row, expected_exception):
with pytest.raises(expected_exception) as exc_info:
_fit_row_length(expected_length, row)
assert str(exc_info.value) == 'expected must be larger than or equal 0.'
@@ -0,0 +1,199 @@
# Copyright 2026 jCloud
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import docstring_parser
import pytest
from src.jcloud_docsgen.core.python._core import (
_function_params_info,
_FunctionParamInfo,
)
from src.jcloud_docsgen.core.python.arguments import (
PythonArgumentKind,
PythonFunctionArgument,
)
def docstring_args_list(
docstring: str,
) -> list[docstring_parser.DocstringParam]:
"""
Returns the parameter list of a docstring.
:param docstring: The docstring
:type docstring: str
:return: The parameter list.
:rtype: list[docstring_parser.DocstringParam]
"""
return docstring_parser.parse(docstring).params
@pytest.mark.parametrize(
'arg_list,docstring,expected',
[
(
[
PythonFunctionArgument(
'a', PythonArgumentKind.NORMAL, '', 'int'
),
],
':param a: a\n:type a: str',
{
'a': _FunctionParamInfo('int', 'a'),
},
),
(
[
PythonFunctionArgument(
'a', PythonArgumentKind.NORMAL, '', None
),
],
':param a: a\n:type a: str',
{
'a': _FunctionParamInfo('str', 'a'),
},
),
(
[
PythonFunctionArgument(
'a', PythonArgumentKind.NORMAL, '', None
),
PythonFunctionArgument(
'b', PythonArgumentKind.NORMAL, '', None
),
],
':param a: a\n:type a: str',
{
'a': _FunctionParamInfo('str', 'a'),
'b': _FunctionParamInfo(None, None),
},
),
(
[
PythonFunctionArgument(
'a', PythonArgumentKind.NORMAL, '', None
),
PythonFunctionArgument(
'b', PythonArgumentKind.NORMAL, '', 'int'
),
],
':param a: a\n:type a: str',
{
'a': _FunctionParamInfo('str', 'a'),
'b': _FunctionParamInfo('int', None),
},
),
(
[
PythonFunctionArgument(
'a', PythonArgumentKind.NORMAL, '', None
),
PythonFunctionArgument(
'b', PythonArgumentKind.NORMAL, '', 'int'
),
],
':param a: a\n:type a: str\n:param b: b\n:type b: str',
{
'a': _FunctionParamInfo('str', 'a'),
'b': _FunctionParamInfo('int', 'b'),
},
),
(
[
PythonFunctionArgument(
'a', PythonArgumentKind.NORMAL, '', None
),
],
':param a: a\n:type a: str\n:param b: b\n:type b: int',
{
'a': _FunctionParamInfo('str', 'a'),
},
),
(
[
PythonFunctionArgument(
'a', PythonArgumentKind.NORMAL, '', None
),
],
'',
{
'a': _FunctionParamInfo(None, None),
},
),
(
[
PythonFunctionArgument(
'a', PythonArgumentKind.NORMAL, '', 'str'
),
],
'',
{
'a': _FunctionParamInfo('str', None),
},
),
(
[
PythonFunctionArgument(
'a', PythonArgumentKind.NORMAL, '', 'str'
),
],
':param a:\n:type a: int',
{
'a': _FunctionParamInfo('str', ''),
},
),
(
[
PythonFunctionArgument(
'a', PythonArgumentKind.NORMAL, '', None
),
],
':param a:\n:type a: int',
{
'a': _FunctionParamInfo('int', ''),
},
),
(
[
PythonFunctionArgument(
'a', PythonArgumentKind.NORMAL, '', None
),
],
':param a: a',
{
'a': _FunctionParamInfo(None, 'a'),
},
),
(
[
PythonFunctionArgument(
'a', PythonArgumentKind.NORMAL, '', 'str'
),
],
':param a: a',
{
'a': _FunctionParamInfo('str', 'a'),
},
),
([], ':param a: a\n:type a: str', {}),
([], '', {}),
],
)
def test__function_params_types(arg_list, docstring, expected):
assert (
_function_params_info(arg_list, docstring_args_list(docstring))
== expected
)
@@ -0,0 +1,424 @@
# Copyright 2026 jCloud
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import pytest
from src.jcloud_docsgen.core.python._core import _markdown_table
@pytest.mark.parametrize(
(
'header,body,allow_markdown_table,force_row_le'
'ngths,one_line_values,missing_value,expected'
),
[
(
[],
[],
False,
False,
True,
'--',
'',
),
(
[],
[('a',)],
False,
False,
True,
'--',
'',
),
(
['a'],
[],
False,
False,
True,
'--',
'',
),
(
['a'],
[(1,)],
True,
False,
True,
'--',
"""| a |
| --- |
| 1 |""",
),
(
['a'],
[(1, 2)],
True,
False,
True,
'--',
"""| a |
| --- |
| 1 |""",
),
(
['a', 'b'],
[(1, 2)],
True,
False,
True,
'--',
"""| a | b |
| --- | --- |
| 1 | 2 |""",
),
(
['a', 'b'],
[(1,)],
True,
False,
True,
'--',
"""| a | b |
| --- | --- |
| 1 | -- |""",
),
(
['a', 'b'],
[()],
True,
False,
True,
'--',
"""| a | b |
| --- | --- |
| -- | -- |""",
),
(
['a', 'b'],
[(1, 2), (3, 4)],
True,
False,
True,
'--',
"""| a | b |
| --- | --- |
| 1 | 2 |
| 3 | 4 |""",
),
(
['a'],
[('line1\nline2',)],
True,
False,
True,
'--',
"""| a |
| --- |
| line1 line2 |""",
),
(
['a'],
[('line1\nline2',)],
True,
False,
False,
'--',
"""| a |
| --- |
| line1\nline2 |""",
),
(
[],
[],
False,
False,
True,
'--',
'',
),
(
[],
[('a',)],
False,
False,
True,
'--',
'',
),
(
['a'],
[],
False,
False,
True,
'--',
'',
),
(
['a'],
[(1,)],
False,
False,
True,
'--',
'- a: 1',
),
(
['a'],
[(1, 2)],
False,
False,
True,
'--',
'- a: 1',
),
(
['a', 'b'],
[(1, 2)],
False,
False,
True,
'--',
"""- a: 1
b: 2""",
),
(
['a', 'b'],
[(1,)],
False,
False,
True,
'--',
"""- a: 1
b: --""",
),
(
['a', 'b'],
[()],
False,
False,
True,
'--',
"""- a: --
b: --""",
),
(
['a', 'b'],
[(1, 2), (3, 4)],
False,
False,
True,
'--',
"""- a: 1
b: 2
- a: 3
b: 4""",
),
(
['a'],
[('line1\nline2',)],
False,
False,
True,
'--',
'- a: line1 line2',
),
(
['a'],
[('line1\nline2',)],
False,
False,
False,
'--',
'- a: line1\nline2',
),
],
)
def test__markdown_table(
header,
body,
allow_markdown_table,
force_row_lengths,
one_line_values,
missing_value,
expected,
):
assert (
_markdown_table(
header,
body,
allow_markdown_table=allow_markdown_table,
force_row_lengths=force_row_lengths,
one_line_values=one_line_values,
missing_value=missing_value,
)
== expected
)
@pytest.mark.parametrize(
(
'header,body,allow_markdown_table,force_row_lengths,one_line_v'
'alues,missing_value,expected_exception,expected_exception_msg'
),
[
(
['a'],
[(1, 2)],
True,
True,
True,
'--',
ValueError,
'expected 1 value, got 2',
),
(
['a'],
[(1, 2, 3)],
True,
True,
True,
'--',
ValueError,
'expected 1 value, got 3',
),
(
['a'],
[()],
True,
True,
True,
'--',
ValueError,
'expected 1 value, got 0',
),
(
['a', 'b'],
[()],
True,
True,
True,
'--',
ValueError,
'expected 2 values, got 0',
),
(
['a', 'b'],
[(1,)],
True,
True,
True,
'--',
ValueError,
'expected 2 values, got 1',
),
(
['a', 'b'],
[(1, 2, 3)],
True,
True,
True,
'--',
ValueError,
'expected 2 values, got 3',
),
(
['a'],
[(1, 2)],
False,
True,
True,
'--',
ValueError,
'expected 1 value, got 2',
),
(
['a'],
[(1, 2, 3)],
False,
True,
True,
'--',
ValueError,
'expected 1 value, got 3',
),
(
['a'],
[()],
False,
True,
True,
'--',
ValueError,
'expected 1 value, got 0',
),
(
['a', 'b'],
[()],
False,
True,
True,
'--',
ValueError,
'expected 2 values, got 0',
),
(
['a', 'b'],
[(1,)],
False,
True,
True,
'--',
ValueError,
'expected 2 values, got 1',
),
(
['a', 'b'],
[(1, 2, 3)],
False,
True,
True,
'--',
ValueError,
'expected 2 values, got 3',
),
],
)
def test__markdown_table_exceptions(
header,
body,
allow_markdown_table,
force_row_lengths,
one_line_values,
missing_value,
expected_exception,
expected_exception_msg,
):
with pytest.raises(expected_exception) as exc_info:
_markdown_table(
header,
body,
allow_markdown_table=allow_markdown_table,
force_row_lengths=force_row_lengths,
one_line_values=one_line_values,
missing_value=missing_value,
)
assert str(exc_info.value) == expected_exception_msg
@@ -0,0 +1,358 @@
# Copyright 2026 jCloud
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import pytest
from src.jcloud_docsgen.core.python._core import _signature_argument_list
from src.jcloud_docsgen.core.python.arguments import (
PythonArgumentKind,
PythonFunctionArgument,
)
@pytest.mark.parametrize(
'argument_list,expected',
[
(
[
PythonFunctionArgument(
'a',
PythonArgumentKind.NORMAL,
None,
None,
),
],
['a'],
),
(
[
PythonFunctionArgument(
'a',
PythonArgumentKind.POSITIONAL_ONLY,
None,
None,
),
],
['a', '/'],
),
(
[
PythonFunctionArgument(
'a',
PythonArgumentKind.POSITIONAL_ONLY,
None,
None,
),
PythonFunctionArgument(
'b',
PythonArgumentKind.NORMAL,
None,
None,
),
],
['a', '/', 'b'],
),
(
[
PythonFunctionArgument(
'a',
PythonArgumentKind.POSITIONAL_ONLY,
None,
None,
),
PythonFunctionArgument(
'b',
PythonArgumentKind.NORMAL,
None,
None,
),
PythonFunctionArgument(
'c',
PythonArgumentKind.KEYWORD_ONLY,
None,
None,
),
],
['a', '/', 'b', '*', 'c'],
),
(
[
PythonFunctionArgument(
'b',
PythonArgumentKind.NORMAL,
None,
None,
),
PythonFunctionArgument(
'c',
PythonArgumentKind.KEYWORD_ONLY,
None,
None,
),
],
['b', '*', 'c'],
),
(
[
PythonFunctionArgument(
'c',
PythonArgumentKind.KEYWORD_ONLY,
None,
None,
),
],
['*', 'c'],
),
(
[
PythonFunctionArgument(
'b',
PythonArgumentKind.POSITIONAL_ONLY,
None,
None,
),
PythonFunctionArgument(
'c',
PythonArgumentKind.KEYWORD_ONLY,
None,
None,
),
],
['b', '/', '*', 'c'],
),
(
[
PythonFunctionArgument(
'args',
PythonArgumentKind.VARARG,
None,
None,
)
],
['*args'],
),
(
[
PythonFunctionArgument(
'a',
PythonArgumentKind.NORMAL,
None,
None,
),
PythonFunctionArgument(
'args',
PythonArgumentKind.VARARG,
None,
None,
),
],
['a', '*args'],
),
(
[
PythonFunctionArgument(
'a',
PythonArgumentKind.POSITIONAL_ONLY,
None,
None,
),
PythonFunctionArgument(
'args',
PythonArgumentKind.VARARG,
None,
None,
),
],
['a', '/', '*args'],
),
(
[
PythonFunctionArgument(
'args',
PythonArgumentKind.VARARG,
None,
None,
),
PythonFunctionArgument(
'a',
PythonArgumentKind.KEYWORD_ONLY,
None,
None,
),
],
['*args', 'a'],
),
(
[
PythonFunctionArgument(
'args',
PythonArgumentKind.VARARG,
None,
None,
),
PythonFunctionArgument(
'kwargs',
PythonArgumentKind.KWARGS,
None,
None,
),
],
['*args', '**kwargs'],
),
(
[
PythonFunctionArgument(
'a',
PythonArgumentKind.POSITIONAL_ONLY,
None,
None,
),
PythonFunctionArgument(
'args',
PythonArgumentKind.VARARG,
None,
None,
),
PythonFunctionArgument(
'kwargs',
PythonArgumentKind.KWARGS,
None,
None,
),
],
['a', '/', '*args', '**kwargs'],
),
(
[
PythonFunctionArgument(
'a',
PythonArgumentKind.NORMAL,
None,
None,
),
PythonFunctionArgument(
'args',
PythonArgumentKind.VARARG,
None,
None,
),
PythonFunctionArgument(
'kwargs',
PythonArgumentKind.KWARGS,
None,
None,
),
],
['a', '*args', '**kwargs'],
),
(
[
PythonFunctionArgument(
'args',
PythonArgumentKind.VARARG,
None,
None,
),
PythonFunctionArgument(
'a',
PythonArgumentKind.KEYWORD_ONLY,
None,
None,
),
PythonFunctionArgument(
'kwargs',
PythonArgumentKind.KWARGS,
None,
None,
),
],
['*args', 'a', '**kwargs'],
),
(
[
PythonFunctionArgument(
'a',
PythonArgumentKind.KEYWORD_ONLY,
None,
None,
),
PythonFunctionArgument(
'kwargs',
PythonArgumentKind.KWARGS,
None,
None,
),
],
['*', 'a', '**kwargs'],
),
(
[
PythonFunctionArgument(
'a',
PythonArgumentKind.NORMAL,
None,
None,
),
PythonFunctionArgument(
'kwargs',
PythonArgumentKind.KWARGS,
None,
None,
),
],
['a', '**kwargs'],
),
(
[
PythonFunctionArgument(
'a',
PythonArgumentKind.POSITIONAL_ONLY,
None,
None,
),
PythonFunctionArgument(
'kwargs',
PythonArgumentKind.KWARGS,
None,
None,
),
],
['a', '/', '**kwargs'],
),
(
[
PythonFunctionArgument(
'a',
PythonArgumentKind.NORMAL,
'1',
'int',
),
],
['a: int = 1'],
),
(
[
PythonFunctionArgument(
'a',
PythonArgumentKind.NORMAL,
'1',
None,
),
],
['a=1'],
),
],
)
def test__signature_argument_list(argument_list, expected):
assert _signature_argument_list(argument_list) == expected
Whitespace-only changes.
Whitespace-only changes.
Whitespace-only changes.
@@ -0,0 +1,61 @@
"""
A test module.
"""
def fn(a: int, b: int = 42, c=42, d=42):
"""
:param c:
:type c: int
"""
def add(a: int | float, b: int | float) -> int | float:
"""
Adds two numbers.
:param a: The first number.
:type a: int | float
:param b: The second number.
:type b: int | float
:return: The sum.
:rtype: int | float
"""
return a + b
class Animal:
"""
A class representing an animal.
:param name: Its name.
:type name: str
"""
def __init__(self, name: str) -> None:
self.name = name
def greet(self):
"""
Returns a greeting.
:return: The greeting.
:rtype: str
"""
return f'Hi there! My name is {self.name}'
class Dog(Animal):
"""
A dog.
"""
def bark(self) -> None:
"""
Barks.
"""
print('Woof!')
Whitespace-only changes.
Whitespace-only changes.
@@ -0,0 +1,951 @@
# Copyright 2026 jCloud
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import ast
import pytest
from src.jcloud_docsgen.core.python.arguments import (
PythonArgumentKind,
PythonASTArgumentsListParser,
PythonFunctionArgument,
)
def argument_list(argument_list: str) -> ast.arguments:
"""
Returns an AST argument list from the argument list source code
:param argument_list: The argument list.
:type argument_list: str
:return: The AST argument list.
:rtype: ast.arguments
"""
return ast.parse('def func(' + argument_list + '): ...').body[0].args
@pytest.mark.parametrize(
'ast_arguments_list,expected',
[
(
argument_list('a'),
[
PythonFunctionArgument(
'a',
PythonArgumentKind.NORMAL,
None,
None,
),
],
),
(
argument_list('a: str'),
[
PythonFunctionArgument(
'a',
PythonArgumentKind.NORMAL,
None,
'str',
),
],
),
(
argument_list('a: str | int'),
[
PythonFunctionArgument(
'a',
PythonArgumentKind.NORMAL,
None,
'str | int',
),
],
),
(
argument_list('a: str, b'),
[
PythonFunctionArgument(
'a',
PythonArgumentKind.NORMAL,
None,
'str',
),
PythonFunctionArgument(
'b',
PythonArgumentKind.NORMAL,
None,
None,
),
],
),
(
argument_list('a: str, b: int'),
[
PythonFunctionArgument(
'a',
PythonArgumentKind.NORMAL,
None,
'str',
),
PythonFunctionArgument(
'b',
PythonArgumentKind.NORMAL,
None,
'int',
),
],
),
(
argument_list('a, b: int'),
[
PythonFunctionArgument(
'a',
PythonArgumentKind.NORMAL,
None,
None,
),
PythonFunctionArgument(
'b',
PythonArgumentKind.NORMAL,
None,
'int',
),
],
),
(
argument_list('a, b, *args'),
[
PythonFunctionArgument(
'a',
PythonArgumentKind.NORMAL,
None,
None,
),
PythonFunctionArgument(
'b',
PythonArgumentKind.NORMAL,
None,
None,
),
PythonFunctionArgument(
'args',
PythonArgumentKind.VARARG,
None,
None,
),
],
),
(
argument_list('a: int, b, *args'),
[
PythonFunctionArgument(
'a',
PythonArgumentKind.NORMAL,
None,
'int',
),
PythonFunctionArgument(
'b',
PythonArgumentKind.NORMAL,
None,
None,
),
PythonFunctionArgument(
'args',
PythonArgumentKind.VARARG,
None,
None,
),
],
),
(
argument_list('a, b: str, *args'),
[
PythonFunctionArgument(
'a',
PythonArgumentKind.NORMAL,
None,
None,
),
PythonFunctionArgument(
'b',
PythonArgumentKind.NORMAL,
None,
'str',
),
PythonFunctionArgument(
'args',
PythonArgumentKind.VARARG,
None,
None,
),
],
),
(
argument_list('a: int, b: str, *args'),
[
PythonFunctionArgument(
'a',
PythonArgumentKind.NORMAL,
None,
'int',
),
PythonFunctionArgument(
'b',
PythonArgumentKind.NORMAL,
None,
'str',
),
PythonFunctionArgument(
'args',
PythonArgumentKind.VARARG,
None,
None,
),
],
),
(
argument_list('a: int, b: str, *args: float'),
[
PythonFunctionArgument(
'a',
PythonArgumentKind.NORMAL,
None,
'int',
),
PythonFunctionArgument(
'b',
PythonArgumentKind.NORMAL,
None,
'str',
),
PythonFunctionArgument(
'args',
PythonArgumentKind.VARARG,
None,
'float',
),
],
),
(
argument_list('a: int, b, *args: float'),
[
PythonFunctionArgument(
'a',
PythonArgumentKind.NORMAL,
None,
'int',
),
PythonFunctionArgument(
'b',
PythonArgumentKind.NORMAL,
None,
None,
),
PythonFunctionArgument(
'args',
PythonArgumentKind.VARARG,
None,
'float',
),
],
),
(
argument_list('a: int, *args: float'),
[
PythonFunctionArgument(
'a',
PythonArgumentKind.NORMAL,
None,
'int',
),
PythonFunctionArgument(
'args',
PythonArgumentKind.VARARG,
None,
'float',
),
],
),
(
argument_list('a: int, /, **kwargs'),
[
PythonFunctionArgument(
'a',
PythonArgumentKind.POSITIONAL_ONLY,
None,
'int',
),
PythonFunctionArgument(
'kwargs',
PythonArgumentKind.KWARGS,
None,
None,
),
],
),
(
argument_list('a: int, **kwargs: str'),
[
PythonFunctionArgument(
'a',
PythonArgumentKind.NORMAL,
None,
'int',
),
PythonFunctionArgument(
'kwargs',
PythonArgumentKind.KWARGS,
None,
'str',
),
],
),
(
argument_list('a: int, /, b, **kwargs'),
[
PythonFunctionArgument(
'a',
PythonArgumentKind.POSITIONAL_ONLY,
None,
'int',
),
PythonFunctionArgument(
'b',
PythonArgumentKind.NORMAL,
None,
None,
),
PythonFunctionArgument(
'kwargs',
PythonArgumentKind.KWARGS,
None,
None,
),
],
),
(
argument_list('a: int, /, b'),
[
PythonFunctionArgument(
'a',
PythonArgumentKind.POSITIONAL_ONLY,
None,
'int',
),
PythonFunctionArgument(
'b',
PythonArgumentKind.NORMAL,
None,
None,
),
],
),
(
argument_list('a: int, /, b, *, c'),
[
PythonFunctionArgument(
'a',
PythonArgumentKind.POSITIONAL_ONLY,
None,
'int',
),
PythonFunctionArgument(
'b',
PythonArgumentKind.NORMAL,
None,
None,
),
PythonFunctionArgument(
'c',
PythonArgumentKind.KEYWORD_ONLY,
None,
None,
),
],
),
(
argument_list('a, b, *, c'),
[
PythonFunctionArgument(
'a',
PythonArgumentKind.NORMAL,
None,
None,
),
PythonFunctionArgument(
'b',
PythonArgumentKind.NORMAL,
None,
None,
),
PythonFunctionArgument(
'c',
PythonArgumentKind.KEYWORD_ONLY,
None,
None,
),
],
),
(
argument_list('a, b, *args, c'),
[
PythonFunctionArgument(
'a',
PythonArgumentKind.NORMAL,
None,
None,
),
PythonFunctionArgument(
'b',
PythonArgumentKind.NORMAL,
None,
None,
),
PythonFunctionArgument(
'args',
PythonArgumentKind.VARARG,
None,
None,
),
PythonFunctionArgument(
'c',
PythonArgumentKind.KEYWORD_ONLY,
None,
None,
),
],
),
(
argument_list('a, b, *args, c, **kwargs'),
[
PythonFunctionArgument(
'a',
PythonArgumentKind.NORMAL,
None,
None,
),
PythonFunctionArgument(
'b',
PythonArgumentKind.NORMAL,
None,
None,
),
PythonFunctionArgument(
'args',
PythonArgumentKind.VARARG,
None,
None,
),
PythonFunctionArgument(
'c',
PythonArgumentKind.KEYWORD_ONLY,
None,
None,
),
PythonFunctionArgument(
'kwargs',
PythonArgumentKind.KWARGS,
None,
None,
),
],
),
(
argument_list('a, b, /, *, c, **kwargs'),
[
PythonFunctionArgument(
'a',
PythonArgumentKind.POSITIONAL_ONLY,
None,
None,
),
PythonFunctionArgument(
'b',
PythonArgumentKind.POSITIONAL_ONLY,
None,
None,
),
PythonFunctionArgument(
'c',
PythonArgumentKind.KEYWORD_ONLY,
None,
None,
),
PythonFunctionArgument(
'kwargs',
PythonArgumentKind.KWARGS,
None,
None,
),
],
),
(
argument_list('a, b, /, c, *, d, **kwargs'),
[
PythonFunctionArgument(
'a',
PythonArgumentKind.POSITIONAL_ONLY,
None,
None,
),
PythonFunctionArgument(
'b',
PythonArgumentKind.POSITIONAL_ONLY,
None,
None,
),
PythonFunctionArgument(
'c',
PythonArgumentKind.NORMAL,
None,
None,
),
PythonFunctionArgument(
'd',
PythonArgumentKind.KEYWORD_ONLY,
None,
None,
),
PythonFunctionArgument(
'kwargs',
PythonArgumentKind.KWARGS,
None,
None,
),
],
),
(
argument_list(
(
'a: str, b: int, /, c: float, d: list,'
' *, e: tuple, f: dict, **kwargs: set'
),
),
[
PythonFunctionArgument(
'a',
PythonArgumentKind.POSITIONAL_ONLY,
None,
'str',
),
PythonFunctionArgument(
'b',
PythonArgumentKind.POSITIONAL_ONLY,
None,
'int',
),
PythonFunctionArgument(
'c',
PythonArgumentKind.NORMAL,
None,
'float',
),
PythonFunctionArgument(
'd',
PythonArgumentKind.NORMAL,
None,
'list',
),
PythonFunctionArgument(
'e',
PythonArgumentKind.KEYWORD_ONLY,
None,
'tuple',
),
PythonFunctionArgument(
'f',
PythonArgumentKind.KEYWORD_ONLY,
None,
'dict',
),
PythonFunctionArgument(
'kwargs',
PythonArgumentKind.KWARGS,
None,
'set',
),
],
),
(
argument_list(
(
'a: str, b: int, /, c: float, d: list, *arg'
's: bytes, e: tuple, f: dict, **kwargs: set'
),
),
[
PythonFunctionArgument(
'a',
PythonArgumentKind.POSITIONAL_ONLY,
None,
'str',
),
PythonFunctionArgument(
'b',
PythonArgumentKind.POSITIONAL_ONLY,
None,
'int',
),
PythonFunctionArgument(
'c',
PythonArgumentKind.NORMAL,
None,
'float',
),
PythonFunctionArgument(
'd',
PythonArgumentKind.NORMAL,
None,
'list',
),
PythonFunctionArgument(
'args',
PythonArgumentKind.VARARG,
None,
'bytes',
),
PythonFunctionArgument(
'e',
PythonArgumentKind.KEYWORD_ONLY,
None,
'tuple',
),
PythonFunctionArgument(
'f',
PythonArgumentKind.KEYWORD_ONLY,
None,
'dict',
),
PythonFunctionArgument(
'kwargs',
PythonArgumentKind.KWARGS,
None,
'set',
),
],
),
(
argument_list(
(
'a: str, b: int, /, *args: bytes, '
'c: tuple, d: dict, **kwargs: set'
),
),
[
PythonFunctionArgument(
'a',
PythonArgumentKind.POSITIONAL_ONLY,
None,
'str',
),
PythonFunctionArgument(
'b',
PythonArgumentKind.POSITIONAL_ONLY,
None,
'int',
),
PythonFunctionArgument(
'args',
PythonArgumentKind.VARARG,
None,
'bytes',
),
PythonFunctionArgument(
'c',
PythonArgumentKind.KEYWORD_ONLY,
None,
'tuple',
),
PythonFunctionArgument(
'd',
PythonArgumentKind.KEYWORD_ONLY,
None,
'dict',
),
PythonFunctionArgument(
'kwargs',
PythonArgumentKind.KWARGS,
None,
'set',
),
],
),
(
argument_list(
"a=1, b='a', /, *args, c=None, d=True, **kwargs",
),
[
PythonFunctionArgument(
'a',
PythonArgumentKind.POSITIONAL_ONLY,
'1',
None,
),
PythonFunctionArgument(
'b',
PythonArgumentKind.POSITIONAL_ONLY,
"'a'",
None,
),
PythonFunctionArgument(
'args',
PythonArgumentKind.VARARG,
None,
None,
),
PythonFunctionArgument(
'c',
PythonArgumentKind.KEYWORD_ONLY,
'None',
None,
),
PythonFunctionArgument(
'd',
PythonArgumentKind.KEYWORD_ONLY,
'True',
None,
),
PythonFunctionArgument(
'kwargs',
PythonArgumentKind.KWARGS,
None,
None,
),
],
),
(
argument_list("a, b='a', /, *args, c=None, d=True, **kwargs"),
[
PythonFunctionArgument(
'a',
PythonArgumentKind.POSITIONAL_ONLY,
None,
None,
),
PythonFunctionArgument(
'b',
PythonArgumentKind.POSITIONAL_ONLY,
"'a'",
None,
),
PythonFunctionArgument(
'args',
PythonArgumentKind.VARARG,
None,
None,
),
PythonFunctionArgument(
'c', PythonArgumentKind.KEYWORD_ONLY, 'None', None
),
PythonFunctionArgument(
'd',
PythonArgumentKind.KEYWORD_ONLY,
'True',
None,
),
PythonFunctionArgument(
'kwargs',
PythonArgumentKind.KWARGS,
None,
None,
),
],
),
(
argument_list('a, b , /, *args, c=None, d=True, **kwargs'),
[
PythonFunctionArgument(
'a',
PythonArgumentKind.POSITIONAL_ONLY,
None,
None,
),
PythonFunctionArgument(
'b',
PythonArgumentKind.POSITIONAL_ONLY,
None,
None,
),
PythonFunctionArgument(
'args',
PythonArgumentKind.VARARG,
None,
None,
),
PythonFunctionArgument(
'c',
PythonArgumentKind.KEYWORD_ONLY,
'None',
None,
),
PythonFunctionArgument(
'd',
PythonArgumentKind.KEYWORD_ONLY,
'True',
None,
),
PythonFunctionArgument(
'kwargs',
PythonArgumentKind.KWARGS,
None,
None,
),
],
),
(
argument_list('a, b , /, *args, c, d=True, **kwargs'),
[
PythonFunctionArgument(
'a',
PythonArgumentKind.POSITIONAL_ONLY,
None,
None,
),
PythonFunctionArgument(
'b',
PythonArgumentKind.POSITIONAL_ONLY,
None,
None,
),
PythonFunctionArgument(
'args',
PythonArgumentKind.VARARG,
None,
None,
),
PythonFunctionArgument(
'c',
PythonArgumentKind.KEYWORD_ONLY,
None,
None,
),
PythonFunctionArgument(
'd',
PythonArgumentKind.KEYWORD_ONLY,
'True',
None,
),
PythonFunctionArgument(
'kwargs',
PythonArgumentKind.KWARGS,
None,
None,
),
],
),
(
argument_list('a, b , /, *args, c, d, **kwargs'),
[
PythonFunctionArgument(
'a',
PythonArgumentKind.POSITIONAL_ONLY,
None,
None,
),
PythonFunctionArgument(
'b',
PythonArgumentKind.POSITIONAL_ONLY,
None,
None,
),
PythonFunctionArgument(
'args',
PythonArgumentKind.VARARG,
None,
None,
),
PythonFunctionArgument(
'c',
PythonArgumentKind.KEYWORD_ONLY,
None,
None,
),
PythonFunctionArgument(
'd',
PythonArgumentKind.KEYWORD_ONLY,
None,
None,
),
PythonFunctionArgument(
'kwargs',
PythonArgumentKind.KWARGS,
None,
None,
),
],
),
(
argument_list('a, b , /, *args, c=None, d, **kwargs'),
[
PythonFunctionArgument(
'a',
PythonArgumentKind.POSITIONAL_ONLY,
None,
None,
),
PythonFunctionArgument(
'b',
PythonArgumentKind.POSITIONAL_ONLY,
None,
None,
),
PythonFunctionArgument(
'args',
PythonArgumentKind.VARARG,
None,
None,
),
PythonFunctionArgument(
'c',
PythonArgumentKind.KEYWORD_ONLY,
'None',
None,
),
PythonFunctionArgument(
'd',
PythonArgumentKind.KEYWORD_ONLY,
None,
None,
),
PythonFunctionArgument(
'kwargs',
PythonArgumentKind.KWARGS,
None,
None,
),
],
),
(
argument_list('a=1'),
[
PythonFunctionArgument(
'a',
PythonArgumentKind.NORMAL,
'1',
None,
),
],
),
],
)
def test_PythonASTArgumentsListParser_to_argument_list(
ast_arguments_list,
expected,
):
assert (
PythonASTArgumentsListParser(ast_arguments_list).to_argument_list()
== expected
)
@@ -0,0 +1,629 @@
# Copyright 2026 jCloud
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import pytest
from src.jcloud_docsgen.core.python.arguments import (
PythonArgumentKind,
PythonFunctionArgument,
)
from src.jcloud_docsgen.exceptions import (
InvalidPythonAnnotationError,
InvalidPythonIdentifierError,
PythonArgumentStructureError,
)
@pytest.mark.parametrize(
'name,kind,default,annotation,expected_exception,expected_exception_msg',
[
(
None,
PythonArgumentKind.NORMAL,
None,
None,
TypeError,
"expected 'str', got 'NoneType'",
),
(
1,
PythonArgumentKind.NORMAL,
None,
None,
TypeError,
"expected 'str', got 'int'",
),
(
42,
PythonArgumentKind.NORMAL,
None,
None,
TypeError,
"expected 'str', got 'int'",
),
(
4.2,
PythonArgumentKind.NORMAL,
None,
None,
TypeError,
"expected 'str', got 'float'",
),
(
4.2,
PythonArgumentKind.NORMAL,
None,
None,
TypeError,
"expected 'str', got 'float'",
),
(
'a',
PythonArgumentKind.NORMAL,
1,
None,
TypeError,
"expected either 'str' or 'NoneType', got 'int'",
),
(
'a',
PythonArgumentKind.NORMAL,
None,
1,
TypeError,
"expected either 'str' or 'NoneType', got 'int'",
),
(
'a',
0,
None,
None,
TypeError,
"expected 'PythonArgumentKind', got 'int'",
),
(
'a',
PythonArgumentKind.NORMAL,
None,
'',
InvalidPythonAnnotationError,
'invalid annotation',
),
(
'',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier',
),
(
'.',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: .',
),
(
'#',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: #',
),
(
'1a',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: 1a',
),
(
'1_',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: 1_',
),
(
'1,',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: 1,',
),
(
',',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: ,',
),
(
':',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: :',
),
(
'-',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: -',
),
(
';',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: ;',
),
(
'|',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: |',
),
(
'<',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: <',
),
(
'>',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: >',
),
(
'a,',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: a,',
),
(
',a',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: ,a',
),
(
'a:',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: a:',
),
(
':a',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: :a',
),
(
'a-',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: a-',
),
(
'-a',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: -a',
),
(
'a;',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: a;',
),
(
';a',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: ;a',
),
(
'a|',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: a|',
),
(
'|a',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: |a',
),
(
'a<',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: a<',
),
(
'<a',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: <a',
),
(
'a>',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: a>',
),
(
'>a',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: >a',
),
(
'class',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: class',
),
(
'def',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: def',
),
(
'lambda',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: lambda',
),
(
'None',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: None',
),
(
'True',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: True',
),
(
'a',
PythonArgumentKind.VARARG,
'1',
None,
PythonArgumentStructureError,
'* or ** arguments cannot have default values: *a=1',
),
(
'a',
PythonArgumentKind.KWARGS,
'1',
None,
PythonArgumentStructureError,
'* or ** arguments cannot have default values: **a=1',
),
],
)
def test_PythonFunctionArgument_exceptions(
name,
kind,
default,
annotation,
expected_exception,
expected_exception_msg,
):
with pytest.raises(expected_exception) as exc_info:
PythonFunctionArgument(name, kind, default, annotation)
assert str(exc_info.value) == expected_exception_msg
@pytest.mark.parametrize(
'name',
[
'a',
'_a',
'a1',
'a0',
'a_',
'_',
'_abc',
'abc',
'abc_',
'match',
'case',
'type',
'ä',
'none',
'true',
'false',
],
)
@pytest.mark.parametrize(
'kind',
[
PythonArgumentKind.NORMAL,
PythonArgumentKind.POSITIONAL_ONLY,
PythonArgumentKind.KEYWORD_ONLY,
],
)
@pytest.mark.parametrize(
'default',
[
None,
'x',
'1',
None,
],
)
@pytest.mark.parametrize(
'annotation',
[
None,
'a',
'list[int]',
'_',
],
)
def test_PythonFunctionArgument(name, kind, default, annotation):
PythonFunctionArgument(name, kind, default, annotation)
@pytest.mark.parametrize(
'name',
[
'a',
'_a',
'a1',
'a0',
'a_',
'_',
'_abc',
'abc',
'abc_',
'match',
'case',
'type',
'ä',
'none',
'true',
'false',
],
)
@pytest.mark.parametrize(
'kind',
[
PythonArgumentKind.VARARG,
PythonArgumentKind.KWARGS,
],
)
@pytest.mark.parametrize(
'annotation',
[
None,
'a',
'list[int]',
'_',
],
)
def test_PythonFunctionArgument_asterik(name, kind, annotation):
PythonFunctionArgument(name, kind, None, annotation)
@pytest.mark.parametrize(
'argument,expected',
[
(
PythonFunctionArgument(
'a',
PythonArgumentKind.NORMAL,
None,
None,
),
'a',
),
(
PythonFunctionArgument(
'a',
PythonArgumentKind.NORMAL,
'1',
None,
),
'a=1',
),
(
PythonFunctionArgument(
'a', PythonArgumentKind.NORMAL, None, 'int'
),
'a: int',
),
(
PythonFunctionArgument(
'a',
PythonArgumentKind.NORMAL,
'1',
'int',
),
'a: int = 1',
),
(
PythonFunctionArgument(
'a',
PythonArgumentKind.POSITIONAL_ONLY,
None,
None,
),
'a',
),
(
PythonFunctionArgument(
'a',
PythonArgumentKind.POSITIONAL_ONLY,
'1',
None,
),
'a=1',
),
(
PythonFunctionArgument(
'a',
PythonArgumentKind.POSITIONAL_ONLY,
None,
'int',
),
'a: int',
),
(
PythonFunctionArgument(
'a',
PythonArgumentKind.POSITIONAL_ONLY,
'1',
'int',
),
'a: int = 1',
),
(
PythonFunctionArgument(
'a',
PythonArgumentKind.KEYWORD_ONLY,
None,
None,
),
'a',
),
(
PythonFunctionArgument(
'a',
PythonArgumentKind.KEYWORD_ONLY,
'1',
None,
),
'a=1',
),
(
PythonFunctionArgument(
'a',
PythonArgumentKind.KEYWORD_ONLY,
None,
'int',
),
'a: int',
),
(
PythonFunctionArgument(
'a',
PythonArgumentKind.KEYWORD_ONLY,
'1',
'int',
),
'a: int = 1',
),
(
PythonFunctionArgument(
'a',
PythonArgumentKind.VARARG,
None,
None,
),
'*a',
),
(
PythonFunctionArgument(
'a',
PythonArgumentKind.VARARG,
None,
'int',
),
'*a: int',
),
(
PythonFunctionArgument(
'a',
PythonArgumentKind.KWARGS,
None,
None,
),
'**a',
),
(
PythonFunctionArgument(
'a',
PythonArgumentKind.KWARGS,
None,
'int',
),
'**a: int',
),
],
)
def test_PythonFunctionArgument_signature_repr(argument, expected):
assert argument.signature_repr() == expected
Loaded 100 of 113 files, more files were not shown because too many files have changed in this diff. Show more