Skip to documentation
SLOP

tiny.smg.exports

Reference tiny.smg exports

Defined in tiny.smg.

API (10)

Actions

Public operations.

Values and defaults

Public values and defaults.

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

Source

Called byCallstest; no linktools.smg.src.exporttest: architecture coupling excludes ...apiisTestDeclarationexportsisCouplingexportsisNamespaceHandleImportexportsisArchitectureCoupling
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallsNo direct callsexportsisArchitectureCouplingtest; no linktools.smg.src.exporttest: architecture coupling excludes ...test; no linktools.smg.src.exporttest: coupling relation table matches...exportstoDsmexportsisCoupling
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callsexportsisArchitectureCouplingtest; no linktools.smg.src.exporttest: namespace handle imports cover ...exportsisNamespaceHandleImport
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callersprivate; no linktools.smg.src.exportwriteNodeDataFieldsexportsnodeJson
Static calls · unresolved targets: 1 · external targets: 9.
Called byCallstest; no linktools.smg.src.exporttest: text mermaid dot and dsm match ...private; no linktools.smg.src.exportidentviewallEdgesviewallNodesexportstoDot
Static calls · unresolved targets: 2 · external targets: 3.
Called byCallstest; no linktools.smg.src.exporttest: text mermaid dot and dsm match ...private; no linktools.smg.src.exportcontainingOwnerprivate; no linktools.smg.src.exportcontainsNameexportsisCouplingviewallEdgesviewallNodesexportstoDsm
Static calls · unresolved targets: 5 · external targets: 5.
Called byCallstest; no linktools.smg.src.exporttest: indented json export renders ne...test; no linktools.smg.src.exporttest: json export omits storage kindprivate; no linktools.smg.src.exportwriteEdgeDataObjectprivate; no linktools.smg.src.exportwriteNodeDataObjectviewallEdgesviewallNodesexportstoJson
Static calls · unresolved targets: 0 · external targets: 9.
Called byCallstest; no linktools.smg.src.exporttest: text mermaid dot and dsm match ...private; no linktools.smg.src.exportidentviewallEdgesviewallNodesexportstoMermaid
Static calls · unresolved targets: 1 · external targets: 3.
Called byCallstest; no linktools.smg.src.exporttest: text mermaid dot and dsm match ...viewallEdgesviewallNodesexportstoText
Static calls · unresolved targets: 2 · external targets: 5.

Source: tools/smg/src/export.zig

