tiny.smg.command.query
Defined in command.
API (1)
Actions
Public operations.
Source
Source: tools/smg/src/command/query.zig
zig
const std = @import("std");const smg = @import("../root.zig");const exports = smg.exports;const model = smg.model;const query_mod = smg.query;const text = smg.text;const view = smg.view;const cli_graph = smg.cli.graph;const cli_json = smg.cli.json;const cli_options = smg.cli.options;const cli_output = smg.cli.output;const cli_resolve = smg.cli.resolve;const cli_session = smg.command.session;const cli_subgraph = smg.cli.subgraph;const cli_table = smg.cli.table;const cli_validation = smg.cli.validation;pub fn run(allocator: std.mem.Allocator, phases: std.mem.Allocator, args: []const []const u8, limits: smg.Limits, boundary: cli_session.PhaseBoundary) !u8 { const kind = args[0]; const rest = args[1..]; if (!text.list.containsString(&.{ "deps", "callers", "path", "subgraph", "incoming", "outgoing" }, kind)) { try cli_output.writeClickUsageError(allocator, "query", "[OPTIONS] COMMAND [ARGS]...", try std.fmt.allocPrint(allocator, "No such command '{s}'.", .{kind})); return 2; } if (std.mem.eql(u8, kind, "deps")) { if (try cli_validation.validateQueryNameCommand(allocator, "query deps", "[OPTIONS] NAME", rest, &.{ "--depth", "--format", "--limit" }, &.{ "text", "json", "mermaid", "dot" })) return 2; const pos = try cli_options.positional(allocator, rest); const loaded = try cli_session.loadRead(allocator, phases, limits, boundary); defer boundary.beginTeardown(); const graph = loaded[1]; const name = try cli_resolve.graphNode(allocator, graph, pos[0], limits.suggestions); const names = try query_mod.transitiveDeps(allocator, graph, name, cli_options.depthValue(rest)); return try cli_graph.writeNames(allocator, graph, name, names, try std.fmt.allocPrint(allocator, "Dependencies of {s}", .{name}), cli_options.flagValue(rest, "--format") orelse "text", cli_options.signedLimitValue(rest)); } if (std.mem.eql(u8, kind, "callers")) { if (try cli_validation.validateQueryNameCommand(allocator, "query callers", "[OPTIONS] NAME", rest, &.{ "--depth", "--format", "--limit" }, &.{ "text", "json", "mermaid", "dot" })) return 2; const pos = try cli_options.positional(allocator, rest); const loaded = try cli_session.loadRead(allocator, phases, limits, boundary); defer boundary.beginTeardown(); const graph = loaded[1]; const name = try cli_resolve.graphNode(allocator, graph, pos[0], limits.suggestions); const names = try query_mod.transitiveCallers(allocator, graph, name, cli_options.depthValue(rest)); return try cli_graph.writeNames(allocator, graph, name, names, try std.fmt.allocPrint(allocator, "Callers of {s}", .{name}), cli_options.flagValue(rest, "--format") orelse "text", cli_options.signedLimitValue(rest)); } if (std.mem.eql(u8, kind, "path")) return try path(allocator, phases, rest, limits, boundary); if (std.mem.eql(u8, kind, "subgraph")) return try subgraph(allocator, phases, rest, limits, boundary); if (std.mem.eql(u8, kind, "incoming")) { if (try cli_validation.validateQueryNameCommand(allocator, "query incoming", "[OPTIONS] NAME", rest, &.{ "--rel", "--format", "--limit" }, &.{ "text", "json" })) return 2; const pos = try cli_options.positional(allocator, rest); const loaded = try cli_session.loadRead(allocator, phases, limits, boundary); defer boundary.beginTeardown(); const graph = loaded[1]; const name = try cli_resolve.graphNode(allocator, graph, pos[0], limits.suggestions); return try outputEdges(allocator, try view.incoming(graph, allocator, name, cli_options.flagValue(rest, "--rel")), cli_options.flagValue(rest, "--format") orelse "text", cli_options.signedLimitValue(rest)); } if (std.mem.eql(u8, kind, "outgoing")) { if (try cli_validation.validateQueryNameCommand(allocator, "query outgoing", "[OPTIONS] NAME", rest, &.{ "--rel", "--format", "--limit" }, &.{ "text", "json" })) return 2; const pos = try cli_options.positional(allocator, rest); const loaded = try cli_session.loadRead(allocator, phases, limits, boundary); defer boundary.beginTeardown(); const graph = loaded[1]; const name = try cli_resolve.graphNode(allocator, graph, pos[0], limits.suggestions); return try outputEdges(allocator, try view.outgoing(graph, allocator, name, cli_options.flagValue(rest, "--rel")), cli_options.flagValue(rest, "--format") orelse "text", cli_options.signedLimitValue(rest)); } unreachable;}fn path(allocator: std.mem.Allocator, phases: std.mem.Allocator, args: []const []const u8, limits: smg.Limits, boundary: cli_session.PhaseBoundary) !u8 { if (try cli_validation.rejectMissingOptionValues(allocator, args, &.{"--format"})) return 2; if (try cli_validation.rejectUnexpectedOptionsPlain(allocator, "query path", "[OPTIONS] SOURCE TARGET", args, &.{"--format"})) return 2; if (try cli_validation.rejectInvalidChoiceOption(allocator, "query path", "[OPTIONS] SOURCE TARGET", args, "--format", &.{ "text", "json" })) return 2; const pos = try cli_options.positional(allocator, args); if (pos.len == 0) { try cli_output.writeClickUsageError(allocator, "query path", "[OPTIONS] SOURCE TARGET", "Missing argument 'SOURCE'."); return 2; } if (pos.len == 1) { try cli_output.writeClickUsageError(allocator, "query path", "[OPTIONS] SOURCE TARGET", "Missing argument 'TARGET'."); return 2; } if (pos.len > 2) { try cli_output.writeClickUsageError(allocator, "query path", "[OPTIONS] SOURCE TARGET", try cli_validation.unexpectedArgumentMessage(allocator, pos[2..])); return 2; } const loaded = try cli_session.loadRead(allocator, phases, limits, boundary); defer boundary.beginTeardown(); const graph = loaded[1]; const source = try cli_resolve.graphNode(allocator, graph, pos[0], limits.suggestions); const target = try cli_resolve.graphNode(allocator, graph, pos[1], limits.suggestions); const found_path = (try query_mod.shortestPath(allocator, graph, source, target)) orelse { try cli_output.writeErrFmt(allocator, "Error: no path from {s} to {s}\n", .{ source, target }); return 1; }; if (std.mem.eql(u8, cli_options.flagValue(args, "--format") orelse "", "json")) return try cli_json.stringArray(allocator, found_path); var out: std.Io.Writer.Allocating = .init(allocator); try writePathText(&out.writer, found_path); return try cli_output.writeOut(out.written());}fn subgraph(allocator: std.mem.Allocator, phases: std.mem.Allocator, args: []const []const u8, limits: smg.Limits, boundary: cli_session.PhaseBoundary) !u8 { if (try cli_validation.rejectMissingOptionValues(allocator, args, &.{ "--depth", "--format", "--limit" })) return 2; if (try cli_validation.rejectUnexpectedOptionsPlain(allocator, "query subgraph", "[OPTIONS] NAME", args, &.{ "--depth", "--format", "--full", "--limit" })) return 2; if (try cli_validation.rejectInvalidChoiceOption(allocator, "query subgraph", "[OPTIONS] NAME", args, "--format", &.{ "text", "json", "mermaid", "dot" })) return 2; if (try cli_validation.rejectInvalidIntegerOption(allocator, "query subgraph", "[OPTIONS] NAME", args, "--depth")) return 2; if (try cli_validation.rejectInvalidIntegerOption(allocator, "query subgraph", "[OPTIONS] NAME", args, "--limit")) return 2; const pos = try cli_options.positional(allocator, args); if (pos.len == 0) { try cli_output.writeClickUsageError(allocator, "query subgraph", "[OPTIONS] NAME", "Missing argument 'NAME'."); return 2; } if (pos.len > 1) { try cli_output.writeClickUsageError(allocator, "query subgraph", "[OPTIONS] NAME", try cli_validation.unexpectedArgumentMessage(allocator, pos[1..])); return 2; } const loaded = try cli_session.loadRead(allocator, phases, limits, boundary); defer boundary.beginTeardown(); const graph = loaded[1]; const name = try cli_resolve.graphNode(allocator, graph, pos[0], limits.suggestions); const depth = cli_options.signedOptionValue(args, "--depth", 2); const sub = try query_mod.subgraph(allocator, graph, name, cli_options.unsignedTraversalDepth(depth), false); const fmt = cli_options.flagValue(args, "--format") orelse "text"; if (!std.mem.eql(u8, fmt, "text")) return try cli_graph.write(allocator, sub, fmt); if (cli_options.hasFlag(args, "--full")) { const cap = if (cli_options.flagValue(args, "--limit")) |_| cli_options.signedLimitValue(args) else @as(i64, 50); if (cap > 0 and sub.nodes.items.len > @as(usize, @intCast(cap))) { const pruned = try cli_subgraph.prune(allocator, sub, @intCast(cap)); var out: std.Io.Writer.Allocating = .init(allocator); try out.writer.writeAll(try exports.toText(allocator, pruned)); try out.writer.print("(showing {d} of {d} nodes -- use --limit 0 for all)\n", .{ cap, sub.nodes.items.len }); return try cli_output.writeOut(out.written()); } return try cli_graph.write(allocator, sub, "text"); } const limit = if (cli_options.flagValue(args, "--limit")) |_| cli_options.signedLimitValue(args) else @as(i64, 10); var out: std.Io.Writer.Allocating = .init(allocator); try cli_subgraph.writeSummary(allocator, &out.writer, sub, name, depth, limit); return try cli_output.writeOut(out.written());}fn outputEdges(allocator: std.mem.Allocator, edges: []const model.Edge, fmt: []const u8, limit: i64) !u8 { if (std.mem.eql(u8, fmt, "json")) return try cli_json.edgeArray(allocator, edges); var out: std.Io.Writer.Allocating = .init(allocator); try writeEdgesText(allocator, &out.writer, edges, limit); return try cli_output.writeOut(out.written());}fn writePathText(writer: *std.Io.Writer, path_items: []const []const u8) !void { for (path_items, 0..) |item, index| { if (index != 0) try writer.writeAll(" -> "); try writer.writeAll(item); } try writer.writeByte('\n');}fn writeEdgesText(allocator: std.mem.Allocator, writer: *std.Io.Writer, edges: []const model.Edge, limit: i64) !void { if (edges.len == 0) { try writer.writeAll("(none)\n"); return; } const displayed = cli_options.slicedLen(edges.len, limit); const columns = [_]cli_table.Column{ .{ .header = "source", .max_width = 48 }, .{ .header = "rel", .max_width = 16 }, .{ .header = "target", .max_width = 48 }, }; var scratch_state = std.heap.ArenaAllocator.init(allocator); defer scratch_state.deinit(); const scratch = scratch_state.allocator(); var rows: std.ArrayList([]const []const u8) = .empty; for (edges[0..displayed]) |edge| try cli_table.appendRow(scratch, &rows, &.{ edge.source, edge.rel, edge.target }); try cli_table.writeJsonOption(allocator, writer, &columns, rows.items, edges.len, "--format json");}test "query path text joins nodes with arrows" { var out: std.Io.Writer.Allocating = .init(std.testing.allocator); defer out.deinit(); try writePathText(&out.writer, &.{ "app.main", "app.helper", "lib.util" }); try std.testing.expectEqualStrings("app.main -> app.helper -> lib.util\n", out.written());}test "query edge text renders empty and limited tables" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); var empty: std.Io.Writer.Allocating = .init(allocator); try writeEdgesText(allocator, &empty.writer, &.{}, 10); try std.testing.expectEqualStrings("(none)\n", empty.written()); var out: std.Io.Writer.Allocating = .init(allocator); try writeEdgesText(allocator, &out.writer, &.{ .{ .source = "app.main", .rel = model.RelType.calls, .target = "app.helper" }, .{ .source = "app.main", .rel = model.RelType.imports, .target = "lib.util" }, }, 1); try std.testing.expect(std.mem.indexOf(u8, out.written(), "source") != null); try std.testing.expect(std.mem.indexOf(u8, out.written(), "app.main") != null); try std.testing.expect(std.mem.indexOf(u8, out.written(), "showing 1 of 2") != null); try std.testing.expect(std.mem.indexOf(u8, out.written(), "--format json") != null);}Source: tools/smg/src/command/root.zig:17
zig
pub const query = @import("query.zig");Audit
| Definitions | 2 |
|---|---|
| Public names | 2 |
| Members | 0 |
| Version | 26.7.0 |
| Revision | daab053ee433 |