1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
|
# Show a Tomo dependency graph
use file
_USAGE := "Usage: dependencies <files...>"
_HELP := "
dependencies: Show a file dependency graph for Tomo source files.
$_USAGE
"
func _get_module_imports_from_file(file:Text, imports:&{Text}, visited_files:&{Text}):
return if visited_files:has(file)
reader := when LineReader.from_file(file) is Failure(msg):
!! Failed: $msg
return
is Open(reader): reader
visited_files:add(file)
while when reader:next_line() is Success(line):
if line:matches($/use {..}.tm/):
local_import := line:replace($/use {..}/, "\1")
resolved := relative_path(resolve_path(local_import, file))
if resolved != "":
local_import = resolved
_get_module_imports_from_file(local_import, imports, visited_files)
else if line:matches($/use {id}/):
other_module := line:replace($/use {..}/, "\1")
imports:add(other_module)
func _get_module_imports_from_module(module:Text)->{Text}:
files_path := resolve_path("~/.local/src/tomo/$module/lib$(module).files")
if files_path == "":
!! couldn't resolve: $files_path
return {:Text}
when read(files_path) is Failure(msg):
!! couldn't read: $files_path $msg
return {:Text}
is Success(files_content):
imports := {:Text}
visited := {:Text}
for line in files_content:lines():
line_resolved := resolve_path(line, relative_to="~/.local/src/tomo/$module/")
skip if line_resolved == ""
_get_module_imports_from_file(line_resolved, &imports, &visited)
return imports
func _build_module_dependency_graph(module:Text, dependencies:&{Text:@{Text}}):
return if dependencies:has(module)
module_deps := @{:Text}
dependencies:set(module, module_deps)
for dep in _get_module_imports_from_module(module):
module_deps:add(dep)
_build_module_dependency_graph(dep, dependencies)
func _build_file_dependency_graph(filename:Text, dependencies:&{Text:@{Text}}):
return if dependencies:has(filename)
reader := when LineReader.from_file(filename) is Failure(msg):
!! Failed: $msg
return
is Open(reader): reader
file_deps := @{:Text}
dependencies:set(filename, file_deps)
while when reader:next_line() is Success(line):
if line:matches($/use {..}.tm/):
used_file := line:replace($/use {..}/, "\1")
resolved := relative_path(resolve_path(used_file, filename))
if resolved != "":
used_file = resolved
file_deps:add(used_file)
_build_file_dependency_graph(used_file, dependencies)
else if line:matches($/use {id}/):
module := line:replace($/use {..}/, "\1")
file_deps:add(module)
_build_module_dependency_graph(module, dependencies)
func get_dependency_graph(file:Text)->{Text:{Text}}:
graph := {:Text:@{Text}}
resolved := relative_path(file)
_build_file_dependency_graph(resolved, &graph)
return {f:deps[] for f,deps in graph}
func _draw_tree(file:Text, dependencies:{Text:{Text}}, already_printed:&{Text}, prefix="", is_last=yes):
color_file := if file:matches($/{id}/):
"$\x1b[34;1m$file$\x1b[m"
else if resolve_path(file) != "":
file
else:
"$\x1b[31;1m$file (could not resolve)$\x1b[m"
if already_printed:has(file):
say(prefix ++ (if is_last: "└── " else: "├── ") ++ color_file ++ " $\x1b[2m(recursive)$\x1b[m")
return
say(prefix ++ (if is_last: "└── " else: "├── ") ++ color_file)
already_printed:add(file)
child_prefix := prefix ++ (if is_last: " " else: "│ ")
children := dependencies:get(file, {:Text})
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(file:Text, dependencies:{Text:{Text}}):
printed := {:Text}
resolved := relative_path(file)
if resolved != "":
file = resolved
say(file)
printed:add(file)
children := dependencies:get(file, {:Text})
for i,child in children.items:
is_child_last := (i == children.length)
_draw_tree(child, dependencies, already_printed=&printed, is_last=is_child_last)
func main(files:[Text]):
if files.length == 0:
say("
Please provide at least one file!
$_USAGE
")
return
for file in files:
if not file:matches($/{..}.tm/):
say("$\x1b[2mSkipping $file$\x1b[m")
skip
dependencies := get_dependency_graph(file)
draw_tree(file, dependencies)
|