deps.ha (3576B)
1 // SPDX-License-Identifier: GPL-3.0-only 2 // (c) Hare authors <https://harelang.org> 3 4 use fmt; 5 use getopt; 6 use hare::ast; 7 use hare::module; 8 use hare::parse; 9 use os; 10 use path; 11 use sort; 12 use sort::cmp; 13 14 type deps_fmt = enum { 15 DOT, 16 TERM, 17 }; 18 19 type link = struct { 20 depth: uint, 21 child: size, 22 final: bool, 23 }; 24 25 fn deps(name: str, cmd: *getopt::command) (void | error) = { 26 let tags = default_tags(); 27 defer free(tags); 28 29 let build_dir: str = ""; 30 let goal = deps_fmt::TERM; 31 32 for (let opt .. cmd.opts) { 33 switch (opt.0) { 34 case 'd' => 35 goal = deps_fmt::DOT; 36 case 'T' => 37 merge_tags(&tags, opt.1)?; 38 case => 39 abort(); 40 }; 41 }; 42 43 if (len(cmd.args) > 1) { 44 getopt::printusage(os::stderr, name, cmd.help)!; 45 os::exit(os::status::FAILURE); 46 }; 47 48 const input = if (len(cmd.args) == 0) os::getcwd() else cmd.args[0]; 49 50 let ctx = module::context { 51 harepath = harepath(), 52 harecache = harecache(), 53 tags = tags, 54 }; 55 let mods: []module::module = []; 56 57 let mod = match (parse::identstr(input)) { 58 case let id: ast::ident => 59 yield id; 60 case parse::error => 61 static let buf = path::buffer { ... }; 62 path::set(&buf, os::realpath(input)?)?; 63 yield &buf; 64 }; 65 module::gather(&ctx, &mods, mod)?; 66 defer module::free_slice(mods); 67 68 switch (goal) { 69 case deps_fmt::TERM => 70 deps_graph(&mods); 71 case deps_fmt::DOT => 72 fmt::println("strict digraph deps {")!; 73 for (let mod .. mods) { 74 for (let dep .. mod.deps) { 75 const child = mods[dep.0]; 76 fmt::printfln("\t\"{}\" -> \"{}\";", 77 mod.name, child.name)!; 78 }; 79 }; 80 fmt::println("}")!; 81 }; 82 }; 83 84 fn deps_graph(mods: *[]module::module) void = { 85 if (len(mods) == 1 && len(mods[0].deps) == 0) { 86 fmt::println(mods[0].name, "has no dependencies")!; 87 return; 88 }; 89 90 let links: []link = []; 91 defer free(links); 92 let depth: []uint = alloc([0...], len(mods)); 93 // traverse in reverse because reverse-topo-sort 94 for (let i = len(mods) - 1; i < len(mods); i -= 1) { 95 // reverse-sort deps so that we know the last in the list is the 96 // "final" child during show_deps 97 sort::sort(mods[i].deps, size((size, ast::ident)), &revsort); 98 99 for (let j = 0z; j < len(links); j += 1) { 100 if (i < links[j].child) { 101 continue; 102 }; 103 if (depth[i] <= links[j].depth) { 104 depth[i] = links[j].depth + 1; 105 }; 106 }; 107 108 // print in-between row 109 for (let d = 0u; d < depth[i]; d += 1) { 110 let passing = false; 111 for (let j = 0z; j < len(links); j += 1) { 112 if (i < links[j].child) { 113 continue; 114 }; 115 if (d == links[j].depth) { 116 passing = true; 117 }; 118 }; 119 fmt::print(if (passing) "│ " else " ")!; 120 }; 121 if (i < len(mods) - 1) { 122 fmt::println()!; 123 }; 124 125 // print row itself 126 let on_path = false; 127 for (let d = 0u; d < depth[i]; d += 1) { 128 let connected = false; 129 let passing = false; 130 let final = false; 131 for (let j = 0z; j < len(links); j += 1) { 132 if (i < links[j].child) { 133 continue; 134 }; 135 if (d == links[j].depth) { 136 passing = true; 137 if (i == links[j].child) { 138 connected = true; 139 on_path = true; 140 if (links[j].final) { 141 final = true; 142 }; 143 }; 144 }; 145 }; 146 fmt::print( 147 if (final) "└──" 148 else if (connected) "├──" 149 else if (on_path) "───" 150 else if (passing) "│ " 151 else " " 152 )!; 153 }; 154 fmt::println(mods[i].name)!; 155 for (let j = 0z; j < len(mods[i].deps); j += 1) { 156 append(links, link{ 157 depth = depth[i], 158 child = mods[i].deps[j].0, 159 final = len(mods[i].deps) == j + 1, 160 }); 161 }; 162 }; 163 }; 164 165 // sorts in reverse 166 fn revsort(a: const *opaque, b: const *opaque) int = -cmp::sizes(a, b);