diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml
index f694dbbc7d37e91e8c57319d51d10b9417d13a25..6a1ce19e9c870e40521eeca9c528d77741f49c02 100644
--- a/.github/workflows/push.yml
+++ b/.github/workflows/push.yml
@@ -59,6 +59,10 @@ jobs:
- run: ./test_repo.sh https://github.com/nektro/zigmod-test-local-dep
- run: ./test_repo.sh https://github.com/nektro/zigmod-test-c-code
+ - run: git diff <(cat __snapshots__/explain.tree.txt) <(zigmod explain --locked --format tree)
+ - run: git diff <(cat __snapshots__/explain.mermaid.txt) <(zigmod explain --locked --format mermaid)
+ - run: git diff <(cat __snapshots__/explain.dot.txt) <(zigmod explain --locked --format dot)
+
# Github Release
- run: ./changelog.sh
- run: ./make_release.sh ${{ secrets.GITHUB_TOKEN }}
diff --git a/__snapshots__/explain.dot.txt b/__snapshots__/explain.dot.txt
new file mode 100644
index 0000000000000000000000000000000000000000..e987344a7f47f010258d762bf1c8268b2c2a20f5
--- /dev/null
+++ b/__snapshots__/explain.dot.txt
@@ -0,0 +1,33 @@
+digraph {
+ "root" -> "zigmod";
+ "root" -> "win32";
+ "root" -> "extras";
+ "root" -> "ansi";
+ "zigmod" -> "yaml";
+ "zigmod" -> "ansi";
+ "zigmod" -> "known-folders";
+ "zigmod" -> "licenses";
+ "zigmod" -> "zfetch";
+ "zigmod" -> "detect-license";
+ "zigmod" -> "inquirer";
+ "zigmod" -> "ini";
+ "zigmod" -> "time";
+ "zigmod" -> "extras";
+ "zigmod" -> "git";
+ "zigmod" -> "json";
+ "yaml" -> "extras";
+ "zfetch" -> "hzzp";
+ "zfetch" -> "iguanaTLS";
+ "detect-license" -> "licenses-text";
+ "detect-license" -> "leven";
+ "inquirer" -> "ansi";
+ "time" -> "extras";
+ "git" -> "time";
+ "git" -> "extras";
+ "git" -> "tracer";
+ "tracer" -> "extras";
+ "json" -> "extras";
+ "json" -> "tracer";
+ "json" -> "intrusive-parser";
+ "intrusive-parser" -> "extras";
+}
diff --git a/__snapshots__/explain.mermaid.txt b/__snapshots__/explain.mermaid.txt
new file mode 100644
index 0000000000000000000000000000000000000000..3d535bb27f68f0526e497ff8715d0fa2143e0ce3
--- /dev/null
+++ b/__snapshots__/explain.mermaid.txt
@@ -0,0 +1,32 @@
+graph TD;
+ root-->zigmod;
+ root-->win32;
+ root-->extras;
+ root-->ansi;
+ zigmod-->yaml;
+ zigmod-->ansi;
+ zigmod-->known-folders;
+ zigmod-->licenses;
+ zigmod-->zfetch;
+ zigmod-->detect-license;
+ zigmod-->inquirer;
+ zigmod-->ini;
+ zigmod-->time;
+ zigmod-->extras;
+ zigmod-->git;
+ zigmod-->json;
+ yaml-->extras;
+ zfetch-->hzzp;
+ zfetch-->iguanaTLS;
+ detect-license-->licenses-text;
+ detect-license-->leven;
+ inquirer-->ansi;
+ time-->extras;
+ git-->time;
+ git-->extras;
+ git-->tracer;
+ tracer-->extras;
+ json-->extras;
+ json-->tracer;
+ json-->intrusive-parser;
+ intrusive-parser-->extras;
diff --git a/__snapshots__/explain.tree.txt b/__snapshots__/explain.tree.txt
new file mode 100644
index 0000000000000000000000000000000000000000..1fa7aa87d05c2e06aeaacc2fec04bc06750b535b
--- /dev/null
+++ b/__snapshots__/explain.tree.txt
@@ -0,0 +1,35 @@
+root
+ zigmod
+ yaml
+
+ extras
+ ansi
+ known-folders
+ licenses
+ zfetch
+ hzzp
+ iguanaTLS
+ detect-license
+ licenses-text
+ leven
+ inquirer
+ ansi
+ ini
+ time
+ extras
+ extras
+ git
+ time
+ extras
+ extras
+ tracer
+ extras
+ json
+ extras
+ tracer
+ extras
+ intrusive-parser
+ extras
+ win32
+ extras
+ ansi
diff --git a/docs/commands/explain.md b/docs/commands/explain.md
new file mode 100644
index 0000000000000000000000000000000000000000..20b65727755bdbfb0ce28fe58f5f3b9dcd684e43
--- /dev/null
+++ b/docs/commands/explain.md
@@ -0,0 +1,37 @@
+## `explain` command
+
+```
+zigmod explain
+```
+
+Use this command to create a visual description of your dependency graph.
+
+## `--locked`
+
+Pass this to use the dependency versions from `zigmod.lock` instead of your current `.zigmod` folder.
+
+## `--format`
+
+May be followed by one of the following values:
+
+- `tree`
+- `mermaid`
+- `dot`
+
+## `--format tree`
+
+
+
+## `--format mermaid`
+
+https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/creating-diagrams
+
+
+
+## `--format dot`
+
+This format prints output compatible with the `dot` program from https://graphviz.org/.
+
+Run `dot -Tpng <(zigmod explain --locked --format dot) -o explain.png` to generate the image.
+
+
diff --git a/src/cmd/explain.zig b/src/cmd/explain.zig
new file mode 100644
index 0000000000000000000000000000000000000000..25f119dfb36decbfbcb6d26527ce30e4da9418ed
--- /dev/null
+++ b/src/cmd/explain.zig
@@ -0,0 +1,100 @@
+const std = @import("std");
+const builtin = @import("builtin");
+const extras = @import("extras");
+
+const zigmod = @import("../lib.zig");
+const u = @import("./../util/funcs.zig");
+const common = @import("./../common.zig");
+
+//
+//
+
+pub fn execute(self_name: []const u8, args: [][:0]u8) !void {
+ _ = self_name;
+
+ const gpa = std.heap.c_allocator;
+ const cachepath = try u.find_cachepath();
+ const dir = std.fs.cwd();
+ const should_lock = args.len >= 1 and std.mem.eql(u8, args[0], "--locked");
+ const format_i: usize = if (should_lock) 1 else 0;
+ const Format = enum {
+ tree,
+ mermaid,
+ dot,
+ };
+ const format_s = if (args.len >= 1 + format_i and std.mem.eql(u8, args[format_i], "--format")) args[format_i + 1] else "";
+ const format = std.meta.stringToEnum(Format, format_s) orelse u.fail("unrecognized --format: {s}", .{format_s});
+
+ var options = common.CollectOptions{
+ .log = false,
+ .update = false,
+ .alloc = gpa,
+ .lock = if (should_lock) try common.parse_lockfile(gpa, dir) else null,
+ };
+ const top_module = try common.collect_deps_deep(cachepath, dir, &options);
+
+ var seencache = std.ArrayList([48]u8).init(gpa);
+ defer seencache.deinit();
+
+ const stdout = std.io.getStdOut();
+ const w = stdout.writer();
+
+ switch (format) {
+ .tree => {
+ try printTree(w, top_module, 0);
+ },
+ .mermaid => {
+ try w.writeAll("graph TD;\n");
+ try printMermaid(w, top_module, &seencache);
+ },
+ .dot => {
+ try w.writeAll("digraph {\n");
+ try printDot(w, top_module, &seencache);
+ try w.writeAll("}\n");
+ },
+ }
+}
+
+fn printTree(writer: anytype, module: zigmod.Module, depth: u16) !void {
+ try writer.writeByteNTimes('\t', depth);
+ try writer.writeAll(module.name);
+ try writer.writeByte('\n');
+
+ for (module.deps) |dep| {
+ try printTree(writer, dep, depth + 1);
+ }
+}
+
+fn printMermaid(writer: anytype, module: zigmod.Module, seencache: *std.ArrayList([48]u8)) !void {
+ for (seencache.items) |item| {
+ if (std.mem.eql(u8, &module.id, &item)) {
+ return;
+ }
+ }
+ try seencache.append(module.id);
+
+ for (module.deps) |dep| {
+ if (dep.name.len == 0) continue;
+ try writer.print(" {s}-->{s};\n", .{ module.name, dep.name });
+ }
+ for (module.deps) |dep| {
+ try printMermaid(writer, dep, seencache);
+ }
+}
+
+fn printDot(writer: anytype, module: zigmod.Module, seencache: *std.ArrayList([48]u8)) !void {
+ for (seencache.items) |item| {
+ if (std.mem.eql(u8, &module.id, &item)) {
+ return;
+ }
+ }
+ try seencache.append(module.id);
+
+ for (module.deps) |dep| {
+ if (dep.name.len == 0) continue;
+ try writer.print(" \"{s}\" -> \"{s}\";\n", .{ module.name, dep.name });
+ }
+ for (module.deps) |dep| {
+ try printDot(writer, dep, seencache);
+ }
+}
diff --git a/src/lib.zig b/src/lib.zig
index 241038e3baa31eb9f0153422c22f7b7cbb7b666c..8116ca41ee469c9f7f10346d3d7016088e5f2c40 100644
--- a/src/lib.zig
+++ b/src/lib.zig
@@ -13,6 +13,7 @@ pub const commands = struct {
pub const zpm = @import("./cmd/zpm.zig");
pub const license = @import("./cmd/license.zig");
pub const generate = @import("./cmd/generate.zig");
+ pub const explain = @import("./cmd/explain.zig");
};
pub fn init() !void {