@@ -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
4047fn 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.\n Ensure 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.\n Ensure 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: {:?}.\n Ensure 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: {:?}.\n Ensure 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