Skip to content

Commit e96c3c6

Browse files
authored
Allow read from STDIN and write to STDOUT (#3)
1 parent c162df8 commit e96c3c6

File tree

7 files changed

+175
-58
lines changed

7 files changed

+175
-58
lines changed

.vscode/extensions.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"recommendations": ["jkillian.custom-local-formatters"]
3+
}

.vscode/settings.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"customLocalFormatters.formatters": [
3+
{
4+
"command": "./target/debug/hledger-fmt - --no-diff",
5+
"languages": ["hledger"]
6+
}
7+
]
8+
}

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
# CHANGELOG
22

3+
## 2024-09-29 - [0.1.4]
4+
5+
### New features
6+
7+
- Allow to read from STDIN with `-` argument.
8+
- Allow to print formatted content to STDOUT with `--no-diff` option.
9+
10+
### Changes
11+
12+
- Exit with code 0 when a file is formatted.
13+
314
## 2024-09-28 - [0.1.3]
415

516
### Enhancements
@@ -23,6 +34,7 @@
2334

2435
First beta release
2536

37+
[0.1.4]: https://github.com/mondeja/hledger-fmt/compare/v0.1.3...v0.1.4
2638
[0.1.3]: https://github.com/mondeja/hledger-fmt/compare/v0.1.2...v0.1.3
2739
[0.1.2]: https://github.com/mondeja/hledger-fmt/compare/v0.1.1...v0.1.2
2840
[0.1.1]: https://github.com/mondeja/hledger-fmt/compare/v0.1.0...v0.1.1

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "hledger-fmt"
3-
version = "0.1.3"
3+
version = "0.1.4"
44
edition = "2021"
55
description = "An opinionated hledger's journal files formatter."
66
repository = "https://github.com/mondeja/hledger-fmt"
@@ -15,7 +15,8 @@ exclude = [
1515
".pre-commit-config.yaml",
1616
".pre-commit-hooks.yaml",
1717
".markdown-link-check.json",
18-
".gitignore"
18+
".gitignore",
19+
".vscode",
1920
]
2021

2122
[lib]