zig
const std = @import("std");const graph_mod = @import("graph.zig");const api = @import("api.zig");const view = @import("view.zig");const model = @import("model.zig");const pretty = @import("pretty");const json = pretty.json;pub fn toJson(allocator: std.mem.Allocator, graph: graph_mod.Graph, indent: bool) ![]const u8 {    var out: std.Io.Writer.Allocating = .init(allocator);    errdefer out.deinit();    const nodes = try view.allNodes(graph, allocator, null);    const edges = try view.allEdges(graph, allocator);    var writer = json.Writer.init(&out.writer, if (indent) .indent_2 else .minified);    try writer.beginObject();    try writer.objectField("nodes");    try writer.beginArray();    for (nodes) |node| try writeNodeDataObject(allocator, &writer, node);    try writer.endArray();    try writer.objectField("edges");    try writer.beginArray();    for (edges) |edge| try writeEdgeDataObject(allocator, &writer, edge);    try writer.endArray();    try writer.endObject();    try out.writer.writeByte('\n');    return try out.toOwnedSlice();}pub fn nodeJson(allocator: std.mem.Allocator, node: model.Node, incoming: []const model.Edge, outgoing: []const model.Edge) ![]const u8 {    var out: std.Io.Writer.Allocating = .init(allocator);    errdefer out.deinit();    var writer = json.Writer.init(&out.writer, .minified);    try writer.beginObject();    try writeNodeDataFields(allocator, node, &writer);    try writer.objectField("incoming");    try writer.beginArray();    for (incoming) |edge| {        try writer.beginObject();        try writer.objectField("source");        try writer.write(edge.source);        try writer.objectField("rel");        try writer.write(edge.rel);        try writer.endObject();    }    try writer.endArray();    try writer.objectField("outgoing");    try writer.beginArray();    for (outgoing) |edge| {        try writer.beginObject();        try writer.objectField("target");        try writer.write(edge.target);        try writer.objectField("rel");        try writer.write(edge.rel);        try writer.endObject();    }    try writer.endArray();    try writer.endObject();    try out.writer.writeByte('\n');    return try out.toOwnedSlice();}fn writeNodeDataObject(allocator: std.mem.Allocator, writer: *json.Writer, node: model.Node) !void {    try writer.beginObject();    try writeNodeDataFields(allocator, node, writer);    try writer.endObject();}fn writeNodeDataFields(allocator: std.mem.Allocator, node: model.Node, writer: *json.Writer) !void {    try writer.objectField("name");    try writer.write(node.name);    try writer.objectField("type");    try writer.write(node.type);    if (node.file) |file| {        try writer.objectField("file");        try writer.write(file);    }    if (node.line) |line| {        try writer.objectField("line");        try writer.write(line);    }    if (node.end_line) |line| {        try writer.objectField("end_line");        try writer.write(line);    }    if (node.docstring) |doc| {        try writer.objectField("docstring");        try writer.write(doc);    }    if (node.metadata.len != 0) try writeMetadata(allocator, writer, node.metadata);}fn writeEdgeDataObject(allocator: std.mem.Allocator, writer: *json.Writer, edge: model.Edge) !void {    try writer.beginObject();    try writer.objectField("source");    try writer.write(edge.source);    try writer.objectField("rel");    try writer.write(edge.rel);    try writer.objectField("target");    try writer.write(edge.target);    if (edge.metadata.len != 0) try writeMetadata(allocator, writer, edge.metadata);    try writer.endObject();}fn writeMetadata(allocator: std.mem.Allocator, writer: *json.Writer, metadata: []const model.Pair) !void {    try writer.objectField("metadata");    try writer.beginObject();    for (metadata) |pair| {        try writer.objectField(pair.key);        if (pair.json) {            var parsed = std.json.parseFromSlice(std.json.Value, allocator, pair.value, .{}) catch {                try writer.raw(pair.value);                continue;            };            defer parsed.deinit();            try writer.write(parsed.value);        } else {            try writer.write(pair.value);        }    }    try writer.endObject();}pub fn toText(allocator: std.mem.Allocator, graph: graph_mod.Graph) ![]const u8 {    var out: std.Io.Writer.Allocating = .init(allocator);    errdefer out.deinit();    const nodes = try view.allNodes(graph, allocator, null);    if (nodes.len == 0) return try allocator.dupe(u8, "Empty graph.");    const edges = try view.allEdges(graph, allocator);    try out.writer.print("Nodes ({d}):\n", .{nodes.len});    for (nodes) |node| {        try out.writer.print("  [{s}] {s}", .{ node.type, node.name });        if (node.file) |file| {            try out.writer.print("\n    @ {s}", .{file});            if (node.line) |line| try out.writer.print(":{d}", .{line});        }        if (node.docstring) |doc| try out.writer.print("\n    # {s}", .{doc});        try out.writer.writeByte('\n');    }    if (edges.len != 0) {        try out.writer.print("\nEdges ({d}):\n", .{edges.len});        for (edges) |edge| try out.writer.print("  {s} --{s}--> {s}\n", .{ edge.source, edge.rel, edge.target });    }    return try out.toOwnedSlice();}pub fn toMermaid(allocator: std.mem.Allocator, graph: graph_mod.Graph) ![]const u8 {    var out: std.Io.Writer.Allocating = .init(allocator);    errdefer out.deinit();    const nodes = try view.allNodes(graph, allocator, null);    const edges = try view.allEdges(graph, allocator);    try out.writer.writeAll("graph TD\n");    for (nodes) |node| {        try out.writer.print("    {s}[\"{s} ({s})\"]\n", .{ try ident(allocator, node.name), node.name, node.type });    }    for (edges) |edge| {        try out.writer.print("    {s} -->|{s}| {s}\n", .{ try ident(allocator, edge.source), edge.rel, try ident(allocator, edge.target) });    }    return try out.toOwnedSlice();}pub fn toDot(allocator: std.mem.Allocator, graph: graph_mod.Graph) ![]const u8 {    var out: std.Io.Writer.Allocating = .init(allocator);    errdefer out.deinit();    const nodes = try view.allNodes(graph, allocator, null);    const edges = try view.allEdges(graph, allocator);    try out.writer.writeAll("digraph smg {\n    rankdir=LR;\n");    for (nodes) |node| {        try out.writer.print("    {s} [label=\"{s}\\n({s})\"];\n", .{ try ident(allocator, node.name), node.name, node.type });    }    for (edges) |edge| {        try out.writer.print("    {s} -> {s} [label=\"{s}\"];\n", .{ try ident(allocator, edge.source), try ident(allocator, edge.target), edge.rel });    }    try out.writer.writeAll("}\n");    return try out.toOwnedSlice();}pub fn toDsm(allocator: std.mem.Allocator, graph: graph_mod.Graph, level: []const u8) ![]const u8 {    const nodes = try view.allNodes(graph, allocator, null);    const edges = try view.allEdges(graph, allocator);    var names: std.ArrayList([]const u8) = .empty;    for (nodes) |node| {        if (std.mem.eql(u8, level, "all") or std.mem.eql(u8, node.type, model.NodeType.module) or std.mem.eql(u8, node.type, model.NodeType.package) or (std.mem.eql(u8, level, "class") and std.mem.eql(u8, node.type, model.NodeType.class))) {            try names.append(allocator, node.name);        }    }    if (names.items.len == 0) return try allocator.dupe(u8, "");    var map = std.StringHashMap([]const u8).init(allocator);    for (nodes) |node| {        if (containsName(names.items, node.name)) {            try map.put(node.name, node.name);            continue;        }        if (std.mem.eql(u8, level, "all")) continue;        if (try containingOwner(allocator, graph, node.name, names.items)) |owner_name| try map.put(node.name, owner_name);    }    var out: std.Io.Writer.Allocating = .init(allocator);    errdefer out.deinit();    try out.writer.writeAll("");    for (names.items) |name| {        try out.writer.writeByte(',');        try out.writer.writeAll(name);    }    try out.writer.writeByte('\n');    for (names.items) |row| {        try out.writer.writeAll(row);        for (names.items) |col| {            var count: usize = 0;            for (edges) |edge| {                if (!isCoupling(edge.rel)) continue;                const source = map.get(edge.source) orelse continue;                const target = map.get(edge.target) orelse continue;                if (!std.mem.eql(u8, source, target) and std.mem.eql(u8, source, row) and std.mem.eql(u8, target, col)) count += 1;            }            try out.writer.print(",{d}", .{count});        }        try out.writer.writeByte('\n');    }    return try out.toOwnedSlice();}pub fn isCoupling(rel: []const u8) bool {    return std.mem.eql(u8, rel, model.RelType.calls) or        std.mem.eql(u8, rel, model.RelType.imports) or        std.mem.eql(u8, rel, model.RelType.inherits) or        std.mem.eql(u8, rel, model.RelType.implements) or        std.mem.eql(u8, rel, model.RelType.depends_on);}pub const coupling_relations: []const []const u8 = relations: {    const names = @typeInfo(model.RelType).@"struct".decl_names;    var selected: [names.len][]const u8 = undefined;    var count: usize = 0;    for (names) |name| {        const relation: []const u8 = @field(model.RelType, name);        if (!isCoupling(relation)) continue;        selected[count] = relation;        count += 1;    }    const bound = selected[0..count].*;    break :relations &bound;};pub fn isNamespaceHandleImport(rel: []const u8, source: []const u8, target: []const u8) bool {    if (!std.mem.eql(u8, rel, model.RelType.imports)) return false;    if (std.mem.eql(u8, source, target)) return false;    if (!std.mem.endsWith(u8, target, ".root")) return false;    const namespace = target[0 .. target.len - ".root".len];    if (source.len <= namespace.len + 1) return false;    if (!std.mem.startsWith(u8, source, namespace)) return false;    return source[namespace.len] == '.';}/// Returns whether an edge belongs in architecture coupling metrics.////// Raw graph queries and exports retain namespace-handle and test edges.pub fn isArchitectureCoupling(graph: graph_mod.Graph, edge: model.Edge) bool {    if (!isCoupling(edge.rel)) return false;    if (isNamespaceHandleImport(edge.rel, edge.source, edge.target)) return false;    if (graph_mod.getNode(&graph, edge.source)) |node| {        if (api.isTestDeclaration(node)) return false;    }    if (graph_mod.getNode(&graph, edge.target)) |node| {        if (api.isTestDeclaration(node)) return false;    }    return true;}test "coupling relation table matches the coupling predicate" {    const names = @typeInfo(model.RelType).@"struct".decl_names;    var coupling: usize = 0;    inline for (names) |name| {        const relation: []const u8 = @field(model.RelType, name);        var listed = false;        for (coupling_relations) |candidate| {            if (std.mem.eql(u8, candidate, relation)) listed = true;        }        try std.testing.expectEqual(isCoupling(relation), listed);        if (listed) coupling += 1;    }    try std.testing.expectEqual(coupling, coupling_relations.len);}test "namespace handle imports cover ancestor handles but not cross-package roots" {    try std.testing.expect(isNamespaceHandleImport("imports", "lib.agent.src.facet", "lib.agent.src.root"));    try std.testing.expect(isNamespaceHandleImport("imports", "lib.agent.src.facets.lisp", "lib.agent.src.root"));    try std.testing.expect(!isNamespaceHandleImport("imports", "lib.lang.src.runtime.bindings", "lib.preserves.src.root"));    try std.testing.expect(!isNamespaceHandleImport("imports", "lib.agent.src.root", "lib.agent.src.facet"));    try std.testing.expect(!isNamespaceHandleImport("calls", "lib.agent.src.facet.step", "lib.agent.src.root.Event"));    try std.testing.expect(!isNamespaceHandleImport("imports", "lib.agent.src.root", "lib.agent.src.root"));}test "architecture coupling excludes namespace handles and test declarations" {    var graph = graph_mod.init(std.testing.allocator);    defer graph_mod.deinit(&graph);    const test_metadata = [_]model.Pair{.{ .key = api.test_key, .value = "true" }};    try graph_mod.addNode(&graph, .{ .name = "pkg.root", .type = model.NodeType.module });    try graph_mod.addNode(&graph, .{ .name = "pkg.worker", .type = model.NodeType.function });    try graph_mod.addNode(&graph, .{ .name = "pkg.test_worker", .type = model.NodeType.function, .metadata = &test_metadata });    const handle: model.Edge = .{ .source = "pkg.worker", .rel = model.RelType.imports, .target = "pkg.root" };    const production: model.Edge = .{ .source = "pkg.root", .rel = model.RelType.calls, .target = "pkg.worker" };    const test_call: model.Edge = .{ .source = "pkg.test_worker", .rel = model.RelType.calls, .target = "pkg.worker" };    try std.testing.expect(isCoupling(handle.rel));    try std.testing.expect(isCoupling(test_call.rel));    try std.testing.expect(!isArchitectureCoupling(graph, handle));    try std.testing.expect(isArchitectureCoupling(graph, production));    try std.testing.expect(!isArchitectureCoupling(graph, test_call));}fn ident(allocator: std.mem.Allocator, value: []const u8) ![]const u8 {    const out = try allocator.dupe(u8, value);    for (out) |*byte| {        if (!std.ascii.isAlphanumeric(byte.*)) byte.* = '_';    }    return out;}fn csvCell(writer: *std.Io.Writer, value: []const u8) !void {    if (std.mem.indexOfAny(u8, value, ",\"\n") == null) {        try writer.writeAll(value);        return;    }    try writer.writeByte('"');    for (value) |byte| {        if (byte == '"') try writer.writeByte('"');        try writer.writeByte(byte);    }    try writer.writeByte('"');}fn cmpString(_: void, a: []const u8, b: []const u8) bool {    return std.mem.lessThan(u8, a, b);}fn containsName(names: []const []const u8, name: []const u8) bool {    for (names) |item| if (std.mem.eql(u8, item, name)) return true;    return false;}fn containingOwner(allocator: std.mem.Allocator, graph: graph_mod.Graph, name: []const u8, names: []const []const u8) !?[]const u8 {    var current = name;    while (true) {        const incoming = try view.incoming(graph, allocator, current, model.RelType.contains);        if (incoming.len == 0) return null;        current = incoming[0].source;        if (containsName(names, current)) return current;    }}fn sampleGraph(allocator: std.mem.Allocator) !graph_mod.Graph {    var graph = graph_mod.init(allocator);    try graph_mod.addNode(&graph, .{ .name = "app", .type = model.NodeType.module });    try graph_mod.addNode(&graph, .{ .name = "app.main", .type = model.NodeType.function, .file = "app.py", .line = 1 });    try graph_mod.addEdge(&graph, .{ .source = "app", .target = "app.main", .rel = model.RelType.contains });    return graph;}test "json export omits storage kind" {    var arena = std.heap.ArenaAllocator.init(std.testing.allocator);    defer arena.deinit();    const allocator = arena.allocator();    const graph = try sampleGraph(allocator);    const rendered_json = try toJson(allocator, graph, false);    try std.testing.expect(std.mem.indexOf(u8, rendered_json, "\"kind\"") == null);    try std.testing.expect(std.mem.indexOf(u8, rendered_json, "\"nodes\"") != null);    try std.testing.expect(std.mem.indexOf(u8, rendered_json, "\"edges\"") != null);    const pretty_json = try toJson(allocator, graph, true);    try std.testing.expectEqualStrings(        \\{        \\  "nodes": [        \\    {        \\      "name": "app",        \\      "type": "module"        \\    },        \\    {        \\      "name": "app.main",        \\      "type": "function",        \\      "file": "app.py",        \\      "line": 1        \\    }        \\  ],        \\  "edges": [        \\    {        \\      "source": "app",        \\      "rel": "contains",        \\      "target": "app.main"        \\    }        \\  ]        \\}        \\    , pretty_json);}test "indented json export renders nested metadata through pretty json" {    var arena = std.heap.ArenaAllocator.init(std.testing.allocator);    defer arena.deinit();    const allocator = arena.allocator();    const metadata = [_]model.Pair{        .{ .key = "metrics", .value = "{\"fan_in\":1,\"tags\":[\"alpha\",2]}", .json = true },        .{ .key = "source", .value = "manual" },    };    var graph = graph_mod.init(allocator);    try graph_mod.addNode(&graph, .{ .name = "app", .type = model.NodeType.module, .metadata = &metadata });    const pretty_json = try toJson(allocator, graph, true);    try std.testing.expect(std.mem.indexOf(u8, pretty_json,        \\      "metadata": {        \\        "metrics": {        \\          "fan_in": 1,        \\          "tags": [        \\            "alpha",        \\            2        \\          ]        \\        },        \\        "source": "manual"        \\      }    ) != null);}test "text mermaid dot and dsm match python contract markers" {    var arena = std.heap.ArenaAllocator.init(std.testing.allocator);    defer arena.deinit();    const allocator = arena.allocator();    const empty = graph_mod.init(allocator);    try std.testing.expectEqualStrings("Empty graph.", try toText(allocator, empty));    var graph = try sampleGraph(allocator);    const text_out = try toText(allocator, graph);    try std.testing.expect(std.mem.indexOf(u8, text_out, "Nodes (2):") != null);    try std.testing.expect(std.mem.indexOf(u8, text_out, "[module] app") != null);    const mermaid = try toMermaid(allocator, graph);    try std.testing.expect(std.mem.startsWith(u8, mermaid, "graph TD"));    try std.testing.expect(std.mem.indexOf(u8, mermaid, "-->|contains|") != null);    const dot = try toDot(allocator, graph);    try std.testing.expect(std.mem.startsWith(u8, dot, "digraph smg {"));    try std.testing.expect(std.mem.indexOf(u8, dot, "label=\"contains\"") != null);    try graph_mod.addNode(&graph, .{ .name = "lib", .type = model.NodeType.module });    try graph_mod.addNode(&graph, .{ .name = "lib.f", .type = model.NodeType.function });    try graph_mod.addEdge(&graph, .{ .source = "lib", .target = "lib.f", .rel = model.RelType.contains });    try graph_mod.addEdge(&graph, .{ .source = "app.main", .target = "lib.f", .rel = model.RelType.calls });    const dsm = try toDsm(allocator, graph, "module");    try std.testing.expect(std.mem.startsWith(u8, dsm, ",app,lib"));    try std.testing.expect(std.mem.indexOf(u8, dsm, "app,0,1") != null);}

Source: tools/smg/src/root.zig:21

zig
pub const exports = @import("export.zig");

Audit

Definitions11
Public names11
Members0
Version26.7.0
Revisiondaab053ee433