Skip to content

Commit c2dbd68

Browse files
author
m.malkin
committed
draft code for listing deps in console and plantuml
1 parent ec7527c commit c2dbd68

File tree

14 files changed

+171
-28
lines changed

14 files changed

+171
-28
lines changed

lib/clean_mixer.ex

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,6 @@
11
defmodule CleanMixer do
2+
@spec path_to_asset(Path.t()) :: Path.t()
3+
def path_to_asset(path) do
4+
:code.priv_dir(:clean_mixer) |> Path.join(path)
5+
end
26
end

lib/clean_mixer/arch_config.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
defmodule CleanMixer.ArchConfig do
22
alias CleanMixer.ArchMap.Component
33

4-
@type component_map :: %{Component.name() => Path.t()}
4+
@type component_map :: [{Component.name(), Path.t()}]
55

6-
defstruct component_map: %{}
6+
defstruct component_map: []
77

88
@type t :: %__MODULE__{
99
component_map: component_map

lib/clean_mixer/arch_map.ex

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,22 @@ defmodule CleanMixer.ArchMap do
1414
def new(components) do
1515
%__MODULE__{
1616
components: components,
17-
dependencies: find_dependencies(components)
17+
dependencies: build_dependencies(components)
1818
}
1919
end
2020

21-
defp find_dependencies(components) do
21+
@spec dependencies_for(t(), Component.t()) :: list(Dependency.t())
22+
def dependencies_for(%__MODULE__{dependencies: deps}, %Component{} = component) do
23+
Enum.filter(deps, &(&1.source == component))
24+
end
25+
26+
defp build_dependencies(components) do
2227
components
23-
|> Enum.flat_map(&dependencies_for(&1, components))
28+
|> Enum.flat_map(&build_dependencies_for(&1, components))
2429
|> Enum.uniq()
2530
end
2631

27-
defp dependencies_for(component, all_components) do
32+
defp build_dependencies_for(component, all_components) do
2833
other_components = all_components -- [component]
2934

3035
Enum.flat_map(other_components, fn other_comp ->

lib/clean_mixer/arch_map/component.ex

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
defmodule CleanMixer.ArchMap.Component do
22
alias CleanMixer.CodeMap.SourceFile
33
alias CleanMixer.CodeMap.FileDependency
4-
@type name :: String.t()
4+
@type name :: String.t() | atom
55

66
defstruct [:name, :files, :file_dependencies]
77

@@ -18,6 +18,8 @@ defmodule CleanMixer.ArchMap.Component do
1818

1919
@spec file_dependencies(t, t) :: list(FileDependency.t())
2020
def file_dependencies(component, other_component) do
21-
component.file_dependencies |> Enum.filter(&(&1.target in other_component.files))
21+
component.file_dependencies
22+
# TODO test this and refactor
23+
|> Enum.filter(&(&1.target in other_component.files && &1.target not in component.files))
2224
end
2325
end

lib/clean_mixer/arch_map/dependency.ex

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,20 @@ defmodule CleanMixer.ArchMap.Dependency do
22
alias CleanMixer.ArchMap.Component
33
alias CleanMixer.CodeMap.FileDependency
44

5-
defstruct [:source, :target, :file_dependencies]
5+
defstruct [:source, :target, :files]
66

77
@type t :: %__MODULE__{
88
source: Component.t(),
99
target: Component.t(),
10-
file_dependencies: list(FileDependency.t())
10+
files: list(FileDependency.t())
1111
}
1212

1313
@spec new(Component.t(), Component.t(), list(FileDependency.t())) :: t
14-
def new(source, target, file_dependencies) do
14+
def new(source, target, file_deps) do
1515
%__MODULE__{
1616
source: source,
1717
target: target,
18-
file_dependencies: file_dependencies
18+
files: file_deps
1919
}
2020
end
21-
22-
defimpl String.Chars do
23-
def to_string(dep),
24-
do: "#{dep.source.name} -> #{dep.target.name}"
25-
end
2621
end

lib/clean_mixer/code_map.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ defmodule CleanMixer.CodeMap do
3333

3434
new_dependency(source_file, target, ref_types)
3535
end)
36+
|> Enum.reject(&is_nil/1)
3637
end
3738

3839
defp source_file_for(module_name, files) do

lib/clean_mixer/code_map/file_dependency.ex

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,4 @@ defmodule CleanMixer.CodeMap.FileDependency do
1717
ref_types: ref_types |> List.wrap() |> Enum.uniq()
1818
}
1919
end
20-
21-
defimpl String.Chars do
22-
def to_string(dep),
23-
do: "#{dep.source.path} -> #{dep.target.path} (#{dep.ref_types |> Enum.join(",")})"
24-
end
2520
end

