8000 GitHub - miekg/cf at refs/tags/v0.1.32
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

miekg/cf

Repository files navigation

CFEngine pretty printer

Cf is a formatter for CFEngine files, think of it as 'gofmt' (from golang) for .cf files. See cmd/cffmt for the CLI.

Cf should handle all CFEngine files, allthough the syntax is so liberal, especially where you can place comments (the official yacc parser/lexer throws away comments) that there is always a chance a file isn't parseable. The new data type which can parse inline json can also cause trouble.

If a file has a top-level comment of the form: # cffmt:no the file will not be parsed and the original input will be outputted instead.

If you have an slist in a contraint you can put # cffmt:list above it if you want each item to be printed on a new line. If a list has less then 10 items and at least one of these items is a comment, it will be printed as if cffmt:list has been given.

If you have a "normal" looking CFEngine file that isn't parsed correctly, please open an issue with the most minimal CFEngine syntax that fails to parse.

Comments that are placed in "obvious"(*) places are handled well, but there are corner cases where they lead to a parse error. Directly after a bundle or body for instance. Some of these are f A7F5 ixable (and you should file a bug), others are in the hard-to-fix area and will not be supported. Comments are coalesced into a single block, even if they were separated by a newline. I.e.

# a comment

# another comment

Becomes:

# a comment
# another comment

If you want to keep the separation you need to add '#' on the empty lines.

  • (*) "obvious": not in a list, not in a function argument.

Layout

Cf uses an indent of 2 spaces to indent elements of the tree when pretty printing. Further more:

  • the promise guard (i.e. files: has 2 newlines above it, if it's not the first in the file
  • the class guard (i.e. any::) (if given) has a empty line above it, but is attached to the promiser.
  • the promiser is always attached to the constraint expressions

Empty promise guards are removed, i.e. commands: without any commands defined will be removed from the output:

any::
  "Clients" or => { machine3, machine32 }

commands:

files:

Becomes:

any::
  "Clients" or => { machine3, machine32 }

Cf aligns fat-arrows in constraint expressions, this is also true for selections in bodies.

"/etc/apparmor.d"
             delete => tidy,
 	depth_search => recurse("0"),
             file_select => by_name("session");

Becomes:

"/etc/apparmor.d"
  delete       => tidy,
  depth_search => recurse("0"),
  file_select  => by_name("lightdm-guest-session");

If there is only a single constraint it will be printed on the same line:

   "getcapExists"
        expression => fileexists("/sbin/getcap");

Becomes:

"getcapExists" expression => fileexists("/sbin/getcap");

If there are multiple promises and they all have single constraints, the promises themselves are aligned and the newline between them is deleted:

"getcapExists"
     expression => fileexists("/sbin/getcap");

"setcapExists"  expression => fileexists("/sbin/setcap");

To:

"getcapExists" expression => fileexists("/sbin/getcap");
"setcapExists" expression => fileexists("/sbin/setcap");

If a single constraint has a 'contain =>' or 'comment =>' they will not be printed on the same line. This is to show important things on the left hand side, (see align.go for details), i.e:

printvm::
 "printer[xxx]"	string => "ps.ppd";
  "printer[xxx]"		string => "ps.ppd";
  "printer[xxx]" slist => {"ps.ppd"};

To:

printvm::
  "printer[xxx]" string => "ps.ppd";
  "printer[xxx]" string => "ps.ppd";
  "printer[xxx]" slist  => {"ps.ppd"};

But if one of the constraints was contain or comment:

printvm::
  "printer[xxx]" string => "ps.ppd";
  "printer[xxx]" comment => "ps.ppd";
  "printer[xxx]" string => "ps.ppd";

Will instead become:

printvm::
  "printer[xxx]" string => "ps.ppd";

  "printer[xxx]"
    comment => "ps.ppd";

  "printer[xxx]" string => "ps.ppd";

Trailing commas of lists are removed. List are wrapped at the 120th column: (assuming ggg, is on the 120th column):

"Clients"         or => { aaa, bbb, ccc, dddd, eee, fff,
  ggg, hhhh };

To:

"Clients"         or => { aaa, bbb, ccc, dddd, eee, fff, ggg,
                          hhhh };

It also makes sure there isn't a dangling }; on a line. Empty lists are compressed to {}.

Installation

Install the cffmt binary with: go install github.com/miekg/cf/cmd/cffmt@main. Then use it by giving it a filename or piping to standard input. The pretty printed document is printed to standard output.

cffmt ../../testdata/promtest.cf

Abstract Syntax Tree

If you only want see the AST use -a, and throw away standard output:

cmd/cffmt/cffmt -a -p=false testdata/arg-list.cf >/dev/null
2023/03/11 22:29:51 Parse Tree:
Specification
└─ Bundle
   ├─ {Keyword bundle}
   ├─ {Keyword agent}
   ├─ {NameFunction bla}
   └─ BundleBody
      ├─ PromiseGuard
      │  └─ {KeywordDeclaration vars}
      └─ ClassPromises
         └─ Promise
            ├─ {TokenType(-994) "installed_canonified"}
            ├─ Constraint
            │  ├─ {KeywordType slist}
            │  ├─ FatArrow
            │  │  └─ {TokenType(-996) =>}
            │  └─ Rval
            │     └─ Qstring
            │        └─ {TokenType(-994) "aaa"}
            └─ {Punctuation ;}

From this input file:

bundle agent bla
{
 vars:
    "installed_canonified"
        slist => "aaa";
}

The plain strings, i.e. Bundle are non-terminals, while the {TokenType(-994) ...} and {KeywordTYpe ...} are terminals. That first terminal has a "local" type that we define, in this case it is a token.Qstring, otherwise it's a original chroma.Token. In both cases the type is a chroma.Token. See internal/parse/print.go on how that tree is walked.

Autofmt in (neo)vim

au FileType cf3 command! Fmt call Fmt("cffmt /dev/stdin") " fmt
au BufWritePost *.cf silent call Fmt("cffmt /dev/stdin") " fmt on save

Developing

Lexing is via Chroma (not 100% perfect, but we work around this in lex.go). We have a recursive descent parser to create the AST, this is using *rd.Builder. Once we have the AST the printing is relatively simple (internal/parse/print.go).

https://github.com/cfengine/core/blob/master/libpromises/cf3parse.y contains the grammar we're reimplementing here. Note that one doesn't deal with comments, and is not used to build an AST.

About

CFEngine formatter

Topics

Resources

License

Stars

Watchers

Forks

Contributors 4

  •  
  •  
  •  
  •  
0