deps.ha (4372B)
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 TEXT, 18 }; 19 20 type link = struct { 21 depth: uint, 22 child: size, 23 final: bool, 24 }; 25 26 fn deps(name: str, cmd: *getopt::command) (void | error) = { 27 let tags = default_tags(); 28 defer free(tags); 29 30 let build_dir: str = ""; 31 let goal = deps_fmt::TERM; 32 let recursive = true; 33 let submodules = false; 34 35 for (let opt .. cmd.opts) { 36 switch (opt.0) { 37 case 'D' => 38 recursive = false; 39 case 'd' => 40 goal = deps_fmt::DOT; 41 case 'T' => 42 merge_tags(&tags, opt.1)?; 43 case 's' => 44 submodules = true; 45 case 't' => 46 goal = deps_fmt::TEXT; 47 case => 48 abort(); 49 }; 50 }; 51 52 if (len(cmd.args) > 1) { 53 getopt::printusage(os::stderr, name, cmd.help)!; 54 os::exit(os::status::FAILURE); 55 }; 56 57 const input = if (len(cmd.args) == 0) os::getcwd() else cmd.args[0]; 58 59 let ctx = module::context { 60 harepath = harepath(), 61 harecache = harecache(), 62 tags = tags, 63 }; 64 let mods: []module::module = []; 65 defer module::free_slice(mods); 66 67 let mod = match (parse::identstr(input)) { 68 case let id: ast::ident => 69 yield id; 70 case parse::error => 71 static let p = path::path { ... }; 72 path::set(&p, os::realpath(input)?)?; 73 yield &p; 74 }; 75 76 if (submodules) { 77 module::gather_submodules(&ctx, &mods, mod, recursive)?; 78 }; 79 80 match (module::gather(&ctx, &mods, mod, recursive)) { 81 case let err: module::error => 82 if (!(module::unwrap_error(err) is module::not_found) || 83 len(mods) == 0) { 84 return err; 85 }; 86 case => 87 void; 88 }; 89 90 switch (goal) { 91 case deps_fmt::TERM => 92 deps_graph(&mods); 93 case deps_fmt::DOT => 94 fmt::println("strict digraph deps {")!; 95 for (let mod .. mods) { 96 if (module::gathered(&mod) && len(mod.deps) == 0) { 97 fmt::printfln("\t\"{}\"", mod.name)!; 98 } else for (let dep .. mod.deps) { 99 const child = mods[dep.0]; 100 fmt::printfln("\t\"{}\" -> \"{}\";", 101 mod.name, child.name)!; 102 }; 103 }; 104 fmt::println("}")!; 105 case deps_fmt::TEXT => 106 for (let mod .. mods) { 107 if (module::gathered(&mod) && len(mod.deps) == 0) { 108 fmt::printfln("{} -", mod.name)!; 109 } else for (let dep .. mod.deps) { 110 const child = mods[dep.0]; 111 fmt::printfln("{} {}", mod.name, child.name)!; 112 }; 113 }; 114 }; 115 }; 116 117 fn deps_graph(mods: *[]module::module) void = { 118 if (len(mods) == 1 && len(mods[0].deps) == 0) { 119 fmt::println(mods[0].name, "has no dependencies")!; 120 return; 121 }; 122 123 let links: []link = []; 124 defer free(links); 125 let depth: []uint = alloc([0...], len(mods))!; 126 // traverse in reverse because reverse-topo-sort 127 for (let i = len(mods) - 1; i < len(mods); i -= 1) { 128 // reverse-sort deps so that we know the last in the list is the 129 // "final" child during show_deps 130 sort::sort(mods[i].deps, size((size, ast::ident)), &revsort); 131 132 for (let j = 0z; j < len(links); j += 1) { 133 if (i < links[j].child) { 134 continue; 135 }; 136 if (depth[i] <= links[j].depth) { 137 depth[i] = links[j].depth + 1; 138 }; 139 }; 140 141 // print in-between row 142 for (let d = 0u; d < depth[i]; d += 1) { 143 let passing = false; 144 for (let j = 0z; j < len(links); j += 1) { 145 if (i < links[j].child) { 146 continue; 147 }; 148 if (d == links[j].depth) { 149 passing = true; 150 }; 151 }; 152 fmt::print(if (passing) "│ " else " ")!; 153 }; 154 if (i < len(mods) - 1) { 155 fmt::println()!; 156 }; 157 158 // print row itself 159 let on_path = false; 160 for (let d = 0u; d < depth[i]; d += 1) { 161 let connected = false; 162 let passing = false; 163 let final = false; 164 for (let j = 0z; j < len(links); j += 1) { 165 if (i < links[j].child) { 166 continue; 167 }; 168 if (d == links[j].depth) { 169 passing = true; 170 if (i == links[j].child) { 171 connected = true; 172 on_path = true; 173 if (links[j].final) { 174 final = true; 175 }; 176 }; 177 }; 178 }; 179 fmt::print( 180 if (final) "└──" 181 else if (connected) "├──" 182 else if (on_path) "───" 183 else if (passing) "│ " 184 else " " 185 )!; 186 }; 187 fmt::println(mods[i].name)!; 188 for (let j = 0z; j < len(mods[i].deps); j += 1) { 189 append(links, link{ 190 depth = depth[i], 191 child = mods[i].deps[j].0, 192 final = len(mods[i].deps) == j + 1, 193 })!; 194 }; 195 }; 196 }; 197 198 // sorts in reverse 199 fn revsort(a: const *opaque, b: const *opaque) int = -cmp::sizes(a, b);