Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,17 @@ When fixing issues, these are usually the files to look at:
- The implementation of `cfengine format` is in `src/cfengine_cli/format.py`.
- The implementation of `cfengine lint` is in `src/cfengine_cli/lint.py`.

## Syntax trees

When working on the formatter or the linter, it is often useful to look at the syntax tree of the policy file.
There is a `dev` subcommand for this:

```bash
uv run cfengine dev syntax-tree tests/lint/001_hello_world.cf
```

The command above prints the syntax tree for `tests/lint/001_hello_world.cf` to the terminal (standard output).

## Test suites

As mentioned above, the `make check` command runs all the tests.
Expand Down
3 changes: 3 additions & 0 deletions src/cfengine_cli/dev.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
print_release_dependency_tables,
)
from cfengine_cli.docs import update_docs, check_docs
from cfengine_cli.syntax_tree import syntax_tree


def generate_release_information_command(
Expand Down Expand Up @@ -72,6 +73,8 @@ def dispatch_dev_subcommand(subcommand, args) -> int:
return format_docs(args.files)
if subcommand == "lint-docs":
return lint_docs()
if subcommand == "syntax-tree":
return syntax_tree(args.file)
if subcommand == "generate-release-information":
return generate_release_information(
args.omit_download, args.check_against_git, args.minimum_version
Expand Down
2 changes: 2 additions & 0 deletions src/cfengine_cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ def _get_arg_parser():
parser.add_argument("files", nargs="*")
parser = dev_subparsers.add_parser("lint-docs")
parser.add_argument("files", nargs="*")
parser = dev_subparsers.add_parser("syntax-tree")
parser.add_argument("file", help="CFEngine policy file to print syntax tree for")
parser = dev_subparsers.add_parser("generate-release-information")

parser.add_argument(
Expand Down
42 changes: 42 additions & 0 deletions src/cfengine_cli/syntax_tree.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""Print tree-sitter syntax tree for a .cf file."""

import tree_sitter_cfengine as tscfengine
from tree_sitter import Language, Parser

_LANGUAGE = Language(tscfengine.language())
_PARSER = Parser(_LANGUAGE)


def format_sexp(sexp: str) -> str:
"""Format an S-expression with indentation."""
out = []
indent = 0
i = 0
while i < len(sexp):
c = sexp[i]
if c == "(":
if out and out[-1] != "\n":
out.append("\n")
out.append(" " * indent)
out.append("(")
indent += 1
i += 1
elif c == ")":
indent -= 1
out.append(")")
i += 1
elif c == " " and i + 1 < len(sexp) and sexp[i + 1] == "(":
i += 1 # skip space before '(', the '(' handler adds newline
else:
out.append(c)
i += 1
out.append("\n")
return "".join(out)


def syntax_tree(path: str) -> int:
with open(path, "rb") as f:
data = f.read()
tree = _PARSER.parse(data)
print(format_sexp(str(tree.root_node)), end="")
return 0
Loading