cfengine 0.13.0__tar.gz → 0.14.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. cfengine-0.14.0/.github/workflows/make-check.yml +51 -0
  2. cfengine-0.14.0/.github/workflows/update-syntax-description.yml +69 -0
  3. cfengine-0.14.0/.python-version +1 -0
  4. cfengine-0.14.0/CLAUDE.md +51 -0
  5. {cfengine-0.13.0 → cfengine-0.14.0}/HACKING.md +6 -6
  6. cfengine-0.14.0/Makefile +26 -0
  7. {cfengine-0.13.0 → cfengine-0.14.0}/PKG-INFO +2 -2
  8. {cfengine-0.13.0 → cfengine-0.14.0}/pyproject.toml +4 -1
  9. {cfengine-0.13.0 → cfengine-0.14.0}/src/cfengine.egg-info/PKG-INFO +2 -2
  10. {cfengine-0.13.0 → cfengine-0.14.0}/src/cfengine.egg-info/SOURCES.txt +51 -4
  11. {cfengine-0.13.0 → cfengine-0.14.0}/src/cfengine.egg-info/requires.txt +1 -1
  12. {cfengine-0.13.0 → cfengine-0.14.0}/src/cfengine_cli/commands.py +43 -23
  13. {cfengine-0.13.0 → cfengine-0.14.0}/src/cfengine_cli/deptool.py +2 -2
  14. {cfengine-0.13.0 → cfengine-0.14.0}/src/cfengine_cli/dev.py +18 -26
  15. {cfengine-0.13.0 → cfengine-0.14.0}/src/cfengine_cli/docs.py +19 -23
  16. cfengine-0.14.0/src/cfengine_cli/format.py +901 -0
  17. {cfengine-0.13.0 → cfengine-0.14.0}/src/cfengine_cli/lint.py +627 -104
  18. {cfengine-0.13.0 → cfengine-0.14.0}/src/cfengine_cli/main.py +8 -5
  19. {cfengine-0.13.0 → cfengine-0.14.0}/src/cfengine_cli/masterfiles/analyze.py +11 -17
  20. {cfengine-0.13.0 → cfengine-0.14.0}/src/cfengine_cli/masterfiles/download.py +1 -1
  21. {cfengine-0.13.0 → cfengine-0.14.0}/src/cfengine_cli/masterfiles/generate_release_information.py +53 -22
  22. {cfengine-0.13.0 → cfengine-0.14.0}/src/cfengine_cli/profile.py +1 -1
  23. cfengine-0.14.0/src/cfengine_cli/syntax-description.json +10343 -0
  24. cfengine-0.14.0/src/cfengine_cli/syntax_tree.py +42 -0
  25. {cfengine-0.13.0 → cfengine-0.14.0}/tests/format/002_basics.expected.cf +8 -4
  26. {cfengine-0.13.0 → cfengine-0.14.0}/tests/format/002_basics.input.cf +7 -2
  27. {cfengine-0.13.0 → cfengine-0.14.0}/tests/format/003_wrapping.expected.cf +33 -7
  28. {cfengine-0.13.0 → cfengine-0.14.0}/tests/format/003_wrapping.input.cf +29 -3
  29. {cfengine-0.13.0 → cfengine-0.14.0}/tests/format/004_comments.expected.cf +20 -0
  30. {cfengine-0.13.0 → cfengine-0.14.0}/tests/format/004_comments.input.cf +18 -0
  31. {cfengine-0.13.0 → cfengine-0.14.0}/tests/format/005_bundle_comments.expected.cf +5 -0
  32. {cfengine-0.13.0 → cfengine-0.14.0}/tests/format/005_bundle_comments.input.cf +5 -0
  33. cfengine-0.14.0/tests/format/007_class_guarded_empty_lines.expected.cf +9 -0
  34. cfengine-0.14.0/tests/format/008_long_string.expected.cf +6 -0
  35. cfengine-0.14.0/tests/format/008_long_string.input.cf +5 -0
  36. cfengine-0.14.0/tests/format/009_single_line.expected.cf +58 -0
  37. cfengine-0.14.0/tests/format/009_single_line.input.cf +76 -0
  38. cfengine-0.14.0/tests/format/010_stakeholder.expected.cf +119 -0
  39. cfengine-0.14.0/tests/format/010_stakeholder.input.cf +99 -0
  40. cfengine-0.14.0/tests/format/011_macros.expected.cf +192 -0
  41. cfengine-0.14.0/tests/format/011_macros.input.cf +182 -0
  42. cfengine-0.14.0/tests/format/011_promises.expected.cf +516 -0
  43. cfengine-0.14.0/tests/format/011_promises.input.cf +409 -0
  44. {cfengine-0.13.0 → cfengine-0.14.0}/tests/lint/005_bundle_type.expected.txt +1 -1
  45. {cfengine-0.13.0 → cfengine-0.14.0}/tests/lint/008_namespace.cf +2 -2
  46. cfengine-0.14.0/tests/lint/008_namespace.expected.txt +7 -0
  47. {cfengine-0.13.0 → cfengine-0.14.0}/tests/lint/008_namespace.x.cf +2 -2
  48. cfengine-0.14.0/tests/lint/009_bundle_shadows_function.cf +5 -0
  49. cfengine-0.14.0/tests/lint/009_bundle_shadows_function.expected.txt +6 -0
  50. cfengine-0.14.0/tests/lint/009_bundle_shadows_function.x.cf +5 -0
  51. cfengine-0.14.0/tests/lint/010_unknown_function_inside_vars.cf +24 -0
  52. cfengine-0.14.0/tests/lint/010_unknown_function_inside_vars.expected.txt +12 -0
  53. cfengine-0.14.0/tests/lint/010_unknown_function_inside_vars.x.cf +22 -0
  54. cfengine-0.14.0/tests/lint/011_mutually_exclusive_types_vars.cf +14 -0
  55. cfengine-0.14.0/tests/lint/011_mutually_exclusive_types_vars.expected.txt +12 -0
  56. cfengine-0.14.0/tests/lint/011_mutually_exclusive_types_vars.x.cf +17 -0
  57. cfengine-0.14.0/tests/lint/012_invalid_attributes.expected.txt +12 -0
  58. cfengine-0.14.0/tests/lint/012_invalid_attributes.x.cf +13 -0
  59. cfengine-0.14.0/tests/lint/013_function_call_arg_count.cf +8 -0
  60. cfengine-0.14.0/tests/lint/013_function_call_arg_count.expected.txt +12 -0
  61. cfengine-0.14.0/tests/lint/013_function_call_arg_count.x.cf +14 -0
  62. cfengine-0.14.0/tests/lint/014_num_args_body.expected.txt +20 -0
  63. cfengine-0.14.0/tests/lint/014_num_args_body.x.cf +24 -0
  64. cfengine-0.14.0/tests/lint/014_num_args_bundle.expected.txt +20 -0
  65. cfengine-0.14.0/tests/lint/014_num_args_bundle.x.cf +17 -0
  66. cfengine-0.14.0/tests/lint/014_num_args_bundle_body.cf +11 -0
  67. cfengine-0.14.0/tests/lint/015_variadic_func_arg_count.cf +16 -0
  68. cfengine-0.14.0/tests/lint/015_variadic_func_arg_count.expected.txt +12 -0
  69. cfengine-0.14.0/tests/lint/015_variadic_func_arg_count.x.cf +18 -0
  70. cfengine-0.14.0/tests/lint/016_macro_multi_def_bundle.cf +25 -0
  71. cfengine-0.14.0/tests/lint/016_macro_multi_def_bundle.expected.txt +14 -0
  72. cfengine-0.14.0/tests/lint/016_macro_multi_def_bundle.x.cf +25 -0
  73. cfengine-0.14.0/tests/lint/017_half_promises.cf +12 -0
  74. cfengine-0.14.0/tests/lint/017_half_promises.expected.txt +17 -0
  75. cfengine-0.14.0/tests/lint/017_half_promises.x.cf +29 -0
  76. cfengine-0.14.0/tests/lint/018_implies_body.cf +12 -0
  77. cfengine-0.14.0/tests/lint/018_implies_body.expected.txt +38 -0
  78. cfengine-0.14.0/tests/lint/018_implies_body.x.cf +31 -0
  79. cfengine-0.14.0/tests/lint/019_nested_calls.cf +22 -0
  80. cfengine-0.14.0/tests/lint/019_nested_calls.expected.txt +22 -0
  81. cfengine-0.14.0/tests/lint/019_nested_calls.x.cf +24 -0
  82. cfengine-0.14.0/tests/lint/020_bundle_name_expansion.cf +15 -0
  83. cfengine-0.14.0/tests/shell/004-format-check.sh +67 -0
  84. cfengine-0.14.0/tests/unit/test_format.py +514 -0
  85. cfengine-0.14.0/uv.lock +474 -0
  86. cfengine-0.13.0/.github/workflows/format.yml +0 -61
  87. cfengine-0.13.0/.github/workflows/lint.yml +0 -44
  88. cfengine-0.13.0/.github/workflows/test.yml +0 -53
  89. cfengine-0.13.0/.python-version +0 -1
  90. cfengine-0.13.0/Makefile +0 -22
  91. cfengine-0.13.0/src/cfengine_cli/format.py +0 -407
  92. cfengine-0.13.0/src/cfengine_cli/policy_language.py +0 -239
  93. cfengine-0.13.0/tests/format/007_class_guarded_empty_lines.expected.cf +0 -11
  94. cfengine-0.13.0/tests/lint/008_namespace.expected.txt +0 -7
  95. cfengine-0.13.0/tests/unit/test_format.py +0 -70
  96. cfengine-0.13.0/uv.lock +0 -474
  97. {cfengine-0.13.0 → cfengine-0.14.0}/.github/dependabot.yml +0 -0
  98. {cfengine-0.13.0 → cfengine-0.14.0}/.github/workflows/pypi-publish.yml +0 -0
  99. {cfengine-0.13.0 → cfengine-0.14.0}/.gitignore +0 -0
  100. {cfengine-0.13.0 → cfengine-0.14.0}/LICENSE +0 -0
  101. {cfengine-0.13.0 → cfengine-0.14.0}/README.md +0 -0
  102. {cfengine-0.13.0 → cfengine-0.14.0}/ci/01-install.sh +0 -0
  103. {cfengine-0.13.0 → cfengine-0.14.0}/ci/02-safe-tests.sh +0 -0
  104. {cfengine-0.13.0 → cfengine-0.14.0}/ci/03-unsafe-tests.sh +0 -0
  105. {cfengine-0.13.0 → cfengine-0.14.0}/setup.cfg +0 -0
  106. {cfengine-0.13.0 → cfengine-0.14.0}/src/cfengine.egg-info/dependency_links.txt +0 -0
  107. {cfengine-0.13.0 → cfengine-0.14.0}/src/cfengine.egg-info/entry_points.txt +0 -0
  108. {cfengine-0.13.0 → cfengine-0.14.0}/src/cfengine.egg-info/top_level.txt +0 -0
  109. {cfengine-0.13.0 → cfengine-0.14.0}/src/cfengine_cli/__init__.py +0 -0
  110. {cfengine-0.13.0 → cfengine-0.14.0}/src/cfengine_cli/__main__.py +0 -0
  111. {cfengine-0.13.0 → cfengine-0.14.0}/src/cfengine_cli/deptool-README.md +0 -0
  112. {cfengine-0.13.0 → cfengine-0.14.0}/src/cfengine_cli/masterfiles/__init__.py +0 -0
  113. {cfengine-0.13.0 → cfengine-0.14.0}/src/cfengine_cli/masterfiles/check_download_matches_git.py +0 -0
  114. {cfengine-0.13.0 → cfengine-0.14.0}/src/cfengine_cli/masterfiles/generate_git_tags.py +0 -0
  115. {cfengine-0.13.0 → cfengine-0.14.0}/src/cfengine_cli/masterfiles/generate_vcf_download.py +0 -0
  116. {cfengine-0.13.0 → cfengine-0.14.0}/src/cfengine_cli/masterfiles/generate_vcf_git_checkout.py +0 -0
  117. {cfengine-0.13.0 → cfengine-0.14.0}/src/cfengine_cli/paths.py +0 -0
  118. {cfengine-0.13.0 → cfengine-0.14.0}/src/cfengine_cli/shell.py +0 -0
  119. {cfengine-0.13.0 → cfengine-0.14.0}/src/cfengine_cli/utils.py +0 -0
  120. {cfengine-0.13.0 → cfengine-0.14.0}/src/cfengine_cli/version.py +0 -0
  121. {cfengine-0.13.0 → cfengine-0.14.0}/tests/README.md +0 -0
  122. {cfengine-0.13.0 → cfengine-0.14.0}/tests/format/001_hello_world.expected.cf +0 -0
  123. {cfengine-0.13.0 → cfengine-0.14.0}/tests/format/001_hello_world.input.cf +0 -0
  124. {cfengine-0.13.0 → cfengine-0.14.0}/tests/format/006_remove_empty_comments.expected.cf +0 -0
  125. {cfengine-0.13.0 → cfengine-0.14.0}/tests/format/006_remove_empty_comments.input.cf +0 -0
  126. {cfengine-0.13.0 → cfengine-0.14.0}/tests/format/007_class_guarded_empty_lines.input.cf +0 -0
  127. {cfengine-0.13.0 → cfengine-0.14.0}/tests/lint/001_hello_world.cf +0 -0
  128. {cfengine-0.13.0 → cfengine-0.14.0}/tests/lint/002_ifvarclass.expected.txt +0 -0
  129. {cfengine-0.13.0 → cfengine-0.14.0}/tests/lint/002_ifvarclass.x.cf +0 -0
  130. {cfengine-0.13.0 → cfengine-0.14.0}/tests/lint/003_deprecated_promise_type.cf +0 -0
  131. {cfengine-0.13.0 → cfengine-0.14.0}/tests/lint/003_deprecated_promise_type.expected.txt +0 -0
  132. {cfengine-0.13.0 → cfengine-0.14.0}/tests/lint/003_deprecated_promise_type.x.cf +0 -0
  133. {cfengine-0.13.0 → cfengine-0.14.0}/tests/lint/004_bundle_name_lowercase.cf +0 -0
  134. {cfengine-0.13.0 → cfengine-0.14.0}/tests/lint/004_bundle_name_lowercase.expected.txt +0 -0
  135. {cfengine-0.13.0 → cfengine-0.14.0}/tests/lint/004_bundle_name_lowercase.x.cf +0 -0
  136. {cfengine-0.13.0 → cfengine-0.14.0}/tests/lint/005_bundle_type.cf +0 -0
  137. {cfengine-0.13.0 → cfengine-0.14.0}/tests/lint/005_bundle_type.x.cf +0 -0
  138. {cfengine-0.13.0 → cfengine-0.14.0}/tests/lint/006_syntax_error.cf +0 -0
  139. {cfengine-0.13.0 → cfengine-0.14.0}/tests/lint/006_syntax_error.expected.txt +0 -0
  140. {cfengine-0.13.0 → cfengine-0.14.0}/tests/lint/006_syntax_error.x.cf +0 -0
  141. {cfengine-0.13.0 → cfengine-0.14.0}/tests/lint/007_empty_file.expected.txt +0 -0
  142. {cfengine-0.13.0 → cfengine-0.14.0}/tests/lint/007_empty_file.x.cf +0 -0
  143. {cfengine-0.13.0 → cfengine-0.14.0}/tests/run-format-tests.sh +0 -0
  144. {cfengine-0.13.0 → cfengine-0.14.0}/tests/run-lint-tests.sh +0 -0
  145. {cfengine-0.13.0 → cfengine-0.14.0}/tests/run-shell-tests.sh +0 -0
  146. {cfengine-0.13.0 → cfengine-0.14.0}/tests/shell/001-help.sh +0 -0
  147. {cfengine-0.13.0 → cfengine-0.14.0}/tests/shell/002-version.sh +0 -0
  148. {cfengine-0.13.0 → cfengine-0.14.0}/tests/shell/003-format.sh +0 -0
  149. {cfengine-0.13.0 → cfengine-0.14.0}/tests/unit/__init__.py +0 -0
  150. {cfengine-0.13.0 → cfengine-0.14.0}/tests/unit/test_deps.py +0 -0
  151. {cfengine-0.13.0 → cfengine-0.14.0}/tests/unit/test_paths.py +0 -0
  152. {cfengine-0.13.0 → cfengine-0.14.0}/tests/unit/test_utils.py +0 -0
  153. {cfengine-0.13.0 → cfengine-0.14.0}/tests/unit/test_version.py +0 -0
