api.zig

5.6 KB · raw

1// api.zig — nix data access: search, eval, options2// No HTTP. No curl. nix is on PATH by definition on NixOS.3const 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        // Key format: "legacyPackages.x86_64-linux.firefox"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}