1const std = @import("std");
2const string = []const u8;
3const gpa = std.heap.c_allocator;
4const extras = @import("extras");
5const git = @import("git");
6const ansi = @import("ansi");
7const nfs = @import("nfs");
8const time = @import("time");
9const root = @import("root");
10
11//
12//
13
14pub const b = 1;
15pub const kb = b * 1024;
16pub const mb = kb * 1024;
17pub const gb = mb * 1024;
18
19pub fn assert(ok: bool, comptime fmt: string, args: anytype) void {
20 if (!ok) {
21 std.debug.print(ansi.color.Fg(.Red, fmt) ++ "\n", args);
22 std.process.exit(1);
23 }
24}
25
26pub fn fail(comptime fmt: string, args: anytype) noreturn {
27 assert(false, fmt, args);
28 unreachable;
29}
30
31pub fn try_index(comptime T: type, array: []const T, n: usize, def: T) T {
32 if (array.len <= n) {
33 return def;
34 }
35 return array[n];
36}
37
38pub fn split(alloc: std.mem.Allocator, in: string, delim: u8) ![]string {
39 var list = std.array_list.Managed(string).init(alloc);
40 errdefer list.deinit();
41
42 var iter = std.mem.splitScalar(u8, in, delim);
43 while (iter.next()) |str| {
44 try list.append(str);
45 }
46 return list.toOwnedSlice();
47}
48
49pub fn file_list(alloc: std.mem.Allocator, dpath: [:0]const u8) ![]const [:0]const u8 {
50 var dir = try nfs.cwd().openDir(dpath, .{});
51 defer dir.close();
52 var list = std.array_list.Managed([:0]u8).init(alloc);
53 errdefer list.deinit();
54 errdefer for (list.items) |x| alloc.free(x);
55 var walker = try dir.walk(alloc);
56 defer walker.deinit();
57 while (try walker.next()) |entry| {
58 if (entry.type != .REG) continue;
59 try list.append(try alloc.dupeZ(u8, entry.path));
60 }
61 return list.toOwnedSlice();
62}
63
64pub fn run_cmd_raw(alloc: std.mem.Allocator, dir: ?string, args: []const string) !std.process.RunResult {
65 const io = root.io;
66 return std.process.run(alloc, io, .{ .cwd = if (dir) |d| .{ .path = d } else .inherit, .argv = args }) catch |e| switch (e) {
67 error.FileNotFound => {
68 fail("\"{s}\" command not found", .{args[0]});
69 },
70 else => |ee| return ee,
71 };
72}
73
74pub fn run_cmd(alloc: std.mem.Allocator, dir: ?string, args: []const string) !u32 {
75 const result = try run_cmd_raw(alloc, dir, args);
76 alloc.free(result.stdout);
77 alloc.free(result.stderr);
78 return result.term.exited;
79}
80
81pub fn list_remove(alloc: std.mem.Allocator, input: []string, search: string) ![]string {
82 var list = std.array_list.Managed(string).init(alloc);
83 errdefer list.deinit();
84 for (input) |item| {
85 if (!std.mem.eql(u8, item, search)) {
86 try list.append(item);
87 }
88 }
89 return list.toOwnedSlice();
90}
91
92pub fn last(in: []string) ?string {
93 if (in.len == 0) return null;
94 return in[in.len - 1];
95}
96
97const alphabet = "0123456789abcdefghijklmnopqrstuvwxyz";
98
99pub fn random_string(comptime len: usize) [len]u8 {
100 const now: u64 = @intCast(time.nanoTimestamp());
101 var rand = std.Random.DefaultPrng.init(now);
102 var r = rand.random();
103 var buf: [len]u8 = undefined;
104 var i: usize = 0;
105 while (i < len) : (i += 1) {
106 buf[i] = alphabet[r.int(usize) % alphabet.len];
107 }
108 return buf;
109}
110
111pub fn parse_split(comptime T: type, comptime delim: u8) type {
112 return struct {
113 const Self = @This();
114
115 id: T,
116 string: string,
117
118 pub fn do(input: string) !Self {
119 var iter = std.mem.splitScalar(u8, input, delim);
120 const start = iter.next() orelse return error.IterEmpty;
121 const id = std.meta.stringToEnum(T, start) orelse return error.NoMemberFound;
122 return Self{
123 .id = id,
124 .string = iter.rest(),
125 };
126 }
127 };
128}
129
130pub const HashFn = enum {
131 blake3,
132 sha256,
133 sha512,
134};
135
136pub fn validate_hash(alloc: std.mem.Allocator, input: string, file_path: [:0]const u8) !bool {
137 const hash = parse_split(HashFn, '-').do(input) catch return false;
138 const file = try nfs.cwd().openFile(file_path, .{});
139 defer file.close();
140 const data = try file.readAllAlloc(alloc, gb);
141 const expected = hash.string;
142 const actual = switch (hash.id) {
143 .blake3 => &try do_hash(std.crypto.hash.Blake3, data),
144 .sha256 => &try do_hash(std.crypto.hash.sha2.Sha256, data),
145 .sha512 => &try do_hash(std.crypto.hash.sha2.Sha512, data),
146 };
147 const result = std.mem.startsWith(u8, actual, expected);
148 if (!result) {
149 std.log.info("expected: {s}, actual: {s}", .{ expected, actual });
150 }
151 return result;
152}
153
154pub fn do_hash(comptime algo: type, data: string) ![algo.digest_length * 2]u8 {
155 return extras.to_hex(extras.hashBytes(algo, data));
156}
157
158/// Returns the result of running `git rev-parse HEAD`
159pub fn git_rev_HEAD(alloc: std.mem.Allocator, dir: nfs.Dir) !string {
160 var dirg = try dir.openDir(".git", .{});
161 defer dirg.close();
162 const commitid = try git.getHEAD(alloc, dirg);
163 return if (commitid) |_| commitid.?.id else error.NotAGitRepo;
164}
165
166pub fn slice(comptime T: type, input: []const T, from: usize, to: usize) []const T {
167 const f = @max(from, 0);
168 const t = @min(to, input.len);
169 return input[f..t];
170}
171
172pub fn detect_pkgname(alloc: std.mem.Allocator, override: string, dir: [:0]const u8) !string {
173 if (override.len > 0) return override;
174 const dirO = if (dir.len == 0) nfs.cwd() else try nfs.cwd().openDir(dir, .{});
175 const dpath = try dirO.realpathAlloc(gpa, ".");
176 const splitP = try split(alloc, dpath, std.fs.path.sep);
177 var name = splitP[splitP.len - 1];
178 name = extras.trimPrefix(name, "zig-");
179 assert(name.len > 0, "package name must not be an empty string", .{});
180 return name;
181}
182
183pub fn detct_mainfile(alloc: std.mem.Allocator, override: [:0]const u8, dir: nfs.Dir, name: string) ![:0]const u8 {
184 if (override.len > 0) {
185 if (try dir.exists(override)) {
186 if (std.mem.endsWith(u8, override, ".zig")) {
187 return override;
188 }
189 }
190 }
191 const namedotzig = try std.mem.concatWithSentinel(alloc, u8, &.{ name, ".zig" }, 0);
192 if (try dir.exists(namedotzig)) {
193 return namedotzig;
194 }
195 if (try dir.exists("lib.zig")) {
196 return "lib.zig";
197 }
198 if (try dir.exists("main.zig")) {
199 return "main.zig";
200 }
201 if (try dir.exists(try std.fs.path.joinZ(alloc, &.{ "src", "lib.zig" }))) {
202 return "src/lib.zig";
203 }
204 if (try dir.exists(try std.fs.path.joinZ(alloc, &.{ "src", "main.zig" }))) {
205 return "src/main.zig";
206 }
207 return error.CantFindMain;
208}
209
210pub fn indexOfN(haystack: string, needle: u8, n: usize) ?usize {
211 var i: usize = 0;
212 var c: usize = 0;
213 while (c < n) {
214 i = indexOfAfter(haystack, needle, i) orelse return null;
215 c += 1;
216 }
217 return i;
218}
219
220pub fn indexOfAfter(haystack: string, needle: u8, after: usize) ?usize {
221 for (haystack, 0..) |c, i| {
222 if (i <= after) continue;
223 if (c == needle) return i;
224 }
225 return null;
226}
227
228pub fn find_cachepath() ![:0]const u8 {
229 const haystack = try nfs.cwd().realpathAlloc(gpa, ".");
230 const needle = "/.zigmod/deps";
231
232 if (std.mem.indexOf(u8, haystack, needle)) |index| {
233 return gpa.dupeZ(u8, haystack[0 .. index + needle.len]);
234 }
235 return try std.fs.path.joinZ(gpa, &.{ haystack, ".zigmod", "deps" });
236}
237
238pub fn altStringEscape(data: []const u8) StringEscape {
239 return .{ .data = data };
240}
241const StringEscape = struct {
242 data: []const u8,
243
244 pub fn nprint(self: StringEscape, writer: anytype) !void {
245 for (self.data) |c| switch (c) {
246 '\n' => try writer.writeAll("\\n"),
247 '\r' => try writer.writeAll("\\r"),
248 '\t' => try writer.writeAll("\\t"),
249 '\\' => try writer.writeAll("\\\\"),
250 '"' => try writer.writeAll("\\\""),
251 '\'' => try writer.writeAll(&.{'\''}),
252 ' ', '!', '#'...'&', '('...'[', ']'...'~' => try writer.writeAll(&.{c}),
253 else => {
254 try writer.print("\\x{x:0>2}", .{c});
255 },
256 };
257 }
258};
259
260pub fn altSemanticVersion(data: std.SemanticVersion) SemanticVersion {
261 return .{ .data = data };
262}
263const SemanticVersion = struct {
264 data: std.SemanticVersion,
265
266 pub fn nprint(self: SemanticVersion, writer: anytype) !void {
267 try writer.print("{d}.{d}.{d}", .{ self.data.major, self.data.minor, self.data.patch });
268 if (self.data.pre) |pre| try writer.print("-{s}", .{pre});
269 if (self.data.build) |build| try writer.print("+{s}", .{build});
270 }
271};