Skip to content

Commit

Permalink
Add --stdin option for allows us format pest source from STDIN.
Browse files Browse the repository at this point in the history
Use `clap` for cli parse.
Fix #26
  • Loading branch information
huacnlee committed Jan 12, 2024
1 parent b43de0b commit 1edf328
Show file tree
Hide file tree
Showing 8 changed files with 140 additions and 84 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ jobs:
run: cargo build
- name: Tests
run: cargo test
- name: Test Cli
run: make test
lint:
name: Lint
runs-on: ubuntu-latest
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pest_derive = "2.5"
pest_meta = "2.5"
text-utils = "0.2"
toml = "0.5"
clap = { version = "4.4.15", features = ["derive"] }

[dev-dependencies]
criterion = "0.4.0"
Expand Down
5 changes: 3 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
run:
cargo run . -- .
test:
cargo run . tests/fixtures/pest.expected.pest
cat tests/fixtures/json.actual.pest | cargo run . --stdin
41 changes: 33 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,31 +14,56 @@ cargo install pest_fmt

Then use the `pestfmt` command to format your `.pest` files.

```shell
pestfmt .
```bash
$ pestfmt -h
A formatter tool for pest

Usage: pestfmt [OPTIONS] [FILE]...

Arguments:
[FILE]... The file or path to format [default: .]

Options:
-s, --stdin
-h, --help Print help
-V, --version Print version
```

It will find all `.pest` files in the current directory and format them.
### Format pest files

```bash
$ pestfmt .
```

It will find all `.pest` files in the current directory and format and overwrite them.

Output:

```
```bash
Pest Formatter
-------------------------------------
2 files formatted.
```

## Usage as a library
### Format from stdin

Add `pest_fmt` into your `Cargo.toml`:
You can use `--stdin` option to format Pest source code from stdin, it will read from stdin and write to stdout.

```bash
cat file.pest | pestfmt --stdin
```
cargo add pest_fmt

### Usage as a library

Add `pest_fmt` into your `Cargo.toml`:

```bash
$ cargo add pest_fmt
```

Then use the `Formatter` struct to format pest grammar.

```rust
```rs
use pest_fmt::Formatter;

let mut fmt = Formatter::new("a={ASCII_DIGIT}");
Expand Down
2 changes: 1 addition & 1 deletion src/grammar.pest
Original file line number Diff line number Diff line change
Expand Up @@ -214,4 +214,4 @@ grammar_doc = ${ "//!" ~ space? ~ inner_doc }
line_doc = ${ "///" ~ space? ~ !"/" ~ inner_doc }

/// A comment content.
inner_doc = @{ (!newline ~ ANY)* }
inner_doc = @{ (!newline ~ ANY)* }
57 changes: 42 additions & 15 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,46 +1,73 @@
use ignore::{overrides::OverrideBuilder, WalkBuilder};
use pest_fmt::{Formatter, PestResult};
use std::{error::Error, fs, path::Path};
use std::{error::Error, fs, io::Read, path::Path};
use toml::Value;

use clap::Parser;

#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
struct Cli {
/// The file or path to format
#[arg(default_value = ".")]
file: Vec<String>,
#[clap(long, short, default_value = "false")]
stdin: bool,
}

fn main() -> Result<(), Box<dyn Error>> {
// TODO: Rewrite this to use clap
let argv: Vec<String> = std::env::args().collect();
let mut updated = 0;
let cli = Cli::parse();

if cli.stdin {
let mut source = String::new();
std::io::stdin().read_to_string(&mut source).expect("failed read source from stdin");
println!("{}", source);
} else {
process_files(cli.file)?;
}

let mut paths = argv[1..].to_vec();
Ok(())
}

