| 1 | const std = @import("std"); |
| 2 | const string = []const u8; |
| 3 | const gpa = std.heap.c_allocator; |
| 4 | const extras = @import("extras"); |
| 5 | const git = @import("git"); |
| 6 | const ansi = @import("ansi"); |
| 7 | const nfs = @import("nfs"); |
| 8 | const time = @import("time"); |
| 9 | const root = @import("root"); |
| 10 | |
| 11 | // |
| 12 | // |
| 13 | |
| 14 | pub const b = 1; |
| 15 | pub const kb = b * 1024; |
| 16 | pub const mb = kb * 1024; |
| 17 | pub const gb = mb * 1024; |
| 18 | |
| 19 | pub 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 | |
| 26 | pub fn fail(comptime fmt: string, args: anytype) noreturn { |
| 27 | assert(false, fmt, args); |
| 28 | unreachable; |
| 29 | } |
| 30 | |
| 31 | pub 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 | |
| 38 | pub 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 | |
| 49 | pub 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 | |
| 64 | pub 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 | |
| 74 | pub 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 | |
| 81 | pub 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 | |
| 92 | pub fn last(in: []string) ?string { |
| 93 | if (in.len == 0) return null; |
| 94 | return in[in.len - 1]; |
| 95 | } |
| 96 | |
| 97 | const alphabet = "0123456789abcdefghijklmnopqrstuvwxyz"; |
| 98 | |
| 99 | pub 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 | |
| 111 | pub 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 | |
| 130 | pub const HashFn = enum { |
| 131 | blake3, |
| 132 | sha256, |
| 133 | sha512, |
| 134 | }; |
| 135 | |
| 136 | pub 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 | |
| 154 | pub 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` |
| 159 | pub 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 | |
| 166 | pub 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 | |
| 172 | pub 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 | |
| 183 | pub 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 | |
| 210 | pub 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 | |
| 220 | pub 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 | |
| 228 | pub 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 | |
| 238 | pub fn altStringEscape(data: []const u8) StringEscape { |
| 239 | return .{ .data = data }; |
| 240 | } |
| 241 | const 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 | |
| 260 | pub fn altSemanticVersion(data: std.SemanticVersion) SemanticVersion { |
| 261 | return .{ .data = data }; |
| 262 | } |
| 263 | const 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 | }; |