README.md: The public documentation for thebeman-tidytool.docs/: The internal documentation.beman_tidy/: The package/production code for the tool.beman_tidy/cli.py: The CLI / entry point for the tool.beman_tidy/lib/: The library for the tool.beman_tidy/lib/checks/: The checks for the tool.beman_tidy/lib/pipeline.py: The checks pipeline for thebeman-tidytool.
beman_tidy/.beman-standard.yaml: Stable (offline) version of the standard.beman_tidy/.beman-tidy.yaml: Default configuration file forbeman-tidy. Will be merged with the user's.
tests/: Unit tests for the tool.- Structure is similar to the
beman_tidy/directory. pytestis used for testing.
- Structure is similar to the
Find an unimplemented check in the beman_standard.md file and check that is not already assigned in Planning for beman-tidy: The Codebase Bemanification Tool.
Check this PR example: beman-tidy: add check - readme.library_status.
Step by step tutorial: add a new check
-
[mandatory]Make surebeman_tidy/.beman-standard.yamlreflects your check metadata (latest status from beman_standard.md).[optional]New syntax / keys from yaml config can be added in beman-tidy/beman_tidy/lib/utils_git.py:load_beman_standard_config() if not already implemented. Checks for TODOs inload_beman_standard_config().
-
[mandatory]Add the check to thebeman_tidy/lib/checks/beman_standard/directory.-
[mandatory]e.g.,readme.*checks will most likely go to a path similar tobeman_tidy/lib/checks/beman_standard/readme.py. -
[mandatory]Use an appropriate base class - e.g., defaults likeFileBaseCheck/DirectoryBaseCheckor create specializations for reusing code - e.g.,ReadmeBaseCheck(FileBaseCheck)/CmakeBaseCheck(FileBaseCheck)/CppBaseCheck(FileBaseCheck)etc. -
[mandatory]Register the new check via@register_beman_standard_checkdecorator - e.g.,@register_beman_standard_check("readme.title") class ReadmeTitleCheck(ReadmeBaseCheck):
-
[mandatory]Implement the actual check.
-
-
[mandatory]Add tests for the check to thetests/beman_standard/directory. More in Writing Tests. -
[optional]Update docs if needed inREADME.mdanddocs/dev-guide.mdfiles. -
[optional]Update thebeman_tidy/cli.pyfile if the public API has changed.
-
The recommended workflow relies on Astral's uv
-
To build (in root):
uv build
-
To install:
pipx install dist/*.whl
Use this as a fast local cycle while implementing checks:
uv run ruff check --diff
uv run ruff check --fix
uv run pytest
uv run beman-tidy --verbose --require-all /path/to/exemplarIf your environment has trouble launching uv run pytest, use uv run python -m pytest.
Run the linter on the beman-tidy's codebase:
uv run ruff check --diff
uv run ruff check --fixRun the tests:
$ uv run pytest
============================= test session starts ==============================
platform linux -- Python 3.12.3, pytest-9.0.3, pluggy-1.6.0 -- /home/goodman/acs/le_projects/beman_project/beman-tidy/.venv/bin/python3
cachedir: .pytest_cache
rootdir: /home/goodman/acs/le_projects/beman_project/beman-tidy
configfile: pyproject.toml
collecting ... collected 87 items
tests/lib/checks/beman_standard/cpp/test_cpp.py::test__cpp_namespace__valid PASSED [ 1%]
tests/lib/checks/beman_standard/cpp/test_cpp.py::test__cpp_namespace__invalid PASSED [ 2%]
tests/lib/checks/beman_standard/cpp/test_cpp.py::test__cpp_namespace__fix_inplace PASSED [ 3%]
tests/lib/checks/beman_standard/directory/test_directory.py::test__directory_sources__valid PASSED [ 4%]
tests/lib/checks/beman_standard/directory/test_directory.py::test__directory_sources__invalid PASSED [ 5%]
tests/lib/checks/beman_standard/directory/test_directory.py::test__directory_sources__fix_inplace SKIPPED [ 6%]
...
tests/lib/checks/beman_standard/readme/test_readme.py::test__readme_title__valid PASSED [ 35%]
tests/lib/checks/beman_standard/readme/test_readme.py::test__readme_title__invalid PASSED [ 36%]
tests/lib/checks/beman_standard/readme/test_readme.py::test__readme_title__fix_inplace PASSED [ 37%]
tests/lib/checks/beman_standard/readme/test_readme.py::test__readme_badges__valid PASSED [ 39%]
tests/lib/checks/beman_standard/readme/test_readme.py::test__readme_badges__invalid PASSED [ 40%]
tests/lib/checks/beman_standard/readme/test_readme.py::test__readme_badges__fix_inplace SKIPPED (not implemented) [ 41%]
...
tests/lib/utils/test_config.py::test_load_repo_config_invalid_yaml PASSED [ 98%]
tests/lib/utils/test_config.py::test_load_repo_config_invalid_validation PASSED [100%]
======================== 69 passed, 18 skipped in 1.22s ========================tests/lib/checks/beman_standard/<check_category>/test_<check_category>.py: The test file for the<check_category>check.- e.g., for
check_category = "readme"the test file istests/lib/checks/beman_standard/readme/test_readme.py.
- e.g., for
test__<check_category>_<check_rule>__<test_case_name>()function inside the test file.- e.g., for
check_category = "readme"thecheck_rulecan be:title,badges,purpose,implements,library_statusorlicense. test_case_namecan bevalid,invalid,fix_inplaceorskipped.- If the check is implemented and must be run, add 3 test functions:
valid,invalidandfix_inplace(some of them can be a@pytest.mark.skip(reason="not implemented")decorator, but at least one must be actually implemented). - If the check is implemented as a dummy (e.g., cannot be properly implemented), add the
skippedfunction.should_skip()must log a reason why it is skipped. - Note: The number of tests is already enforced by a unit test in
tests/lib/checks/system/test_registry.py, which is looking for the test functions for new added checks! - Examples:
- Runnable check -
readme.title:- for
check_category = "readme",check_rule = "title"andtest_case_name = "valid"the function istest__readme_title__valid(). - for
check_category = "readme",check_rule = "title"andtest_case_name = "invalid"the function istest__readme_title__invalid(). - for
check_category = "readme",check_rule = "title"andtest_case_name = "fix_inplace"the function istest__readme_title__fix_inplace().
- for
- Skippable check -
license.criteria:- for
check_category = "license",check_rule = "criteria"andtest_case_name = "skipped"the function istest__license_criteria__skipped(). should_skip()must log a reason why it is skipped.should_skip()must returnTrue.check()andfix()must provide areturn Trueimplementation.
- for
- Runnable check -
- e.g., for
tests/lib/checks/beman_standard/<check_category>/data/: The data for the tests (e.g., files, directories, etc.).- e.g., for
check_category = "readme"andtest_case_name = "valid"the data is intests/lib/checks/beman_standard/readme/data/valid/. - e.g., for
check_category = "readme"andtest_case_name = "invalid"the data is intests/lib/checks/beman_standard/readme/data/invalid/. - e.g., for
check_category = "readme"andtest_case_name = "fix_inplace"the data may use bothvalidandinvalidfiles. It is recommended to not change these files and use temporary copies having suffix.delete_me(which are not tracked by git).
- e.g., for
- Default setup / mocks:
repo_info: The repository information (e.g., path, name, etc.). Mocked with hardcoded values ofbeman.exemplar.beman_standard_check_config: The Beman Standard configuration file. Actual load of the.beman-standard.yamlfile.
- Always add at least 3 test cases for each check.
valid: The test case for the valid case.invalid: The test case for the invalid case.fix_inplace: The test case for the fix invalid case. If the fix is not (yet) implementable, add a@pytest.mark.skip(reason="not implemented")decorator to track the progress.
- Add / update the dependency to the
pyproject.tomlfile. - Run
uv cleanto make sure the dependencies are updated. - Run
uv sync --upgradeto upgrade and re-resolve dependencies. - Run
uv syncto update the uv lockfile - Run
uv export -o pylock.tomlto updatepylock.toml - Run
uv buildto build the wheel. - Run
uv run beman-tidy --helpto check if the new dependency is available. - Commit the changes from
pyproject.toml,pylock.tomlanduv.lock.
Requirements:
beman-tidymust be able to run on Windows, Linux, and macOS, thus it's 100% Python.beman-tidymust NOT use internet access. A local snapshot of the standard is used (check.beman-standard.yaml).beman-tidymust haveverboseandnon-verbosemodes. Default isnon-verbose.beman-tidymust havedry-runandfix-inplacemodes. Default isdry-run.beman-tidymust detect types of checks: failed, passed, skipped (not implemented) and print the summary/coverage.beman-tidycan access configuration files shipped with the tool itself (e.g.,.beman-standard.yamlorLICENSE). All such files must be in thebeman_tidy/directory to be automatically available in exported packages.
Limitations:
2025-06-07:beman-tidywill not support the--fix-inplaceflag in the first iteration for most of the checks.2025-06-07:beman-tidymay generate small changes to the standard (e.g., for automated fixes), while the standard is not stable. Thus, the tool itself may be unstable.