README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,33 @@ repos:
5151
- id: hledger-fmt-check # Use this id to check files without formatting
5252
```
5353
54+
### VSCode
55+
56+
With hledger-fmt in your PATH, use the [VSCode Custom Local Formatters]
57+
extension. Just install it and add the next configuration to your
58+
_settings.json_:
59+
60+
```json
61+
{
62+
"customLocalFormatters.formatters": [
63+
{
64+
"command": "hledger-fmt - --no-diff",
65+
"languages": ["hledger"]
66+
}
67+
]
68+
}
69+
```
70+
71+
To format on save:
72+
73+
```json5
74+
{
75+
"editor.formatOnSave": true,
76+
}
77+
```
78+
79+
Just ensure that `hledger-fmt` is in your PATH.
80+
5481
## Usage
5582

5683
When you don't pass files to format, it reads all the files with
@@ -82,3 +109,4 @@ See `hledger-fmt --help` for more information.
82109
[cargo]: https://doc.rust-lang.org/cargo/
83110
[releases page]: https://github.com/mondeja/hledger-fmt/releases
84111
[pre-commit]: https://pre-commit.com
112+
[VSCode Custom Local Formatters]: https://marketplace.visualstudio.com/items?itemName=jkillian.custom-local-formatters

src/main.rs

Lines changed: 120 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ struct Args {
1717
///
1818
/// If the paths passed are directories, hledger-fmt will search for
1919
/// hledger files in those directories and their subdirectories.
20+
///
21+
/// STDIN can be read by passing `-` as a file.
2022
#[arg(num_args(0..))]
2123
files: Vec<String>,
2224

@@ -35,6 +37,11 @@ struct Args {
3537
/// You can also use the environment variable NO_COLOR to disable colors.
3638
#[arg(long)]
3739
no_color: bool,
40+
41+
/// Do not print the diff between original and formatted files,
42+
/// but the new formatted content.
43+
#[arg(long)]
44+
no_diff: bool,
3845
}
3946

4047
fn main() {
@@ -43,62 +50,76 @@ fn main() {
4350

4451
// if no files, search in current directory and its subdirectories
4552
let mut files = Vec::new();
46-
if args.files.is_empty() {
47-
gather_files_from_directory_and_subdirectories(".", &mut files);
48-
49-
if files.is_empty() {
50-
eprintln!(
51-
"{}",
52-
concat!(
53-
"No hledger journal files found in the current directory nor its",
54-
" subdirectories.\nEnsure that have extensions '.hledger', '.journal'",
55-
" or '.j'."
56-
)
57-
);
58-
exitcode = 1;
59-
std::process::exit(exitcode);
60-
}
53+
let stdin = if std::env::args().any(|arg| arg == "-") {
54+
read_stdin()
6155
} else {
62-
for file in &args.files {
63-
let pathbuf = std::path::PathBuf::from(&file);
64-
if pathbuf.is_dir() {
65-
gather_files_from_directory_and_subdirectories(file, &mut files);
66-
break;
67-
} else if pathbuf.is_file() {
68-
files.push(file.clone());
69-
} else if !pathbuf.exists() {
70-
eprintln!("Path {file} does not exist.");
56+
"".to_string()
57+
};
58+
59+
if stdin.is_empty() {
60+
if args.files.is_empty() {
61+
if gather_files_from_directory_and_subdirectories(".", &mut files).is_err() {
7162
exitcode = 1;
72-
} else if pathbuf.is_symlink() {
73-
eprintln!("Path {file} is a symlink. Symbolic links are not supported.");
63+
}
64+
65+
if files.is_empty() {
66+
eprintln!(
67+
"{}",
68+
concat!(
69+
"No hledger journal files found in the current directory nor its",
70+
" subdirectories.\nEnsure that have extensions '.hledger', '.journal'",
71+
" or '.j'."
72+
)
73+
);
7474
exitcode = 1;
75+
std::process::exit(exitcode);
76+
}
77+
} else {
78+
for file in &args.files {
79+
let pathbuf = std::path::PathBuf::from(&file);
80+
if pathbuf.is_dir() {
81+
if gather_files_from_directory_and_subdirectories(file, &mut files).is_err() {
82+
exitcode = 1;
83+
}
84+
break;
85+
} else if pathbuf.is_file() {
86+
if let Ok(content) = read_file(file) {
87+
files.push((file.clone(), content));
88+
} else {
89+
exitcode = 1;
90+
}
91+
} else if !pathbuf.exists() {
92+
eprintln!("Path '{file}' does not exist.");
93+
exitcode = 1;
94+
} else if pathbuf.is_symlink() {
95+
eprintln!("Path '{file}' is a symlink. Symbolic links are not supported.");
96+
exitcode = 1;
97+
}
7598
}
76-
}
7799

78-
if files.is_empty() {
79-
eprintln!(
80-
"No hledger journal files found looking for next files and/or directories: {:?}.\nEnsure that have extensions '.hledger', '.journal' or '.j'.",
81-
args.files,
82-
);
83-
exitcode = 1;
84-
std::process::exit(exitcode);
100+
if files.is_empty() {
101+
eprintln!(
102+
"No hledger journal files found looking for next files and/or directories: {:?}.\nEnsure that have extensions '.hledger', '.journal' or '.j'.",
103+
args.files,
104+
);
105+
exitcode = 1;
106+
std::process::exit(exitcode);
107+
}
85108
}
109+
} else if files.is_empty() {
110+
files.push(("".into(), stdin));
111+
} else {
112+
eprintln!("Cannot read from STDIN and pass files at the same time.");
113+
exitcode = 1;
114+
std::process::exit(exitcode);
86115
}
87116

88117
#[cfg(feature = "color")]
89118
let no_color = args.no_color || std::env::var("NO_COLOR").is_ok();
90119
let mut something_printed = false;
120+
let n_files = files.len();
91121

92-
for file in files {
93-
let content = match std::fs::read_to_string(&file) {
94-
Ok(content) => content,
95-
Err(e) => {
96-
eprintln!("Error reading file {file}: {e}");
97-
exitcode = 1;
98-
continue;
99-
}
100-
};
101-
122+
for (file, content) in files {
102123
// 1. Parse content
103124
// 2. Format content
104125
// 3 Contents are the same? OK
@@ -125,8 +146,6 @@ fn main() {
125146
continue;
126147
}
127148

128-
exitcode = 1;
129-
130149
if args.fix {
131150
match std::fs::write(&file, &formatted) {
132151
Ok(_) => {}
@@ -140,16 +159,26 @@ fn main() {
140159
}
141160
}
142161
} else {
143-
if !something_printed {
144-
something_printed = true;
145-
} else {
146-
eprintln!();
162+
if n_files > 1 {
163+
if something_printed {
164+
eprintln!();
165+
} else {
166+
something_printed = true;
167+
}
168+
// only print the file name if there are more than one file
169+
let separator = "=".repeat(file.len());
170+
eprintln!("{separator}\n{file}\n{separator}");
147171
}
148172

149-
let diff = TextDiff::from_lines(&content, &formatted);
173+
if args.no_diff {
174+
#[allow(clippy::print_stdout)]
175+
{
176+
print!("{formatted}");
177+
}
178+
continue;
179+
}
150180

151-
let separator = "=".repeat(file.len());
152-
eprintln!("{separator}\n{file}\n{separator}");
181+
let diff = TextDiff::from_lines(&content, &formatted);
153182
for change in diff.iter_all_changes() {
154183
#[cfg(feature = "color")]
155184
let line = if no_color {
@@ -182,7 +211,11 @@ fn main() {
182211
}
183212

184213
/// Search for hledger files in the passed directory and its subdirectories
185-
fn gather_files_from_directory_and_subdirectories(root: &str, files: &mut Vec<String>) {
214+
fn gather_files_from_directory_and_subdirectories(
215+
root: &str,
216+
files: &mut Vec<(String, String)>,
217+
) -> Result<(), ()> {
218+
let mut error = false;
186219
for entry in WalkDir::new(root)
187220
.follow_links(true)
188221
.into_iter()
@@ -197,8 +230,40 @@ fn gather_files_from_directory_and_subdirectories(root: &str, files: &mut Vec<St
197230
]
198231
.contains(&ext)
199232
{
200-
files.push(entry.path().to_str().unwrap().to_string());
233+
let file_path = entry.path().to_str().unwrap().to_string();
234+
let maybe_file_content = read_file(&file_path);
235+
if let Ok(content) = maybe_file_content {
236+
files.push((file_path, content));
237+
} else {
238+
error = true;
239+
}
201240
}
202241
}
203242
}
243+
244+
if error {
245+
Err(())
246+
} else {
247+
Ok(())
248+
}
249+
}
250+
251+
fn read_file(file_path: &str) -> Result<String, ()> {
252+
match std::fs::read_to_string(file_path) {
253+
Ok(content) => Ok(content),
254+
Err(e) => {
255+
eprintln!("Error reading file {file_path}: {e}");
256+
Err(())
257+
}
258+
}
259+
}
260+
261+
fn read_stdin() -> String {
262+
let mut buffer = String::new();
263+
let lines = std::io::stdin().lines();
264+
for line in lines {
265+
buffer.push_str(&line.unwrap());
266+
buffer.push('\n');
267+
}
268+
buffer
204269
}

0 commit comments

Comments
 (0)