diff options
Diffstat (limited to 'examples/tomodeps')
| -rw-r--r-- | examples/tomodeps/tomodeps.tm | 124 |
1 files changed, 124 insertions, 0 deletions
diff --git a/examples/tomodeps/tomodeps.tm b/examples/tomodeps/tomodeps.tm new file mode 100644 index 00000000..e3354136 --- /dev/null +++ b/examples/tomodeps/tomodeps.tm @@ -0,0 +1,124 @@ +# Show a Tomo dependency graph + +_USAGE := "Usage: dependencies <files...>" + +_HELP := " + dependencies: Show a file dependency graph for Tomo source files. + $_USAGE +" + +enum Dependency(File(path:Path), Module(name:Text)) + +func _get_file_dependencies(file:Path)->{Dependency}: + if not file:is_file(): + !! Could not read file: $file + return {:Dependency} + + deps := {:Dependency} + if lines := file:by_line(): + for line in lines: + if line:matches($/use {..}.tm/): + file_import := Path.from_unsafe_text(line:replace($/use {..}/, "\1")):resolved(relative_to=file) + deps:add(Dependency.File(file_import)) + else if line:matches($/use {id}/): + module_name := line:replace($/use {..}/, "\1") + deps:add(Dependency.Module(module_name)) + return deps + +func _build_dependency_graph(dep:Dependency, dependencies:&{Dependency:{Dependency}}): + return if dependencies:has(dep) + + dependencies:set(dep, {:Dependency}) # Placeholder + + dep_deps := when dep is File(path): + _get_file_dependencies(path) + is Module(module): + files_path := (~/.local/src/tomo/$module/lib$module.files):resolved() + if not files_path:is_file(): + !! Could not read file: $files_path + return + + unvisited := {:Path} + if lines := files_path:by_line(): + for line in lines: + tm_path := Path.from_unsafe_text(line):resolved(relative_to=(~/.local/src/tomo/$module/)) + unvisited:add(tm_path) + + module_deps := {:Dependency} + visited := {:Path} + while unvisited.length > 0: + file := unvisited.items[-1] + unvisited:remove(file) + visited:add(file) + + for file_dep in _get_file_dependencies(file): + when file_dep is File(f): + if not visited:has(f): + unvisited:add(f) + is Module(m): + module_deps:add(file_dep) + module_deps + + dependencies:set(dep, dep_deps) + + for dep2 in dep_deps: + _build_dependency_graph(dep2, dependencies) + +func get_dependency_graph(dep:Dependency)->{Dependency:{Dependency}}: + graph := {:Dependency:{Dependency}} + _build_dependency_graph(dep, &graph) + return graph + +func _printable_name(dep:Dependency)->Text: + when dep is Module(module): + return "$(\x1b)[34;1m$module$(\x1b)[m" + is File(f): + f = f:relative() + if f:exists(): + return "$(f.text_content)" + else: + return "$(\x1b)[31;1m$(f.text_content) (not found)$(\x1b)[m" + +func _draw_tree(dep:Dependency, dependencies:{Dependency:{Dependency}}, already_printed:&{Dependency}, prefix="", is_last=yes): + if already_printed:has(dep): + say(prefix ++ (if is_last: "└── " else: "├── ") ++ _printable_name(dep) ++ " $\x1b[2m(recursive)$\x1b[m") + return + + say(prefix ++ (if is_last: "└── " else: "├── ") ++ _printable_name(dep)) + already_printed:add(dep) + + child_prefix := prefix ++ (if is_last: " " else: "│ ") + + children := dependencies:get(dep):or_else({:Dependency}) + for i,child in children.items: + is_child_last := (i == children.length) + _draw_tree(child, dependencies, already_printed, child_prefix, is_child_last) + +func draw_tree(dep:Dependency, dependencies:{Dependency:{Dependency}}): + printed := {:Dependency} + say(_printable_name(dep)) + printed:add(dep) + deps := dependencies:get(dep):or_else({:Dependency}) + for i,child in deps.items: + is_child_last := (i == deps.length) + _draw_tree(child, dependencies, already_printed=&printed, is_last=is_child_last) + +func main(files:[Text]): + if files.length == 0: + exit(" + Please provide at least one file! + $_USAGE + ") + + for arg in files: + if arg:matches($/{..}.tm/): + path := Path.from_unsafe_text(arg):resolved() + dependencies := get_dependency_graph(File(path)) + draw_tree(File(path), dependencies) + else if arg:matches($/{id}/): + dependencies := get_dependency_graph(Module(arg)) + draw_tree(Module(arg), dependencies) + else: + say("$\x1b[2mSkipping $arg$\x1b[m") + skip + |
