123const std = @import("std");4const types = @import("types.zig");5const errors = @import("errors.zig");67pub fn searchPackages(gpa: std.mem.Allocator, io: std.Io, query: []const u8) ![]types.NixPackage {8 const result = std.process.run(gpa, io, .{9 .argv = &.{ "nix", "search", "nixpkgs", query, "--json", "--no-update-lock-file" },10 }) catch {11 errors.error_info.setSuggestion("run nix-channel --update and try again", .{});12 return error.SearchFailed;13 };14 defer gpa.free(result.stderr);15 defer gpa.free(result.stdout);1617 switch (result.term) {18 .exited => |code| if (code != 0) {19 errors.error_info.setSuggestion("run nix-channel --update and try again", .{});20 return error.SearchFailed;21 },22 else => return error.SearchFailed,23 }2425 return parseSearchJson(gpa, result.stdout);26}2728fn parseSearchJson(gpa: std.mem.Allocator, json_str: []const u8) ![]types.NixPackage {29 const parsed = std.json.parseFromSlice(std.json.Value, gpa, json_str, .{}) catch return error.SearchFailed;30 defer parsed.deinit();3132 if (parsed.value != .object) return error.SearchFailed;3334 var packages: std.ArrayList(types.NixPackage) = .empty;35 var it = parsed.value.object.iterator();36 while (it.next()) |entry| {37 const key = entry.key_ptr.*;38 const val = entry.value_ptr.*;39 if (val != .object) continue;4041 42 const last_dot = std.mem.lastIndexOfScalar(u8, key, '.') orelse continue;43 const attr = key[last_dot + 1 ..];4445 const pname = if (val.object.get("pname")) |v|46 if (v == .string) v.string else attr47 else48 attr;4950 const version = if (val.object.get("version")) |v|51 if (v == .string) v.string else ""52 else53 "";5455 const description = if (val.object.get("description")) |v|56 if (v == .string) v.string else ""57 else58 "";5960 try packages.append(gpa, .{61 .attr = try gpa.dupe(u8, attr),62 .pname = try gpa.dupe(u8, pname),63 .version = try gpa.dupe(u8, version),64 .description = try gpa.dupe(u8, description),65 });66 }6768 return packages.toOwnedSlice(gpa);69}7071pub fn getLicense(gpa: std.mem.Allocator, io: std.Io, attr: []const u8) ?[]const u8 {72 const expr = std.fmt.allocPrint(gpa, "nixpkgs#{s}.meta.license.shortName", .{attr}) catch return null;73 defer gpa.free(expr);7475 const result = std.process.run(gpa, io, .{76 .argv = &.{ "nix", "eval", "--raw", expr },77 }) catch return null;78 defer gpa.free(result.stderr);7980 switch (result.term) {81 .exited => |code| if (code != 0) {82 gpa.free(result.stdout);83 return null;84 },85 else => {86 gpa.free(result.stdout);87 return null;88 },89 }9091 const license = std.mem.trim(u8, result.stdout, " \t\n\r");92 if (license.len == 0) {93 gpa.free(result.stdout);94 return null;95 }96 return license;97}9899pub fn searchOptions(gpa: std.mem.Allocator, io: std.Io, query: []const u8) ![]NixOption {100 const result = std.process.run(gpa, io, .{101 .argv = &.{ "nix", "eval", "--json", "nixpkgs#lib.optionAttrSetToDocList", "nixpkgs#nixosModules" },102 }) catch return error.SearchFailed;103 defer gpa.free(result.stderr);104 defer gpa.free(result.stdout);105106 switch (result.term) {107 .exited => |code| if (code != 0) return error.SearchFailed,108 else => return error.SearchFailed,109 }110111 return filterOptions(gpa, result.stdout, query);112}113114pub const NixOption = struct {115 name: []const u8,116 type_str: []const u8,117 default: ?[]const u8,118 description: []const u8,119};120121fn filterOptions(gpa: std.mem.Allocator, json_str: []const u8, query: []const u8) ![]NixOption {122 const parsed = std.json.parseFromSlice(std.json.Value, gpa, json_str, .{}) catch return error.SearchFailed;123 defer parsed.deinit();124125 if (parsed.value != .array) return error.SearchFailed;126127 var opts: std.ArrayList(NixOption) = .empty;128 for (parsed.value.array.items) |item| {129 if (item != .object) continue;130 const name = if (item.object.get("name")) |v| if (v == .string) v.string else continue else continue;131 if (query.len > 0 and std.mem.indexOf(u8, name, query) == null) continue;132133 const type_str = if (item.object.get("type")) |v| if (v == .string) v.string else "any" else "any";134 const description = if (item.object.get("description")) |v| if (v == .string) v.string else "" else "";135 const default_val: ?[]const u8 = if (item.object.get("default")) |v| switch (v) {136 .string => |s| s,137 else => null,138 } else null;139140 try opts.append(gpa, .{141 .name = try gpa.dupe(u8, name),142 .type_str = try gpa.dupe(u8, type_str),143 .default = if (default_val) |d| try gpa.dupe(u8, d) else null,144 .description = try gpa.dupe(u8, description),145 });146 }147148 return opts.toOwnedSlice(gpa);149}150151pub fn findSystemPackagesLine(io: std.Io, config_path: []const u8) ?u32 {152 var buf: [65536]u8 = undefined;153 const content = std.Io.Dir.readFile(.cwd(), io, config_path, &buf) catch return null;154 var lines = std.mem.splitScalar(u8, content, '\n');155 var line_num: u32 = 1;156 while (lines.next()) |line| : (line_num += 1) {157 if (std.mem.indexOf(u8, line, "systemPackages") != null) return line_num;158 }159 return null;160}