11const std = @import ("std" );
2+ const Allocator = std .mem .Allocator ;
3+ const Writer = std .Io .Writer ;
24const super = @import ("superhtml" );
35
4- const FileType = enum { html , super };
6+ var bufout : [4096 ]u8 = undefined ;
7+ var buferr : [4096 ]u8 = undefined ;
58
6- pub fn run (gpa : std.mem.Allocator , args : []const []const u8 ) ! void {
7- const cmd = Command .parse (args );
8- var any_error = false ;
9- switch (cmd .mode ) {
10- .stdin = > {
11- var fr = std .fs .File .stdin ().reader (&.{});
12- var aw : std.Io.Writer.Allocating = .init (gpa );
13- _ = try fr .interface .streamRemaining (& aw .writer );
14- const in_bytes = try aw .toOwnedSliceSentinel (0 );
9+ var syntax_errors = false ;
10+ pub fn run (gpa : Allocator , args : []const []const u8 ) ! noreturn {
11+ // Prints html errors found in the document
12+ var stderr_writer = std .fs .File .stderr ().writerStreaming (& buferr );
13+ const stderr = & stderr_writer .interface ;
1514
16- const out_bytes = try fmtHtml (gpa , null , in_bytes , cmd .strict );
15+ // Prints file paths of files that were modified on disk
16+ var stdout_writer = std .fs .File .stdout ().writerStreaming (& bufout );
17+ const stdout = & stdout_writer .interface ;
1718
18- try std . fs . File . stdout (). writeAll ( out_bytes );
19- },
20- .stdin_super = > {
19+ const cmd = Command . parse ( args );
20+ switch ( cmd . mode ) {
21+ .stdin , . stdin_super = > {
2122 var fr = std .fs .File .stdin ().reader (&.{});
22- var aw : std.Io. Writer.Allocating = .init (gpa );
23+ var aw : Writer.Allocating = .init (gpa );
2324 _ = try fr .interface .streamRemaining (& aw .writer );
2425 const in_bytes = try aw .toOwnedSliceSentinel (0 );
25-
26- const out_bytes = try fmtSuper (gpa , null , in_bytes , cmd .strict );
27- try std .fs .File .stdout ().writeAll (out_bytes );
26+ const lang : super.Language = switch (cmd .mode ) {
27+ else = > unreachable ,
28+ .stdin = > .html ,
29+ .stdin_super = > .superhtml ,
30+ };
31+
32+ if (try fmt (gpa , stderr , null , in_bytes , lang , cmd .strict )) | fmt_src | {
33+ try std .fs .File .stdout ().writeAll (fmt_src );
34+ }
2835 },
2936 .paths = > | paths | {
3037 // checkFile will reset the arena at the end of each call
3138 var arena_impl = std .heap .ArenaAllocator .init (gpa );
3239 for (paths ) | path | {
3340 formatFile (
3441 & arena_impl ,
42+ stdout ,
43+ stderr ,
3544 cmd .check ,
3645 std .fs .cwd (),
3746 path ,
3847 path ,
39- & any_error ,
4048 cmd .strict ,
4149 ) catch | err | switch (err ) {
42- error .IsDir , error .AccessDenied = > {
43- formatDir (
44- gpa ,
45- & arena_impl ,
46- cmd .check ,
50+ error .IsDir , error .AccessDenied = > formatDir (
51+ gpa ,
52+ & arena_impl ,
53+ stdout ,
54+ stderr ,
55+ cmd .check ,
56+ path ,
57+ cmd .strict ,
58+ ) catch | dir_err | {
59+ std .debug .print ("error walking dir '{s}': {s}\n " , .{
4760 path ,
48- & any_error ,
49- cmd .strict ,
50- ) catch | dir_err | {
51- std .debug .print ("Error walking dir '{s}': {s}\n " , .{
52- path ,
53- @errorName (dir_err ),
54- });
55- std .process .exit (1 );
56- };
61+ @errorName (dir_err ),
62+ });
63+ std .process .exit (1 );
5764 },
5865 else = > {
59- std .debug .print ("Error while accessing '{s}': {s}\n " , .{
66+ std .debug .print ("error while accessing '{s}': {s}\n " , .{
6067 path , @errorName (err ),
6168 });
6269 std .process .exit (1 );
@@ -66,48 +73,51 @@ pub fn run(gpa: std.mem.Allocator, args: []const []const u8) !void {
6673 },
6774 }
6875
69- if ( any_error ) {
70- std . process . exit ( 1 );
71- }
76+ try stdout . flush ();
77+ try stderr . flush ( );
78+ std . process . exit ( @intFromBool ( syntax_errors ));
7279}
7380
7481fn formatDir (
7582 gpa : std.mem.Allocator ,
7683 arena_impl : * std.heap.ArenaAllocator ,
84+ stdout : * Writer ,
85+ stderr : * Writer ,
7786 check : bool ,
7887 path : []const u8 ,
79- any_error : * bool ,
8088 strict : bool ,
8189) ! void {
8290 var dir = try std .fs .cwd ().openDir (path , .{ .iterate = true });
8391 defer dir .close ();
92+
8493 var walker = dir .walk (gpa ) catch oom ();
8594 defer walker .deinit ();
95+
8696 while (try walker .next ()) | item | {
8797 switch (item .kind ) {
88- .file = > {
89- try formatFile (
90- arena_impl ,
91- check ,
92- item .dir ,
93- item .basename ,
94- item .path ,
95- any_error ,
96- strict ,
97- );
98- },
98+ .file = > try formatFile (
99+ arena_impl ,
100+ stdout ,
101+ stderr ,
102+ check ,
103+ item .dir ,
104+ item .basename ,
105+ item .path ,
106+ strict ,
107+ ),
99108 else = > {},
100109 }
101110 }
102111}
103112
104113fn formatFile (
105114 arena_impl : * std.heap.ArenaAllocator ,
115+ stdout : * Writer ,
116+ stderr : * Writer ,
106117 check : bool ,
107118 base_dir : std.fs.Dir ,
108119 sub_path : []const u8 ,
109120 full_path : []const u8 ,
110- any_error : * bool ,
111121 strict : bool ,
112122) ! void {
113123 defer _ = arena_impl .reset (.retain_capacity );
@@ -121,7 +131,7 @@ fn formatFile(
121131 0 ,
122132 );
123133
124- const file_type : FileType = blk : {
134+ const language : super.Language = blk : {
125135 const ext = std .fs .path .extension (sub_path );
126136 if (std .mem .eql (u8 , ext , ".html" ) or
127137 std .mem .eql (u8 , ext , ".htm" ))
@@ -130,74 +140,60 @@ fn formatFile(
130140 }
131141
132142 if (std .mem .eql (u8 , ext , ".shtml" )) {
133- break :blk .super ;
143+ break :blk .superhtml ;
134144 }
145+
146+ // Unkown file, skip it
135147 return ;
136148 };
137149
138- const out_bytes = switch (file_type ) {
139- .html = > try fmtHtml (
140- arena ,
141- full_path ,
142- in_bytes ,
143- strict ,
144- ),
145- .super = > try fmtSuper (
146- arena ,
147- full_path ,
148- in_bytes ,
149- strict ,
150- ),
151- };
150+ if (try fmt (arena , stderr , full_path , in_bytes , language , strict )) | fmt_src | {
151+ if (std .mem .eql (u8 , fmt_src , in_bytes )) return ;
152+ if (check ) {
153+ syntax_errors = true ;
154+ try stdout .print ("{s}\n " , .{full_path });
155+ return ;
156+ }
152157
153- if (std .mem .eql (u8 , out_bytes , in_bytes )) return ;
158+ var af = try base_dir .atomicFile (sub_path , .{ .write_buffer = &.{} });
159+ defer af .deinit ();
154160
155- var stdout_writer = std .fs .File .stdout ().writer (&.{});
156- const stdout = & stdout_writer .interface ;
157- if (check ) {
158- any_error .* = true ;
161+ try af .file_writer .interface .writeAll (fmt_src );
162+ try af .finish ();
163+ try stdout .print ("{s}\n " , .{full_path });
164+ } else if (check ) {
165+ syntax_errors = true ;
166+ // if (true) @panic("hit");
159167 try stdout .print ("{s}\n " , .{full_path });
160168 return ;
161169 }
162-
163- var af = try base_dir .atomicFile (sub_path , .{ .write_buffer = &.{} });
164- defer af .deinit ();
165-
166- try af .file_writer .interface .writeAll (out_bytes );
167- try af .finish ();
168- try stdout .print ("{s}\n " , .{full_path });
169170}
170171
171- pub fn fmtHtml (
172+ pub fn fmt (
172173 arena : std.mem.Allocator ,
174+ stderr : * Writer ,
173175 path : ? []const u8 ,
174- code : [:0 ]const u8 ,
176+ src : [:0 ]const u8 ,
177+ language : super.Language ,
175178 strict : bool ,
176- ) ! []const u8 {
177- const ast = try super .html .Ast .init (arena , code , .html , strict );
178- if (ast .errors .len > 0 ) {
179- var ew = std .fs .File .stderr ().writer (&.{});
180- try ast .printErrors (code , path , & ew .interface );
181- std .process .exit (1 );
182- }
183-
184- return std .fmt .allocPrint (arena , "{f}" , .{ast .formatter (code )});
185- }
186-
187- fn fmtSuper (
188- arena : std.mem.Allocator ,
189- path : ? []const u8 ,
190- code : [:0 ]const u8 ,
191- strict : bool ,
192- ) ! []const u8 {
193- const ast = try super .html .Ast .init (arena , code , .superhtml , strict );
194- if (ast .errors .len > 0 ) {
195- var ew = std .fs .File .stderr ().writer (&.{});
196- try ast .printErrors (code , path , & ew .interface );
197- std .process .exit (1 );
179+ ) ! ? []const u8 {
180+ const html_ast = try super .html .Ast .init (arena , src , language , strict );
181+ if (html_ast .errors .len > 0 ) {
182+ try html_ast .printErrors (src , path , stderr );
183+ if (html_ast .has_syntax_errors ) {
184+ syntax_errors = true ;
185+ return null ;
186+ }
187+ } else if (language == .superhtml ) {
188+ const super_ast = try super .Ast .init (arena , html_ast , src );
189+ if (super_ast .errors .len > 0 ) {
190+ try html_ast .printErrors (src , path , stderr );
191+ }
198192 }
199193
200- return std .fmt .allocPrint (arena , "{f}" , .{ast .formatter (code )});
194+ return try std .fmt .allocPrint (arena , "{f}" , .{
195+ html_ast .formatter (src ),
196+ });
201197}
202198
203199fn oom () noreturn {
@@ -301,6 +297,9 @@ const Command = struct {
301297 \\
302298 \\ Formats input paths inplace. If PATH is a directory, it will
303299 \\ be searched recursively for HTML and SuperHTML files.
300+ \\ HTML errors will be printed to stderr but will only cause a
301+ \\ non-zero exit code if they prevent formatting (e.g. syntax
302+ \\ errors).
304303 \\
305304 \\ Detected extensions:
306305 \\ HTML .html, .htm
0 commit comments