lib/mix/tasks/list.ex

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
defmodule Mix.Tasks.CleanMixer.List do
2+
use Mix.Task
3+
4+
@shortdoc "Lists all project components and their deps"
5+
6+
alias Mix.Tasks.CleanMixer.UI.Config
7+
alias Mix.Tasks.CleanMixer.UI.ArchMapRendering.TextFormat
8+
alias CleanMixer.Project
9+
10+
# TODO test this
11+
12+
@impl Mix.Task
13+
def run(_args, _options \\ []) do
14+
Config.load()
15+
|> Project.new()
16+
|> get_arch_map()
17+
|> TextFormat.render()
18+
|> IO.puts()
19+
end
20+
21+
defp get_arch_map(%Project{arch_map: arch_map}), do: arch_map
22+
end

lib/mix/tasks/plant_uml.ex

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
defmodule Mix.Tasks.CleanMixer.PlantUml do
2+
use Mix.Task
3+
4+
@shortdoc "Renders component dependencies with plantuml"
5+
6+
alias Mix.Tasks.CleanMixer.UI.Config
7+
alias Mix.Tasks.CleanMixer.UI.ArchMapRendering.PlantUML
8+
alias CleanMixer.Project
9+
10+
# TODO test this
11+
# refactor this
12+
13+
@file_name "clean_mixer"
14+
@plantuml_file_name "#{@file_name}.plantuml"
15+
@image_file_name "#{@file_name}.png"
16+
17+
@impl Mix.Task
18+
def run(_args, _options \\ []) do
19+
Config.load()
20+
|> Project.new()
21+
|> get_arch_map()
22+
|> PlantUML.render()
23+
|> render_image(@plantuml_file_name)
24+
25+
Mix.Shell.IO.info("image file created at #{@image_file_name}")
26+
end
27+
28+
defp get_arch_map(%Project{arch_map: arch_map}), do: arch_map
29+
30+
defp render_image(uml_data, filename) do
31+
File.write!(filename, uml_data)
32+
Mix.Shell.IO.cmd("java -jar #{planutml_jar_path()} #{filename}")
33+
end
34+
35+
defp planutml_jar_path() do
36+
CleanMixer.path_to_asset("plantuml.jar")
37+
end
38+
end
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
defmodule Mix.Tasks.CleanMixer.UI.ArchMapRendering.PlantUML do
2+
alias CleanMixer.ArchMap
3+
alias CleanMixer.ArchMap.Dependency
4+
5+
# TODO test this
6+
7+
@spec render(ArchMap.t()) :: String.t()
8+
def render(%ArchMap{} = arch_map) do
9+
[
10+
"@startuml",
11+
Enum.map(arch_map.dependencies, &format_dependency/1),
12+
"@enduml"
13+
]
14+
|> List.flatten()
15+
|> Enum.join("\n")
16+
end
17+
18+
defp format_dependency(%Dependency{} = dep) do
19+
"[#{dep.source.name}] --> [#{dep.target.name}]"
20+
end
21+
end
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
defmodule Mix.Tasks.CleanMixer.UI.ArchMapRendering.TextFormat do
2+
alias CleanMixer.ArchMap
3+
alias CleanMixer.ArchMap.Component
4+
alias CleanMixer.ArchMap.Dependency
5+
alias CleanMixer.CodeMap.FileDependency
6+
7+
# TODO test this
8+
9+
@spec render(ArchMap.t()) :: String.t()
10+
def render(%ArchMap{} = arch_map) do
11+
arch_map.components
12+
|> Enum.map(&{&1, ArchMap.dependencies_for(arch_map, &1)})
13+
|> with_dependencies()
14+
|> Enum.map_join("\n\n", &format_component/1)
15+
end
16+
17+
defp with_dependencies(components), do: Enum.reject(components, &match?({_, [] = _deps}, &1))
18+
19+
defp format_component({%Component{}, deps}) do
20+
Enum.map_join(deps, "\n", &format_component_dependency/1)
21+
end
22+
23+
defp format_component_dependency(%Dependency{} = dep) do
24+
[
25+
"===> #{dep.source.name} -> #{dep.target.name}",
26+
"\n",
27+
dep.files |> Enum.group_by(& &1.source) |> Enum.map_join("\n", &format_file_dependencies/1)
28+
]
29+
end
30+
31+
defp format_file_dependencies({source_file, deps}) do
32+
[
33+
[" * #{source_file.path}"],
34+
"\n",
35+
Enum.map_join(deps, "\n", &format_file_dependency/1)
36+
]
37+
end
38+
39+
defp format_file_dependency(%FileDependency{} = dep) do
40+
[" |", "---> #{dep.target.path} (#{dep.ref_types |> Enum.join(",")})"]
41+
end
42+
end