@@ -0,0 +1,51 @@
1
+ # This workflow installs the necessary dependencies, runs make check, and
2
+ # checks if there are any edits. It should be very similar to what developers
3
+ # are expected to do locally. We want to ensure that all tests pass (make
4
+ # check succeeds) and that there is no additional formatting / untracked
5
+ # files generated.
6
+ #
7
+ # Note that make check is a bit special in this repo, it has some things you
8
+ # might not expect:
9
+ # 1. It doesn't only run tests, it also runs formatters and linters
10
+ # 2. It installs the CFEngine CLI, so that shell tests will work
11
+
12
+ name: Run make check
13
+ on:
14
+ push:
15
+ branches: [main]
16
+ pull_request:
17
+ branches: [main]
18
+ permissions:
19
+ contents: read
20
+ jobs:
21
+ check:
22
+ runs-on: ubuntu-24.04
23
+ strategy:
24
+ fail-fast: true
25
+ matrix:
26
+ python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
27
+ steps:
28
+ - uses: actions/checkout@v4
29
+ - name: Set up Python ${{ matrix.python-version }}
30
+ uses: actions/setup-python@v5
31
+ with:
32
+ python-version: ${{ matrix.python-version }}
33
+ - name: Install dependencies
34
+ run: |
35
+ python -m pip install --upgrade pip
36
+ python -m pip install uv
37
+ sudo apt-get install npm
38
+ npm install --global prettier
39
+ - name: Set python version file for uv
40
+ run: |
41
+ echo "${{ matrix.python-version }}" > .python-version
42
+ - name: Run make check
43
+ run: |
44
+ make check
45
+ - name: Reset python version file
46
+ run: |
47
+ git restore .python-version
48
+ - name: See if there are changes
49
+ run: |
50
+ git diff --exit-code
51
+ git ls-files --other --directory --exclude-standard | sed q1
@@ -0,0 +1,69 @@
1
+ name: Update syntax-description
2
+
3
+ on:
4
+ schedule:
5
+ - cron: "0 7 * * 1" # Run every Monday at 7am UTC
6
+ # | | | | |
7
+ # | | | | day of the week (0-6) (Sunday to Saturday)
8
+ # | | | month (1-12)
9
+ # | | day of the month (1-31)
10
+ # | hour (0-23)
11
+ # minute (0-59)
12
+ workflow_dispatch: # Enables manual trigger
13
+
14
+ jobs:
15
+ update_syntax_desc:
16
+ if: contains(fromJSON('["cfengine","mendersoftware","NorthernTechHQ"]'), github.repository_owner)
17
+ name: Update syntax-description
18
+ runs-on: ubuntu-24.04
19
+ permissions:
20
+ contents: write
21
+ pull-requests: write
22
+ steps:
23
+ - name: Checks-out repository
24
+ uses: actions/checkout@v4
25
+ with:
26
+ ref: "main"
27
+ - name: Set up Python 3.12
28
+ uses: actions/setup-python@v5
29
+ with:
30
+ python-version: "3.12"
31
+ - name: Install dependencies
32
+ run: |
33
+ python -m pip install --upgrade pip
34
+ python -m pip install cf-remote cfengine
35
+ - name: Install cfengine
36
+ run: |
37
+ cf-remote --version master download --edition community ubuntu24 amd64 hub
38
+ sudo dpkg -i ~/.cfengine/cf-remote/packages/cfengine-community*.deb
39
+ - name: Extract new syntax-description
40
+ run: |
41
+ (
42
+ sudo cf-promises --syntax-description json
43
+ ) > new.json
44
+ cfengine format new.json
45
+ - name: Set Git user
46
+ run: |
47
+ git config user.name 'github-actions[bot]'
48
+ git config user.email 'github-actions[bot]@users.noreply.github.com'
49
+ - name: Update contents of syntax-description
50
+ run: |
51
+ if ! cmp -s new.json ./src/cfengine_cli/syntax-description.json; then
52
+ cat new.json > ./src/cfengine_cli/syntax-description.json
53
+ echo "CHANGES_DETECTED=true" >> $GITHUB_ENV
54
+ rm new.json
55
+ fi
56
+ - name: Create Pull Request
57
+ if: env.CHANGES_DETECTED == 'true'
58
+ uses: cfengine/create-pull-request@v6
59
+ with:
60
+ title: Updated syntax-description.json
61
+ body: Automated update to syntax-description.json [the `update-syntax-description` workflow](https://github.com/cfengine/cfengine-cli/tree/main/.github/workflows/update-syntax-description.yml).
62
+ reviewers: |
63
+ simonthalvorsen
64
+ olehermanse
65
+ larsewi
66
+ nickanderson
67
+ craigcomstock
68
+ branch: update-syntax-description
69
+ branch-suffix: timestamp
@@ -0,0 +1 @@
1
+ 3.14
@@ -0,0 +1,51 @@
1
+ # CFEngine CLI information for LLMs
2
+
3
+ ## Fix the implementation
4
+
5
+ In general, when the prompter asks you to fix the implementation, this means that they have adjusted the tests already and they want you to fix the implementation.
6
+ Typically you should not touch the tests in this case, unless there is something obviously wrong in them, like a typo.
7
+ The first step to identify what is necessary should be to run the tests and see which ones are failing.
8
+
9
+ ## Running tests
10
+
11
+ In general, the main command to run for testing is:
12
+
13
+ ```bash
14
+ make check
15
+ ```
16
+
17
+ This will run all the test suites.
18
+
19
+ ## Running python tools
20
+
21
+ This project uses `uv`.
22
+ That means that you should not run `python`, `python3`, `pip`, `pip3` directly.
23
+ Instead, run the appropriate uv command to ensure we're using the right python and the right dependencies.
24
+
25
+ ## Pointers for the source code
26
+
27
+ When fixing issues, these are usually the files to look at:
28
+
29
+ - The implementation of `cfengine format` is in `src/cfengine_cli/format.py`.
30
+ - The implementation of `cfengine lint` is in `src/cfengine_cli/lint.py`.
31
+
32
+ ## Syntax trees
33
+
34
+ When working on the formatter or the linter, it is often useful to look at the syntax tree of the policy file.
35
+ There is a `dev` subcommand for this:
36
+
37
+ ```bash
38
+ uv run cfengine dev syntax-tree tests/lint/001_hello_world.cf
39
+ ```
40
+
41
+ The command above prints the syntax tree for `tests/lint/001_hello_world.cf` to the terminal (standard output).
42
+
43
+ ## Test suites
44
+
45
+ As mentioned above, the `make check` command runs all the tests.
46
+ We have different suites:
47
+
48
+ - Unit tests in `tests/unit` test individual python functions.
49
+ - Formatting tests in `tests/format` test the formatter (`cfengine format`).
50
+ - Linting tests in `tests/lint` test the linter.
51
+ - Shell tests in `tests/shell` tests various subcommands and the tool as a whole in an end-to-end fashion.
@@ -18,17 +18,17 @@ make format
18
18
 
19
19
  ## Installing from source:
20
20
 
21
- For developers working on CFEngine CLI, it is recommended to install an editable version of the tool:
21
+ For developers working on CFEngine CLI, you can install it globally using pipx:
22
22
 
23
23
  ```bash
24
24
  make install
25
25
  ```
26
26
 
27
- Some of the tests require that you have the CLI installed (they run `cfengine` commands).
27
+ This is optional `make check` and `uv run` work without a global install.
28
28
 
29
29
  ## Running commands without installing
30
30
 
31
- You can also run commands without installing, using `uv`:
31
+ You can run commands without installing globally, using `uv`:
32
32
 
33
33
  ```bash
34
34
  uv run cfengine format
@@ -46,9 +46,9 @@ Running individual test suites:
46
46
 
47
47
  ```bash
48
48
  uv run pytest
49
- bash tests/run-lint-tests.sh
50
- bash tests/run-format-tests.sh
51
- bash tests/run-shell-tests.sh
49
+ uv run bash tests/run-lint-tests.sh
50
+ uv run bash tests/run-format-tests.sh
51
+ uv run bash tests/run-shell-tests.sh
52
52
  ```
53
53
 
54
54
  ## Releasing new versions
@@ -0,0 +1,26 @@
1
+ .PHONY: default format lint install check venv
2
+
3
+ default: check
4
+
5
+ venv:
6
+ uv venv --clear
7
+ uv sync
8
+
9
+ format: venv
10
+ uv tool run black . --target-version py310
11
+ prettier . --write
12
+
13
+ lint: venv
14
+ uv tool run black --check . --fast
15
+ uv tool run flake8 src/ --ignore=E203,W503,E722,E731 --max-complexity=100 --max-line-length=160
16
+ uv tool run pyflakes src/
17
+ uv tool run pyright src/
18
+
19
+ install:
20
+ pipx install --force --editable .
21
+
22
+ check: venv format lint
23
+ uv run pytest
24
+ uv run bash tests/run-lint-tests.sh
25
+ uv run bash tests/run-format-tests.sh
26
+ uv run bash tests/run-shell-tests.sh
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cfengine
3
- Version: 0.13.0
3
+ Version: 0.14.0
4
4
  Summary: Human-oriented CLI for interacting with CFEngine tools
5
5
  License: GNU GENERAL PUBLIC LICENSE
6
6
  Version 3, 29 June 2007
@@ -696,7 +696,7 @@ Requires-Python: >=3.10
696
696
  Description-Content-Type: text/markdown
697
697
  Requires-Dist: cf-remote>=0.7.3
698
698
  Requires-Dist: cfbs>=5.5.0
699
- Requires-Dist: tree-sitter-cfengine>=1.1.8
699
+ Requires-Dist: tree-sitter-cfengine>=1.1.12
700
700
  Requires-Dist: tree-sitter>=0.25
701
701
  Requires-Dist: markdown-it-py>=3.0.0
702
702
 
@@ -12,7 +12,7 @@ requires-python = ">=3.10"
12
12
  dependencies = [
13
13
  "cf-remote>=0.7.3",
14
14
  "cfbs>=5.5.0",
15
- "tree-sitter-cfengine>=1.1.8",
15
+ "tree-sitter-cfengine>=1.1.12",
16
16
  "tree-sitter>=0.25",
17
17
  "markdown-it-py>=3.0.0",
18
18
  ]
@@ -40,6 +40,9 @@ cfengine = "cfengine_cli.main:main"
40
40
  [tool.setuptools]
41
41
  license-files = [] # Workaround bug in setuptools https://github.com/astral-sh/uv/issues/9513
42
42
 
43
+ [tool.setuptools.package-data]
44
+ cfengine_cli = ["*.json"] # syntax-description.json
45
+
43
46
  [tool.pyright]
44
47
  include = ["src"]
45
48
  venvPath = "."
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cfengine
3
- Version: 0.13.0
3
+ Version: 0.14.0
4
4
  Summary: Human-oriented CLI for interacting with CFEngine tools
5
5
  License: GNU GENERAL PUBLIC LICENSE
6
6
  Version 3, 29 June 2007
@@ -696,7 +696,7 @@ Requires-Python: >=3.10
696
696
  Description-Content-Type: text/markdown
697
697
  Requires-Dist: cf-remote>=0.7.3
698
698
  Requires-Dist: cfbs>=5.5.0
699
- Requires-Dist: tree-sitter-cfengine>=1.1.8
699
+ Requires-Dist: tree-sitter-cfengine>=1.1.12
700
700
  Requires-Dist: tree-sitter>=0.25
701
701
  Requires-Dist: markdown-it-py>=3.0.0
702
702
 
@@ -1,5 +1,6 @@
1
1
  .gitignore
2
2
  .python-version
3
+ CLAUDE.md
3
4
  HACKING.md
4
5
  LICENSE
5
6
  Makefile
@@ -7,10 +8,9 @@ README.md
7
8
  pyproject.toml
8
9
  uv.lock
9
10
  .github/dependabot.yml
10
- .github/workflows/format.yml
11
- .github/workflows/lint.yml
11
+ .github/workflows/make-check.yml
12
12
  .github/workflows/pypi-publish.yml
13
- .github/workflows/test.yml
13
+ .github/workflows/update-syntax-description.yml
14
14
  ci/01-install.sh
15
15
  ci/02-safe-tests.sh
16
16
  ci/03-unsafe-tests.sh
@@ -31,9 +31,10 @@ src/cfengine_cli/format.py
31
31
  src/cfengine_cli/lint.py
32
32
  src/cfengine_cli/main.py
33
33
  src/cfengine_cli/paths.py
34
- src/cfengine_cli/policy_language.py
35
34
  src/cfengine_cli/profile.py
36
35
  src/cfengine_cli/shell.py
36
+ src/cfengine_cli/syntax-description.json
37
+ src/cfengine_cli/syntax_tree.py
37
38
  src/cfengine_cli/utils.py
38
39
  src/cfengine_cli/version.py
39
40
  src/cfengine_cli/masterfiles/__init__.py
@@ -62,6 +63,16 @@ tests/format/006_remove_empty_comments.expected.cf
62
63
  tests/format/006_remove_empty_comments.input.cf
63
64
  tests/format/007_class_guarded_empty_lines.expected.cf
64
65
  tests/format/007_class_guarded_empty_lines.input.cf
66
+ tests/format/008_long_string.expected.cf
67
+ tests/format/008_long_string.input.cf
68
+ tests/format/009_single_line.expected.cf
69
+ tests/format/009_single_line.input.cf
70
+ tests/format/010_stakeholder.expected.cf
71
+ tests/format/010_stakeholder.input.cf
72
+ tests/format/011_macros.expected.cf
73
+ tests/format/011_macros.input.cf
74
+ tests/format/011_promises.expected.cf
75
+ tests/format/011_promises.input.cf
65
76
  tests/lint/001_hello_world.cf
66
77
  tests/lint/002_ifvarclass.expected.txt
67
78
  tests/lint/002_ifvarclass.x.cf
@@ -82,9 +93,45 @@ tests/lint/007_empty_file.x.cf
82
93
  tests/lint/008_namespace.cf
83
94
  tests/lint/008_namespace.expected.txt
84
95
  tests/lint/008_namespace.x.cf
96
+ tests/lint/009_bundle_shadows_function.cf
97
+ tests/lint/009_bundle_shadows_function.expected.txt
98
+ tests/lint/009_bundle_shadows_function.x.cf
99
+ tests/lint/010_unknown_function_inside_vars.cf
100
+ tests/lint/010_unknown_function_inside_vars.expected.txt
101
+ tests/lint/010_unknown_function_inside_vars.x.cf
102
+ tests/lint/011_mutually_exclusive_types_vars.cf
103
+ tests/lint/011_mutually_exclusive_types_vars.expected.txt
104
+ tests/lint/011_mutually_exclusive_types_vars.x.cf
105
+ tests/lint/012_invalid_attributes.expected.txt
106
+ tests/lint/012_invalid_attributes.x.cf
107
+ tests/lint/013_function_call_arg_count.cf
108
+ tests/lint/013_function_call_arg_count.expected.txt
109
+ tests/lint/013_function_call_arg_count.x.cf
110
+ tests/lint/014_num_args_body.expected.txt
111
+ tests/lint/014_num_args_body.x.cf
112
+ tests/lint/014_num_args_bundle.expected.txt
113
+ tests/lint/014_num_args_bundle.x.cf
114
+ tests/lint/014_num_args_bundle_body.cf
115
+ tests/lint/015_variadic_func_arg_count.cf
116
+ tests/lint/015_variadic_func_arg_count.expected.txt
117
+ tests/lint/015_variadic_func_arg_count.x.cf
118
+ tests/lint/016_macro_multi_def_bundle.cf
119
+ tests/lint/016_macro_multi_def_bundle.expected.txt
120
+ tests/lint/016_macro_multi_def_bundle.x.cf
121
+ tests/lint/017_half_promises.cf
122
+ tests/lint/017_half_promises.expected.txt
123
+ tests/lint/017_half_promises.x.cf
124
+ tests/lint/018_implies_body.cf
125
+ tests/lint/018_implies_body.expected.txt
126
+ tests/lint/018_implies_body.x.cf
127
+ tests/lint/019_nested_calls.cf
128
+ tests/lint/019_nested_calls.expected.txt
129
+ tests/lint/019_nested_calls.x.cf
130
+ tests/lint/020_bundle_name_expansion.cf
85
131
  tests/shell/001-help.sh
86
132
  tests/shell/002-version.sh
87
133
  tests/shell/003-format.sh
134
+ tests/shell/004-format-check.sh
88
135
  tests/unit/__init__.py
89
136
  tests/unit/test_deps.py
90
137
  tests/unit/test_format.py
@@ -1,5 +1,5 @@
1
1
  cf-remote>=0.7.3
2
2
  cfbs>=5.5.0
3
- tree-sitter-cfengine>=1.1.8
3
+ tree-sitter-cfengine>=1.1.12
4
4
  tree-sitter>=0.25
5
5
  markdown-it-py>=3.0.0
@@ -4,7 +4,7 @@ import re
4
4
  import json
5
5
  from cfengine_cli.profile import profile_cfengine, generate_callstack
6
6
  from cfengine_cli.dev import dispatch_dev_subcommand
7
- from cfengine_cli.lint import lint_args
7
+ from cfengine_cli.lint import lint_args, PolicySyntaxError
8
8
  from cfengine_cli.shell import user_command
9
9
  from cfengine_cli.paths import bin
10
10
  from cfengine_cli.version import cfengine_cli_version_string
@@ -14,7 +14,6 @@ from cfengine_cli.format import (
14
14
  format_policy_fin_fout,
15
15
  )
16
16
  from cfengine_cli.utils import UserError
17
- from cfbs.utils import find
18
17
  from cfbs.commands import build_command
19
18
  from cf_remote.commands import deploy as deploy_command
20
19
 
@@ -50,34 +49,53 @@ def deploy() -> int:
50
49
  return r
51
50
 
52
51
 
53
- def _format_filename(filename, line_length):
54
- if filename.startswith("./."):
55
- return
52
+ def _format_filename(filename: str, line_length: int, check: bool) -> int:
53
+ """Format a single file.
54
+
55
+ Raises PolicySyntaxError for .cf files with syntax errors."""
56
56
  if filename.endswith(".json"):
57
- format_json_file(filename)
58
- return
57
+ return format_json_file(filename, check)
59
58
  if filename.endswith(".cf"):
60
- format_policy_file(filename, line_length)
61
- return
59
+ return format_policy_file(filename, line_length, check)
62
60
  raise UserError(f"Unrecognized file format: {filename}")
63
61
 
64
62
 
65
- def _format_dirname(directory, line_length):
66
- for filename in find(directory, extension=".json"):
67
- _format_filename(filename, line_length)
68
- for filename in find(directory, extension=".cf"):
69
- _format_filename(filename, line_length)
70
-
71
-
72
- def format(names, line_length) -> int:
63
+ def _format_dirname(directory: str, line_length: int, check: bool) -> int:
64
+ ret = 0
65
+ for root, dirs, files in os.walk(directory):
66
+ # Don't recurse into hidden folders
67
+ dirs[:] = [d for d in dirs if not d.startswith(".")]
68
+ for name in sorted(files):
69
+ if name.startswith("."):
70
+ continue # Hidden files are ignored by default
71
+ if name.endswith(".x.cf") or name.endswith(".input.cf"):
72
+ continue # Test files skipped during directory traversal
73
+ if name.endswith(
74
+ (".input.json", ".jqinput.json", ".x.json", ".expected.json")
75
+ ):
76
+ continue # Test files skipped during directory traversal
77
+ filepath = os.path.join(root, name)
78
+ if name.endswith(".json") or name.endswith(".cf"):
79
+ ret |= _format_filename(filepath, line_length, check)
80
+ return ret
81
+
82
+
83
+ def format(names, line_length, check) -> int:
84
+ try:
85
+ return _format_inner(names, line_length, check)
86
+ except PolicySyntaxError as e:
87
+ print(f"Error: {e}")
88
+ return 1
89
+
90
+
91
+ def _format_inner(names, line_length, check) -> int:
73
92
  if not names:
74
- _format_dirname(".", line_length)
75
- return 0
93
+ return _format_dirname(".", line_length, check)
76
94
  if len(names) == 1 and names[0] == "-":
77
95
  # Special case, format policy file from stdin to stdout
78
- format_policy_fin_fout(sys.stdin, sys.stdout, line_length)
79
- return 0
96
+ return format_policy_fin_fout(sys.stdin, sys.stdout, line_length, check)
80
97
 
98
+ ret = 0
81
99
  for name in names:
82
100
  if name == "-":
83
101
  raise UserError(
@@ -86,11 +104,13 @@ def format(names, line_length) -> int:
86
104
  if not os.path.exists(name):
87
105
  raise UserError(f"{name} does not exist")
88
106
  if os.path.isfile(name):
89
- _format_filename(name, line_length)
107
+ ret |= _format_filename(name, line_length, check)
90
108
  continue
91
109
  if os.path.isdir(name):
92
- _format_dirname(name, line_length)
110
+ ret |= _format_dirname(name, line_length, check)
93
111
  continue
112
+ if check:
113
+ return ret
94
114
  return 0
95
115
 
96
116
 
@@ -8,7 +8,7 @@ import re
8
8
  import subprocess
9
9
  import sys
10
10
 
11
- ACTIVE_BRANCHES = ["3.21.x", "3.24.x", "master"]
11
+ ACTIVE_BRANCHES = ["3.24.x", "3.27.x", "master"]
12
12
 
13
13
  HUMAN_NAME = {
14
14
  "diffutils": "diffutils",
@@ -260,7 +260,7 @@ class DepsReader:
260
260
  # currently only_deps is generator of space-separated deps,
261
261
  # i.e. each item can contain several items, like this:
262
262
  # list(only_deps) = ["lcov", "pthreads-w32 libgnurx"]
263
- # to "flattern" it we first join using spaces and then split on spaces
263
+ # to "flatten" it we first join using spaces and then split on spaces
264
264
  # in the middle we also do some clean-ups
265
265
  only_deps = " ".join(only_deps).replace("libgcc ", "").split(" ")
266
266
  # now only_deps looks like this: ["lcov", "pthreads-w32", "libgnurx"]
@@ -8,6 +8,7 @@ from cfengine_cli.deptool import (
8
8
  print_release_dependency_tables,
9
9
  )
10
10
  from cfengine_cli.docs import update_docs, check_docs
11
+ from cfengine_cli.syntax_tree import syntax_tree
11
12
 
12
13
 
13
14
  def generate_release_information_command(
@@ -25,53 +26,42 @@ def _continue_prompt() -> bool:
25
26
  return answer in ("y", "yes")
26
27
 
27
28
 
28
- def _expect_repo(repo) -> bool:
29
+ def _expect_repo(repo):
29
30
  cwd = os.getcwd()
30
31
  if cwd.endswith(repo):
31
- return True
32
+ return
32
33
  print(f"Note: This command is intended to be run in the {repo} repo")
33
34
  print(f" https://github.com/cfengine/{repo}")
34
- answer = _continue_prompt()
35
- return answer
35
+ if not _continue_prompt():
36
+ raise UserError(f"Aborted (expected to be in {repo} repo)")
36
37
 
37
38
 
38
39
  def update_dependency_tables() -> int:
39
- answer = _expect_repo("buildscripts")
40
- if answer:
41
- return _update_dependency_tables()
42
- return 1
40
+ _expect_repo("buildscripts")
41
+ return _update_dependency_tables()
43
42
 
44
43
 
45
44
  def print_dependency_tables(args) -> int:
46
- versions = args.versions
47
- answer = _expect_repo("buildscripts")
48
- if answer:
49
- return print_release_dependency_tables(versions)
50
- return 1
45
+ _expect_repo("buildscripts")
46
+ return print_release_dependency_tables(args.versions)
51
47
 
52
48
 
53
49
  def format_docs(files) -> int:
54
- answer = _expect_repo("documentation")
55
- if answer:
56
- return update_docs(files)
57
- return 1
50
+ _expect_repo("documentation")
51
+ return update_docs(files)
58
52
 
59
53
 
60
54
  def lint_docs() -> int:
61
- answer = _expect_repo("documentation")
62
- if answer:
63
- return check_docs()
64
- return 1
55
+ _expect_repo("documentation")
56
+ return check_docs()
65
57
 
66
58
 
67
59
  def generate_release_information(
68
60
  omit_download=False, check=False, min_version=None
69
61
  ) -> int:
70
- answer = _expect_repo("release-information")
71
- if answer:
72
- generate_release_information_command(omit_download, check, min_version)
73
- return 0
74
- return 1
62
+ _expect_repo("release-information")
63
+ generate_release_information_command(omit_download, check, min_version)
64
+ return 0
75
65
 
76
66
 
77
67
  def dispatch_dev_subcommand(subcommand, args) -> int:
@@ -83,6 +73,8 @@ def dispatch_dev_subcommand(subcommand, args) -> int:
83
73
  return format_docs(args.files)
84
74
  if subcommand == "lint-docs":
85
75
  return lint_docs()
76
+ if subcommand == "syntax-tree":
77
+ return syntax_tree(args.file)
86
78
  if subcommand == "generate-release-information":
87
79
  return generate_release_information(
88
80
  args.omit_download, args.check_against_git, args.minimum_version
@@ -212,7 +212,7 @@ def fn_autoformat(_origin_path, snippet_path, language, _first_line, _last_line)
212
212
  raise UserError(f"Invalid json in '{snippet_path}'")
213
213
  case "cf":
214
214
  # Note: Dead code - Not used for CFEngine policy yet
215
- format_policy_file(snippet_path, 80)
215
+ format_policy_file(snippet_path, 80, False)
216
216
 
217
217
 
218
218
  def _translate_language(x):
@@ -346,20 +346,22 @@ def _process_markdown_code_blocks(
346
346
  os.remove(snippet_path)
347
347
 
348
348
 
349
- def _run_black(path):
350
- print(f"Formatting '{path}' with black...")
349
+ def _run_formatter(tool, args, cwd, install_hint):
350
+ print(f"Formatting with {tool}...")
351
351
  try:
352
352
  subprocess.run(
353
- ["black", path],
353
+ [tool, *args],
354
354
  capture_output=True,
355
355
  text=True,
356
356
  check=True,
357
- cwd=".",
357
+ cwd=cwd,
358
358
  )
359
359
  except:
360
- raise UserError(
361
- "Encountered an error running black\nInstall: pipx install black"
362
- )
360
+ raise UserError(f"Encountered an error running {tool}\nInstall: {install_hint}")
361
+
362
+
363
+ def _run_black(path):
364
+ _run_formatter("black", [path], ".", "pipx install black")
363
365
 
364
366
 
365
367
  def _run_prettier(path):
@@ -378,21 +380,15 @@ def _run_prettier(path):
378
380
  args.append("**.md")
379
381
  if not args:
380
382
  return
381
- try:
382
- # Warning: Beware of shell expansion if you try to run this in your terminal.
383
- # Wrong: prettier --write **.markdown **.md
384
- # Right: prettier --write '**.markdown' '**.md'
385
- subprocess.run(
386
- ["prettier", "--embedded-language-formatting", "off", "--write", *args],
387
- capture_output=True,
388
- text=True,
389
- check=True,
390
- cwd=directory,
391
- )
392
- except:
393
- raise UserError(
394
- "Encountered an error running prettier\nInstall: npm install --global prettier"
395
- )
383
+ # Warning: Beware of shell expansion if you try to run this in your terminal.
384
+ # Wrong: prettier --write **.markdown **.md
385
+ # Right: prettier --write '**.markdown' '**.md'
386
+ _run_formatter(
387
+ "prettier",
388
+ ["--embedded-language-formatting", "off", "--write", *args],
389
+ directory,
390
+ "npm install --global prettier",
391
+ )
396
392
 
397
393
 
398
394
  def _update_docs_single_arg(path):