Skip to content

Commit d90455b

Browse files
authored
Merge pull request #45 from olehermanse/rebase
Refactored linting code and fixed issues
2 parents ca84699 + 1551b05 commit d90455b

18 files changed

Lines changed: 942 additions & 432 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ __pycache__
1111
/tmp/
1212
*.log
1313
*.output.cf
14+
*.output.txt
1415
git_diff_exists
1516
commit_message.txt
1617
black_output.txt

HACKING.md

Lines changed: 47 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,53 @@ This document aims to have relevant information for people contributing to and m
44
It is not necessary for users of the tool to know about these processes.
55
For general user information, see the [README](./README.md).
66

7+
## Code formatting
8+
9+
We use automated code formatters to ensure consistent code style / indentation.
10+
Please format Python code with [black](https://pypi.org/project/black/), and YAML and markdown files with [Prettier](https://prettier.io/).
11+
For simplicity's sake, we don't have a custom configuration, we use the tool's defaults.
12+
13+
If your editor does not do this automatically, you can run these tools from the command line:
14+
15+
```bash
16+
make format
17+
```
18+
19+
## Installing from source:
20+
21+
For developers working on CFEngine CLI, it is recommended to install an editable version of the tool:
22+
23+
```bash
24+
make install
25+
```
26+
27+
Some of the tests require that you have the CLI installed (they run `cfengine` commands).
28+
29+
## Running commands without installing
30+
31+
You can also run commands without installing, using `uv`:
32+
33+
```bash
34+
uv run cfengine format
35+
```
36+
37+
## Running tests
38+
39+
Use the makefile command to run all linting and tests:
40+
41+
```bash
42+
make check
43+
```
44+
45+
Running individual test suites:
46+
47+
```bash
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
52+
```
53+
754
## Releasing new versions
855

956
Releases are [automated using a GH Action](https://github.com/cfengine/cfengine-cli/blob/main/.github/workflows/pypi-publish.yml)
@@ -79,62 +126,11 @@ Copy the token and paste it into the GitHub Secret named `PYPI_PASSWORD`.
79126
`PYPI_USERNAME` should be there already, you don't have to edit it, it is simply `__token__`.
80127
Don't store the token anywhere else - we generate new tokens if necessary.
81128

82-
## Code formatting
83-
84-
We use automated code formatters to ensure consistent code style / indentation.
85-
Please format Python code with [black](https://pypi.org/project/black/), and YAML and markdown files with [Prettier](https://prettier.io/).
86-
For simplicity's sake, we don't have a custom configuration, we use the tool's defaults.
87-
88-
If your editor does not do this automatically, you can run these tools from the command line:
89-
90-
```bash
91-
black . && prettier . --write
92-
```
93-
94-
## Running commands during development
95-
96-
This project uses `uv`.
97-
This makes it easy to run commands without installing the project, for example:
98-
99-
```bash
100-
uv run cfengine format
101-
```
102-
103-
## Installing from source:
104-
105-
```bash
106-
git fetch --all --tags
107-
pip3 install .
108-
```
109-
110-
## Running tests
111-
112-
Unit tests:
113-
114-
```bash
115-
py.test
116-
```
117-
118-
Shell tests (requires installing first):
119-
120-
```bash
121-
cat tests/shell/*.sh | bash
122-
```
123-
124129
## Not implemented yet / TODOs
125130

126131
- `cfengine run`
127132
- The command could automatically detect that you have CFEngine installed on a remote hub, and run it there instead (using `cf-remote`).
128133
- Handle when `cf-agent` is not installed, help users install.
129134
- Prompt / help users do what they meant (i.e. build and deploy and run).
130-
- `cfengine format`
131-
- Automatically break up and indent method calls, function calls, and nested function calls.
132-
- Smarter placement of comments based on context.
133-
- The command should be able to take a filename as an argument, and also operate using stdin and stdout.
134-
(Receive file content on stdin, file type using command line arg, output formatted file to stdout).
135-
- We can add a shortcut, `cfengine fmt`, since that matches other tools, like `deno`.
136-
- `cfengine lint`
137-
- The command should be able to take a filename as an argument, and also take file content from stdin.
138-
- It would be nice if we refactored `validate_config()` in `cfbs` so it would take a simple dictionary (JSON) instead of a special CFBSConfig object.
139135
- Missing commands:
140136
- `cfengine install` - Install CFEngine packages / binaries (Wrapping `cf-remote install`).

Makefile

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
.PHONY: default format lint install check
2+
3+
default: check
4+
5+
format:
6+
uv tool run black .
7+
prettier . --write
8+
9+
lint:
10+
uv tool run black --check .
11+
uv tool run flake8 src/ --ignore=E203,W503,E722,E731 --max-complexity=100 --max-line-length=160
12+
uv tool run pyflakes src/
13+
uv tool run pyright src/
14+
15+
install:
16+
pipx install --force --editable .
17+
18+
check: format lint install
19+
uv run pytest
20+
bash tests/run-lint-tests.sh
21+
bash tests/run-format-tests.sh
22+
bash tests/run-shell-tests.sh

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,8 @@ When it finds a mistake, it points out where the problem is like this;
6060
ifvarclass => "cfengine";
6161
^--------^
6262
Deprecation: Use 'if' instead of 'ifvarclass' at main.cf:5:7
63-
FAIL: main.cf (1 errors)
64-
Failure, 1 errors in total.
63+
FAIL: main.cf (1 error)
64+
Failure, 1 error in total.
6565
```
6666

6767
Note that since we use a different parser than `cf-agent` / `cf-promises`, they are not 100% in sync.

src/cfengine_cli/commands.py

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import json
55
from cfengine_cli.profile import profile_cfengine, generate_callstack
66
from cfengine_cli.dev import dispatch_dev_subcommand
7-
from cfengine_cli.lint import lint_folder, lint_single_arg
7+
from cfengine_cli.lint import lint_args
88
from cfengine_cli.shell import user_command
99
from cfengine_cli.paths import bin
1010
from cfengine_cli.version import cfengine_cli_version_string
@@ -96,20 +96,16 @@ def format(names, line_length) -> int:
9696

9797
def _lint(files, strict) -> int:
9898
if not files:
99-
return lint_folder(".", strict)
100-
101-
errors = 0
102-
103-
for file in files:
104-
errors += lint_single_arg(file, strict)
105-
106-
return errors
99+
return lint_args(["."], strict)
100+
return lint_args(files, strict)
107101

108102

109103
def lint(files, strict) -> int:
110104
errors = _lint(files, strict)
111105
if errors == 0:
112106
print("Success, no errors found.")
107+
elif errors == 1:
108+
print("Failure, 1 error in total.")
113109
else:
114110
print(f"Failure, {errors} errors in total.")
115111
return errors

src/cfengine_cli/docs.py

Lines changed: 59 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
from cfbs.pretty import pretty_file
1717
from cfbs.utils import find
1818

19-
from cfengine_cli.lint import lint_folder, lint_policy_file
19+
from cfengine_cli.format import format_policy_file
20+
from cfengine_cli.lint import lint_args, lint_policy_file_snippet
2021
from cfengine_cli.utils import UserError
2122

2223
IGNORED_DIRS = [".git"]
@@ -127,7 +128,7 @@ def fn_check_syntax(
127128
first_line,
128129
_last_line,
129130
snippet_number,
130-
prefix=None,
131+
prefix,
131132
):
132133
snippet_abs_path = os.path.abspath(snippet_path)
133134

@@ -138,8 +139,13 @@ def fn_check_syntax(
138139

139140
match language:
140141
case "cf":
141-
r = lint_policy_file(
142-
snippet_abs_path, origin_path, first_line + 1, snippet_number, prefix
142+
r = lint_policy_file_snippet(
143+
snippet_abs_path,
144+
origin_path,
145+
first_line + 1,
146+
snippet_number,
147+
prefix,
148+
strict=False,
143149
)
144150
if r != 0:
145151
raise UserError(f"Error when checking '{origin_path}'")
@@ -189,6 +195,7 @@ def fn_replace(origin_path, snippet_path, _language, first_line, last_line, inde
189195

190196

191197
def fn_autoformat(_origin_path, snippet_path, language, _first_line, _last_line):
198+
assert language in ("cf", "json")
192199
match language:
193200
case "json":
194201
try:
@@ -203,6 +210,9 @@ def fn_autoformat(_origin_path, snippet_path, language, _first_line, _last_line)
203210
raise UserError(f"Couldn't open '{snippet_path}'")
204211
except json.decoder.JSONDecodeError:
205212
raise UserError(f"Invalid json in '{snippet_path}'")
213+
case "cf":
214+
# Note: Dead code - Not used for CFEngine policy yet
215+
format_policy_file(snippet_path, 80)
206216

207217

208218
def _translate_language(x):
@@ -241,10 +251,23 @@ def _process_markdown_code_blocks(
241251
origin_paths = sorted(parsed_markdowns["files"].keys())
242252
origin_paths_len = len(origin_paths)
243253

254+
if origin_paths_len == 0:
255+
print("No markdown files found.")
256+
return
257+
258+
if syntax_check:
259+
# We currently only print the filenames during linting, not formatting
260+
print(
261+
f"Processing code blocks (snippets) inside {origin_paths_len} markdown files:"
262+
)
244263
for origin_paths_i, origin_path in enumerate(origin_paths):
245264
percentage = int(100 * (origin_paths_i + 1) / origin_paths_len)
246-
prefix = f"[{origin_paths_i + 1}/{origin_paths_len} ({percentage}%)] "
265+
spaces = " " * (4 - len(str(percentage)))
266+
prefix = f"[{origin_paths_i + 1}/{origin_paths_len}{spaces}({percentage}%)] "
247267
offset = 0
268+
if syntax_check and not parsed_markdowns["files"][origin_path]["code-blocks"]:
269+
print(f"{prefix}SKIP: No code blocks in '{origin_path}'")
270+
continue
248271
for i, code_block in enumerate(
249272
parsed_markdowns["files"][origin_path]["code-blocks"]
250273
):
@@ -259,33 +282,44 @@ def _process_markdown_code_blocks(
259282
snippet_path = f"{origin_path}.snippet-{snippet_number}.{language}"
260283

261284
flags = code_block["flags"]
285+
first_line = code_block["first_line"]
286+
last_line = code_block["last_line"]
262287
if "noextract" in flags or "skip" in flags:
263288
# code block was marked to be skipped
289+
if syntax_check:
290+
print(
291+
f"{prefix}SKIP: Snippet {snippet_number} at '{origin_path}:{first_line}' ({language} {' '.join(flags)})"
292+
)
264293
continue
265294
if extract:
266295
fn_extract(
267296
origin_path,
268297
snippet_path,
269298
language,
270-
code_block["first_line"],
271-
code_block["last_line"],
299+
first_line,
300+
last_line,
272301
)
273302

274-
if syntax_check and "novalidate" not in flags:
275-
try:
276-
fn_check_syntax(
277-
origin_path,
278-
snippet_path,
279-
language,
280-
code_block["first_line"],
281-
code_block["last_line"],
282-
snippet_number,
283-
prefix,
303+
if syntax_check:
304+
if "novalidate" in flags:
305+
print(
306+
f"{prefix}SKIP: Snippet {snippet_number} at '{origin_path}:{first_line}' ({language} {' '.join(flags)})"
284307
)
285-
except Exception as e:
286-
if cleanup:
287-
os.remove(snippet_path)
288-
raise e
308+
else:
309+
try:
310+
fn_check_syntax(
311+
origin_path,
312+
snippet_path,
313+
language,
314+
first_line,
315+
last_line,
316+
snippet_number,
317+
prefix,
318+
)
319+
except Exception as e:
320+
if cleanup:
321+
os.remove(snippet_path)
322+
raise e
289323

290324
if output_check and "noexecute" not in flags:
291325
fn_check_output()
@@ -295,16 +329,16 @@ def _process_markdown_code_blocks(
295329
origin_path,
296330
snippet_path,
297331
language,
298-
code_block["first_line"],
299-
code_block["last_line"],
332+
first_line,
333+
last_line,
300334
)
301335

302336
if replace and "noreplace" not in flags:
303337
offset = fn_replace(
304338
origin_path,
305339
snippet_path,
306340
language,
307-
code_block["first_line"],
341+
first_line,
308342
code_block["last_line"],
309343
code_block["indent"],
310344
)
@@ -409,7 +443,7 @@ def check_docs() -> int:
409443
410444
Run by the command:
411445
cfengine dev lint-docs"""
412-
r = lint_folder(".", strict=False)
446+
r = lint_args(["."], strict=False)
413447
if r != 0:
414448
return r
415449
_process_markdown_code_blocks(

0 commit comments

Comments
 (0)