-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a function to sort import dependencies from leaf to root
- Loading branch information
1 parent
c23042c
commit 872fc05
Showing
5 changed files
with
226 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import glance | ||
|
||
pub type GlimpseError(a) { | ||
LoadError(a) | ||
ParseError( | ||
glance_error: glance.Error, | ||
module_name: String, | ||
module_content: String, | ||
) | ||
ImportError(GlimpseImportError) | ||
} | ||
|
||
pub type GlimpseImportError { | ||
CircularDependencyError(module_name: String) | ||
MissingImportError(module_name: String) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
import gleam/dict | ||
import gleam/list | ||
import gleam/result | ||
import gleam/set | ||
import glimpse/error | ||
|
||
/// Type alias for sorting dependencies. | ||
pub type ImportGraph = | ||
dict.Dict(String, List(String)) | ||
|
||
type FoldState { | ||
FoldState(visited: set.Set(String), oldest_list_first: List(String)) | ||
} | ||
|
||
/// Given a dict representing a graph mapping module names to the names of modules | ||
/// that module imports, return a list of all modules in the graph that are reachable | ||
/// via import from the given entry_point module. | ||
/// | ||
/// Any modules in the graph not reachable from the entry_point will be exculided. | ||
/// | ||
/// The resulting list will be ordered from leaf node to entry_point. If you process it | ||
/// in order from head to tail, you will never encounter a module that imports a module | ||
/// that has not already be processed. | ||
/// | ||
/// Returns a CircularDependencyError if there are circular depnedencies, or a NotFoundError | ||
/// if a module imports a module that is not in the input graph. | ||
pub fn sort_dependencies( | ||
dependencies: ImportGraph, | ||
entry_point: String, | ||
) -> Result(List(String), error.GlimpseError(a)) { | ||
sort_dependencies_recursive(dependencies, set.new(), set.new(), entry_point) | ||
|> result.map_error(error.ImportError) | ||
|> result.map(list.reverse) | ||
} | ||
|
||
/// Perform a depth-first sorting of the graph. ancestors of the current node | ||
/// are maintained in a set to detect cycles, and a visited set is used | ||
/// to avoid replicated work. | ||
/// | ||
/// This function is NOT currently tail recursive. | ||
fn sort_dependencies_recursive( | ||
maybe_dag: ImportGraph, | ||
ancestors: set.Set(String), | ||
visited: set.Set(String), | ||
module: String, | ||
) -> Result(List(String), error.GlimpseImportError) { | ||
case | ||
set.contains(ancestors, module), | ||
dict.get(maybe_dag, module), | ||
set.contains(visited, module) | ||
{ | ||
True, _, _ -> Error(error.CircularDependencyError(module)) | ||
False, Error(_), _ -> Error(error.MissingImportError(module)) | ||
False, Ok(_), True -> Ok([]) | ||
False, Ok([]), False -> Ok([module]) | ||
False, Ok(dependencies), False -> { | ||
let next_ancestors = set.insert(ancestors, module) | ||
list.fold(dependencies, Ok(FoldState(visited, [])), fn(state_result, dep) { | ||
use state <- result.try(state_result) | ||
use sort_result <- result.try(sort_dependencies_recursive( | ||
maybe_dag, | ||
next_ancestors, | ||
state.visited, | ||
dep, | ||
)) | ||
let next_visited = | ||
set.from_list(sort_result) |> set.union(state.visited) | ||
Ok(FoldState( | ||
next_visited, | ||
list.append(sort_result, state.oldest_list_first), | ||
)) | ||
}) | ||
|> result.map(fn(state) { list.prepend(state.oldest_list_first, module) }) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
import gleam/dict | ||
import gleeunit/should | ||
import glimpse/error | ||
import glimpse/internal/import_dependencies | ||
|
||
pub fn sort_empty_dependencies_test() { | ||
let graph = dict.from_list([#("main_module", [])]) | ||
|
||
graph | ||
|> import_dependencies.sort_dependencies("main_module") | ||
|> should.be_ok | ||
|> should.equal(["main_module"]) | ||
} | ||
|
||
pub fn sort_simple_dependency_test() { | ||
let graph = | ||
dict.from_list([#("main_module", ["other_module"]), #("other_module", [])]) | ||
|
||
graph | ||
|> import_dependencies.sort_dependencies("main_module") | ||
|> should.be_ok | ||
|> should.equal(["other_module", "main_module"]) | ||
} | ||
|
||
pub fn sort_diamond_dependency_test() { | ||
let graph = | ||
dict.from_list([ | ||
#("main_module", ["a", "b"]), | ||
#("a", ["c"]), | ||
#("b", ["c"]), | ||
#("c", []), | ||
]) | ||
|
||
graph | ||
|> import_dependencies.sort_dependencies("main_module") | ||
|> should.be_ok | ||
|> should.equal(["c", "a", "b", "main_module"]) | ||
} | ||
|
||
pub fn sort_arbitrary_complicated_dependency_test() { | ||
let graph = | ||
dict.from_list([ | ||
#("main_module", ["a"]), | ||
#("a", ["b", "c"]), | ||
#("b", ["d", "g"]), | ||
#("c", ["d"]), | ||
#("d", ["e", "f"]), | ||
#("e", ["g"]), | ||
#("f", ["g", "h"]), | ||
#("g", []), | ||
#("h", []), | ||
]) | ||
|
||
graph | ||
|> import_dependencies.sort_dependencies("main_module") | ||
|> should.be_ok | ||
|> should.equal(["g", "e", "h", "f", "d", "b", "c", "a", "main_module"]) | ||
} | ||
|
||
pub fn sort_complete_binary_tree_dependency_test() { | ||
let graph = | ||
dict.from_list([ | ||
#("main_module", ["a"]), | ||
#("a", ["b", "c"]), | ||
#("b", ["d", "e"]), | ||
#("c", ["f", "g"]), | ||
#("d", ["h", "i"]), | ||
#("e", ["j", "k"]), | ||
#("f", ["l", "m"]), | ||
#("g", ["n", "o"]), | ||
#("h", []), | ||
#("i", []), | ||
#("j", []), | ||
#("k", []), | ||
#("l", []), | ||
#("m", []), | ||
#("n", []), | ||
#("o", []), | ||
]) | ||
|
||
graph | ||
|> import_dependencies.sort_dependencies("main_module") | ||
|> should.be_ok | ||
|> should.equal([ | ||
"h", "i", "d", "j", "k", "e", "b", "l", "m", "f", "n", "o", "g", "c", "a", | ||
"main_module", | ||
]) | ||
} | ||
|
||
pub fn sort_circular_import_test() { | ||
let graph = | ||
dict.from_list([ | ||
#("main_module", ["a"]), | ||
#("a", ["b"]), | ||
#("b", ["c"]), | ||
#("c", ["a"]), | ||
]) | ||
|
||
graph | ||
|> import_dependencies.sort_dependencies("main_module") | ||
|> should.be_error | ||
|> should.equal(error.ImportError(error.CircularDependencyError("a"))) | ||
} | ||
|
||
pub fn sort_missing_import_test() { | ||
let graph = | ||
dict.from_list([ | ||
#("main_module", ["a"]), | ||
#("a", ["b"]), | ||
#("b", ["c"]), | ||
#("c", ["a"]), | ||
]) | ||
|
||
graph | ||
|> import_dependencies.sort_dependencies("main_module") | ||
|> should.be_error | ||
|> should.equal(error.ImportError(error.CircularDependencyError("a"))) | ||
} |