aboutsummaryrefslogtreecommitdiff
path: root/examples/dependencies.tm
blob: 3719e074fcdbceb9e110c4e36072f8bd4b827c2d (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
# 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 main(files:[Text]):
    for f,file in files:
        if not file:matches($/{..}.tm/):
            say("$\x1b[2mSkipping $file$\x1b[m")
            skip

        printed := {:Text}
        resolved := relative_path(file)
        if resolved != "":
            file = resolved

        deps := get_dependency_graph(file)

        say(file)
        printed:add(file)
        children := deps:get(file, {:Text})
        for i,child in children.items:
            is_child_last := (i == children.length)
            draw_tree(child, deps, already_printed=&printed, is_last=is_child_last)