11const std = @import ("std" );
22const utils = @import ("utils.zig" );
3+
4+ pub const Plugin = struct {
5+ path : []const u8 ,
6+ is_super : bool ,
7+ };
8+
39pub const ParseResult = struct {
410 deps : std .StringHashMap (void ),
511 libs : std .StringHashMap (void ),
612 vars_dict : std .StringHashMap ([]const u8 ),
13+ local_vars : std .StringHashMap ([]const u8 ),
714 cmds : std .ArrayList ([]const u8 ),
815 includes : std .ArrayList ([]const u8 ),
916 binaries : std .ArrayList ([]const u8 ),
10- plugins : std .ArrayList ([]const u8 ), // Nowe: pluginy
17+ plugins : std .ArrayList (Plugin ),
18+ functions : std .StringHashMap (std .ArrayList ([]const u8 )),
1119 errors : std .ArrayList ([]const u8 ),
1220 config_data : std .StringHashMap ([]const u8 ),
1321};
22+
1423pub fn parse_hacker_file (allocator : std.mem.Allocator , file_path : []const u8 , verbose : bool ) ! ParseResult {
1524 var deps = std .StringHashMap (void ).init (allocator );
1625 var libs = std .StringHashMap (void ).init (allocator );
1726 var vars_dict = std .StringHashMap ([]const u8 ).init (allocator );
27+ var local_vars = std .StringHashMap ([]const u8 ).init (allocator );
1828 var cmds = std .ArrayList ([]const u8 ).init (allocator );
1929 var includes = std .ArrayList ([]const u8 ).init (allocator );
2030 var binaries = std .ArrayList ([]const u8 ).init (allocator );
21- var plugins = std .ArrayList ([]const u8 ).init (allocator ); // Nowe
31+ var plugins = std .ArrayList (Plugin ).init (allocator );
32+ var functions = std .StringHashMap (std .ArrayList ([]const u8 )).init (allocator );
2233 var errors = std .ArrayList ([]const u8 ).init (allocator );
2334 var config_data = std .StringHashMap ([]const u8 ).init (allocator );
2435 var in_config = false ;
36+ var in_comment = false ;
37+ var in_function : ? []const u8 = null ;
2538 var line_num : u32 = 0 ;
2639 const home = std .posix .getenv ("HOME" ) orelse "" ;
2740 const hacker_dir = try std .fs .path .join (allocator , &.{ home , utils .HACKER_DIR_SUFFIX });
@@ -35,10 +48,12 @@ pub fn parse_hacker_file(allocator: std.mem.Allocator, file_path: []const u8, ve
3548 .deps = deps ,
3649 .libs = libs ,
3750 .vars_dict = vars_dict ,
51+ .local_vars = local_vars ,
3852 .cmds = cmds ,
3953 .includes = includes ,
4054 .binaries = binaries ,
4155 .plugins = plugins ,
56+ .functions = functions ,
4257 .errors = errors ,
4358 .config_data = config_data ,
4459 };
@@ -52,12 +67,26 @@ pub fn parse_hacker_file(allocator: std.mem.Allocator, file_path: []const u8, ve
5267 line_num += 1 ;
5368 const line_trimmed = std .mem .trim (u8 , line_slice , " \t \r \n " );
5469 if (line_trimmed .len == 0 ) continue ;
55- const line = try allocator .dupe (u8 , line_trimmed );
70+ var line = try allocator .dupe (u8 , line_trimmed );
5671 defer allocator .free (line );
72+ if (std .mem .eql (u8 , line , "!!" )) {
73+ in_comment = ! in_comment ;
74+ continue ;
75+ }
76+ if (in_comment ) continue ;
77+ const is_super = std .mem .startsWith (u8 , line , "^" );
78+ if (is_super ) {
79+ const new_line = std .mem .trim (u8 , line [1.. ], " \t " );
80+ allocator .free (line );
81+ line = try allocator .dupe (u8 , new_line );
82+ }
5783 if (std .mem .eql (u8 , line , "[" )) {
5884 if (in_config ) {
5985 try errors .append (try std .fmt .allocPrint (allocator , "Line {d}: Nested config section" , .{line_num }));
6086 }
87+ if (in_function != null ) {
88+ try errors .append (try std .fmt .allocPrint (allocator , "Line {d}: Config in function" , .{line_num }));
89+ }
6190 in_config = true ;
6291 continue ;
6392 } else if (std .mem .eql (u8 , line , "]" )) {
@@ -75,14 +104,64 @@ pub fn parse_hacker_file(allocator: std.mem.Allocator, file_path: []const u8, ve
75104 }
76105 continue ;
77106 }
107+ if (std .mem .eql (u8 , line , ":" )) {
108+ if (in_function != null ) {
109+ in_function = null ;
110+ } else {
111+ try errors .append (try std .fmt .allocPrint (allocator , "Line {d}: Ending function without start" , .{line_num }));
112+ }
113+ continue ;
114+ } else if (std .mem .startsWith (u8 , line , ":" )) {
115+ const func_name = std .mem .trim (u8 , line [1.. ], " \t " );
116+ if (func_name .len == 0 ) {
117+ try errors .append (try std .fmt .allocPrint (allocator , "Line {d}: Empty function name" , .{line_num }));
118+ continue ;
119+ }
120+ if (in_function != null ) {
121+ try errors .append (try std .fmt .allocPrint (allocator , "Line {d}: Nested function" , .{line_num }));
122+ }
123+ const func_name_dupe = try allocator .dupe (u8 , func_name );
124+ try functions .put (func_name_dupe , std .ArrayList ([]const u8 ).init (allocator ));
125+ in_function = func_name_dupe ;
126+ continue ;
127+ } else if (std .mem .startsWith (u8 , line , "." )) {
128+ const func_name = std .mem .trim (u8 , line [1.. ], " \t " );
129+ if (func_name .len == 0 ) {
130+ try errors .append (try std .fmt .allocPrint (allocator , "Line {d}: Empty function call" , .{line_num }));
131+ continue ;
132+ }
133+ if (functions .get (func_name )) | func_cmds | {
134+ var target = if (in_function ) | f | functions .getPtr (f ).? else & cmds ;
135+ for (func_cmds .items ) | c | {
136+ try target .append (try allocator .dupe (u8 , c ));
137+ }
138+ } else {
139+ try errors .append (try std .fmt .allocPrint (allocator , "Line {d}: Unknown function {s}" , .{line_num , func_name }));
140+ }
141+ continue ;
142+ }
143+ if (in_function != null ) {
144+ if (! std .mem .startsWith (u8 , line , ">" ) and ! std .mem .startsWith (u8 , line , "=" ) and ! std .mem .startsWith (u8 , line , "?" ) and ! std .mem .startsWith (u8 , line , "&" ) and ! std .mem .startsWith (u8 , line , "!" ) and ! std .mem .startsWith (u8 , line , "@" ) and ! std .mem .startsWith (u8 , line , "$" ) and ! std .mem .startsWith (u8 , line , "\\ " )) {
145+ try errors .append (try std .fmt .allocPrint (allocator , "Line {d}: Invalid in function" , .{line_num }));
146+ continue ;
147+ }
148+ }
78149 if (std .mem .startsWith (u8 , line , "//" )) {
150+ if (in_function != null ) {
151+ try errors .append (try std .fmt .allocPrint (allocator , "Line {d}: Deps not allowed in function" , .{line_num }));
152+ continue ;
153+ }
79154 const dep = std .mem .trim (u8 , line [2.. ], " \t " );
80155 if (dep .len > 0 ) {
81156 _ = try deps .put (try allocator .dupe (u8 , dep ), {});
82157 } else {
83158 try errors .append (try std .fmt .allocPrint (allocator , "Line {d}: Empty system dependency" , .{line_num }));
84159 }
85160 } else if (std .mem .startsWith (u8 , line , "#" )) {
161+ if (in_function != null ) {
162+ try errors .append (try std .fmt .allocPrint (allocator , "Line {d}: Libs not allowed in function" , .{line_num }));
163+ continue ;
164+ }
86165 const lib = std .mem .trim (u8 , line [1.. ], " \t " );
87166 if (lib .len > 0 ) {
88167 const lib_dir = try std .fs .path .join (allocator , &.{ hacker_dir , "libs" , lib });
@@ -97,10 +176,12 @@ pub fn parse_hacker_file(allocator: std.mem.Allocator, file_path: []const u8, ve
97176 try utils .mergeHashMaps (void , & deps , sub .deps , allocator );
98177 try utils .mergeHashMaps (void , & libs , sub .libs , allocator );
99178 try utils .mergeStringHashMaps (& vars_dict , sub .vars_dict , allocator );
179+ try utils .mergeStringHashMaps (& local_vars , sub .local_vars , allocator );
100180 try cmds .appendSlice (sub .cmds .items );
101181 try includes .appendSlice (sub .includes .items );
102182 try binaries .appendSlice (sub .binaries .items );
103- try plugins .appendSlice (sub .plugins .items ); // Merge pluginów
183+ try plugins .appendSlice (sub .plugins .items );
184+ try utils .mergeFunctionMaps (& functions , sub .functions , allocator );
104185 for (sub .errors .items ) | sub_err | {
105186 try errors .append (try std .fmt .allocPrint (allocator , "In {s}: {s}" , .{ lib , sub_err }));
106187 }
@@ -116,8 +197,15 @@ pub fn parse_hacker_file(allocator: std.mem.Allocator, file_path: []const u8, ve
116197 }
117198 } else if (std .mem .startsWith (u8 , line , ">" )) {
118199 const cmd_part = std .mem .trim (u8 , if (std .mem .indexOfScalar (u8 , line [1.. ], '!' )) | pos | line [1 .. 1 + pos ] else line [1.. ], " \t " );
119- if (cmd_part .len > 0 ) {
120- try cmds .append (try allocator .dupe (u8 , cmd_part ));
200+ var cmd = try allocator .dupe (u8 , cmd_part );
201+ if (is_super ) {
202+ const sudo_cmd = try std .fmt .allocPrint (allocator , "sudo {s}" , .{cmd });
203+ allocator .free (cmd );
204+ cmd = sudo_cmd ;
205+ }
206+ if (cmd .len > 0 ) {
207+ var target = if (in_function ) | f | functions .getPtr (f ).? else & cmds ;
208+ try target .append (cmd );
121209 } else {
122210 try errors .append (try std .fmt .allocPrint (allocator , "Line {d}: Empty command" , .{line_num }));
123211 }
@@ -131,24 +219,38 @@ pub fn parse_hacker_file(allocator: std.mem.Allocator, file_path: []const u8, ve
131219 try errors .append (try std .fmt .allocPrint (allocator , "Line {d}: Invalid variable" , .{line_num }));
132220 }
133221 } else {
134- // Nowe: plugin @ nazwa (bez =)
135- const plugin_name = std .mem .trim (u8 , line [1.. ], " \t " );
136- if (plugin_name .len > 0 ) {
137- const plugin_dir = try std .fs .path .join (allocator , &.{ hacker_dir , "plugins" , plugin_name });
138- defer allocator .free (plugin_dir );
139- if (std .posix .access (plugin_dir , std .posix .X_OK )) | _ | {
140- try plugins .append (try allocator .dupe (u8 , plugin_dir ));
141- } else | _ | {
142- try errors .append (try std .fmt .allocPrint (allocator , "Line {d}: Plugin {s} not found or not executable" , .{line_num , plugin_name }));
143- }
222+ try errors .append (try std .fmt .allocPrint (allocator , "Line {d}: Invalid @ syntax" , .{line_num }));
223+ }
224+ } else if (std .mem .startsWith (u8 , line , "$" )) {
225+ if (std .mem .indexOfScalar (u8 , line [1.. ], '=' )) | eq_pos | {
226+ const var_name = std .mem .trim (u8 , line [1 .. 1 + eq_pos ], " \t " );
227+ const value = std .mem .trim (u8 , line [1 + eq_pos + 1 .. ], " \t " );
228+ if (var_name .len > 0 and value .len > 0 ) {
229+ try local_vars .put (try allocator .dupe (u8 , var_name ), try allocator .dupe (u8 , value ));
144230 } else {
145- try errors .append (try std .fmt .allocPrint (allocator , "Line {d}: Empty plugin name " , .{line_num }));
231+ try errors .append (try std .fmt .allocPrint (allocator , "Line {d}: Invalid local variable " , .{line_num }));
146232 }
233+ } else {
234+ try errors .append (try std .fmt .allocPrint (allocator , "Line {d}: Invalid $ syntax" , .{line_num }));
235+ }
236+ } else if (std .mem .startsWith (u8 , line , "\\ " )) {
237+ const plugin_name = std .mem .trim (u8 , line [1.. ], " \t " );
238+ if (plugin_name .len > 0 ) {
239+ const plugin_dir = try std .fs .path .join (allocator , &.{ hacker_dir , "plugins" , plugin_name });
240+ if (std .posix .access (plugin_dir , std .posix .X_OK )) | _ | {
241+ try plugins .append (Plugin { .path = plugin_dir , .is_super = is_super });
242+ } else | _ | {
243+ allocator .free (plugin_dir );
244+ try errors .append (try std .fmt .allocPrint (allocator , "Line {d}: Plugin {s} not found or not executable" , .{line_num , plugin_name }));
245+ }
246+ } else {
247+ try errors .append (try std .fmt .allocPrint (allocator , "Line {d}: Empty plugin name" , .{line_num }));
147248 }
148249 } else if (std .mem .startsWith (u8 , line , "=" )) {
149250 if (std .mem .indexOfScalar (u8 , line [1.. ], '>' )) | gt_pos | {
150251 const num_str = std .mem .trim (u8 , line [1 .. 1 + gt_pos ], " \t " );
151- const cmd_part = std .mem .trim (u8 , if (std .mem .indexOfScalar (u8 , line [1 + gt_pos + 1 .. ], '!' )) | pos | line [1 + gt_pos + 1 .. 1 + gt_pos + 1 + pos ] else line [1 + gt_pos + 1 .. ], " \t " );
252+ const cmd_part_str = if (std .mem .indexOfScalar (u8 , line [1 + gt_pos + 1 .. ], '!' )) | pos | line [1 + gt_pos + 1 .. 1 + gt_pos + 1 + pos ] else line [1 + gt_pos + 1 .. ];
253+ const cmd_part = std .mem .trim (u8 , cmd_part_str , " \t " );
152254 const num = std .fmt .parseInt (i32 , num_str , 10 ) catch {
153255 try errors .append (try std .fmt .allocPrint (allocator , "Line {d}: Invalid loop count" , .{line_num }));
154256 continue ;
@@ -157,10 +259,17 @@ pub fn parse_hacker_file(allocator: std.mem.Allocator, file_path: []const u8, ve
157259 try errors .append (try std .fmt .allocPrint (allocator , "Line {d}: Negative loop count" , .{line_num }));
158260 continue ;
159261 }
262+ var target = if (in_function ) | f | functions .getPtr (f ).? else & cmds ;
160263 if (cmd_part .len > 0 ) {
161264 var i : i32 = 0 ;
162265 while (i < num ) : (i += 1 ) {
163- try cmds .append (try allocator .dupe (u8 , cmd_part ));
266+ var cmd = try allocator .dupe (u8 , cmd_part );
267+ if (is_super ) {
268+ const sudo_cmd = try std .fmt .allocPrint (allocator , "sudo {s}" , .{cmd });
269+ allocator .free (cmd );
270+ cmd = sudo_cmd ;
271+ }
272+ try target .append (cmd );
164273 }
165274 } else {
166275 try errors .append (try std .fmt .allocPrint (allocator , "Line {d}: Empty loop command" , .{line_num }));
@@ -171,21 +280,35 @@ pub fn parse_hacker_file(allocator: std.mem.Allocator, file_path: []const u8, ve
171280 } else if (std .mem .startsWith (u8 , line , "?" )) {
172281 if (std .mem .indexOfScalar (u8 , line [1.. ], '>' )) | gt_pos | {
173282 const condition = std .mem .trim (u8 , line [1 .. 1 + gt_pos ], " \t " );
174- const cmd_part = std .mem .trim (u8 , if (std .mem .indexOfScalar (u8 , line [1 + gt_pos + 1 .. ], '!' )) | pos | line [1 + gt_pos + 1 .. 1 + gt_pos + 1 + pos ] else line [1 + gt_pos + 1 .. ], " \t " );
283+ const cmd_part_str = if (std .mem .indexOfScalar (u8 , line [1 + gt_pos + 1 .. ], '!' )) | pos | line [1 + gt_pos + 1 .. 1 + gt_pos + 1 + pos ] else line [1 + gt_pos + 1 .. ];
284+ const cmd_part = std .mem .trim (u8 , cmd_part_str , " \t " );
285+ var cmd = cmd_part ;
286+ if (is_super ) {
287+ cmd = try std .fmt .allocPrint (allocator , "sudo {s}" , .{cmd_part });
288+ defer allocator .free (cmd );
289+ }
175290 if (condition .len > 0 and cmd_part .len > 0 ) {
176- const if_cmd = try std .fmt .allocPrint (allocator , "if {s}; then {s}; fi" , .{ condition , cmd_part });
177- try cmds .append (if_cmd );
291+ const if_cmd = try std .fmt .allocPrint (allocator , "if {s}; then {s}; fi" , .{ condition , cmd });
292+ var target = if (in_function ) | f | functions .getPtr (f ).? else & cmds ;
293+ try target .append (if_cmd );
178294 } else {
179295 try errors .append (try std .fmt .allocPrint (allocator , "Line {d}: Invalid conditional" , .{line_num }));
180296 }
181297 } else {
182298 try errors .append (try std .fmt .allocPrint (allocator , "Line {d}: Invalid conditional syntax" , .{line_num }));
183299 }
184300 } else if (std .mem .startsWith (u8 , line , "&" )) {
185- const cmd_part = std .mem .trim (u8 , if (std .mem .indexOfScalar (u8 , line [1.. ], '!' )) | pos | line [1 .. 1 + pos ] else line [1.. ], " \t " );
301+ const cmd_part_str = if (std .mem .indexOfScalar (u8 , line [1.. ], '!' )) | pos | line [1 .. 1 + pos ] else line [1.. ];
302+ const cmd_part = std .mem .trim (u8 , cmd_part_str , " \t " );
303+ var cmd = try std .fmt .allocPrint (allocator , "{s} &" , .{ cmd_part });
304+ if (is_super ) {
305+ const sudo_cmd = try std .fmt .allocPrint (allocator , "sudo {s}" , .{cmd });
306+ allocator .free (cmd );
307+ cmd = sudo_cmd ;
308+ }
186309 if (cmd_part .len > 0 ) {
187- const bg_cmd = try std . fmt . allocPrint ( allocator , "{s} &" , .{ cmd_part }) ;
188- try cmds .append (bg_cmd );
310+ var target = if ( in_function ) | f | functions . getPtr ( f ) .? else & cmds ;
311+ try target .append (cmd );
189312 } else {
190313 try errors .append (try std .fmt .allocPrint (allocator , "Line {d}: Empty background command" , .{line_num }));
191314 }
@@ -198,6 +321,12 @@ pub fn parse_hacker_file(allocator: std.mem.Allocator, file_path: []const u8, ve
198321 if (in_config ) {
199322 try errors .append (try allocator .dupe (u8 , "Unclosed config section" ));
200323 }
324+ if (in_comment ) {
325+ try errors .append (try allocator .dupe (u8 , "Unclosed comment block" ));
326+ }
327+ if (in_function != null ) {
328+ try errors .append (try allocator .dupe (u8 , "Unclosed function block" ));
329+ }
201330 if (verbose ) {
202331 var dep_keys = try allocator .alloc ([]const u8 , deps .count ());
203332 defer allocator .free (dep_keys );
@@ -218,10 +347,12 @@ pub fn parse_hacker_file(allocator: std.mem.Allocator, file_path: []const u8, ve
218347 }
219348 try console .print ("Custom Libs: {any}\n " , .{lib_keys });
220349 try console .print ("Vars: {any}\n " , .{vars_dict });
350+ try console .print ("Local Vars: {any}\n " , .{local_vars });
221351 try console .print ("Cmds: {any}\n " , .{cmds .items });
222352 try console .print ("Includes: {any}\n " , .{includes .items });
223353 try console .print ("Binaries: {any}\n " , .{binaries .items });
224354 try console .print ("Plugins: {any}\n " , .{plugins .items });
355+ try console .print ("Functions: {any}\n " , .{functions });
225356 try console .print ("Config: {any}\n " , .{config_data });
226357 if (errors .items .len > 0 ) {
227358 try console .print ("Errors: {any}\n " , .{errors .items });
@@ -231,10 +362,12 @@ pub fn parse_hacker_file(allocator: std.mem.Allocator, file_path: []const u8, ve
231362 .deps = deps ,
232363 .libs = libs ,
233364 .vars_dict = vars_dict ,
365+ .local_vars = local_vars ,
234366 .cmds = cmds ,
235367 .includes = includes ,
236368 .binaries = binaries ,
237369 .plugins = plugins ,
370+ .functions = functions ,
238371 .errors = errors ,
239372 .config_data = config_data ,
240373 };
0 commit comments