lib/ui/config.ex

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
defmodule Mix.Tasks.CleanMixer.UI.Config do
2+
alias CleanMixer.ArchConfig
3+
4+
@default_name ".clean_mixer.exs"
5+
6+
@spec load :: ArchConfig.t()
7+
def load() do
8+
@default_name
9+
|> File.read!()
10+
|> parse()
11+
end
12+
13+
defp parse(data) do
14+
# TODO raise a meaningfull exc
15+
{params, _} = Code.eval_string(data)
16+
params |> Keyword.fetch!(:components) |> ArchConfig.new()
17+
end
18+
end

priv/plantuml.jar

7.63 MB
Binary file not shown.

test/clean_mixer/project_test.exs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ defmodule CleanMixer.ProjectTest do
2929
end
3030

3131
test "build arch map from code map" do
32-
component_map = %{"component1" => "path1", "component2" => "path2"}
32+
component_map = [component1: "path1", component2: "path2"]
3333

3434
project =
3535
component_map
@@ -38,12 +38,12 @@ defmodule CleanMixer.ProjectTest do
3838

3939
assert [
4040
Component.new(
41-
"component1",
41+
:component1,
4242
[SourceFile.new("path1/file1"), SourceFile.new("path1/file2")],
4343
[FileDependency.new(SourceFile.new("path1/file1"), SourceFile.new("path2/file1"), [:runtime])]
4444
),
4545
Component.new(
46-
"component2",
46+
:component2,
4747
[SourceFile.new("path2/file1")],
4848
[FileDependency.new(SourceFile.new("path2/file1"), SourceFile.new("path2/file2"), [:runtime])]
4949
)
@@ -64,16 +64,16 @@ defmodule CleanMixer.ProjectTest do
6464
end
6565

6666
test "supports nested components" do
67-
component_map = %{"component" => "path", "subcomponent" => "path/subpath"}
67+
component_map = [component: "path", subcomponent: "path/subpath"]
6868

6969
project =
7070
component_map
7171
|> ArchConfig.new()
7272
|> Project.new(CodeCartographer.new(NestedFakeCodeCartographer))
7373

7474
assert [
75-
Component.new("component", [SourceFile.new("path/file"), SourceFile.new("path/subpath/file")]),
76-
Component.new("subcomponent", [SourceFile.new("path/subpath/file")])
75+
Component.new(:component, [SourceFile.new("path/file"), SourceFile.new("path/subpath/file")]),
76+
Component.new(:subcomponent, [SourceFile.new("path/subpath/file")])
7777
] == project.arch_map.components
7878
end
7979
end

0 commit comments

Comments
 (0)