tiny.smg.query
Defined in tiny.smg.
API (9)
Actions
Public operations.
ancestorscontainmentPathdescendantsimpactimpactFromReadershortestPathsubgraphtransitiveCallerstransitiveDeps
Source
Source: tools/smg/src/query.zig
zig
const std = @import("std");const exports = @import("export.zig");const graph_mod = @import("graph.zig");const view = @import("view.zig");const model = @import("model.zig");const database = @import("storage/database/root.zig");const traversal = @import("traversal.zig");pub fn containmentPath(allocator: std.mem.Allocator, graph: graph_mod.Graph, name: []const u8) ![]const []const u8 { var out: std.ArrayList([]const u8) = .empty; var current = name; while (true) { if (containsName(out.items, current)) break; try out.append(allocator, current); var parent: ?[]const u8 = null; const inc = try view.incoming(graph, allocator, current, model.RelType.contains); if (inc.len != 0) parent = inc[0].source; if (parent == null) break; current = parent.?; } std.mem.reverse([]const u8, out.items); return try out.toOwnedSlice(allocator);}fn containsName(names: []const []const u8, target: []const u8) bool { for (names) |name| { if (std.mem.eql(u8, name, target)) return true; } return false;}pub fn transitiveDeps(allocator: std.mem.Allocator, graph: graph_mod.Graph, name: []const u8, max_depth: ?usize) ![]const []const u8 { return try traverse(allocator, graph, name, .out, &.{ model.RelType.imports, model.RelType.depends_on }, max_depth);}pub fn transitiveCallers(allocator: std.mem.Allocator, graph: graph_mod.Graph, name: []const u8, max_depth: ?usize) ![]const []const u8 { return try traverse(allocator, graph, name, .in, &.{model.RelType.calls}, max_depth);}pub fn impact(allocator: std.mem.Allocator, graph: graph_mod.Graph, name: []const u8, max_depth: ?usize, coupling_only: bool) ![]const []const u8 { return try traversal.impact(allocator, .{ .graph = graph }, name, max_depth, coupling_only);}pub fn impactFromReader( allocator: std.mem.Allocator, reader: *database.Reader, name: []const u8, max_depth: ?usize, coupling_only: bool,) ![]const []const u8 { return try traversal.impact( allocator, .{ .stored = reader }, name, max_depth, coupling_only, );}pub fn ancestors(allocator: std.mem.Allocator, graph: graph_mod.Graph, name: []const u8, rel: []const u8, max_depth: ?usize) ![]const []const u8 { return try traverse(allocator, graph, name, .in, &.{rel}, max_depth);}pub fn descendants(allocator: std.mem.Allocator, graph: graph_mod.Graph, name: []const u8, rel: []const u8, max_depth: ?usize) ![]const []const u8 { return try traverse(allocator, graph, name, .out, &.{rel}, max_depth);}pub fn subgraph(allocator: std.mem.Allocator, graph: graph_mod.Graph, name: []const u8, depth: usize, coupling_only: bool) !graph_mod.Graph { var names = std.StringHashMap(usize).init(allocator); try names.put(name, 0); var frontier: std.ArrayList([]const u8) = .empty; try frontier.append(allocator, name); var current_depth: usize = 0; while (current_depth < depth and frontier.items.len != 0) : (current_depth += 1) { var next: std.ArrayList([]const u8) = .empty; for (frontier.items) |item| { for (graph.edges.items) |edge| { if (coupling_only and !exports.isCoupling(edge.rel)) continue; if (std.mem.eql(u8, edge.source, item) and !names.contains(edge.target)) { try names.put(edge.target, current_depth + 1); try next.append(allocator, edge.target); } if (std.mem.eql(u8, edge.target, item) and !names.contains(edge.source)) { try names.put(edge.source, current_depth + 1); try next.append(allocator, edge.source); } } } frontier = next; } var out = graph_mod.init(allocator); for (graph.nodes.items) |node| if (names.contains(node.name)) try graph_mod.addNode(&out, node); for (graph.edges.items) |edge| { if (names.contains(edge.source) and names.contains(edge.target) and (!coupling_only or exports.isCoupling(edge.rel))) try graph_mod.addEdge(&out, edge); } return out;}pub fn shortestPath(allocator: std.mem.Allocator, graph: graph_mod.Graph, source: []const u8, target: []const u8) !?[]const []const u8 { var queue: std.ArrayList([]const u8) = .empty; var prev = std.StringHashMap([]const u8).init(allocator); try queue.append(allocator, source); try prev.put(source, ""); var head: usize = 0; while (head < queue.items.len) : (head += 1) { const item = queue.items[head]; if (std.mem.eql(u8, item, target)) break; for (graph.edges.items) |edge| { var neighbor: ?[]const u8 = null; if (std.mem.eql(u8, edge.source, item)) neighbor = edge.target; if (std.mem.eql(u8, edge.target, item)) neighbor = edge.source; if (neighbor) |next| { if (!prev.contains(next)) { try prev.put(next, item); try queue.append(allocator, next); } } } } if (!prev.contains(target)) return null; var path: std.ArrayList([]const u8) = .empty; var current = target; while (current.len != 0) { try path.append(allocator, current); current = prev.get(current) orelse ""; } std.mem.reverse([]const u8, path.items); return try path.toOwnedSlice(allocator);}const Direction = enum { in, out };fn traverse(allocator: std.mem.Allocator, graph: graph_mod.Graph, name: []const u8, direction: Direction, rels: ?[]const []const u8, max_depth: ?usize) ![]const []const u8 { var seen = std.StringHashMap(usize).init(allocator); var queue: std.ArrayList([]const u8) = .empty; try seen.put(name, 0); try queue.append(allocator, name); var head: usize = 0; while (head < queue.items.len) : (head += 1) { const item = queue.items[head]; const depth = seen.get(item).?; if (max_depth != null and depth >= max_depth.?) continue; for (graph.edges.items) |edge| { if (rels != null and !relIn(edge.rel, rels.?)) continue; const next = switch (direction) { .out => if (std.mem.eql(u8, edge.source, item)) edge.target else continue, .in => if (std.mem.eql(u8, edge.target, item)) edge.source else continue, }; if (!seen.contains(next)) { try seen.put(next, depth + 1); try queue.append(allocator, next); } } } var out: std.ArrayList([]const u8) = .empty; var it = seen.iterator(); while (it.next()) |entry| { if (!std.mem.eql(u8, entry.key_ptr.*, name)) try out.append(allocator, entry.key_ptr.*); } std.mem.sort([]const u8, out.items, {}, cmpString); return try out.toOwnedSlice(allocator);}fn relIn(rel: []const u8, rels: []const []const u8) bool { for (rels) |item| if (std.mem.eql(u8, rel, item)) return true; return false;}fn cmpString(_: void, a: []const u8, b: []const u8) bool { return std.mem.lessThan(u8, a, b);}test "query transitive deps callers and paths match python contract" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); var graph = graph_mod.init(allocator); for ([_][]const u8{ "app", "lib", "core" }) |name| try graph_mod.addNode(&graph, .{ .name = name, .type = model.NodeType.module }); try graph_mod.addEdge(&graph, .{ .source = "app", .target = "lib", .rel = model.RelType.depends_on }); try graph_mod.addEdge(&graph, .{ .source = "lib", .target = "core", .rel = model.RelType.depends_on }); const deps = try transitiveDeps(allocator, graph, "app", null); try std.testing.expectEqualStrings("core", deps[0]); try std.testing.expectEqualStrings("lib", deps[1]); const depth = try transitiveDeps(allocator, graph, "app", 1); try std.testing.expectEqual(@as(usize, 1), depth.len); try std.testing.expectEqualStrings("lib", depth[0]); const anc = try ancestors(allocator, graph, "core", model.RelType.depends_on, null); try std.testing.expectEqualStrings("app", anc[0]); try std.testing.expectEqualStrings("lib", anc[1]); const desc = try descendants(allocator, graph, "app", model.RelType.depends_on, null); try std.testing.expectEqualStrings("core", desc[0]); try std.testing.expectEqualStrings("lib", desc[1]); const path = (try shortestPath(allocator, graph, "app", "core")).?; try std.testing.expectEqualStrings("app", path[0]); try std.testing.expectEqualStrings("core", path[2]);}test "containment path terminates at a manual containment cycle" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); var graph = graph_mod.init(allocator); try graph_mod.addNode( &graph, .{ .name = "a", .type = model.NodeType.module }, ); try graph_mod.addNode( &graph, .{ .name = "b", .type = model.NodeType.module }, ); try graph_mod.addEdge( &graph, .{ .source = "a", .target = "b", .rel = model.RelType.contains }, ); try graph_mod.addEdge( &graph, .{ .source = "b", .target = "a", .rel = model.RelType.contains }, ); const path = try containmentPath(allocator, graph, "a"); try std.testing.expectEqual(@as(usize, 2), path.len); try std.testing.expectEqualStrings("b", path[0]); try std.testing.expectEqualStrings("a", path[1]);}Source: tools/smg/src/root.zig:28
zig
pub const query = @import("query.zig");Audit
| Definitions | 10 |
|---|---|
| Public names | 10 |
| Members | 0 |
| Version | 26.7.0 |
| Revision | daab053ee433 |