Skip to documentation
SLOP

tiny.smg.query

Reference tiny.smg query

Defined in tiny.smg.

API (9)

Actions

Public operations.

No direct callersNo direct callstiny.smgquery
Static calls · unresolved targets: unknown · external targets: unknown.

Source

Called byCallstest; no linktools.smg.src.querytest: query transitive deps callers a...private; no linktools.smg.src.querytraversequeryancestors
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallscommand.about.commandruntest; no linktools.smg.src.command.about.rendertest: about text renders coupling con...test; no linktools.smg.src.querytest: containment path terminates at ...private; no linktools.smg.src.querycontainsNameviewincomingquerycontainmentPath
Static calls · unresolved targets: 1 · external targets: 1.
Called byCallstest; no linktools.smg.src.querytest: query transitive deps callers a...private; no linktools.smg.src.querytraversequerydescendants
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallstest; no linktools.smg.src.command.impacttest: impact renders all relationship...traversalimpactqueryimpact
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallscommand.impactruntraversalimpactqueryimpactFromReader
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callscommand.betweenruntest; no linktools.smg.src.command.betweentest: between renders python text con...test; no linktools.smg.src.querytest: query transitive deps callers a...queryshortestPath
Static calls · unresolved targets: 3 · external targets: 2.
Called byCallsNo direct callersprivate; no linktools.smg.src.querytraversequerytransitiveCallers
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallstest; no linktools.smg.src.querytest: query transitive deps callers a...private; no linktools.smg.src.querytraversequerytransitiveDeps
Static calls · unresolved targets: 0 · external targets: 0.

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

Definitions10
Public names10
Members0
Version26.7.0
Revisiondaab053ee433