erd_map 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,298 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ErdMap
4
+ class GraphRenderer
5
+ extend Forwardable
6
+ def_delegators :@graph_renderer, :node_renderer
7
+
8
+ attr_reader :graph_renderer
9
+
10
+ VISIBLE = 1.0
11
+ TRANSLUCENT = 0.01
12
+ HIGHLIGHT_NODE_COLOR = "black"
13
+ HIGHLIGHT_EDGE_COLOR = "orange"
14
+ HIGHLIGHT_TEXT_COLOR = "white"
15
+ BASIC_COLOR = "darkslategray"
16
+ EMPTHASIS_NODE_SIZE = 80
17
+
18
+ def renderers
19
+ [circle_renderer, rect_renderer]
20
+ end
21
+
22
+ def cardinality_label
23
+ bokeh_models.LabelSet.new(
24
+ x: "x",
25
+ y: "y",
26
+ text: "text",
27
+ source: cardinality_data_source,
28
+ text_font_size: "12pt",
29
+ text_color: "text_color",
30
+ text_alpha: { field: "alpha" },
31
+ )
32
+ end
33
+
34
+ def js_args(plot)
35
+ {
36
+ graphRenderer: graph_renderer,
37
+ rectRenderer: rect_renderer,
38
+ circleRenderer: circle_renderer,
39
+ layoutProvider: layout_provider,
40
+ cardinalityDataSource: cardinality_data_source,
41
+ connectionsData: graph.connections.to_json,
42
+ layoutsByChunkData: graph.layouts_by_chunk.to_json,
43
+ chunkedNodesData: graph.chunked_nodes.to_json,
44
+ nodeWithCommunityIndexData: graph.node_with_community_index.to_json,
45
+ searchBox: plot.button_set[:search_box],
46
+ selectingNodeLabel: plot.button_set[:selecting_node_label],
47
+ zoomModeToggle: plot.button_set[:zoom_mode_toggle],
48
+ tapModeToggle: plot.button_set[:tap_mode_toggle],
49
+ displayTitleModeToggle: plot.button_set[:display_title_mode_toggle],
50
+ nodeLabels: {
51
+ titleModelLabel: title_model_label,
52
+ foreignModelLabel: foreign_model_label,
53
+ foreignColumnsLabel: foreign_columns_label,
54
+ },
55
+ plot: plot.plot,
56
+ VISIBLE: VISIBLE,
57
+ TRANSLUCENT: TRANSLUCENT,
58
+ HIGHLIGHT_NODE_COLOR: HIGHLIGHT_NODE_COLOR,
59
+ HIGHLIGHT_EDGE_COLOR: HIGHLIGHT_EDGE_COLOR,
60
+ HIGHLIGHT_TEXT_COLOR: HIGHLIGHT_TEXT_COLOR,
61
+ BASIC_COLOR: BASIC_COLOR,
62
+ EMPTHASIS_NODE_SIZE: EMPTHASIS_NODE_SIZE,
63
+ }
64
+ end
65
+
66
+ private
67
+
68
+ attr_reader :bokeh_models
69
+ attr_reader :graph
70
+
71
+ def initialize(graph)
72
+ import_modules = ErdMap.py_call_modules.imported_modules
73
+ @bokeh_models = import_modules[:bokeh_models]
74
+ @graph = graph
75
+ @graph_renderer = circle_renderer
76
+ end
77
+
78
+ def node_data_source
79
+ nodes_x, nodes_y = graph.node_names.map { |node| graph.initial_layout[node] ? graph.initial_layout[node] : graph.whole_layout[node] }.transpose
80
+ nodes_alpha = graph.node_names.map { |node| graph.initial_layout[node] ? VISIBLE : TRANSLUCENT }
81
+
82
+ columns_label = []
83
+ title_label = []
84
+ rect_heights = []
85
+ graph.node_names.map do |node_name|
86
+ title_text = format_text([node_name], title: true)
87
+ columns_text = [*title_text.scan("\n"), "\n", format_text(graph.association_columns[node_name])].join
88
+ columns_label << columns_text
89
+ title_label << [title_text, "\n", *columns_text.scan("\n")].join
90
+
91
+ padding = 36
92
+ line_count = columns_text.scan("\n").size + 1
93
+ rect_heights << line_count * 20 + padding
94
+ end
95
+
96
+ bokeh_models.ColumnDataSource.new(
97
+ data: {
98
+ index: graph.node_names,
99
+ alpha: nodes_alpha,
100
+ x: nodes_x,
101
+ y: nodes_y,
102
+ radius: graph.node_radius,
103
+ original_radius: graph.node_radius,
104
+ rect_height: rect_heights,
105
+ title_label: title_label,
106
+ columns_label: columns_label,
107
+ fill_color: graph.node_colors,
108
+ circle_original_color: graph.node_colors,
109
+ rect_original_color: graph.node_names.map { "white" },
110
+ text_color: graph.node_names.map { BASIC_COLOR },
111
+ text_outline_color: graph.node_names.map { nil },
112
+ }
113
+ )
114
+ end
115
+
116
+ def format_text(columns, title: false)
117
+ max_chars_size = title ? 18 : 20
118
+ columns.flat_map { |column| column.scan(/(\w{1,#{max_chars_size}})/) }.join("\n")
119
+ end
120
+
121
+ def circle_renderer
122
+ @circle_renderer ||= bokeh_models.GraphRenderer.new(
123
+ layout_provider: layout_provider,
124
+ visible: true,
125
+ ).tap do |renderer|
126
+ renderer.node_renderer.data_source = node_data_source
127
+ renderer.node_renderer.glyph = circle_glyph
128
+ renderer.node_renderer.selection_glyph = renderer.node_renderer.glyph
129
+ renderer.node_renderer.nonselection_glyph = renderer.node_renderer.glyph
130
+ renderer.edge_renderer.data_source = edge_data_source
131
+ renderer.edge_renderer.glyph = bokeh_models.MultiLine.new(
132
+ line_color: { field: "line_color" },
133
+ line_alpha: { field: "alpha" },
134
+ line_width: 1,
135
+ )
136
+ end
137
+ end
138
+
139
+ def rect_renderer
140
+ @rect_renderer ||= bokeh_models.GraphRenderer.new(
141
+ layout_provider: layout_provider,
142
+ visible: false,
143
+ ).tap do |renderer|
144
+ renderer.node_renderer.data_source = node_data_source
145
+ renderer.node_renderer.glyph = rect_glyph
146
+ renderer.node_renderer.selection_glyph = renderer.node_renderer.glyph
147
+ renderer.node_renderer.nonselection_glyph = renderer.node_renderer.glyph
148
+ renderer.edge_renderer.data_source = edge_data_source
149
+ renderer.edge_renderer.glyph = bokeh_models.MultiLine.new(
150
+ line_color: { field: "line_color" },
151
+ line_alpha: { field: "alpha" },
152
+ line_width: 1,
153
+ )
154
+ end
155
+ end
156
+
157
+ def circle_glyph
158
+ bokeh_models.Circle.new(
159
+ radius: "radius",
160
+ radius_units: "screen",
161
+ fill_color: { field: "fill_color" },
162
+ fill_alpha: { field: "alpha" },
163
+ line_alpha: { field: "alpha" },
164
+ )
165
+ end
166
+
167
+ def rect_glyph
168
+ bokeh_models.Rect.new(
169
+ width: 150,
170
+ height: { field: "rect_height" },
171
+ width_units: "screen",
172
+ height_units: "screen",
173
+ fill_color: { field: "fill_color" },
174
+ fill_alpha: { field: "alpha" },
175
+ line_color: BASIC_COLOR,
176
+ line_alpha: { field: "alpha" },
177
+ )
178
+ end
179
+
180
+ def title_model_label
181
+ bokeh_models.LabelSet.new(
182
+ x: "x",
183
+ y: "y",
184
+ text: "index",
185
+ source: graph_renderer.node_renderer.data_source,
186
+ text_font_size: "12pt",
187
+ text_color: { field: "text_color" },
188
+ text_outline_color: { field: "text_outline_color" },
189
+ text_align: "center",
190
+ text_baseline: "middle",
191
+ text_alpha: { field: "alpha" },
192
+ visible: true,
193
+ )
194
+ end
195
+
196
+ def foreign_model_label
197
+ bokeh_models.LabelSet.new(
198
+ x: "x",
199
+ y: "y",
200
+ text: { field: "title_label" },
201
+ source: graph_renderer.node_renderer.data_source,
202
+ text_font_size: "10pt",
203
+ text_font_style: "bold",
204
+ text_color: { field: "text_color" },
205
+ text_outline_color: { field: "text_outline_color" },
206
+ text_align: "center",
207
+ text_baseline: "middle",
208
+ text_alpha: { field: "alpha" },
209
+ # visible: false,
210
+ )
211
+ end
212
+
213
+ def foreign_columns_label
214
+ bokeh_models.LabelSet.new(
215
+ x: "x",
216
+ y: "y",
217
+ text: { field: "columns_label" },
218
+ source: graph_renderer.node_renderer.data_source,
219
+ text_font_size: "10pt",
220
+ text_font_style: "normal",
221
+ text_color: { field: "text_color" },
222
+ text_outline_color: { field: "text_outline_color" },
223
+ text_align: "center",
224
+ text_baseline: "middle",
225
+ text_alpha: { field: "alpha" },
226
+ # visible: false,
227
+ )
228
+ end
229
+
230
+ def edge_data_source
231
+ edge_start, edge_end = graph.edges.map { |edge| [edge[0], edge[1]] }.transpose
232
+ edges_alpha = graph.edges.map { |edge| graph.initial_nodes.include?(edge[0]) && graph.initial_nodes.include?(edge[1]) ? VISIBLE : TRANSLUCENT }
233
+ bokeh_models.ColumnDataSource.new(
234
+ data: {
235
+ start: edge_start,
236
+ end: edge_end,
237
+ alpha: edges_alpha,
238
+ line_color: graph.edges.map { BASIC_COLOR },
239
+ }
240
+ )
241
+ end
242
+
243
+ def cardinality_data_source
244
+ return @cardinality_data_source if @cardinality_data_source
245
+
246
+ @cardinality_data_source = bokeh_models.ColumnDataSource.new(
247
+ data: {
248
+ x: [],
249
+ y: [],
250
+ source: [],
251
+ target: [],
252
+ text: [],
253
+ alpha: [],
254
+ text_color: [],
255
+ }
256
+ )
257
+
258
+ graph.edges.each do |(source_node, target_node)|
259
+ next if source_node == target_node
260
+
261
+ label_alpha = graph.initial_nodes.include?(source_node) && graph.initial_nodes.include?(target_node) ?
262
+ VISIBLE : 0
263
+
264
+ x_offset = 0.2
265
+ y_offset = 0.3
266
+ source_x, source_y = graph.initial_layout[source_node] || graph.whole_layout[source_node]
267
+ target_x, target_y = graph.initial_layout[target_node] || graph.whole_layout[target_node]
268
+ vector_x = target_x - source_x
269
+ vector_y = target_y - source_y
270
+ length = Math.sqrt(vector_x**2 + vector_y**2)
271
+
272
+ @cardinality_data_source.data[:x] << source_x + (vector_x / length) * x_offset
273
+ @cardinality_data_source.data[:y] << source_y + (vector_y / length) * y_offset
274
+ @cardinality_data_source.data[:source] << source_node
275
+ @cardinality_data_source.data[:target] << target_node
276
+ @cardinality_data_source.data[:text] << "1"
277
+ @cardinality_data_source.data[:alpha] << label_alpha
278
+
279
+ @cardinality_data_source.data[:x] << target_x - (vector_x / length) * x_offset
280
+ @cardinality_data_source.data[:y] << target_y - (vector_y / length) * y_offset
281
+ @cardinality_data_source.data[:source] << source_node
282
+ @cardinality_data_source.data[:target] << target_node
283
+ @cardinality_data_source.data[:text] << "n" # FIXME: Show "1" when has_one association
284
+ @cardinality_data_source.data[:alpha] << label_alpha
285
+ end
286
+ @cardinality_data_source.data[:text_color] = Array.new(@cardinality_data_source.data[:x].to_a.size) { BASIC_COLOR }
287
+ @cardinality_data_source
288
+ end
289
+
290
+ def layout_provider
291
+ return @layout_provider if @layout_provider
292
+
293
+ nodes_x, nodes_y = graph.node_names.map { |node| graph.initial_layout[node] ? graph.initial_layout[node] : graph.whole_layout[node] }.transpose
294
+ graph_layout = graph.node_names.zip(nodes_x, nodes_y).map { |node, x, y| [node, [x, y]] }.to_h
295
+ @layout_provider = bokeh_models.StaticLayoutProvider.new(graph_layout: graph_layout)
296
+ end
297
+ end
298
+ end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ErdMap
4
+ class MapBuilder
5
+ def execute
6
+ import_modules
7
+ @graph = ErdMap::Graph.new
8
+ @graph_renderer = ErdMap::GraphRenderer.new(@graph)
9
+ save(build_layout)
10
+ end
11
+
12
+ private
13
+
14
+ attr_reader :bokeh_io, :bokeh_models, :bokeh_plotting
15
+ attr_reader :graph, :graph_renderer
16
+
17
+ def import_modules
18
+ import_modules = ErdMap.py_call_modules.imported_modules
19
+ @bokeh_io = import_modules[:bokeh_io]
20
+ @bokeh_models = import_modules[:bokeh_models]
21
+ @bokeh_plotting = import_modules[:bokeh_plotting]
22
+ end
23
+
24
+ def build_layout
25
+ plot = Plot.new(graph)
26
+ graph_renderer.renderers.each do |renderer|
27
+ plot.renderers.append(renderer)
28
+ renderer.node_renderer.data_source.selected.js_on_change("indices", toggle_tapped)
29
+ end
30
+ plot.add_layout(graph_renderer.cardinality_label)
31
+ bokeh_io.curdoc.js_on_event("document_ready", setup_graph_manager(plot))
32
+
33
+ bokeh_models.Column.new(
34
+ children: [
35
+ bokeh_models.Row.new(
36
+ children: [
37
+ plot.button_set[:left_spacer],
38
+ plot.button_set[:selecting_node_label],
39
+ plot.button_set[:search_box],
40
+ plot.button_set[:zoom_mode_toggle],
41
+ plot.button_set[:tap_mode_toggle],
42
+ plot.button_set[:display_title_mode_toggle],
43
+ plot.button_set[:re_layout_button],
44
+ plot.button_set[:zoom_in_button],
45
+ plot.button_set[:zoom_out_button],
46
+ plot.button_set[:re_compute_button],
47
+ plot.button_set[:right_spacer],
48
+ ],
49
+ sizing_mode: "stretch_width",
50
+ ),
51
+ plot.plot,
52
+ ],
53
+ sizing_mode: "stretch_both",
54
+ )
55
+ end
56
+
57
+ def save(layout)
58
+ tmp_dir = Rails.root.join("tmp", "erd_map")
59
+ FileUtils.makedirs(tmp_dir) unless Dir.exist?(tmp_dir)
60
+ output_path = File.join(tmp_dir, "map.html")
61
+
62
+ bokeh_io.output_file(output_path)
63
+ bokeh_io.save(layout)
64
+ puts output_path
65
+ end
66
+
67
+ def setup_graph_manager(plot)
68
+ bokeh_models.CustomJS.new(
69
+ args: graph_renderer.js_args(plot),
70
+ code: <<~JS
71
+ #{graph_manager}
72
+ window.graphManager = new GraphManager({
73
+ graphRenderer,
74
+ rectRenderer,
75
+ circleRenderer,
76
+ layoutProvider,
77
+ connectionsData,
78
+ layoutsByChunkData,
79
+ chunkedNodesData,
80
+ nodeWithCommunityIndexData,
81
+ selectingNodeLabel,
82
+ searchBox,
83
+ zoomModeToggle,
84
+ tapModeToggle,
85
+ displayTitleModeToggle,
86
+ nodeLabels,
87
+ plot,
88
+ windowObj: window,
89
+ })
90
+ JS
91
+ )
92
+ end
93
+
94
+ def toggle_tapped
95
+ bokeh_models.CustomJS.new(
96
+ code: <<~JS
97
+ window.graphManager.cbObj = cb_obj
98
+ window.graphManager.toggleTapped()
99
+ JS
100
+ )
101
+ end
102
+
103
+ def graph_manager
104
+ @graph_manager ||= File.read(__dir__ + "/graph_manager.js")
105
+ end
106
+
107
+ class << self
108
+ def build
109
+ Rails.logger.info "build start"
110
+ new.execute
111
+ Rails.logger.info "build completed"
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,195 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ErdMap
4
+ class Plot
5
+ extend Forwardable
6
+ def_delegators :@plot, :renderers, :add_layout
7
+
8
+ def plot
9
+ return @plot if @plot
10
+
11
+ padding_ratio = 0.1
12
+ x_min, x_max, y_min, y_max = graph.initial_layout.values.transpose.map(&:minmax).flatten
13
+ x_padding, y_padding = [(x_max - x_min) * padding_ratio, (y_max - y_min) * padding_ratio]
14
+ @plot = bokeh_models.Plot.new(
15
+ sizing_mode: "stretch_both",
16
+ x_range: bokeh_models.Range1d.new(start: x_min - x_padding, end: x_max + x_padding),
17
+ y_range: bokeh_models.Range1d.new(start: y_min - y_padding, end: y_max + y_padding),
18
+ tools: [
19
+ wheel_zoom_tool = bokeh_models.WheelZoomTool.new,
20
+ bokeh_models.ResetTool.new,
21
+ bokeh_models.PanTool.new,
22
+ bokeh_models.TapTool.new,
23
+ ],
24
+ ).tap do |plot|
25
+ plot.toolbar.active_scroll = wheel_zoom_tool
26
+ end
27
+ end
28
+
29
+ def button_set
30
+ @button_set ||= ButtonSet.new
31
+ end
32
+
33
+ private
34
+
35
+ attr_reader :graph
36
+
37
+ def initialize(graph)
38
+ @graph = graph
39
+ register_callback
40
+ end
41
+
42
+ def register_callback
43
+ plot.x_range.js_on_change("start", custom_js("triggerZoom"))
44
+ plot.x_range.js_on_change("end", custom_js("triggerZoom"))
45
+ plot.js_on_event("mousemove", custom_js("toggleHovered"))
46
+ plot.js_on_event("mousemove", bokeh_models.CustomJS.new(code: save_mouse_position))
47
+ plot.js_on_event("reset", custom_js("resetPlot"))
48
+ end
49
+
50
+ def bokeh_models
51
+ @bokeh_models ||= ErdMap.py_call_modules.imported_modules[:bokeh_models]
52
+ end
53
+
54
+ def custom_js(function_name)
55
+ bokeh_models.CustomJS.new(
56
+ code: <<~JS
57
+ window.graphManager.cbObj = cb_obj
58
+ window.graphManager.#{function_name}()
59
+ JS
60
+ )
61
+ end
62
+
63
+ def save_mouse_position
64
+ <<~JS
65
+ if (window.saveMousePosition !== undefined) { clearTimeout(window.saveMousePosition) }
66
+ window.saveMousePosition = setTimeout(function() {
67
+ window.lastMouseX = cb_obj.x
68
+ window.lastMouseY = cb_obj.y
69
+ }, 100)
70
+ JS
71
+ end
72
+
73
+ class ButtonSet
74
+ extend Forwardable
75
+ def_delegators :@button_set, :[]
76
+
77
+ private
78
+
79
+ def initialize
80
+ @button_set = {
81
+ left_spacer: left_spacer,
82
+ selecting_node_label: selecting_node_label,
83
+ search_box: search_box,
84
+ zoom_mode_toggle: zoom_mode_toggle,
85
+ tap_mode_toggle: tap_mode_toggle,
86
+ display_title_mode_toggle: display_title_mode_toggle,
87
+ re_layout_button: re_layout_button,
88
+ zoom_in_button: zoom_in_button,
89
+ zoom_out_button: zoom_out_button,
90
+ re_compute_button: re_compute_button,
91
+ right_spacer: right_spacer,
92
+ }
93
+ end
94
+
95
+ def left_spacer
96
+ bokeh_models.Spacer.new(width: 0, sizing_mode: "stretch_width")
97
+ end
98
+
99
+ def right_spacer
100
+ bokeh_models.Spacer.new(width: 30, sizing_mode: "fixed")
101
+ end
102
+
103
+ def selecting_node_label
104
+ bokeh_models.Div.new(
105
+ text: "",
106
+ height: 28,
107
+ styles: { display: :flex, align_items: :center },
108
+ )
109
+ end
110
+
111
+ def search_box
112
+ bokeh_models.TextInput.new(placeholder: "🔍 Search model", width: 200).tap do |input|
113
+ input.js_on_change("value", custom_js("searchNodes"))
114
+ end
115
+ end
116
+
117
+ def zoom_mode_toggle
118
+ bokeh_models.Button.new(label: "Wheel mode: fix", button_type: "default").tap do |button|
119
+ button.js_on_click(custom_js("toggleZoomMode"))
120
+ end
121
+ end
122
+
123
+ def tap_mode_toggle
124
+ bokeh_models.Button.new(label: "Tap mode: association", button_type: "default").tap do |button|
125
+ button.js_on_click(custom_js("toggleTapMode"))
126
+ end
127
+ end
128
+
129
+ def display_title_mode_toggle
130
+ bokeh_models.Button.new(label: "Display mode: title", button_type: "default").tap do |button|
131
+ button.js_on_click(custom_js("toggleDisplayTitleMode"))
132
+ end
133
+ end
134
+
135
+ def re_layout_button
136
+ bokeh_models.Button.new(label: "Re-Layout", button_type: "default").tap do |button|
137
+ button.js_on_click(custom_js("reLayout"))
138
+ end
139
+ end
140
+
141
+ def zoom_in_button
142
+ bokeh_models.Button.new(label: "Zoom In", button_type: "primary").tap do |button|
143
+ button.js_on_click(custom_js("zoomIn",))
144
+ end
145
+ end
146
+
147
+ def zoom_out_button
148
+ bokeh_models.Button.new(label: "Zoom Out", button_type: "success").tap do |button|
149
+ button.js_on_click(custom_js("zoomOut"))
150
+ end
151
+ end
152
+
153
+ def re_compute_button
154
+ bokeh_models.Button.new(label: "Re-Compute", button_type: "default").tap do |button|
155
+ button.js_on_click(
156
+ bokeh_models.CustomJS.new(
157
+ args: { button: button },
158
+ code: <<~JS
159
+ button.disabled = true
160
+ button.label = "Computing ..."
161
+
162
+ fetch("/erd_map", { method: "PUT" })
163
+ .then(response => {
164
+ if (response.ok) { return response.text() }
165
+ else { return response.json().then(json => { throw new Error(json.message) }) }
166
+ })
167
+ .then(data => { window.location.reload() })
168
+ .catch(error => {
169
+ alert(error.message)
170
+ console.error(error)
171
+
172
+ button.disabled = false
173
+ button.label = "Re-Compute"
174
+ })
175
+ JS
176
+ )
177
+ )
178
+ end
179
+ end
180
+
181
+ def bokeh_models
182
+ @bokeh_models ||= ErdMap.py_call_modules.imported_modules[:bokeh_models]
183
+ end
184
+
185
+ def custom_js(function_name)
186
+ bokeh_models.CustomJS.new(
187
+ code: <<~JS
188
+ window.graphManager.cbObj = cb_obj
189
+ window.graphManager.#{function_name}()
190
+ JS
191
+ )
192
+ end
193
+ end
194
+ end
195
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pycall"
4
+
5
+ module ErdMap
6
+ class PyCallModules
7
+ def initialize
8
+ @nx = PyCall.import_module("networkx")
9
+ @bokeh_io = PyCall.import_module("bokeh.io")
10
+ @bokeh_models = PyCall.import_module("bokeh.models")
11
+ @bokeh_plotting = PyCall.import_module("bokeh.plotting")
12
+ @networkx_community = PyCall.import_module("networkx.algorithms.community")
13
+ end
14
+
15
+ def imported_modules
16
+ {
17
+ nx: @nx,
18
+ bokeh_io: @bokeh_io,
19
+ bokeh_models: @bokeh_models,
20
+ bokeh_plotting: @bokeh_plotting,
21
+ networkx_community: @networkx_community,
22
+ }
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,3 @@
1
+ module ErdMap
2
+ VERSION = "0.1.0"
3
+ end
data/lib/erd_map.rb ADDED
@@ -0,0 +1,19 @@
1
+ require "erd_map/version"
2
+ require "erd_map/engine"
3
+ require "erd_map/py_call_modules"
4
+ require "erd_map/graph"
5
+ require "erd_map/graph_renderer"
6
+ require "erd_map/map_builder"
7
+ require "erd_map/plot"
8
+
9
+ module ErdMap
10
+ class << self
11
+ def py_call_modules
12
+ @py_call_modules
13
+ end
14
+
15
+ def py_call_modules=(py_call_modules)
16
+ @py_call_modules = py_call_modules
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ desc "Compute erd_map"
4
+ task erd_map: :environment do
5
+ puts "Map computing start."
6
+ ErdMap::MapBuilder.build
7
+ puts "Map computing completed."
8
+ end