├── .clang-format ├── .github ├── dependabot.yaml ├── sync-files.yaml └── workflows │ ├── github-release.yaml │ ├── pre-commit-optional.yaml │ ├── pre-commit.yaml │ ├── pytest.yaml │ ├── semantic-pull-request.yaml │ ├── spell-check-differential.yaml │ ├── sync-files.yaml │ ├── test-pre-commit-hooks.yaml │ └── update-depend-versions.yaml ├── .gitignore ├── .markdown-link-check.json ├── .markdownlint.yaml ├── .pre-commit-config-optional.yaml ├── .pre-commit-config.yaml ├── .pre-commit-hooks.yaml ├── .prettierignore ├── .prettierrc.yaml ├── .yamllint.yaml ├── LICENSE ├── README.md ├── package.json ├── pre_commit_hooks ├── __init__.py ├── ros_include_guard.py └── sort_package_xml.py ├── requirements.txt ├── setup.cfg ├── setup.py └── tests ├── pre-commit-hooks ├── prettier-launch-xml │ └── sample.launch.xml ├── prettier-package-xml │ └── normal │ │ └── package.xml ├── prettier-xacro │ ├── calibration.xacro │ └── sample.xacro ├── ros-include-guard │ └── include │ │ └── foo_bar │ │ └── baz.hpp ├── sort-package-xml │ └── normal │ │ └── package.xml └── test-pre-commit-hooks.yaml ├── test_ros_include_guard.py ├── test_ros_include_guard ├── include │ └── rospkg │ │ ├── foobar.right.h │ │ ├── foobar.right.hpp │ │ ├── foobar.wrong.h │ │ ├── foobar.wrong.hpp │ │ ├── nolint.right.hpp │ │ ├── nolint.wrong.hpp │ │ ├── none.hpp │ │ ├── pragma.only.hpp │ │ ├── pragma.right.hpp │ │ └── pragma.wrong.hpp └── src │ ├── foo │ ├── bar │ │ ├── baz.right.hpp │ │ └── baz.wrong.hpp │ ├── bar_baz.right.hpp │ └── bar_baz.wrong.hpp │ ├── foo_bar │ ├── baz.right.hpp │ └── baz.wrong.hpp │ ├── foo_bar_baz.right.hpp │ └── foo_bar_baz.wrong.hpp ├── test_sort_package_xml.py └── test_sort_package_xml ├── no-format.answer.xml ├── no-format.input.xml ├── normal.answer.xml └── normal.input.xml /.clang-format: -------------------------------------------------------------------------------- 1 | # Modified from https://github.com/ament/ament_lint/blob/master/ament_clang_format/ament_clang_format/configuration/.clang-format 2 | Language: Cpp 3 | BasedOnStyle: Google 4 | 5 | AccessModifierOffset: -2 6 | AlignAfterOpenBracket: AlwaysBreak 7 | AllowShortFunctionsOnASingleLine: InlineOnly 8 | BraceWrapping: 9 | AfterClass: true 10 | AfterFunction: true 11 | AfterNamespace: true 12 | AfterStruct: true 13 | BreakBeforeBraces: Custom 14 | ColumnLimit: 100 15 | ConstructorInitializerIndentWidth: 0 16 | ContinuationIndentWidth: 2 17 | DerivePointerAlignment: false 18 | PointerAlignment: Middle 19 | ReflowComments: true 20 | IncludeCategories: 21 | # C++ system headers 22 | - Regex: <[a-z_]*> 23 | Priority: 6 24 | CaseSensitive: true 25 | # C system headers 26 | - Regex: <.*\.h> 27 | Priority: 5 28 | CaseSensitive: true 29 | # Boost headers 30 | - Regex: boost/.* 31 | Priority: 4 32 | CaseSensitive: true 33 | # Message headers 34 | - Regex: .*_msgs/.* 35 | Priority: 3 36 | CaseSensitive: true 37 | - Regex: .*_srvs/.* 38 | Priority: 3 39 | CaseSensitive: true 40 | # Other Package headers 41 | - Regex: <.*> 42 | Priority: 2 43 | CaseSensitive: true 44 | # Local package headers 45 | - Regex: '".*"' 46 | Priority: 1 47 | CaseSensitive: true 48 | -------------------------------------------------------------------------------- /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: / 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 1 8 | labels: 9 | - bot 10 | - github-actions 11 | -------------------------------------------------------------------------------- /.github/sync-files.yaml: -------------------------------------------------------------------------------- 1 | - repository: autowarefoundation/autoware 2 | files: 3 | - source: .github/dependabot.yaml 4 | - source: .github/workflows/github-release.yaml 5 | - source: .github/workflows/pre-commit.yaml 6 | - source: .github/workflows/pre-commit-optional.yaml 7 | - source: .github/workflows/semantic-pull-request.yaml 8 | - source: .github/workflows/spell-check-differential.yaml 9 | - source: .github/workflows/sync-files.yaml 10 | - source: .clang-format 11 | - source: .markdown-link-check.json 12 | - source: .markdownlint.yaml 13 | - source: .pre-commit-config-optional.yaml 14 | - source: .prettierignore 15 | - source: .prettierrc.yaml 16 | - source: .yamllint.yaml 17 | -------------------------------------------------------------------------------- /.github/workflows/github-release.yaml: -------------------------------------------------------------------------------- 1 | name: github-release 2 | 3 | on: 4 | push: 5 | branches: 6 | - beta/v* 7 | tags: 8 | - v* 9 | workflow_dispatch: 10 | inputs: 11 | beta-branch-or-tag-name: 12 | description: The name of the beta branch or tag to release 13 | type: string 14 | required: true 15 | 16 | jobs: 17 | github-release: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - name: Set tag name 21 | id: set-tag-name 22 | run: | 23 | if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then 24 | REF_NAME="${{ github.event.inputs.beta-branch-or-tag-name }}" 25 | else 26 | REF_NAME="${{ github.ref_name }}" 27 | fi 28 | 29 | echo "ref-name=$REF_NAME" >> $GITHUB_OUTPUT 30 | echo "tag-name=${REF_NAME#beta/}" >> $GITHUB_OUTPUT 31 | 32 | - name: Check out repository 33 | uses: actions/checkout@v3 34 | with: 35 | fetch-depth: 0 36 | ref: ${{ steps.set-tag-name.outputs.ref-name }} 37 | 38 | - name: Set target name for beta branches 39 | id: set-target-name 40 | run: | 41 | if [[ "${{ steps.set-tag-name.outputs.ref-name }}" =~ "beta/" ]]; then 42 | echo "target-name=${{ steps.set-tag-name.outputs.ref-name }}" >> $GITHUB_OUTPUT 43 | fi 44 | 45 | - name: Create a local tag for beta branches 46 | run: | 47 | if [ "${{ steps.set-target-name.outputs.target-name }}" != "" ]; then 48 | git tag "${{ steps.set-tag-name.outputs.tag-name }}" 49 | fi 50 | 51 | - name: Run generate-changelog 52 | id: generate-changelog 53 | uses: autowarefoundation/autoware-github-actions/generate-changelog@v1 54 | 55 | - name: Select verb 56 | id: select-verb 57 | run: | 58 | has_previous_draft=$(gh release view --json isDraft -q ".isDraft" "${{ steps.set-tag-name.outputs.tag-name }}") || true 59 | 60 | verb=create 61 | if [ "$has_previous_draft" = "true" ]; then 62 | verb=edit 63 | fi 64 | 65 | echo "verb=$verb" >> $GITHUB_OUTPUT 66 | env: 67 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 68 | 69 | - name: Release to GitHub 70 | run: | 71 | gh release ${{ steps.select-verb.outputs.verb }} "${{ steps.set-tag-name.outputs.tag-name }}" \ 72 | --draft \ 73 | --target "${{ steps.set-target-name.outputs.target-name }}" \ 74 | --title "Release ${{ steps.set-tag-name.outputs.tag-name }}" \ 75 | --notes "$NOTES" 76 | env: 77 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 78 | NOTES: ${{ steps.generate-changelog.outputs.changelog }} 79 | -------------------------------------------------------------------------------- /.github/workflows/pre-commit-optional.yaml: -------------------------------------------------------------------------------- 1 | name: pre-commit-optional 2 | 3 | on: 4 | pull_request: 5 | 6 | jobs: 7 | pre-commit-optional: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Check out repository 11 | uses: actions/checkout@v3 12 | 13 | - name: Run pre-commit 14 | uses: autowarefoundation/autoware-github-actions/pre-commit@v1 15 | with: 16 | pre-commit-config: .pre-commit-config-optional.yaml 17 | -------------------------------------------------------------------------------- /.github/workflows/pre-commit.yaml: -------------------------------------------------------------------------------- 1 | name: pre-commit 2 | 3 | on: 4 | pull_request: 5 | 6 | jobs: 7 | pre-commit: 8 | if: ${{ github.event.repository.private }} # Use pre-commit.ci for public repositories 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Generate token 12 | id: generate-token 13 | uses: tibdex/github-app-token@v1 14 | with: 15 | app_id: ${{ secrets.APP_ID }} 16 | private_key: ${{ secrets.PRIVATE_KEY }} 17 | 18 | - name: Check out repository 19 | uses: actions/checkout@v3 20 | with: 21 | ref: ${{ github.event.pull_request.head.ref }} 22 | 23 | - name: Run pre-commit 24 | uses: autowarefoundation/autoware-github-actions/pre-commit@v1 25 | with: 26 | pre-commit-config: .pre-commit-config.yaml 27 | token: ${{ steps.generate-token.outputs.token }} 28 | -------------------------------------------------------------------------------- /.github/workflows/pytest.yaml: -------------------------------------------------------------------------------- 1 | name: pytest 2 | 3 | on: 4 | pull_request: 5 | 6 | jobs: 7 | pytest: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Check out repository 11 | uses: actions/checkout@v3 12 | 13 | - name: Install dependent packages 14 | run: python3 -m pip install -r requirements.txt 15 | 16 | - name: Run pytest 17 | run: python3 -m pytest 18 | -------------------------------------------------------------------------------- /.github/workflows/semantic-pull-request.yaml: -------------------------------------------------------------------------------- 1 | name: semantic-pull-request 2 | 3 | on: 4 | pull_request_target: 5 | types: 6 | - opened 7 | - edited 8 | - synchronize 9 | 10 | jobs: 11 | semantic-pull-request: 12 | uses: autowarefoundation/autoware-github-actions/.github/workflows/semantic-pull-request.yaml@v1 13 | -------------------------------------------------------------------------------- /.github/workflows/spell-check-differential.yaml: -------------------------------------------------------------------------------- 1 | name: spell-check-differential 2 | 3 | on: 4 | pull_request: 5 | 6 | jobs: 7 | spell-check-differential: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Check out repository 11 | uses: actions/checkout@v3 12 | 13 | - name: Run spell-check 14 | uses: autowarefoundation/autoware-github-actions/spell-check@v1 15 | with: 16 | cspell-json-url: https://raw.githubusercontent.com/tier4/autoware-spell-check-dict/main/.cspell.json 17 | -------------------------------------------------------------------------------- /.github/workflows/sync-files.yaml: -------------------------------------------------------------------------------- 1 | name: sync-files 2 | 3 | on: 4 | schedule: 5 | - cron: 0 0 * * * 6 | workflow_dispatch: 7 | 8 | jobs: 9 | check-secret: 10 | uses: autowarefoundation/autoware-github-actions/.github/workflows/check-secret.yaml@v1 11 | secrets: 12 | secret: ${{ secrets.APP_ID }} 13 | 14 | sync-files: 15 | needs: check-secret 16 | if: ${{ needs.check-secret.outputs.set == 'true' }} 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Generate token 20 | id: generate-token 21 | uses: tibdex/github-app-token@v1 22 | with: 23 | app_id: ${{ secrets.APP_ID }} 24 | private_key: ${{ secrets.PRIVATE_KEY }} 25 | 26 | - name: Run sync-files 27 | uses: autowarefoundation/autoware-github-actions/sync-files@v1 28 | with: 29 | token: ${{ steps.generate-token.outputs.token }} 30 | pr-labels: | 31 | bot 32 | sync-files 33 | auto-merge-method: squash 34 | -------------------------------------------------------------------------------- /.github/workflows/test-pre-commit-hooks.yaml: -------------------------------------------------------------------------------- 1 | name: test-pre-commit-hooks 2 | 3 | on: 4 | pull_request: 5 | 6 | jobs: 7 | test-pre-commit-hooks: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Check out repository 11 | uses: actions/checkout@v3 12 | 13 | - name: Run pre-commit 14 | uses: autowarefoundation/autoware-github-actions/pre-commit@v1 15 | with: 16 | pre-commit-config: tests/pre-commit-hooks/test-pre-commit-hooks.yaml 17 | 18 | - name: Try pre-commit 19 | run: | 20 | pre-commit try-repo https://github.com/tier4/pre-commit-hooks-ros flake8-ros -a --ref ${{ github.ref }} 21 | 22 | - name: Try pre-commit with fault injected 23 | run: | 24 | echo "import os" >> pre_commit_hooks/sort_package_xml.py 25 | if pre-commit try-repo https://github.com/tier4/pre-commit-hooks-ros flake8-ros -a --ref ${{ github.ref }}; then 26 | exit 1 27 | fi 28 | -------------------------------------------------------------------------------- /.github/workflows/update-depend-versions.yaml: -------------------------------------------------------------------------------- 1 | name: update-depend-versions 2 | 3 | on: 4 | schedule: 5 | - cron: 0 0 * * * 6 | workflow_dispatch: 7 | 8 | jobs: 9 | update-depend-versions: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Generate token 13 | id: generate-token 14 | uses: tibdex/github-app-token@v1 15 | with: 16 | app_id: ${{ secrets.APP_ID }} 17 | private_key: ${{ secrets.PRIVATE_KEY }} 18 | 19 | - name: Check out repository 20 | uses: actions/checkout@v3 21 | 22 | - name: Set up sd 23 | uses: kenji-miyake/setup-sd@v1 24 | 25 | - name: Set up Python 26 | uses: actions/setup-python@v4 27 | with: 28 | python-version: "3.10" 29 | 30 | - name: Get flake8 versions 31 | id: get-flake8-versions 32 | run: | 33 | function get-latest-pip-version() { 34 | pip index versions "$1" 2>/dev/null | head -n1 | sd '.*\((.*)\)' '$1' 35 | } 36 | 37 | echo ::set-output name=flake8-version::$(get-latest-pip-version flake8) 38 | echo ::set-output name=flake8-blind-except-version::$(get-latest-pip-version flake8-blind-except) 39 | echo ::set-output name=flake8-builtins-version::$(get-latest-pip-version flake8-builtins) 40 | echo ::set-output name=flake8-class-newline-version::$(get-latest-pip-version flake8-class-newline) 41 | echo ::set-output name=flake8-comprehensions-version::$(get-latest-pip-version flake8-comprehensions) 42 | echo ::set-output name=flake8-deprecated-version::$(get-latest-pip-version flake8-deprecated) 43 | echo ::set-output name=flake8-docstrings-version::$(get-latest-pip-version flake8-docstrings) 44 | echo ::set-output name=flake8-import-order-version::$(get-latest-pip-version flake8-import-order) 45 | echo ::set-output name=flake8-quotes-version::$(get-latest-pip-version flake8-quotes) 46 | 47 | - name: Update flake8 version 48 | run: | 49 | sd '(flake8)==\d+\.\d+(\.\d+)?' '$1==${{ steps.get-flake8-versions.outputs.flake8-version }}' .pre-commit-hooks.yaml 50 | sd '(flake8-blind-except)==\d+\.\d+(\.\d+)?' '$1==${{ steps.get-flake8-versions.outputs.flake8-blind-except-version }}' .pre-commit-hooks.yaml 51 | sd '(flake8-builtins)==\d+\.\d+(\.\d+)?' '$1==${{ steps.get-flake8-versions.outputs.flake8-builtins-version }}' .pre-commit-hooks.yaml 52 | sd '(flake8-class-newline)==\d+\.\d+(\.\d+)?' '$1==${{ steps.get-flake8-versions.outputs.flake8-class-newline-version }}' .pre-commit-hooks.yaml 53 | sd '(flake8-comprehensions)==\d+\.\d+(\.\d+)?' '$1==${{ steps.get-flake8-versions.outputs.flake8-comprehensions-version }}' .pre-commit-hooks.yaml 54 | sd '(flake8-deprecated)==\d+\.\d+(\.\d+)?' '$1==${{ steps.get-flake8-versions.outputs.flake8-deprecated-version }}' .pre-commit-hooks.yaml 55 | sd '(flake8-docstrings)==\d+\.\d+(\.\d+)?' '$1==${{ steps.get-flake8-versions.outputs.flake8-docstrings-version }}' .pre-commit-hooks.yaml 56 | sd '(flake8-import-order)==\d+\.\d+(\.\d+)?' '$1==${{ steps.get-flake8-versions.outputs.flake8-import-order-version }}' .pre-commit-hooks.yaml 57 | sd '(flake8-quotes)==\d+\.\d+(\.\d+)?' '$1==${{ steps.get-flake8-versions.outputs.flake8-quotes-version }}' .pre-commit-hooks.yaml 58 | 59 | - name: Get prettier versions 60 | id: get-prettier-versions 61 | run: | 62 | function get-latest-npm-version() { 63 | npm show "$1" version 64 | } 65 | 66 | echo ::set-output name=prettier-version::$(get-latest-npm-version prettier) 67 | echo ::set-output name=prettier-xml-version::$(get-latest-npm-version @prettier/plugin-xml) 68 | 69 | - name: Update prettier version 70 | run: | 71 | sd '(prettier)@\d+\.\d+\.\d+' '$1@${{ steps.get-prettier-versions.outputs.prettier-version }}' .pre-commit-hooks.yaml 72 | sd '(@prettier/plugin-xml)@\d+\.\d+\.\d+' '$1@${{ steps.get-prettier-versions.outputs.prettier-xml-version }}' .pre-commit-hooks.yaml 73 | 74 | - name: Create PR 75 | id: create-pr 76 | uses: peter-evans/create-pull-request@v5 77 | with: 78 | token: ${{ steps.generate-token.outputs.token }} 79 | base: ${{ github.event.repository.default_branch }} 80 | branch: update-depend-versions 81 | title: "chore: update depend versions" 82 | commit-message: "chore: update depend versions" 83 | body: "" 84 | labels: | 85 | bot 86 | update-depend-versions 87 | signoff: true 88 | delete-branch: true 89 | 90 | - name: Check outputs 91 | run: | 92 | echo "Pull Request Number - ${{ steps.create-pr.outputs.pull-request-number }}" 93 | echo "Pull Request URL - ${{ steps.create-pr.outputs.pull-request-url }}" 94 | shell: bash 95 | 96 | - name: Enable auto-merge 97 | if: ${{ steps.create-pr.outputs.pull-request-operation == 'created' }} 98 | run: gh pr merge --squash --auto "${{ steps.create-pr.outputs.pull-request-number }}" 99 | env: 100 | GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }} 101 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | # Visual Studio Code 132 | .vscode/ 133 | log/ 134 | -------------------------------------------------------------------------------- /.markdown-link-check.json: -------------------------------------------------------------------------------- 1 | { 2 | "aliveStatusCodes": [200, 206, 403], 3 | "ignorePatterns": [ 4 | { 5 | "pattern": "^http://localhost" 6 | }, 7 | { 8 | "pattern": "^http://127\\.0\\.0\\.1" 9 | }, 10 | { 11 | "pattern": "^https://github.com/.*/discussions/new" 12 | } 13 | ], 14 | "retryOn429": true, 15 | "retryCount": 10 16 | } 17 | -------------------------------------------------------------------------------- /.markdownlint.yaml: -------------------------------------------------------------------------------- 1 | # See https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md for all rules. 2 | default: true 3 | MD013: false 4 | MD024: 5 | siblings_only: true 6 | MD029: 7 | style: ordered 8 | MD033: false 9 | MD041: false 10 | MD046: false 11 | MD049: false 12 | -------------------------------------------------------------------------------- /.pre-commit-config-optional.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/tcort/markdown-link-check 3 | rev: v3.11.1 4 | hooks: 5 | - id: markdown-link-check 6 | args: [--config=.markdown-link-check.json] 7 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | ci: 2 | autofix_commit_msg: "ci(pre-commit): autofix" 3 | autoupdate_commit_msg: "ci(pre-commit): autoupdate" 4 | 5 | repos: 6 | - repo: https://github.com/pre-commit/pre-commit-hooks 7 | rev: v4.4.0 8 | hooks: 9 | - id: check-json 10 | - id: check-merge-conflict 11 | - id: check-toml 12 | - id: check-xml 13 | - id: check-yaml 14 | - id: detect-private-key 15 | - id: end-of-file-fixer 16 | - id: mixed-line-ending 17 | - id: trailing-whitespace 18 | args: [--markdown-linebreak-ext=md] 19 | 20 | - repo: https://github.com/igorshubovych/markdownlint-cli 21 | rev: v0.33.0 22 | hooks: 23 | - id: markdownlint 24 | args: [-c, .markdownlint.yaml, --fix] 25 | 26 | - repo: https://github.com/pre-commit/mirrors-prettier 27 | rev: v3.0.0-alpha.6 28 | hooks: 29 | - id: prettier 30 | 31 | - repo: https://github.com/adrienverge/yamllint 32 | rev: v1.30.0 33 | hooks: 34 | - id: yamllint 35 | 36 | - repo: https://github.com/shellcheck-py/shellcheck-py 37 | rev: v0.9.0.2 38 | hooks: 39 | - id: shellcheck 40 | 41 | - repo: https://github.com/scop/pre-commit-shfmt 42 | rev: v3.6.0-2 43 | hooks: 44 | - id: shfmt 45 | args: [-w, -s, -i=4] 46 | 47 | - repo: https://github.com/pycqa/isort 48 | rev: 5.12.0 49 | hooks: 50 | - id: isort 51 | 52 | - repo: https://github.com/psf/black 53 | rev: 23.3.0 54 | hooks: 55 | - id: black 56 | args: [--line-length=100] 57 | 58 | - repo: https://github.com/PyCQA/flake8 59 | rev: 6.1.0 60 | hooks: 61 | - id: flake8 62 | additional_dependencies: 63 | [ 64 | flake8-blind-except, 65 | flake8-builtins, 66 | flake8-class-newline, 67 | flake8-comprehensions, 68 | flake8-deprecated, 69 | flake8-docstrings, 70 | flake8-import-order, 71 | flake8-quotes, 72 | ] 73 | 74 | - repo: https://github.com/pre-commit/mirrors-clang-format 75 | rev: v16.0.0 76 | hooks: 77 | - id: clang-format 78 | types_or: [c++, c, cuda] 79 | 80 | exclude: .svg 81 | -------------------------------------------------------------------------------- /.pre-commit-hooks.yaml: -------------------------------------------------------------------------------- 1 | # Modified from https://github.com/PyCQA/flake8/blob/463d9b1ce9ee44665f2c6825edd7010ef5dff760/.pre-commit-hooks.yaml 2 | - id: flake8-ros 3 | name: flake8 ros 4 | description: Apply flake8 with the ROS 2 settings. 5 | entry: flake8 6 | language: python 7 | types: [python] 8 | require_serial: true 9 | additional_dependencies: 10 | [ 11 | flake8==6.1.0, 12 | flake8-blind-except==0.2.1, 13 | flake8-builtins==2.1.0, 14 | flake8-class-newline==1.6.0, 15 | flake8-comprehensions==3.12.0, 16 | flake8-deprecated==2.0.1, 17 | flake8-docstrings==1.7.0, 18 | flake8-import-order==0.18.2, 19 | flake8-quotes==3.3.2, 20 | ] 21 | 22 | - id: prettier-xacro 23 | name: prettier xacro 24 | description: Apply Prettier with plugin-xml to xacro. 25 | entry: prettier --write --list-different --ignore-unknown --print-width 200 --xml-self-closing-space false --xml-whitespace-sensitivity ignore 26 | language: node 27 | files: .xacro$ 28 | additional_dependencies: [prettier@2.8.7, "@prettier/plugin-xml@2.2.0"] 29 | 30 | - id: prettier-launch-xml 31 | name: prettier launch.xml 32 | description: Apply Prettier with plugin-xml to launch.xml. 33 | entry: prettier --write --list-different --ignore-unknown --print-width 200 --xml-self-closing-space false --xml-whitespace-sensitivity ignore 34 | language: node 35 | files: launch.xml$ 36 | additional_dependencies: [prettier@2.8.7, "@prettier/plugin-xml@2.2.0"] 37 | 38 | - id: prettier-package-xml 39 | name: prettier package.xml 40 | description: Apply Prettier with plugin-xml to package.xml. 41 | entry: prettier --write --list-different --ignore-unknown --print-width 1000 --xml-self-closing-space false --xml-whitespace-sensitivity ignore 42 | language: node 43 | files: package.xml$ 44 | additional_dependencies: [prettier@2.8.7, "@prettier/plugin-xml@2.2.0"] 45 | 46 | - id: ros-include-guard 47 | name: fix include guard 48 | description: Fix the include guard macro name. 49 | entry: ros-include-guard 50 | language: python 51 | files: .*\.(h|hpp)$ 52 | 53 | - id: sort-package-xml 54 | name: sort package.xml 55 | description: Sort the dependent packages in package.xml. 56 | entry: sort-package-xml 57 | language: python 58 | files: package.xml$ 59 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | *.param.yaml 2 | *.rviz 3 | -------------------------------------------------------------------------------- /.prettierrc.yaml: -------------------------------------------------------------------------------- 1 | printWidth: 100 2 | tabWidth: 2 3 | overrides: 4 | - files: package.xml 5 | options: 6 | printWidth: 1000 7 | xmlSelfClosingSpace: false 8 | xmlWhitespaceSensitivity: ignore 9 | 10 | - files: "*.launch.xml" 11 | options: 12 | printWidth: 200 13 | xmlSelfClosingSpace: false 14 | xmlWhitespaceSensitivity: ignore 15 | 16 | - files: "*.xacro" 17 | options: 18 | printWidth: 200 19 | xmlSelfClosingSpace: false 20 | xmlWhitespaceSensitivity: ignore 21 | -------------------------------------------------------------------------------- /.yamllint.yaml: -------------------------------------------------------------------------------- 1 | extends: default 2 | 3 | ignore: | 4 | *.param.yaml 5 | 6 | rules: 7 | braces: 8 | level: error 9 | max-spaces-inside: 1 # To format with Prettier 10 | comments: 11 | level: error 12 | min-spaces-from-content: 1 # To be compatible with C++ and Python 13 | document-start: 14 | level: error 15 | present: false # Don't need document start markers 16 | line-length: disable # Delegate to Prettier 17 | truthy: 18 | level: error 19 | check-keys: false # To allow 'on' of GitHub Actions 20 | quoted-strings: 21 | level: error 22 | required: only-when-needed # To keep consistent style 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pre-commit-hooks-ros 2 | 3 | [ROS]-related hooks for [pre-commit] 4 | 5 | ## Usage 6 | 7 | Write your `.pre-commit-config.yaml` as below. 8 | 9 | ```yaml 10 | repos: 11 | - repo: https://github.com/tier4/pre-commit-hooks-ros 12 | rev: v0.7.0 13 | hooks: 14 | - id: flake8-ros 15 | - id: prettier-xacro 16 | - id: prettier-launch-xml 17 | - id: prettier-package-xml 18 | - id: ros-include-guard 19 | - id: sort-package-xml 20 | ``` 21 | 22 | ## Hooks available 23 | 24 | ### flake8-ros 25 | 26 | Apply [flake8] with the [ROS 2 settings](https://docs.ros.org/en/rolling/Installation/Ubuntu-Development-Setup.html#install-development-tools-and-ros-tools). 27 | 28 | ### prettier-xacro 29 | 30 | Apply [Prettier] with [plugin-xml] to [xacro]. 31 | 32 | ### prettier-launch-xml 33 | 34 | Apply [Prettier] with [plugin-xml] to [launch.xml]. 35 | 36 | ### prettier-package-xml 37 | 38 | Apply [Prettier] with [plugin-xml] to [package.xml]. 39 | 40 | ### ros-include-guard 41 | 42 | Fix the macro name of include guards. 43 | 44 | ### sort-package-xml 45 | 46 | Sort the dependent packages in [package.xml]. 47 | If you want to exclude a tag from sorting, add `` at the beginning of the line. 48 | 49 | ```xml 50 | rclcpp 51 | ``` 52 | 53 | 54 | 55 | [flake8]: https://github.com/PyCQA/flake8 56 | [launch.xml]: https://design.ros2.org/articles/roslaunch_xml.html 57 | [package.xml]: https://www.ros.org/reps/rep-0149.html 58 | [plugin-xml]: https://github.com/prettier/plugin-xml/ 59 | [pre-commit]: https://github.com/pre-commit/pre-commit 60 | [prettier]: https://prettier.io/ 61 | [ros]: https://ros.org/ 62 | [xacro]: http://wiki.ros.org/xacro 63 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dummy_package", 3 | "description": "", 4 | "version": "0.0.0" 5 | } 6 | -------------------------------------------------------------------------------- /pre_commit_hooks/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tier4/pre-commit-hooks-ros/eb39301759e1d4d1c45efe57db062d19ad8ef421/pre_commit_hooks/__init__.py -------------------------------------------------------------------------------- /pre_commit_hooks/ros_include_guard.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 TIER IV, Inc. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import argparse 16 | from pathlib import Path 17 | 18 | 19 | class DirectiveBase: 20 | def __init__(self, directive): 21 | self.directive = directive 22 | self.line = None 23 | self.text = None 24 | self.expected = None 25 | 26 | def update(self, line, text): 27 | self.line = line 28 | self.text = text 29 | 30 | def is_none(self): 31 | return self.line is None 32 | 33 | def mismatch(self): 34 | return self.text != self.expected 35 | 36 | def overwrite(self, lines): 37 | lines[self.line] = self.expected 38 | 39 | 40 | class OpeningDirective(DirectiveBase): 41 | def prepare(self, macro_name): 42 | self.expected = f"{self.directive} {macro_name}" 43 | if "// NOLINT" in self.text: 44 | self.expected += " // NOLINT" 45 | 46 | 47 | class ClosingDirective(DirectiveBase): 48 | def prepare(self, macro_name): 49 | self.expected = f"{self.directive} // {macro_name}" 50 | if "// NOLINT" in self.text: 51 | self.expected += " // NOLINT" 52 | 53 | 54 | class IncludeGuard: 55 | def __init__(self): 56 | self.ifndef = OpeningDirective("#ifndef") 57 | self.define = OpeningDirective("#define") 58 | self.endif = ClosingDirective("#endif") 59 | self.has_pragma_once = False 60 | 61 | def items(self): 62 | yield self.ifndef 63 | yield self.define 64 | yield self.endif 65 | 66 | def is_none(self): 67 | return any(item.is_none() for item in self.items()) 68 | 69 | def mismatch(self): 70 | return any(item.mismatch() for item in self.items()) 71 | 72 | def overwrite(self, lines): 73 | return any(item.overwrite(lines) for item in self.items()) 74 | 75 | def prepare(self, macro_name): 76 | for item in self.items(): 77 | item.prepare(macro_name) 78 | 79 | 80 | def get_include_guard_info(lines): 81 | guard = IncludeGuard() 82 | for line, text in enumerate(lines): 83 | if text.startswith("#pragma once"): 84 | guard.has_pragma_once = True 85 | if text.startswith("#ifndef") and guard.ifndef.is_none(): 86 | guard.ifndef.update(line, text) 87 | if text.startswith("#define") and guard.define.is_none(): 88 | guard.define.update(line, text) 89 | if text.startswith("#endif"): 90 | guard.endif.update(line, text) 91 | return guard 92 | 93 | 94 | def get_parts_after(parts, targets): 95 | result = [] 96 | for part in reversed(parts): 97 | if part in targets: 98 | break 99 | result.append(part) 100 | return reversed(result) 101 | 102 | 103 | def get_include_guard_macro_name(filepath): 104 | targets = {"include", "src", "test"} 105 | parts = get_parts_after(filepath.parts, targets) 106 | return "__".join(parts).replace(".", "_").upper() + "_" 107 | 108 | 109 | def main(argv=None): 110 | parser = argparse.ArgumentParser() 111 | parser.add_argument("filenames", nargs="*", type=Path, help="Filenames to fix") 112 | args = parser.parse_args(argv) 113 | 114 | return_code = 0 115 | for filepath in args.filenames: 116 | # Search the include guard lines. 117 | lines = filepath.read_text().split("\n") 118 | guard = get_include_guard_info(lines) 119 | # Error if the include guard is not found. 120 | if guard.is_none(): 121 | if not guard.has_pragma_once: 122 | print("No include guard in {}".format(filepath)) 123 | return_code = 1 124 | continue 125 | # Error and auto fix if the macro name is not correct. 126 | macro_name = get_include_guard_macro_name(filepath) 127 | guard.prepare(macro_name) 128 | if guard.mismatch(): 129 | print("Fix include guard in {}".format(filepath)) 130 | return_code = 1 131 | guard.overwrite(lines) 132 | filepath.write_text("\n".join(lines)) 133 | continue 134 | 135 | return return_code 136 | 137 | 138 | if __name__ == "__main__": 139 | exit(main()) 140 | -------------------------------------------------------------------------------- /pre_commit_hooks/sort_package_xml.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Copyright 2021 TIER IV, Inc. All rights reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # Copyright (c) 2014 pre-commit dev team: Anthony Sottile, Ken Struys 18 | 19 | # Permission is hereby granted, free of charge, to any person obtaining a copy 20 | # of this software and associated documentation files (the "Software"), to deal 21 | # in the Software without restriction, including without limitation the rights 22 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 23 | # copies of the Software, and to permit persons to whom the Software is 24 | # furnished to do so, subject to the following conditions: 25 | 26 | # The above copyright notice and this permission notice shall be included in 27 | # all copies or substantial portions of the Software. 28 | 29 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 30 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 31 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 32 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 33 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 34 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 35 | # THE SOFTWARE. 36 | 37 | 38 | import argparse 39 | import re 40 | from typing import List 41 | from typing import Optional 42 | from typing import Sequence 43 | 44 | # Closing bracket absent as conditions can be present within the xml tag 45 | TAGS = [ 46 | " List[str]: 58 | """Sort a XML file in alphabetical order, keeping blocks together. 59 | 60 | :param lines: array of strings 61 | :return: sorted array of strings 62 | """ 63 | # make a copy of lines since we will clobber it 64 | lines = list(lines) 65 | new_lines = [] 66 | 67 | for block in parse_blocks(lines): 68 | new_lines.extend(sorted(block, key=parse_content)) 69 | 70 | return new_lines 71 | 72 | 73 | def parse_block(lines: List[str], tag: str) -> List[str]: 74 | block_lines = [] 75 | for line in lines: 76 | if line.strip().startswith(tag): 77 | block_lines.append(line) 78 | for line in block_lines: 79 | lines.remove(line) 80 | return block_lines 81 | 82 | 83 | def parse_tag(line: str) -> str: 84 | for tag in TAGS: 85 | if line.startswith(tag): 86 | return tag 87 | return "" 88 | 89 | 90 | def parse_blocks(lines: List[str]) -> List[List[str]]: 91 | """Parse and return all possible blocks, popping off the start of `lines`. 92 | 93 | :param lines: list of lines 94 | :return: list of blocks, where each block is a list of lines 95 | """ 96 | blocks = [] 97 | 98 | while lines: 99 | tag = parse_tag(lines[0].strip()) 100 | if tag: 101 | blocks.append(parse_block(lines, tag)) 102 | else: 103 | blocks.append([lines.pop(0)]) 104 | 105 | return blocks 106 | 107 | 108 | def parse_content(line: str) -> str: 109 | reg_obj = re.compile(r"<[^>]*?>") 110 | return reg_obj.sub("", line) 111 | 112 | 113 | def main(argv: Optional[Sequence[str]] = None) -> int: 114 | parser = argparse.ArgumentParser() 115 | parser.add_argument("filenames", nargs="*", help="Filenames to fix") 116 | args = parser.parse_args(argv) 117 | 118 | ret_val = 0 119 | for filename in args.filenames: 120 | with open(filename, "r+") as f: 121 | lines = [line.rstrip() for line in f.readlines()] 122 | new_lines = sort(lines) 123 | 124 | if lines != new_lines: 125 | print(f"Fixing file `{filename}`") 126 | f.seek(0) 127 | f.write("\n".join(new_lines) + "\n") 128 | ret_val = 1 129 | 130 | return ret_val 131 | 132 | 133 | if __name__ == "__main__": 134 | exit(main()) 135 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pytest 2 | pytest-datadir 3 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = pre_commit_hooks_ros 3 | version = 0.1.0 4 | url = https://github.com/pre-commit/pre-commit-hooks 5 | license_file = LICENSE 6 | description = ros-related hooks for pre-commit 7 | long_description = file: README.md 8 | 9 | [options] 10 | packages = find: 11 | python_requires = >=3.8 12 | 13 | [options.entry_points] 14 | console_scripts = 15 | ros-include-guard = pre_commit_hooks.ros_include_guard:main 16 | sort-package-xml = pre_commit_hooks.sort_package_xml:main 17 | 18 | [flake8] 19 | # Modified from https://github.com/ament/ament_lint/blob/ebd524bb9973d5ec1dc48a670ce54f958a5a0243/ament_flake8/ament_flake8/configuration/ament_flake8.ini 20 | application_import_names = pre_commit_hooks 21 | extend-ignore = B902,C816,D100,D101,D102,D103,D104,D105,D106,D107,D203,D212,D404,I202,CNL100,E203,E501,Q000 22 | import-order-style = google 23 | max-line-length = 100 24 | show-source = true 25 | statistics = true 26 | 27 | [isort] 28 | profile=black 29 | line_length=100 30 | force_sort_within_sections=true 31 | force_single_line=true 32 | reverse_relative=true 33 | known_third_party=launch 34 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup() 4 | -------------------------------------------------------------------------------- /tests/pre-commit-hooks/prettier-launch-xml/sample.launch.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 106 | 107 | 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /tests/pre-commit-hooks/prettier-package-xml/normal/package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | rospkg 5 | 0.0.0 6 | The dummy package to test sort_package_xml 7 | Foo Bar 8 | Apache License 2.0 9 | 10 | ament_cmake 11 | 12 | aaaaa 13 | bbbbb 14 | ccccc 15 | ddddd 16 | eeeee 17 | 18 | ament_lint_auto 19 | ament_lint_common 20 | 21 | 22 | ament_cmake 23 | 24 | 25 | -------------------------------------------------------------------------------- /tests/pre-commit-hooks/prettier-xacro/calibration.xacro: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 20 | 21 | 22 | 23 | 35 | 36 | -------------------------------------------------------------------------------- /tests/pre-commit-hooks/prettier-xacro/sample.xacro: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /tests/pre-commit-hooks/ros-include-guard/include/foo_bar/baz.hpp: -------------------------------------------------------------------------------- 1 | #ifndef FOO_BAR__BAZ_HPP_ 2 | #define FOO_BAR__BAZ_HPP_ 3 | 4 | #endif // FOO_BAR__BAZ_HPP_ 5 | -------------------------------------------------------------------------------- /tests/pre-commit-hooks/sort-package-xml/normal/package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | rospkg 5 | 0.0.0 6 | The dummy package to test sort_package_xml 7 | Foo Bar 8 | Apache License 2.0 9 | 10 | ament_cmake 11 | 12 | aaaaa 13 | bbbbb 14 | ccccc 15 | ddddd 16 | eeeee 17 | 18 | ament_lint_auto 19 | ament_lint_common 20 | 21 | 22 | ament_cmake 23 | 24 | 25 | -------------------------------------------------------------------------------- /tests/pre-commit-hooks/test-pre-commit-hooks.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: . 3 | rev: HEAD 4 | hooks: 5 | - id: prettier-xacro 6 | files: tests/pre-commit-hooks/prettier-xacro/.* 7 | - id: prettier-launch-xml 8 | files: tests/pre-commit-hooks/prettier-launch-xml/.* 9 | - id: prettier-package-xml 10 | files: tests/pre-commit-hooks/prettier-package-xml/.* 11 | - id: ros-include-guard 12 | files: tests/pre-commit-hooks/ros-include-guard/.* 13 | - id: sort-package-xml 14 | files: tests/pre-commit-hooks/sort-package-xml/.* 15 | -------------------------------------------------------------------------------- /tests/test_ros_include_guard.py: -------------------------------------------------------------------------------- 1 | import shutil 2 | 3 | import pytest 4 | 5 | from pre_commit_hooks import ros_include_guard 6 | 7 | cases_auto_fix = [ 8 | "include/rospkg/foobar.h", 9 | "include/rospkg/foobar.hpp", 10 | "include/rospkg/nolint.hpp", 11 | "include/rospkg/pragma.hpp", 12 | "src/foo_bar_baz.hpp", 13 | "src/foo_bar/baz.hpp", 14 | "src/foo/bar_baz.hpp", 15 | "src/foo/bar/baz.hpp", 16 | ] 17 | 18 | cases_no_fix = [ 19 | ("include/rospkg/pragma.only.hpp", 0), 20 | ("include/rospkg/none.hpp", 1), 21 | ] 22 | 23 | 24 | @pytest.mark.parametrize(("target_file"), cases_auto_fix) 25 | def test_auto_fix(target_file, datadir): 26 | target_path = datadir.joinpath(target_file) 27 | right_path = target_path.with_suffix(".right" + target_path.suffix) 28 | wrong_path = target_path.with_suffix(".wrong" + target_path.suffix) 29 | 30 | # Test wrong file. 31 | shutil.copy(wrong_path, target_path) 32 | return_code = ros_include_guard.main([str(target_path)]) 33 | assert return_code == 1 34 | assert target_path.read_text() == right_path.read_text() 35 | 36 | # Test right file. 37 | shutil.copy(right_path, target_path) 38 | return_code = ros_include_guard.main([str(target_path)]) 39 | assert return_code == 0 40 | assert target_path.read_text() == right_path.read_text() 41 | 42 | 43 | @pytest.mark.parametrize(("target_file", "answer_code"), cases_no_fix) 44 | def test_no_fix(target_file, answer_code, datadir): 45 | target_path = datadir.joinpath(target_file) 46 | return_code = ros_include_guard.main([str(target_path)]) 47 | assert return_code == answer_code 48 | -------------------------------------------------------------------------------- /tests/test_ros_include_guard/include/rospkg/foobar.right.h: -------------------------------------------------------------------------------- 1 | #ifndef ROSPKG__FOOBAR_H_ 2 | #define ROSPKG__FOOBAR_H_ 3 | 4 | #endif // ROSPKG__FOOBAR_H_ 5 | -------------------------------------------------------------------------------- /tests/test_ros_include_guard/include/rospkg/foobar.right.hpp: -------------------------------------------------------------------------------- 1 | #ifndef ROSPKG__FOOBAR_HPP_ 2 | #define ROSPKG__FOOBAR_HPP_ 3 | 4 | #endif // ROSPKG__FOOBAR_HPP_ 5 | -------------------------------------------------------------------------------- /tests/test_ros_include_guard/include/rospkg/foobar.wrong.h: -------------------------------------------------------------------------------- 1 | #ifndef DUMMY 2 | #define DUMMY 3 | 4 | #endif // DUMMY 5 | -------------------------------------------------------------------------------- /tests/test_ros_include_guard/include/rospkg/foobar.wrong.hpp: -------------------------------------------------------------------------------- 1 | #ifndef DUMMY 2 | #define DUMMY 3 | 4 | #endif // DUMMY 5 | -------------------------------------------------------------------------------- /tests/test_ros_include_guard/include/rospkg/nolint.right.hpp: -------------------------------------------------------------------------------- 1 | #ifndef ROSPKG__NOLINT_HPP_ // NOLINT 2 | #define ROSPKG__NOLINT_HPP_ // NOLINT 3 | 4 | #endif // ROSPKG__NOLINT_HPP_ // NOLINT 5 | -------------------------------------------------------------------------------- /tests/test_ros_include_guard/include/rospkg/nolint.wrong.hpp: -------------------------------------------------------------------------------- 1 | #ifndef DUMMY // NOLINT 2 | #define DUMMY // NOLINT 3 | 4 | #endif // DUMMY // NOLINT 5 | -------------------------------------------------------------------------------- /tests/test_ros_include_guard/include/rospkg/none.hpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tier4/pre-commit-hooks-ros/eb39301759e1d4d1c45efe57db062d19ad8ef421/tests/test_ros_include_guard/include/rospkg/none.hpp -------------------------------------------------------------------------------- /tests/test_ros_include_guard/include/rospkg/pragma.only.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | -------------------------------------------------------------------------------- /tests/test_ros_include_guard/include/rospkg/pragma.right.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef ROSPKG__PRAGMA_HPP_ 4 | #define ROSPKG__PRAGMA_HPP_ 5 | 6 | #endif // ROSPKG__PRAGMA_HPP_ 7 | -------------------------------------------------------------------------------- /tests/test_ros_include_guard/include/rospkg/pragma.wrong.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef DUMMY 4 | #define DUMMY 5 | 6 | #endif // DUMMY 7 | -------------------------------------------------------------------------------- /tests/test_ros_include_guard/src/foo/bar/baz.right.hpp: -------------------------------------------------------------------------------- 1 | #ifndef FOO__BAR__BAZ_HPP_ 2 | #define FOO__BAR__BAZ_HPP_ 3 | #endif // FOO__BAR__BAZ_HPP_ 4 | -------------------------------------------------------------------------------- /tests/test_ros_include_guard/src/foo/bar/baz.wrong.hpp: -------------------------------------------------------------------------------- 1 | #ifndef DUMMY 2 | #define DUMMY 3 | #endif // DUMMY 4 | -------------------------------------------------------------------------------- /tests/test_ros_include_guard/src/foo/bar_baz.right.hpp: -------------------------------------------------------------------------------- 1 | #ifndef FOO__BAR_BAZ_HPP_ 2 | #define FOO__BAR_BAZ_HPP_ 3 | #endif // FOO__BAR_BAZ_HPP_ 4 | -------------------------------------------------------------------------------- /tests/test_ros_include_guard/src/foo/bar_baz.wrong.hpp: -------------------------------------------------------------------------------- 1 | #ifndef DUMMY 2 | #define DUMMY 3 | #endif // DUMMY 4 | -------------------------------------------------------------------------------- /tests/test_ros_include_guard/src/foo_bar/baz.right.hpp: -------------------------------------------------------------------------------- 1 | #ifndef FOO_BAR__BAZ_HPP_ 2 | #define FOO_BAR__BAZ_HPP_ 3 | #endif // FOO_BAR__BAZ_HPP_ 4 | -------------------------------------------------------------------------------- /tests/test_ros_include_guard/src/foo_bar/baz.wrong.hpp: -------------------------------------------------------------------------------- 1 | #ifndef DUMMY 2 | #define DUMMY 3 | #endif // DUMMY 4 | -------------------------------------------------------------------------------- /tests/test_ros_include_guard/src/foo_bar_baz.right.hpp: -------------------------------------------------------------------------------- 1 | #ifndef FOO_BAR_BAZ_HPP_ 2 | #define FOO_BAR_BAZ_HPP_ 3 | #endif // FOO_BAR_BAZ_HPP_ 4 | -------------------------------------------------------------------------------- /tests/test_ros_include_guard/src/foo_bar_baz.wrong.hpp: -------------------------------------------------------------------------------- 1 | #ifndef DUMMY 2 | #define DUMMY 3 | #endif // DUMMY 4 | -------------------------------------------------------------------------------- /tests/test_sort_package_xml.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | import pytest 4 | 5 | from pre_commit_hooks import sort_package_xml 6 | 7 | 8 | @pytest.mark.parametrize( 9 | "case", 10 | [ 11 | "normal", 12 | "no-format", 13 | ], 14 | ) 15 | def test(case: str, datadir: Path, tmp_path: Path): 16 | input_file = datadir / f"{case}.input.xml" 17 | answer_file = datadir / f"{case}.answer.xml" 18 | 19 | # Format 20 | return_code = sort_package_xml.main([str(input_file)]) 21 | print(input_file.read_text()) 22 | assert return_code == 1 23 | assert input_file.read_text() == answer_file.read_text() 24 | 25 | # Re-format 26 | return_code = sort_package_xml.main([str(input_file)]) 27 | assert return_code == 0 28 | assert input_file.read_text() == answer_file.read_text() 29 | -------------------------------------------------------------------------------- /tests/test_sort_package_xml/no-format.answer.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | rospkg 5 | 0.0.0 6 | The dummy package to test pre-commit hooks 7 | Foo Bar 8 | Apache License 2.0 9 | 10 | ament_cmake 11 | 12 | aaaaa 13 | ccccc 14 | 15 | bbbbb 16 | 17 | 18 | ament_cmake 19 | 20 | 21 | -------------------------------------------------------------------------------- /tests/test_sort_package_xml/no-format.input.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | rospkg 5 | 0.0.0 6 | The dummy package to test pre-commit hooks 7 | Foo Bar 8 | Apache License 2.0 9 | 10 | ament_cmake 11 | 12 | ccccc 13 | 14 | bbbbb 15 | aaaaa 16 | 17 | 18 | ament_cmake 19 | 20 | 21 | -------------------------------------------------------------------------------- /tests/test_sort_package_xml/normal.answer.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | rospkg 5 | 0.0.0 6 | The dummy package to test sort_package_xml 7 | Foo Bar 8 | Apache License 2.0 9 | 10 | ament_cmake 11 | 12 | aaaaa 13 | bbbbb 14 | ccccc 15 | ddddd 16 | eeeee 17 | 18 | ament_lint_auto 19 | ament_lint_common 20 | 21 | 22 | ament_cmake 23 | 24 | 25 | -------------------------------------------------------------------------------- /tests/test_sort_package_xml/normal.input.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | rospkg 5 | 0.0.0 6 | The dummy package to test sort_package_xml 7 | Foo Bar 8 | Apache License 2.0 9 | 10 | ament_cmake 11 | 12 | ccccc 13 | bbbbb 14 | ddddd 15 | eeeee 16 | aaaaa 17 | 18 | ament_lint_common 19 | ament_lint_auto 20 | 21 | 22 | ament_cmake 23 | 24 | 25 | --------------------------------------------------------------------------------