fn process_files(paths: Vec<String>) -> Result<(), Box<dyn Error>> {
let mut paths = paths;
let mut updated = 0;

// If there not argument, format the current directory
if paths.is_empty() {
paths.push(".".to_string());
}

for path in paths {
if Path::new(&path).exists() {
if Path::new(&path).is_file() {
let path = Path::new(&path);
if path.exists() {
if path.is_file() {
if let Ok(changed) = format_file(&path, &path) {
if changed {
updated += 1
}
}
} else {
let walker = build_walker(&path);
let walker = build_walker(path);
updated += format_directory(walker)?;
}
} else {
println!("No such file or directory: {}", path);
eprintln!("no such file or directory: {}", path.display());
}
}

println!("Formatted {} files", updated);

Ok(())
}

pub fn format_file<P: AsRef<Path>>(path_from: P, path_to: P) -> PestResult<bool> {
fn format(source: &str) -> PestResult<String> {
let fmt = Formatter::new(source);
fmt.format()
}

fn format_file<P: AsRef<Path>>(path_from: P, path_to: P) -> PestResult<bool> {
let input = std::fs::read_to_string(path_from)?;
let fmt = Formatter::new(&input);
let output = fmt.format()?;
let output = format(&input)?;

let mut file = std::fs::File::create(path_to)?;
std::io::Write::write_all(&mut file, output.as_bytes())?;
Expand All @@ -49,7 +76,7 @@ pub fn format_file<P: AsRef<Path>>(path_from: P, path_to: P) -> PestResult<bool>

/// Format all files in the given directory.
/// Returns the number of files that were formatted.
pub fn format_directory(walker: WalkBuilder) -> Result<usize, Box<dyn Error>> {
fn format_directory(walker: WalkBuilder) -> Result<usize, Box<dyn Error>> {
let mut updated = 0;
for entry in walker.build() {
let entry = entry?;
Expand All @@ -70,7 +97,7 @@ pub fn format_directory(walker: WalkBuilder) -> Result<usize, Box<dyn Error>> {
Ok(updated)
}

fn build_walker(root: &str) -> WalkBuilder {
fn build_walker<P: AsRef<Path> + Copy>(root: P) -> WalkBuilder {
let mut builder = ignore::WalkBuilder::new(root);
builder.follow_links(true).git_ignore(true);

Expand Down
114 changes: 57 additions & 57 deletions tests/fixtures/pest.actual.pest
Original file line number Diff line number Diff line change
Expand Up @@ -8,34 +8,34 @@
// license <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. All files in the project carrying such notice may not be copied,
// modified, or distributed except according to those terms.
grammar_rules = _{ SOI ~ grammar_rule+ ~ EOI }
grammar_rule = {
grammar_rules = _{ SOI ~ grammar_rule+ ~ EOI }
grammar_rule = {
identifier ~ assignment_operator ~ modifier? ~ opening_brace ~ expression ~ closing_brace
}
assignment_operator = { "=" }
opening_brace = { "{" }
closing_brace = { "}" }
opening_paren = { "(" }
closing_paren = { ")" }
opening_brack = { "[" }
closing_brack = { "]" }
modifier = _{
assignment_operator = { "=" }
opening_brace = { "{" }
closing_brace = { "}" }
opening_paren = { "(" }
closing_paren = { ")" }
opening_brack = { "[" }
closing_brack = { "]" }
modifier = _{
silent_modifier
| atomic_modifier
| compound_atomic_modifier
| non_atomic_modifier
}
silent_modifier = { "_" }
atomic_modifier = { "@" }
compound_atomic_modifier = { "$" }
non_atomic_modifier = { "!" }
expression = { term ~ (infix_operator ~ term)* }
term = { prefix_operator* ~ node ~ postfix_operator* }
node = _{ opening_paren ~ expression ~ closing_paren|terminal }
terminal = _{ _push|peek_slice|identifier|string|insensitive_string|range }
prefix_operator = _{ positive_predicate_operator|negative_predicate_operator }
infix_operator = _{ sequence_operator|choice_operator }
postfix_operator = _{
silent_modifier = { "_" }
atomic_modifier = { "@" }
compound_atomic_modifier = { "$" }
non_atomic_modifier = { "!" }
expression = { term ~ (infix_operator ~ term)* }
term = { prefix_operator* ~ node ~ postfix_operator* }
node = _{ opening_paren ~ expression ~ closing_paren | terminal }
terminal = _{ _push | peek_slice | identifier | string | insensitive_string | range }
prefix_operator = _{ positive_predicate_operator | negative_predicate_operator }
infix_operator = _{ sequence_operator | choice_operator }
postfix_operator = _{
optional_operator
| repeat_operator
| repeat_once_operator
Expand All @@ -44,39 +44,39 @@ postfix_operator = _{
| repeat_max
| repeat_min_max
}
positive_predicate_operator = { "&" }
negative_predicate_operator = { "!" }
sequence_operator = { "~" }
choice_operator = { "|" }
optional_operator = { "?" }
repeat_operator = { "*" }
repeat_once_operator = { "+" }
repeat_exact = { opening_brace ~ number ~ closing_brace }
repeat_min = { opening_brace ~ number ~ comma ~ closing_brace }
repeat_max = { opening_brace ~ comma ~ number ~ closing_brace }
repeat_min_max = { opening_brace ~ number ~ comma ~ number ~ closing_brace }
number = @{ '0'..'9'+ }
integer = @{ number|"-" ~ "0"* ~ '1'..'9' ~ number? }
comma = { "," }
_push = { "PUSH" ~ opening_paren ~ expression ~ closing_paren }
peek_slice = { "PEEK" ~ opening_brack ~ integer? ~ range_operator ~ integer? ~ closing_brack }
identifier = @{ !"PUSH" ~ ("_" | alpha) ~ ("_" | alpha_num)* }
alpha = _{ 'a'..'z'|'A'..'Z' }
alpha_num = _{ alpha|'0'..'9' }
string = ${ quote ~ inner_str ~ quote }
insensitive_string = { "^" ~ string }
range = { character ~ range_operator ~ character }
character = ${ single_quote ~ inner_chr ~ single_quote }
inner_str = @{ (!("\"" | "\\") ~ ANY)* ~ (escape ~ inner_str)? }
inner_chr = @{ escape|ANY }
escape = @{ "\\" ~ ("\"" | "\\" | "r" | "n" | "t" | "0" | "'" | code | unicode) }
code = @{ "x" ~ hex_digit{2} }
unicode = @{ "u" ~ opening_brace ~ hex_digit{2, 6} ~ closing_brace }
hex_digit = @{ '0'..'9'|'a'..'f'|'A'..'F' }
quote = { "\"" }
single_quote = { "'" }
range_operator = { ".." }
newline = _{ "\n"|"\r\n" }
WHITESPACE = _{ " "|"\t"|newline }
block_comment = _{ "/*" ~ (block_comment | !"*/" ~ ANY)* ~ "*/" }
COMMENT = _{ block_comment|("//" ~ (!newline ~ ANY)*) }
positive_predicate_operator = { "&" }
negative_predicate_operator = { "!" }
sequence_operator = { "~" }
choice_operator = { "|" }
optional_operator = { "?" }
repeat_operator = { "*" }
repeat_once_operator = { "+" }
repeat_exact = { opening_brace ~ number ~ closing_brace }
repeat_min = { opening_brace ~ number ~ comma ~ closing_brace }
repeat_max = { opening_brace ~ comma ~ number ~ closing_brace }
repeat_min_max = { opening_brace ~ number ~ comma ~ number ~ closing_brace }
number = @{ '0'..'9'+ }
integer = @{ number | "-" ~ "0"* ~ '1'..'9' ~ number? }
comma = { "," }
_push = { "PUSH" ~ opening_paren ~ expression ~ closing_paren }
peek_slice = { "PEEK" ~ opening_brack ~ integer? ~ range_operator ~ integer? ~ closing_brack }
identifier = @{ !"PUSH" ~ ("_" | alpha) ~ ("_" | alpha_num)* }
alpha = _{ 'a'..'z' | 'A'..'Z' }
alpha_num = _{ alpha | '0'..'9' }
string = ${ quote ~ inner_str ~ quote }
insensitive_string = { "^" ~ string }
range = { character ~ range_operator ~ character }
character = ${ single_quote ~ inner_chr ~ single_quote }
inner_str = @{ (!("\"" | "\\") ~ ANY)* ~ (escape ~ inner_str)? }
inner_chr = @{ escape | ANY }
escape = @{ "\\" ~ ("\"" | "\\" | "r" | "n" | "t" | "0" | "'" | code | unicode) }
code = @{ "x" ~ hex_digit{2} }
unicode = @{ "u" ~ opening_brace ~ hex_digit{2, 6} ~ closing_brace }
hex_digit = @{ '0'..'9' | 'a'..'f' | 'A'..'F' }
quote = { "\"" }
single_quote = { "'" }
range_operator = { ".." }
newline = _{ "\n" | "\r\n" }
WHITESPACE = _{ " " | "\t" | newline }
block_comment = _{ "/*" ~ (block_comment | !"*/" ~ ANY)* ~ "*/" }
COMMENT = _{ block_comment | ("//" ~ (!newline ~ ANY)*) }
2 changes: 1 addition & 1 deletion tests/fixtures/pest.expected.pest
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,4 @@ range_operator = { ".." }
newline = _{ "\n" | "\r\n" }
WHITESPACE = _{ " " | "\t" | newline }
block_comment = _{ "/*" ~ (block_comment | !"*/" ~ ANY)* ~ "*/" }
COMMENT = _{ block_comment | ("//" ~ (!newline ~ ANY)*) }
COMMENT = _{ block_comment | ("//" ~ (!newline ~ ANY)*) }

0 comments on commit 1edf328

Please sign in to comment.