aboutsummaryrefslogtreecommitdiff
path: root/examples/tomodeps.tm
blob: 032a87b75cb9a19bcce68a71aec2e1b7583f1955 (plain)
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
# 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:
        exit(1, message="
            Please provide at least one file!
            $_USAGE
        ")

    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)