tiny.smg.command.node
Defined in command.
API (9)
Actions
Public operations.
Source
Source: tools/smg/src/command/node.zig
zig
const std = @import("std");const pretty_json = @import("pretty").json;const smg = @import("../root.zig");const exports = smg.exports;const graph_mod = smg.graph;const model = smg.model;const options = smg.cli.options;const output = smg.cli.output;const resolve = smg.cli.resolve;const session = smg.command.session;const storage = smg.storage;const table = smg.cli.table;const validation = smg.cli.validation;const view = smg.view;pub fn list(allocator: std.mem.Allocator, _: std.mem.Allocator, args: []const []const u8, limits: smg.Limits, boundary: session.PhaseBoundary) !u8 { if (try validation.rejectMissingOptionValues(allocator, args, &.{ "--type", "--format", "--limit" })) return 2; if (try validation.rejectUnexpectedOptions(allocator, "list", "[OPTIONS]", args, &.{ "--type", "--format", "--json", "--limit", "--full" })) return 2; if (try validation.rejectInvalidChoiceOption(allocator, "list", "[OPTIONS]", args, "--format", &.{ "text", "json" })) return 2; if (try validation.rejectInvalidIntegerOption(allocator, "list", "[OPTIONS]", args, "--limit")) return 2; const pos = try options.positional(allocator, args); if (pos.len != 0) { try output.writeClickUsageError(allocator, "list", "[OPTIONS]", try validation.unexpectedArgumentMessage(allocator, pos)); return 2; } const type_filter = options.flagValue(args, "--type"); const limit = options.signedLimitValue(args); const root = try storage.requireRoot(allocator); const listed = try storage.nodes.listed( allocator, root, type_filter, options.storageLimit(limit), limits.storage, ); defer storage.nodes.freeListed(allocator, listed); boundary.seal(); defer boundary.beginTeardown(); const nodes = listed.nodes; const total = listed.total; const displayed = options.slicedLen(total, limit); if (options.hasJson(args)) return try listJson(allocator, nodes, total, displayed, limit); var out: std.Io.Writer.Allocating = .init(allocator); if (nodes.len == 0) try out.writer.writeAll("No nodes.\n") else try writeListTable(allocator, &out.writer, nodes[0..displayed], total); return try output.writeOut(out.written());}pub fn show(allocator: std.mem.Allocator, phases: std.mem.Allocator, args: []const []const u8, limits: smg.Limits, boundary: session.PhaseBoundary) !u8 { const command = "show"; const signature = "[OPTIONS] NAME"; if (try validation.rejectMissingOptionValues(allocator, args, &.{"--format"})) return 2; if (try validation.rejectUnexpectedOptionsPlain(allocator, command, signature, args, &.{ "--format", "--json", "--full" })) return 2; if (try validation.rejectInvalidChoiceOption(allocator, command, signature, args, "--format", &.{ "text", "json" })) return 2; const pos = try options.positional(allocator, args); if (pos.len == 0) { try output.writeClickUsageError(allocator, command, signature, "Missing argument 'NAME'."); return 2; } if (pos.len > 1) { try output.writeClickUsageError(allocator, command, signature, try validation.unexpectedArgumentMessage(allocator, pos[1..])); return 2; } const loaded = try session.loadRead(allocator, phases, limits, boundary); defer boundary.beginTeardown(); const graph = loaded[1]; const name = try resolve.graphNode(allocator, graph, pos[0], limits.suggestions); const node = graph_mod.getNode(&graph, name) orelse return error.NodeNotFound; const incoming = try view.incoming(graph, allocator, name, null); const outgoing = try view.outgoing(graph, allocator, name, null); if (options.hasJson(args)) return try output.writeOut(try exports.nodeJson(allocator, node, incoming, outgoing)); var out: std.Io.Writer.Allocating = .init(allocator); try render(allocator, &out.writer, node, incoming, outgoing, options.hasFlag(args, "--full")); return try output.writeOut(out.written());}pub fn location(allocator: std.mem.Allocator, node: model.Node) ![]const u8 { const file = node.file orelse return ""; if (node.line) |line| { if (node.end_line) |end_line| { if (end_line != line) return try std.fmt.allocPrint(allocator, "{s}:{d}-{d}", .{ file, line, end_line }); } return try std.fmt.allocPrint(allocator, "{s}:{d}", .{ file, line }); } return file;}pub fn render(allocator: std.mem.Allocator, writer: *std.Io.Writer, node: model.Node, incoming: []const model.Edge, outgoing: []const model.Edge, full: bool) !void { try writer.print("{s} [{s}]\n", .{ node.name, node.type }); if (node.file) |file| { _ = file; try writer.print("file: {s}\n", .{try location(allocator, node)}); } if (node.docstring) |doc| try writer.print("doc: {s}\n", .{doc}); try writeMetadata(allocator, writer, node.metadata); const limit = if (full) @as(usize, 0) else @as(usize, 20); if (incoming.len != 0) { try writer.print("\nIncoming ({d})\n", .{incoming.len}); try writeEdgeLines(writer, incoming, limit); } if (outgoing.len != 0) { try writer.print("\nOutgoing ({d})\n", .{outgoing.len}); try writeEdgeLines(writer, outgoing, limit); }}pub fn writeMetadata(allocator: std.mem.Allocator, writer: *std.Io.Writer, pairs: []const model.Pair) !void { const sorted = try allocator.dupe(model.Pair, pairs); std.mem.sort(model.Pair, sorted, {}, metadataLess); for (sorted) |pair| try writer.print("{s}: {s}\n", .{ pair.key, pair.value });}fn metadataLess(_: void, a: model.Pair, b: model.Pair) bool { return std.mem.lessThan(u8, a.key, b.key);}pub fn writeEdgeLines(writer: *std.Io.Writer, edges: []const model.Edge, limit: usize) !void { for (edges, 0..) |edge, index| { if (limit != 0 and index >= limit) { try writer.print(" ... and {d} more\n", .{edges.len - limit}); break; } try writer.print(" {s} --{s}--> {s}\n", .{ edge.source, edge.rel, edge.target }); }}pub fn listJson(allocator: std.mem.Allocator, nodes: []const model.Node, total: usize, displayed: usize, limit: i64) !u8 { return try output.writeOut(try renderListJson(allocator, nodes, total, displayed, limit));}pub fn renderListJson(allocator: std.mem.Allocator, nodes: []const model.Node, total: usize, displayed: usize, limit: i64) ![]const u8 { var out: std.Io.Writer.Allocating = .init(allocator); errdefer out.deinit(); var scratch_state = std.heap.ArenaAllocator.init(allocator); defer scratch_state.deinit(); const scratch = scratch_state.allocator(); var writer = pretty_json.Writer.init(&out.writer, .minified); try writer.beginObject(); try writer.objectField("rows"); try writer.beginArray(); for (nodes[0..displayed]) |item| { try writer.beginObject(); try writer.objectField("type"); try writer.write(item.type); try writer.objectField("name"); try writer.write(item.name); try writer.objectField("file"); try writer.write(try location(scratch, item)); try writer.endObject(); } try writer.endArray(); try writer.objectField("total"); try writer.write(total); try writer.objectField("displayed"); try writer.write(displayed); try writer.objectField("truncated"); try writer.write(displayed < total); try writer.objectField("limit"); try writer.write(limit); try writer.endObject(); try out.writer.writeByte('\n'); return try out.toOwnedSlice();}pub fn writeListTable(allocator: std.mem.Allocator, writer: *std.Io.Writer, nodes: []const model.Node, total: usize) !void { const columns = [_]table.Column{ .{ .header = "type", .max_width = 12 }, .{ .header = "name", .max_width = 40 }, .{ .header = "file", .max_width = 40 }, }; 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 (nodes) |item| { try table.appendRow(scratch, &rows, &.{ item.type, item.name, try location(scratch, item), }); } try table.write(allocator, writer, &columns, rows.items, total);}test "location includes end line when distinct" { const allocator = std.testing.allocator; const value = try location(allocator, .{ .name = "app.range", .type = model.NodeType.function, .file = "src/app.py", .line = 4, .end_line = 8 }); defer allocator.free(value); try std.testing.expectEqualStrings("src/app.py:4-8", value);}test "metadata renders sorted for show text" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); const pairs = [_]model.Pair{ .{ .key = "source", .value = "manual" }, .{ .key = "metrics", .value = "{}" }, }; var out: std.Io.Writer.Allocating = .init(allocator); try writeMetadata(allocator, &out.writer, &pairs); try std.testing.expectEqualStrings( \\metrics: {} \\source: manual \\ , out.written());}test "list table renders sorted nodes with source locations" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); const nodes = [_]model.Node{ .{ .name = "app", .type = model.NodeType.module, .file = "src/app.py" }, .{ .name = "app.helper", .type = model.NodeType.function, .file = "src/app.py", .line = 5 }, .{ .name = "app.main", .type = model.NodeType.function, .file = "src/app.py", .line = 1 }, }; var out: std.Io.Writer.Allocating = .init(allocator); try writeListTable(allocator, &out.writer, &nodes, nodes.len); try std.testing.expectEqualStrings( \\type name file \\-------- ---------- ------------ \\module app src/app.py \\function app.helper src/app.py:5 \\function app.main src/app.py:1 \\ , out.written());}test "list json renders location and truncation fields" { const allocator = std.testing.allocator; const nodes = [_]model.Node{ .{ .name = "app", .type = model.NodeType.module, .file = "src/app.py" }, .{ .name = "app.helper", .type = model.NodeType.function, .file = "src/app.py", .line = 5 }, }; const rendered = try renderListJson(allocator, &nodes, 3, 2, 2); defer allocator.free(rendered); try std.testing.expectEqualStrings( "{\"rows\":[{\"type\":\"module\",\"name\":\"app\",\"file\":\"src/app.py\"},{\"type\":\"function\",\"name\":\"app.helper\",\"file\":\"src/app.py:5\"}],\"total\":3,\"displayed\":2,\"truncated\":true,\"limit\":2}\n", rendered, );}Source: tools/smg/src/command/root.zig:16
zig
pub const node = @import("node.zig");Complete call list for command.node.list
12 direct calls.
tiny.smg.cli.options.flagValue[function] attools/smg/src/cli/options.zig:106tiny.smg.cli.options.hasJson[function] attools/smg/src/cli/options.zig:141tiny.smg.cli.options.positional[function] attools/smg/src/cli/options.zig:35tiny.smg.cli.options.signedLimitValue[function] attools/smg/src/cli/options.zig:145tiny.smg.cli.options.slicedLen[function] attools/smg/src/cli/options.zig:157tiny.smg.cli.options.storageLimit[function] attools/smg/src/cli/options.zig:153tiny.smg.cli.output.writeClickUsageError[function] attools/smg/src/cli/output.zig:85tiny.smg.cli.output.writeOut[function] attools/smg/src/cli/output.zig:8tiny.smg.command.node.listJson[function] attools/smg/src/command/node.zig:127tiny.smg.command.node.writeListTable[function] attools/smg/src/command/node.zig:165tiny.smg.storage.nodes.freeListed[function] attools/smg/src/storage/nodes.zig:150tiny.smg.storage.nodes.listed[function] attools/smg/src/storage/nodes.zig:52
Complete call list for command.node.show
10 direct calls.
tiny.smg.cli.options.hasFlag[function] attools/smg/src/cli/options.zig:101tiny.smg.cli.options.hasJson[function] attools/smg/src/cli/options.zig:141tiny.smg.cli.options.positional[function] attools/smg/src/cli/options.zig:35tiny.smg.cli.output.writeClickUsageError[function] attools/smg/src/cli/output.zig:85tiny.smg.cli.output.writeOut[function] attools/smg/src/cli/output.zig:8tiny.smg.cli.resolve.graphNode[function] attools/smg/src/cli/resolve.zig:9tiny.smg.command.node.render[function] attools/smg/src/command/node.zig:88tiny.smg.command.session.loadRead[function] attools/smg/src/command/session.zig:56tiny.smg.view.incoming[function] attools/smg/src/view.zig:209tiny.smg.view.outgoing[function] attools/smg/src/view.zig:220
Audit
| Definitions | 10 |
|---|---|
| Public names | 10 |
| Members | 0 |
| Version | 26.7.0 |
| Revision | daab053ee433 |