cfengine 0.7.0__tar.gz → 0.7.2__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.
- {cfengine-0.7.0 → cfengine-0.7.2}/.github/workflows/pypi-publish.yml +1 -1
- {cfengine-0.7.0 → cfengine-0.7.2}/PKG-INFO +4 -4
- {cfengine-0.7.0 → cfengine-0.7.2}/pyproject.toml +3 -3
- {cfengine-0.7.0 → cfengine-0.7.2}/src/cfengine.egg-info/PKG-INFO +4 -4
- {cfengine-0.7.0 → cfengine-0.7.2}/src/cfengine.egg-info/SOURCES.txt +2 -2
- {cfengine-0.7.0 → cfengine-0.7.2}/src/cfengine.egg-info/requires.txt +2 -2
- {cfengine-0.7.0 → cfengine-0.7.2}/src/cfengine_cli/commands.py +18 -18
- {cfengine-0.7.0 → cfengine-0.7.2}/src/cfengine_cli/docs.py +97 -57
- {cfengine-0.7.0 → cfengine-0.7.2}/src/cfengine_cli/format.py +27 -27
- {cfengine-0.7.0 → cfengine-0.7.2}/src/cfengine_cli/lint.py +32 -5
- {cfengine-0.7.0 → cfengine-0.7.2}/src/cfengine_cli/main.py +2 -1
- cfengine-0.7.2/tests/shell/003-format.sh +37 -0
- {cfengine-0.7.0 → cfengine-0.7.2}/uv.lock +43 -115
- cfengine-0.7.0/src/cfengine_cli/markdowner.py +0 -251
- {cfengine-0.7.0 → cfengine-0.7.2}/.github/dependabot.yml +0 -0
- {cfengine-0.7.0 → cfengine-0.7.2}/.github/workflows/format.yml +0 -0
- {cfengine-0.7.0 → cfengine-0.7.2}/.github/workflows/lint.yml +0 -0
- {cfengine-0.7.0 → cfengine-0.7.2}/.github/workflows/test.yml +0 -0
- {cfengine-0.7.0 → cfengine-0.7.2}/.gitignore +0 -0
- {cfengine-0.7.0 → cfengine-0.7.2}/.python-version +0 -0
- {cfengine-0.7.0 → cfengine-0.7.2}/HACKING.md +0 -0
- {cfengine-0.7.0 → cfengine-0.7.2}/LICENSE +0 -0
- {cfengine-0.7.0 → cfengine-0.7.2}/README.md +0 -0
- {cfengine-0.7.0 → cfengine-0.7.2}/ci/01-install.sh +0 -0
- {cfengine-0.7.0 → cfengine-0.7.2}/ci/02-safe-tests.sh +0 -0
- {cfengine-0.7.0 → cfengine-0.7.2}/ci/03-unsafe-tests.sh +0 -0
- {cfengine-0.7.0 → cfengine-0.7.2}/setup.cfg +0 -0
- {cfengine-0.7.0 → cfengine-0.7.2}/src/cfengine.egg-info/dependency_links.txt +0 -0
- {cfengine-0.7.0 → cfengine-0.7.2}/src/cfengine.egg-info/entry_points.txt +0 -0
- {cfengine-0.7.0 → cfengine-0.7.2}/src/cfengine.egg-info/top_level.txt +0 -0
- {cfengine-0.7.0 → cfengine-0.7.2}/src/cfengine_cli/__init__.py +0 -0
- {cfengine-0.7.0 → cfengine-0.7.2}/src/cfengine_cli/__main__.py +0 -0
- {cfengine-0.7.0 → cfengine-0.7.2}/src/cfengine_cli/deptool.py +0 -0
- {cfengine-0.7.0 → cfengine-0.7.2}/src/cfengine_cli/dev.py +0 -0
- {cfengine-0.7.0 → cfengine-0.7.2}/src/cfengine_cli/paths.py +0 -0
- {cfengine-0.7.0 → cfengine-0.7.2}/src/cfengine_cli/shell.py +0 -0
- {cfengine-0.7.0 → cfengine-0.7.2}/src/cfengine_cli/utils.py +0 -0
- {cfengine-0.7.0 → cfengine-0.7.2}/src/cfengine_cli/version.py +0 -0
- {cfengine-0.7.0 → cfengine-0.7.2}/tests/__init__.py +0 -0
- {cfengine-0.7.0 → cfengine-0.7.2}/tests/format/001_hello_world.expected.cf +0 -0
- {cfengine-0.7.0 → cfengine-0.7.2}/tests/format/001_hello_world.input.cf +0 -0
- {cfengine-0.7.0 → cfengine-0.7.2}/tests/format/002_basics.expected.cf +0 -0
- {cfengine-0.7.0 → cfengine-0.7.2}/tests/format/002_basics.input.cf +0 -0
- {cfengine-0.7.0 → cfengine-0.7.2}/tests/format/003_wrapping.expected.cf +0 -0
- {cfengine-0.7.0 → cfengine-0.7.2}/tests/format/003_wrapping.input.cf +0 -0
- {cfengine-0.7.0 → cfengine-0.7.2}/tests/format/004_comments.expected.cf +0 -0
- {cfengine-0.7.0 → cfengine-0.7.2}/tests/format/004_comments.input.cf +0 -0
- {cfengine-0.7.0 → cfengine-0.7.2}/tests/run-format-tests.sh +0 -0
- {cfengine-0.7.0 → cfengine-0.7.2}/tests/run-shell-tests.sh +0 -0
- {cfengine-0.7.0 → cfengine-0.7.2}/tests/shell/001-help.sh +0 -0
- {cfengine-0.7.0 → cfengine-0.7.2}/tests/shell/002-version.sh +0 -0
- {cfengine-0.7.0 → cfengine-0.7.2}/tests/test_deps.py +0 -0
- {cfengine-0.7.0 → cfengine-0.7.2}/tests/test_paths.py +0 -0
- {cfengine-0.7.0 → cfengine-0.7.2}/tests/test_utils.py +0 -0
- {cfengine-0.7.0 → cfengine-0.7.2}/tests/test_version.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cfengine
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.2
|
|
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
|
|
@@ -692,12 +692,12 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
692
692
|
Classifier: Programming Language :: Python :: 3.12
|
|
693
693
|
Classifier: Programming Language :: Python :: 3.13
|
|
694
694
|
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
|
695
|
-
Requires-Python: >=3.
|
|
695
|
+
Requires-Python: >=3.10
|
|
696
696
|
Description-Content-Type: text/markdown
|
|
697
697
|
Requires-Dist: cf-remote>=0.6.4
|
|
698
698
|
Requires-Dist: cfbs>=4.4.3
|
|
699
|
-
Requires-Dist: tree-sitter-cfengine>=1.0.
|
|
700
|
-
Requires-Dist: tree-sitter>=0.
|
|
699
|
+
Requires-Dist: tree-sitter-cfengine>=1.0.11
|
|
700
|
+
Requires-Dist: tree-sitter>=0.25
|
|
701
701
|
Requires-Dist: markdown-it-py>=3.0.0
|
|
702
702
|
|
|
703
703
|
# CFEngine command line interface (CLI)
|
|
@@ -8,12 +8,12 @@ dynamic = ["version"]
|
|
|
8
8
|
description = "Human-oriented CLI for interacting with CFEngine tools"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {file = "LICENSE"}
|
|
11
|
-
requires-python = ">=3.
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
12
|
dependencies = [
|
|
13
13
|
"cf-remote>=0.6.4",
|
|
14
14
|
"cfbs>=4.4.3",
|
|
15
|
-
"tree-sitter-cfengine>=1.0.
|
|
16
|
-
"tree-sitter>=0.
|
|
15
|
+
"tree-sitter-cfengine>=1.0.11",
|
|
16
|
+
"tree-sitter>=0.25",
|
|
17
17
|
"markdown-it-py>=3.0.0",
|
|
18
18
|
]
|
|
19
19
|
classifiers = [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cfengine
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.2
|
|
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
|
|
@@ -692,12 +692,12 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
692
692
|
Classifier: Programming Language :: Python :: 3.12
|
|
693
693
|
Classifier: Programming Language :: Python :: 3.13
|
|
694
694
|
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
|
695
|
-
Requires-Python: >=3.
|
|
695
|
+
Requires-Python: >=3.10
|
|
696
696
|
Description-Content-Type: text/markdown
|
|
697
697
|
Requires-Dist: cf-remote>=0.6.4
|
|
698
698
|
Requires-Dist: cfbs>=4.4.3
|
|
699
|
-
Requires-Dist: tree-sitter-cfengine>=1.0.
|
|
700
|
-
Requires-Dist: tree-sitter>=0.
|
|
699
|
+
Requires-Dist: tree-sitter-cfengine>=1.0.11
|
|
700
|
+
Requires-Dist: tree-sitter>=0.25
|
|
701
701
|
Requires-Dist: markdown-it-py>=3.0.0
|
|
702
702
|
|
|
703
703
|
# CFEngine command line interface (CLI)
|
|
@@ -28,7 +28,6 @@ src/cfengine_cli/docs.py
|
|
|
28
28
|
src/cfengine_cli/format.py
|
|
29
29
|
src/cfengine_cli/lint.py
|
|
30
30
|
src/cfengine_cli/main.py
|
|
31
|
-
src/cfengine_cli/markdowner.py
|
|
32
31
|
src/cfengine_cli/paths.py
|
|
33
32
|
src/cfengine_cli/shell.py
|
|
34
33
|
src/cfengine_cli/utils.py
|
|
@@ -49,4 +48,5 @@ tests/format/003_wrapping.input.cf
|
|
|
49
48
|
tests/format/004_comments.expected.cf
|
|
50
49
|
tests/format/004_comments.input.cf
|
|
51
50
|
tests/shell/001-help.sh
|
|
52
|
-
tests/shell/002-version.sh
|
|
51
|
+
tests/shell/002-version.sh
|
|
52
|
+
tests/shell/003-format.sh
|
|
@@ -47,46 +47,46 @@ def deploy() -> int:
|
|
|
47
47
|
return r
|
|
48
48
|
|
|
49
49
|
|
|
50
|
-
def _format_filename(filename):
|
|
50
|
+
def _format_filename(filename, line_length):
|
|
51
51
|
if filename.startswith("./."):
|
|
52
52
|
return
|
|
53
53
|
if filename.endswith(".json"):
|
|
54
54
|
format_json_file(filename)
|
|
55
55
|
return
|
|
56
56
|
if filename.endswith(".cf"):
|
|
57
|
-
format_policy_file(filename)
|
|
57
|
+
format_policy_file(filename, line_length)
|
|
58
58
|
return
|
|
59
59
|
raise UserError(f"Unrecognized file format: {filename}")
|
|
60
60
|
|
|
61
61
|
|
|
62
|
-
def _format_dirname(directory):
|
|
62
|
+
def _format_dirname(directory, line_length):
|
|
63
63
|
for filename in find(directory, extension=".json"):
|
|
64
|
-
_format_filename(filename)
|
|
64
|
+
_format_filename(filename, line_length)
|
|
65
65
|
for filename in find(directory, extension=".cf"):
|
|
66
|
-
_format_filename(filename)
|
|
66
|
+
_format_filename(filename, line_length)
|
|
67
67
|
|
|
68
68
|
|
|
69
|
-
def format(
|
|
70
|
-
if not
|
|
71
|
-
_format_dirname(".")
|
|
69
|
+
def format(names, line_length) -> int:
|
|
70
|
+
if not names:
|
|
71
|
+
_format_dirname(".", line_length)
|
|
72
72
|
return 0
|
|
73
|
-
if len(
|
|
73
|
+
if len(names) == 1 and names[0] == "-":
|
|
74
74
|
# Special case, format policy file from stdin to stdout
|
|
75
|
-
format_policy_fin_fout(sys.stdin, sys.stdout)
|
|
75
|
+
format_policy_fin_fout(sys.stdin, sys.stdout, line_length)
|
|
76
76
|
return 0
|
|
77
77
|
|
|
78
|
-
for
|
|
79
|
-
if
|
|
78
|
+
for name in names:
|
|
79
|
+
if name == "-":
|
|
80
80
|
raise UserError(
|
|
81
81
|
"The - argument has a special meaning and cannot be combined with other paths"
|
|
82
82
|
)
|
|
83
|
-
if not os.path.exists(
|
|
84
|
-
raise UserError(f"{
|
|
85
|
-
if os.path.isfile(
|
|
86
|
-
_format_filename(
|
|
83
|
+
if not os.path.exists(name):
|
|
84
|
+
raise UserError(f"{name} does not exist")
|
|
85
|
+
if os.path.isfile(name):
|
|
86
|
+
_format_filename(name, line_length)
|
|
87
87
|
continue
|
|
88
|
-
if os.path.isdir(
|
|
89
|
-
_format_dirname(
|
|
88
|
+
if os.path.isdir(name):
|
|
89
|
+
_format_dirname(name, line_length)
|
|
90
90
|
continue
|
|
91
91
|
return 0
|
|
92
92
|
|
|
@@ -10,13 +10,12 @@ TODO: This code needs several adjustments to better fit into
|
|
|
10
10
|
|
|
11
11
|
import os
|
|
12
12
|
import json
|
|
13
|
-
from shutil import which
|
|
14
13
|
import subprocess
|
|
15
14
|
|
|
16
15
|
import markdown_it
|
|
17
16
|
from cfbs.pretty import pretty_file
|
|
18
17
|
|
|
19
|
-
from cfengine_cli.
|
|
18
|
+
from cfengine_cli.lint import lint_policy_file
|
|
20
19
|
from cfengine_cli.utils import UserError
|
|
21
20
|
|
|
22
21
|
|
|
@@ -110,7 +109,9 @@ def fn_extract(origin_path, snippet_path, _language, first_line, last_line):
|
|
|
110
109
|
raise UserError(f"Couldn't open '{origin_path}' or '{snippet_path}'")
|
|
111
110
|
|
|
112
111
|
|
|
113
|
-
def fn_check_syntax(
|
|
112
|
+
def fn_check_syntax(
|
|
113
|
+
origin_path, snippet_path, language, first_line, _last_line, snippet_number
|
|
114
|
+
):
|
|
114
115
|
snippet_abs_path = os.path.abspath(snippet_path)
|
|
115
116
|
|
|
116
117
|
if not os.path.exists(snippet_path):
|
|
@@ -120,25 +121,11 @@ def fn_check_syntax(origin_path, snippet_path, language, first_line, _last_line)
|
|
|
120
121
|
|
|
121
122
|
match language:
|
|
122
123
|
case "cf":
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
)
|
|
129
|
-
err = p.stderr
|
|
130
|
-
|
|
131
|
-
if err:
|
|
132
|
-
err = err.replace(snippet_abs_path, f"{origin_path}:{first_line}")
|
|
133
|
-
print(err)
|
|
134
|
-
except OSError:
|
|
135
|
-
raise UserError(f"'{snippet_abs_path}' doesn't exist")
|
|
136
|
-
except ValueError:
|
|
137
|
-
raise UserError("Invalid subprocess arguments")
|
|
138
|
-
except subprocess.CalledProcessError:
|
|
139
|
-
raise UserError(f"Couldn't run cf-promises on '{snippet_abs_path}'")
|
|
140
|
-
except subprocess.TimeoutExpired:
|
|
141
|
-
raise UserError("Timed out")
|
|
124
|
+
r = lint_policy_file(
|
|
125
|
+
snippet_abs_path, origin_path, first_line + 1, snippet_number
|
|
126
|
+
)
|
|
127
|
+
if r != 0:
|
|
128
|
+
raise UserError(f"Error when checking '{origin_path}'")
|
|
142
129
|
case "json":
|
|
143
130
|
try:
|
|
144
131
|
with open(snippet_abs_path, "r") as f:
|
|
@@ -200,25 +187,35 @@ def fn_autoformat(_origin_path, snippet_path, language, _first_line, _last_line)
|
|
|
200
187
|
raise UserError(f"Invalid json in '{snippet_path}'")
|
|
201
188
|
|
|
202
189
|
|
|
203
|
-
def
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
190
|
+
def _translate_language(x):
|
|
191
|
+
if x == "cf3" or x == "cfengine3":
|
|
192
|
+
return "cf"
|
|
193
|
+
if x == "yaml":
|
|
194
|
+
return "yml"
|
|
195
|
+
return x
|
|
196
|
+
|
|
207
197
|
|
|
198
|
+
SUPPORTED_LANGUAGES = ["cf", "cfengine3", "cf3", "json", "yml", "yaml"]
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def _process_markdown_code_blocks(
|
|
202
|
+
path, languages, extract, syntax_check, output_check, autoformat, replace, cleanup
|
|
203
|
+
):
|
|
208
204
|
if not os.path.exists(path):
|
|
209
205
|
raise UserError("This path doesn't exist")
|
|
210
206
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
207
|
+
languages = set(languages)
|
|
208
|
+
if "cf3" in languages or "cf" in languages or "cfengine3" in languages:
|
|
209
|
+
languages.add("cf3")
|
|
210
|
+
languages.add("cfengine3")
|
|
211
|
+
languages.add("cf")
|
|
212
|
+
if "yaml" in languages or "yml" in languages:
|
|
213
|
+
languages.add("yml")
|
|
214
|
+
languages.add("yaml")
|
|
218
215
|
for language in languages:
|
|
219
|
-
if language not in
|
|
216
|
+
if language not in SUPPORTED_LANGUAGES:
|
|
220
217
|
raise UserError(
|
|
221
|
-
f"Unsupported language '{language}'. The supported languages are: {", ".join(
|
|
218
|
+
f"Unsupported language '{language}'. The supported languages are: {", ".join(SUPPORTED_LANGUAGES)}"
|
|
222
219
|
)
|
|
223
220
|
|
|
224
221
|
parsed_markdowns = get_markdown_files(path, languages)
|
|
@@ -234,8 +231,9 @@ def _markdown_code_checker(
|
|
|
234
231
|
cb["first_line"] += offset
|
|
235
232
|
cb["last_line"] += offset
|
|
236
233
|
|
|
237
|
-
language =
|
|
238
|
-
|
|
234
|
+
language = _translate_language(code_block["language"])
|
|
235
|
+
snippet_number = i + 1
|
|
236
|
+
snippet_path = f"{origin_path}.snippet-{snippet_number}.{language}"
|
|
239
237
|
|
|
240
238
|
flags = code_block["flags"]
|
|
241
239
|
if "noextract" in flags or "skip" in flags:
|
|
@@ -258,12 +256,16 @@ def _markdown_code_checker(
|
|
|
258
256
|
language,
|
|
259
257
|
code_block["first_line"],
|
|
260
258
|
code_block["last_line"],
|
|
259
|
+
snippet_number,
|
|
261
260
|
)
|
|
262
261
|
except Exception as e:
|
|
263
262
|
if cleanup:
|
|
264
263
|
os.remove(snippet_path)
|
|
265
264
|
raise e
|
|
266
265
|
|
|
266
|
+
if output_check and "noexecute" not in code_block["flags"]:
|
|
267
|
+
fn_check_output()
|
|
268
|
+
|
|
267
269
|
if autoformat and "noautoformat" not in code_block["flags"]:
|
|
268
270
|
fn_autoformat(
|
|
269
271
|
origin_path,
|
|
@@ -273,9 +275,6 @@ def _markdown_code_checker(
|
|
|
273
275
|
code_block["last_line"],
|
|
274
276
|
)
|
|
275
277
|
|
|
276
|
-
if output_check and "noexecute" not in code_block["flags"]:
|
|
277
|
-
fn_check_output()
|
|
278
|
-
|
|
279
278
|
if replace and "noreplace" not in code_block["flags"]:
|
|
280
279
|
offset = fn_replace(
|
|
281
280
|
origin_path,
|
|
@@ -287,45 +286,86 @@ def _markdown_code_checker(
|
|
|
287
286
|
)
|
|
288
287
|
if cleanup:
|
|
289
288
|
os.remove(snippet_path)
|
|
290
|
-
|
|
291
|
-
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def _run_black():
|
|
292
|
+
path = "."
|
|
293
|
+
assert os.path.isdir(path)
|
|
294
|
+
try:
|
|
295
|
+
subprocess.run(
|
|
296
|
+
["black", path],
|
|
297
|
+
capture_output=True,
|
|
298
|
+
text=True,
|
|
299
|
+
check=True,
|
|
300
|
+
cwd=path,
|
|
301
|
+
)
|
|
302
|
+
except:
|
|
303
|
+
raise UserError(
|
|
304
|
+
"Encountered an error running black\nInstall: pipx install black"
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def _run_prettier():
|
|
309
|
+
path = "."
|
|
310
|
+
assert os.path.isdir(path)
|
|
311
|
+
try:
|
|
312
|
+
subprocess.run(
|
|
313
|
+
["prettier", "--write", "**.markdown", "**.md"],
|
|
314
|
+
capture_output=True,
|
|
315
|
+
text=True,
|
|
316
|
+
check=True,
|
|
317
|
+
cwd=path,
|
|
318
|
+
)
|
|
319
|
+
except:
|
|
320
|
+
raise UserError(
|
|
321
|
+
"Encountered an error running prettier\nInstall: npm install --global prettier"
|
|
322
|
+
)
|
|
292
323
|
|
|
293
324
|
|
|
294
325
|
def update_docs() -> int:
|
|
295
326
|
"""
|
|
296
|
-
Iterate through entire docs repo (.), autoformatting
|
|
297
|
-
|
|
298
|
-
|
|
327
|
+
Iterate through entire docs repo (.), autoformatting as much as possible:
|
|
328
|
+
- python code with black
|
|
329
|
+
- markdown files with prettier
|
|
330
|
+
- code blocks inside markdown files are formatted for the formats supported by prettier
|
|
331
|
+
- JSON code blocks are re-formatted by cfbs pretty (we plan to expand this to CFEngine code blocks)
|
|
299
332
|
|
|
300
333
|
Run by the command:
|
|
301
334
|
cfengine dev docs-format
|
|
302
335
|
"""
|
|
303
|
-
|
|
336
|
+
print("Formatting python files with black...")
|
|
337
|
+
_run_black()
|
|
338
|
+
print("Formatting markdown files with prettier...")
|
|
339
|
+
_run_prettier()
|
|
340
|
+
print("Formatting markdown code blocks according to our rules...")
|
|
341
|
+
_process_markdown_code_blocks(
|
|
304
342
|
path=".",
|
|
305
|
-
|
|
343
|
+
languages=["json"], # TODO: Add cfengine3 here
|
|
306
344
|
extract=True,
|
|
307
|
-
|
|
308
|
-
autoformat=True,
|
|
309
|
-
languages=["json"],
|
|
345
|
+
syntax_check=False,
|
|
310
346
|
output_check=False,
|
|
347
|
+
autoformat=True,
|
|
348
|
+
replace=True,
|
|
311
349
|
cleanup=True,
|
|
312
350
|
)
|
|
313
351
|
return 0
|
|
314
352
|
|
|
315
353
|
|
|
316
354
|
def check_docs() -> int:
|
|
317
|
-
"""
|
|
355
|
+
"""
|
|
356
|
+
Run checks / tests on docs.
|
|
357
|
+
Currently only JSON syntax checking.
|
|
318
358
|
|
|
319
359
|
Run by the command:
|
|
320
|
-
cfengine dev docs-
|
|
321
|
-
|
|
360
|
+
cfengine dev docs-check"""
|
|
361
|
+
_process_markdown_code_blocks(
|
|
322
362
|
path=".",
|
|
323
|
-
|
|
363
|
+
languages=["json", "cf3"],
|
|
324
364
|
extract=True,
|
|
325
|
-
|
|
326
|
-
autoformat=False,
|
|
327
|
-
languages=["json"],
|
|
365
|
+
syntax_check=True,
|
|
328
366
|
output_check=False,
|
|
367
|
+
autoformat=False,
|
|
368
|
+
replace=False,
|
|
329
369
|
cleanup=True,
|
|
330
370
|
)
|
|
331
371
|
return 0
|
|
@@ -80,14 +80,14 @@ def split_generic_value(node, indent):
|
|
|
80
80
|
return [stringify_single_line(node)]
|
|
81
81
|
|
|
82
82
|
|
|
83
|
-
def split_generic_list(middle, indent):
|
|
83
|
+
def split_generic_list(middle, indent, line_length):
|
|
84
84
|
elements = []
|
|
85
85
|
for element in middle:
|
|
86
86
|
if elements and element.type == ",":
|
|
87
87
|
elements[-1] = elements[-1] + ","
|
|
88
88
|
continue
|
|
89
89
|
line = " " * indent + stringify_single_line(element)
|
|
90
|
-
if len(line) <
|
|
90
|
+
if len(line) < line_length:
|
|
91
91
|
elements.append(line)
|
|
92
92
|
else:
|
|
93
93
|
lines = split_generic_value(element, indent)
|
|
@@ -96,50 +96,50 @@ def split_generic_list(middle, indent):
|
|
|
96
96
|
return elements
|
|
97
97
|
|
|
98
98
|
|
|
99
|
-
def maybe_split_generic_list(nodes, indent):
|
|
99
|
+
def maybe_split_generic_list(nodes, indent, line_length):
|
|
100
100
|
string = " " * indent + stringify_children(nodes)
|
|
101
|
-
if len(string) <
|
|
101
|
+
if len(string) < line_length:
|
|
102
102
|
return [string]
|
|
103
|
-
return split_generic_list(nodes, indent)
|
|
103
|
+
return split_generic_list(nodes, indent, line_length)
|
|
104
104
|
|
|
105
105
|
|
|
106
|
-
def split_rval_list(node, indent):
|
|
106
|
+
def split_rval_list(node, indent, line_length):
|
|
107
107
|
assert node.type == "list"
|
|
108
108
|
assert node.children[0].type == "{"
|
|
109
109
|
first = text(node.children[0])
|
|
110
110
|
last = " " * indent + text(node.children[-1])
|
|
111
111
|
middle = node.children[1:-1]
|
|
112
|
-
elements = maybe_split_generic_list(middle, indent + 2)
|
|
112
|
+
elements = maybe_split_generic_list(middle, indent + 2, line_length)
|
|
113
113
|
return [first, *elements, last]
|
|
114
114
|
|
|
115
115
|
|
|
116
|
-
def split_rval_call(node, indent):
|
|
116
|
+
def split_rval_call(node, indent, line_length):
|
|
117
117
|
assert node.type == "call"
|
|
118
118
|
assert node.children[0].type == "calling_identifier"
|
|
119
119
|
assert node.children[1].type == "("
|
|
120
120
|
first = text(node.children[0]) + "("
|
|
121
121
|
last = " " * indent + text(node.children[-1])
|
|
122
122
|
middle = node.children[2:-1]
|
|
123
|
-
elements = maybe_split_generic_list(middle, indent + 2)
|
|
123
|
+
elements = maybe_split_generic_list(middle, indent + 2, line_length)
|
|
124
124
|
return [first, *elements, last]
|
|
125
125
|
|
|
126
126
|
|
|
127
|
-
def split_rval(node, indent):
|
|
127
|
+
def split_rval(node, indent, line_length):
|
|
128
128
|
if node.type == "list":
|
|
129
|
-
return split_rval_list(node, indent)
|
|
129
|
+
return split_rval_list(node, indent, line_length)
|
|
130
130
|
if node.type == "call":
|
|
131
|
-
return split_rval_call(node, indent)
|
|
131
|
+
return split_rval_call(node, indent, line_length)
|
|
132
132
|
return [stringify_single_line(node)]
|
|
133
133
|
|
|
134
134
|
|
|
135
|
-
def maybe_split_rval(node, indent, offset):
|
|
135
|
+
def maybe_split_rval(node, indent, offset, line_length):
|
|
136
136
|
line = stringify_single_line(node)
|
|
137
|
-
if len(line) + offset <
|
|
137
|
+
if len(line) + offset < line_length:
|
|
138
138
|
return [line]
|
|
139
|
-
return split_rval(node, indent)
|
|
139
|
+
return split_rval(node, indent, line_length)
|
|
140
140
|
|
|
141
141
|
|
|
142
|
-
def attempt_split_attribute(node, indent):
|
|
142
|
+
def attempt_split_attribute(node, indent, line_length):
|
|
143
143
|
assert len(node.children) == 3
|
|
144
144
|
lval = node.children[0]
|
|
145
145
|
arrow = node.children[1]
|
|
@@ -148,22 +148,22 @@ def attempt_split_attribute(node, indent):
|
|
|
148
148
|
if rval.type == "list" or rval.type == "call":
|
|
149
149
|
prefix = " " * indent + text(lval) + " " + text(arrow) + " "
|
|
150
150
|
offset = len(prefix)
|
|
151
|
-
lines = maybe_split_rval(rval, indent, offset)
|
|
151
|
+
lines = maybe_split_rval(rval, indent, offset, line_length)
|
|
152
152
|
lines[0] = prefix + lines[0]
|
|
153
153
|
return lines
|
|
154
154
|
return [stringify_single_line(node)]
|
|
155
155
|
|
|
156
156
|
|
|
157
|
-
def stringify(node, indent):
|
|
157
|
+
def stringify(node, indent, line_length):
|
|
158
158
|
single_line = " " * indent + stringify_single_line(node)
|
|
159
|
-
if len(single_line) <
|
|
159
|
+
if len(single_line) < line_length:
|
|
160
160
|
return [single_line]
|
|
161
161
|
if node.type == "attribute":
|
|
162
|
-
return attempt_split_attribute(node, indent)
|
|
162
|
+
return attempt_split_attribute(node, indent, line_length)
|
|
163
163
|
return [single_line]
|
|
164
164
|
|
|
165
165
|
|
|
166
|
-
def autoformat(node, fmt, macro_indent, indent=0):
|
|
166
|
+
def autoformat(node, fmt, line_length, macro_indent, indent=0):
|
|
167
167
|
previous = fmt.update_previous(node)
|
|
168
168
|
if previous and previous.type == "macro" and text(previous).startswith("@else"):
|
|
169
169
|
indent = macro_indent
|
|
@@ -190,12 +190,12 @@ def autoformat(node, fmt, macro_indent, indent=0):
|
|
|
190
190
|
]:
|
|
191
191
|
indent += 2
|
|
192
192
|
if node.type == "attribute":
|
|
193
|
-
lines = stringify(node, indent)
|
|
193
|
+
lines = stringify(node, indent, line_length)
|
|
194
194
|
fmt.print_lines(lines, indent=0)
|
|
195
195
|
return
|
|
196
196
|
if children:
|
|
197
197
|
for child in children:
|
|
198
|
-
autoformat(child, fmt, macro_indent, indent)
|
|
198
|
+
autoformat(child, fmt, line_length, macro_indent, indent)
|
|
199
199
|
return
|
|
200
200
|
if node.type in [",", ";"]:
|
|
201
201
|
fmt.print_same_line(node)
|
|
@@ -203,7 +203,7 @@ def autoformat(node, fmt, macro_indent, indent=0):
|
|
|
203
203
|
fmt.print(node, indent)
|
|
204
204
|
|
|
205
205
|
|
|
206
|
-
def format_policy_file(filename):
|
|
206
|
+
def format_policy_file(filename, line_length):
|
|
207
207
|
assert filename.endswith(".cf")
|
|
208
208
|
PY_LANGUAGE = Language(tscfengine.language())
|
|
209
209
|
parser = Parser(PY_LANGUAGE)
|
|
@@ -216,7 +216,7 @@ def format_policy_file(filename):
|
|
|
216
216
|
|
|
217
217
|
root_node = tree.root_node
|
|
218
218
|
assert root_node.type == "source_file"
|
|
219
|
-
autoformat(root_node, fmt, macro_indent)
|
|
219
|
+
autoformat(root_node, fmt, line_length, macro_indent)
|
|
220
220
|
|
|
221
221
|
new_data = fmt.buffer + "\n"
|
|
222
222
|
if new_data != original_data.decode("utf-8"):
|
|
@@ -225,7 +225,7 @@ def format_policy_file(filename):
|
|
|
225
225
|
print(f"Policy file '{filename}' was reformatted")
|
|
226
226
|
|
|
227
227
|
|
|
228
|
-
def format_policy_fin_fout(fin, fout):
|
|
228
|
+
def format_policy_fin_fout(fin, fout, line_length):
|
|
229
229
|
PY_LANGUAGE = Language(tscfengine.language())
|
|
230
230
|
parser = Parser(PY_LANGUAGE)
|
|
231
231
|
|
|
@@ -236,7 +236,7 @@ def format_policy_fin_fout(fin, fout):
|
|
|
236
236
|
|
|
237
237
|
root_node = tree.root_node
|
|
238
238
|
assert root_node.type == "source_file"
|
|
239
|
-
autoformat(root_node, fmt, macro_indent)
|
|
239
|
+
autoformat(root_node, fmt, line_length, macro_indent)
|
|
240
240
|
|
|
241
241
|
new_data = fmt.buffer + "\n"
|
|
242
242
|
fout.write(new_data)
|
|
@@ -97,9 +97,22 @@ def _walk(filename, lines, node) -> int:
|
|
|
97
97
|
return errors
|
|
98
98
|
|
|
99
99
|
|
|
100
|
-
def lint_policy_file(
|
|
100
|
+
def lint_policy_file(
|
|
101
|
+
filename, original_filename=None, original_line=None, snippet=None
|
|
102
|
+
):
|
|
103
|
+
assert original_filename is None or type(original_filename) is str
|
|
104
|
+
assert original_line is None or type(original_line) is int
|
|
105
|
+
assert snippet is None or type(snippet) is int
|
|
106
|
+
if (
|
|
107
|
+
original_filename is not None
|
|
108
|
+
or original_line is not None
|
|
109
|
+
or snippet is not None
|
|
110
|
+
):
|
|
111
|
+
assert original_filename and os.path.isfile(original_filename)
|
|
112
|
+
assert original_line and original_line > 0
|
|
113
|
+
assert snippet and snippet > 0
|
|
101
114
|
assert os.path.isfile(filename)
|
|
102
|
-
assert filename.endswith(".cf")
|
|
115
|
+
assert filename.endswith((".cf", ".cfengine3", ".cf3", ".cf.sub"))
|
|
103
116
|
PY_LANGUAGE = Language(tscfengine.language())
|
|
104
117
|
parser = Parser(PY_LANGUAGE)
|
|
105
118
|
|
|
@@ -112,12 +125,26 @@ def lint_policy_file(filename):
|
|
|
112
125
|
assert root_node.type == "source_file"
|
|
113
126
|
errors = 0
|
|
114
127
|
if not root_node.children:
|
|
115
|
-
|
|
128
|
+
if snippet:
|
|
129
|
+
assert original_filename and original_line
|
|
130
|
+
print(
|
|
131
|
+
f"Error: Empty policy snippet {snippet} at '{original_filename}:{original_line}'"
|
|
132
|
+
)
|
|
133
|
+
else:
|
|
134
|
+
print(f"Error: Empty policy file '{filename}'")
|
|
116
135
|
errors += 1
|
|
117
136
|
errors += _walk(filename, lines, root_node)
|
|
118
137
|
if errors == 0:
|
|
119
|
-
|
|
138
|
+
if snippet:
|
|
139
|
+
assert original_filename and original_line
|
|
140
|
+
print(f"PASS: Snippet {snippet} at '{original_filename}:{original_line}'")
|
|
141
|
+
else:
|
|
142
|
+
print(f"PASS: {filename}")
|
|
120
143
|
return 0
|
|
121
144
|
|
|
122
|
-
|
|
145
|
+
if snippet:
|
|
146
|
+
assert original_filename and original_line
|
|
147
|
+
print(f"FAIL: Snippet {snippet} at '{original_filename}:{original_line}'")
|
|
148
|
+
else:
|
|
149
|
+
print(f"FAIL: {filename} ({errors} error{'s' if errors > 0 else ''})")
|
|
123
150
|
return errors
|
|
@@ -43,6 +43,7 @@ def _get_arg_parser():
|
|
|
43
43
|
subp.add_parser("deploy", help="Deploy a built policy set")
|
|
44
44
|
fmt = subp.add_parser("format", help="Autoformat .json and .cf files")
|
|
45
45
|
fmt.add_argument("files", nargs="*", help="Files to format")
|
|
46
|
+
fmt.add_argument("--line-length", default=80, type=int, help="Maximum line length")
|
|
46
47
|
subp.add_parser(
|
|
47
48
|
"lint",
|
|
48
49
|
help="Look for syntax errors and other simple mistakes",
|
|
@@ -86,7 +87,7 @@ def run_command_with_args(args) -> int:
|
|
|
86
87
|
if args.command == "deploy":
|
|
87
88
|
return commands.deploy()
|
|
88
89
|
if args.command == "format":
|
|
89
|
-
return commands.format(args.files)
|
|
90
|
+
return commands.format(args.files, args.line_length)
|
|
90
91
|
if args.command == "lint":
|
|
91
92
|
return commands.lint()
|
|
92
93
|
if args.command == "report":
|