diff --git a/scripts/depgraph.luau b/scripts/depgraph.luau index 1c661de..c26581f 100644 --- a/scripts/depgraph.luau +++ b/scripts/depgraph.luau @@ -5,8 +5,10 @@ -- module for generating a dependency graph for all libraries local remove_from_end = require("remove_from_end") +local require_pattern = require("require_pattern") +local iter_scripts = require("iter_scripts") local process = require("@lune/process") -local ends_with = require("ends_with") +local remove = require("remove") local fs = require("@lune/fs") export type DependencyInfo = { @@ -15,30 +17,11 @@ export type DependencyInfo = { deep: { string }, } -local REQUIRE_PATTERN = "local%s+%w+%s*=%s*require%s*%(%-?%-?%-?[\"'`](.-)[\"'`]%)" local GRAPH = {} :: { [string]: DependencyInfo } -local LINE_PATTERN = "[^\r\n]+" +local FOUND = {} :: { [string]: boolean } +local DEPQUEUE = {} :: { string } local GMATCH = string.gmatch local MATCH = string.match -local SUB = string.sub - -local function REMOVE_OR_ERROR(t: { V }, index: number?): V - local value = table.remove(t, index) - - if not value then - error("this is supposed to be unreachable") - end - return value -end - -local function find_lib_in_relative_path(path: string): string? - local found_lib = MATCH(remove_from_end(path, "/init"), "([^/]+)$") - - return if found_lib and GRAPH[found_lib] then - found_lib - else - nil -end for _, lib in fs.readDir("libs") do GRAPH[lib] = { @@ -49,69 +32,79 @@ for _, lib in fs.readDir("libs") do end for lib, depinfo in GRAPH do - local dirqueue = { `libs/{lib}` } local shallow = depinfo.shallow - - while #dirqueue ~= 0 do - local current_dir = REMOVE_OR_ERROR(dirqueue, 1) - - for _, entry in fs.readDir(current_dir) do - local entry_path = `{current_dir}/{entry}` - - if ends_with(entry, ".luau") then - local contents = fs.readFile(entry_path) - - for require_path in GMATCH(contents, REQUIRE_PATTERN) do - local deplib = find_lib_in_relative_path(require_path) - - if deplib and not table.find(shallow, deplib) then - table.insert(shallow, deplib) - end - end - elseif fs.isDir(entry_path) then - table.insert(dirqueue, entry_path) - end - end - end + + iter_scripts(`libs/{lib}`, function(_, src) + for require_path in GMATCH(src, require_pattern) do + local deplib = MATCH(remove_from_end(require_path, "/init"), "([^/]+)$") + + if deplib and GRAPH[deplib] and not table.find(shallow, deplib) then + table.insert(shallow, deplib) + end + end + return nil + end) end for lib, depinfo in GRAPH do local shallow = depinfo.shallow - local depqueue = table.clone(depinfo.shallow) + local shallow_len = #shallow local deep = depinfo.deep - local found = {} - - while #depqueue ~= 0 do - local current = REMOVE_OR_ERROR(depqueue, 1) + + if shallow_len ~= 0 then + local queue_len = shallow_len + table.move(shallow, 1, queue_len, 1, DEPQUEUE) + + while queue_len ~= 0 do + local current = remove(DEPQUEUE, queue_len) + queue_len -= 1 - if not found[current] then - table.insert(deep, current) - found[current] = true + if not FOUND[current] then + table.insert(deep, current) + FOUND[current] = true - -- Add the shallow dependencies of the current item to the queue - for _, dep in GRAPH[current].shallow do - table.insert(depqueue, dep) - end - end - end - - if #deep == #shallow then + -- Add the shallow dependencies of the current item to the queue + for _, dep in GRAPH[current].shallow do + queue_len += 1 + DEPQUEUE[queue_len] = dep + end + end + end + + depinfo.has_deps = true + table.clear(DEPQUEUE) + table.clear(FOUND) + end + + if #deep == shallow_len then depinfo.deep = table.freeze(shallow) else table.freeze(shallow) table.freeze(deep) end -end -for _, depinfo in GRAPH do - if #depinfo.shallow ~= 0 then - depinfo.has_deps = true - end table.freeze(depinfo) end if process.args[1] == "print" then - print(GRAPH) + local pretty_graph = {} :: { [string]: { shallow: { string }, deep: { string } } | { string } } + + for lib, depinfo in GRAPH do + local shallow = depinfo.shallow + local deep = depinfo.deep + + if depinfo.has_deps then + if shallow ~= deep then + pretty_graph[lib] = { + shallow = shallow, + deep = deep, + } + else + pretty_graph[lib] = shallow + end + end + end + print(pretty_graph) end return table.freeze(GRAPH) \ No newline at end of file