lutaml 0.3.3 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a4cf5092c3121c8b3c2e3caa93d8b5fb9b041eb1bdd563e2ad7d5b803f16685f
4
- data.tar.gz: 0e7e102b348acc3b3b7506ec8403f7e598611297c8fe0a3ba82fc4855686d0af
3
+ metadata.gz: 84bb179e752bc157f0de4a59ea8cf88e9bd1fe4430726dcefcf63269d12c86fb
4
+ data.tar.gz: a5f8917e80a33814b9cc6a9e3556b335da72fe05fd73e4d54dc294d1c4b6a27f
5
5
  SHA512:
6
- metadata.gz: f5ee064152d5f79c6f1e37ea3fa9f81fdd494b03108868396839d7c09200d5757a3303d187776d3bdfd8bb895b17e9a73720bdea1bd2d919f4ff1c276375b269
7
- data.tar.gz: 50fef95d71aa35883365b2e5fd7889dafc2ba4a7b6bed62333f8e4ba8ebe281ec45550f0a8b08871573b4c8757f8cbdc94488a08fc0df3df490b8f74f5d4dbd2
6
+ metadata.gz: 64b11704d20aff55c53cb6b1d857d346a6e9016b84b091581372c38b100a3d21d499685550e8d5a8b132e4cc7de5e8da37c3efd14ae325d119a207a76fc0feea
7
+ data.tar.gz: f9800bc7e40536370807a3e4a3429d60dfa1f2fd728054adf6c9a8e73ba813b20bad80e22c85323309089eb41e1455806381513c139de1a99cc804ebb37378f2
@@ -30,6 +30,8 @@ jobs:
30
30
  uses: actions/setup-ruby@v1
31
31
  with:
32
32
  ruby-version: ${{ matrix.ruby }}
33
+ - name: Install Grpahviz macOS
34
+ run: brew install graphviz
33
35
  - name: Update gems
34
36
  run: |
35
37
  sudo gem install bundler --force
@@ -29,6 +29,8 @@ jobs:
29
29
  uses: actions/setup-ruby@v1
30
30
  with:
31
31
  ruby-version: ${{ matrix.ruby }}
32
+ - name: Install Grpahviz Ubuntu
33
+ run: sudo apt-get install graphviz
32
34
  - name: Update gems
33
35
  run: |
34
36
  gem install bundler
@@ -30,6 +30,13 @@ jobs:
30
30
  uses: actions/setup-ruby@v1
31
31
  with:
32
32
  ruby-version: ${{ matrix.ruby }}
33
+ - name: Install graphviz
34
+ uses: nick-invision/retry@v1
35
+ with:
36
+ polling_interval_seconds: 5
37
+ timeout_minutes: 5
38
+ max_attempts: 3
39
+ command: choco install --no-progress graphviz --version 2.38.0.20190211
33
40
  - name: Update gems
34
41
  shell: pwsh
35
42
  run: |
@@ -30,7 +30,9 @@ $ gem install lutaml
30
30
 
31
31
  === Usage
32
32
 
33
- In order to parse files supported by lutaml extensions, use Lutaml::Parser.parse method(currently only .exp are supported):
33
+ == From ruby
34
+
35
+ In order to parse files supported by lutaml extensions, use Lutaml::Parser.parse method.
34
36
 
35
37
  [source,ruby]
36
38
  ----
@@ -38,6 +40,22 @@ In order to parse files supported by lutaml extensions, use Lutaml::Parser.parse
38
40
  Lutaml::Parser.parse(File.new("example.exp")) # will produce Lutaml::LutamlPath::DocumentWrapper object with serialized express repository
39
41
  ----
40
42
 
43
+ == With cli tool
44
+
45
+ There is a cli tool available for parsing lutaml/exp files(also yaml datastruct files are supported).
46
+
47
+ [source,bash]
48
+ ----
49
+ # Will generate `test.dot` file in the current directory
50
+ $: lutaml -o . test.lutaml
51
+
52
+ # Will generate `test.png` file in the `assets` directory
53
+ $: lutaml -o assets -t png test.lutaml
54
+ ----
55
+
56
+ For additional info refer to `lutaml --help output`
57
+
58
+
41
59
  == Development
42
60
 
43
61
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+ # frozen_string_literal: true
4
+
5
+ # resolve bin path, ignoring symlinks
6
+ require "pathname"
7
+ bin_file = Pathname.new(__FILE__).realpath
8
+
9
+ # add self to libpath
10
+ $:.unshift File.expand_path("../../lib", bin_file)
11
+
12
+ # Fixes https://github.com/rubygems/rubygems/issues/1420
13
+ require "rubygems/specification"
14
+
15
+ class Gem::Specification
16
+ def this; self; end
17
+ end
18
+
19
+ require "lutaml"
20
+ require "lutaml/command_line"
21
+
22
+ Lutaml::CommandLine.run(ARGV.dup, STDOUT)
@@ -0,0 +1,261 @@
1
+ require "optparse"
2
+ require "pathname"
3
+ require "lutaml/formatter"
4
+ require "lutaml/uml/has_attributes"
5
+ require "lutaml/uml/parsers/attribute"
6
+ require "lutaml/uml/parsers/dsl"
7
+ require "lutaml/uml/parsers/yaml"
8
+
9
+ module Lutaml
10
+ class CommandLine
11
+ class Error < StandardError; end
12
+ class FileError < Error; end
13
+ class NotSupportedInputFormat < Error; end
14
+
15
+ include ::Lutaml::Uml::HasAttributes
16
+
17
+ SUPPORTED_FORMATS = %w[yaml lutaml exp].freeze
18
+ DEFAULT_INPUT_FORMAT = "lutaml".freeze
19
+
20
+ def self.run(args, out_object, attributes = {})
21
+ new(attributes, out_object).run(args)
22
+ end
23
+
24
+ def initialize(attributes = {}, out_object = STDOUT)
25
+ @formatter = ::Lutaml::Formatter::Graphviz.new
26
+ @verbose = false
27
+ @option_parser = OptionParser.new
28
+ @out_object = out_object
29
+
30
+ setup_parser_options
31
+
32
+ # rubocop:disable Rails/ActiveRecordAliases
33
+ update_attributes(attributes)
34
+ # rubocop:enable Rails/ActiveRecordAliases
35
+ end
36
+
37
+ def output_path=(value)
38
+ @output_path = determine_output_path_value(value)
39
+ end
40
+
41
+ def determine_output_path_value(value)
42
+ unless value.nil? || @output_path = value.is_a?(Pathname)
43
+ return Pathname.new(value.to_s)
44
+ end
45
+
46
+ value
47
+ end
48
+
49
+ def paths=(values)
50
+ @paths = values.to_a.map { |path| Pathname.new(path) }
51
+ end
52
+
53
+ def formatter=(value)
54
+ value = value.to_s.strip.downcase.to_sym
55
+ value = Lutaml::Uml::Formatter.find_by(name: value)
56
+ raise Error, "Formatter not found: #{value}" if value.nil?
57
+
58
+ @formatter = value
59
+ end
60
+
61
+ def input_format=(value)
62
+ if value.nil?
63
+ @input_format = DEFAULT_INPUT_FORMAT
64
+ return
65
+ end
66
+
67
+ @input_format = SUPPORTED_FORMATS.detect { |n| n == value }
68
+ raise(NotSupportedInputFormat, value) if @input_format.nil?
69
+ end
70
+
71
+ def run(original_args)
72
+ args = original_args.dup
73
+ @option_parser.parse!(args) rescue nil
74
+ @paths = args
75
+ @formatter.type = @type
76
+
77
+ if @output_path&.file? && @paths.length > 1
78
+ raise Error,
79
+ 'Output path must be a directory \
80
+ if multiple input files are given'
81
+ end
82
+
83
+ @paths.each do |input_path_string|
84
+ input_path = Pathname.new(input_path_string)
85
+ unless input_path.exist?
86
+ raise FileError, "File does not exist: #{input_path}"
87
+ end
88
+
89
+ document = Lutaml::Parser
90
+ .parse_into_document(File.new(input_path), @input_format)
91
+ result = @formatter.format(document)
92
+
93
+ if @output_path
94
+ output_path = @output_path
95
+ if output_path.directory?
96
+ output_path = output_path.join(input_path
97
+ .basename(".*").to_s +
98
+ ".#{@formatter.type}")
99
+ end
100
+
101
+ output_path.open("w+") { |file| file.write(result) }
102
+ else
103
+ @out_object.puts(result)
104
+ end
105
+ end
106
+ end
107
+
108
+ protected
109
+
110
+ def text_bold(body = nil)
111
+ text_effect(1, body)
112
+ end
113
+
114
+ def text_italic(body = nil)
115
+ text_effect(3, body)
116
+ end
117
+
118
+ def text_bold_italic(body = nil)
119
+ text_bold(text_italic(body))
120
+ end
121
+
122
+ def text_underline(body = nil)
123
+ text_effect(4, body)
124
+ end
125
+
126
+ def text_effect(num, body = nil)
127
+ result = "\e[#{num}m"
128
+ result << "#{body}#{text_reset}" unless body.nil?
129
+
130
+ result
131
+ end
132
+
133
+ def text_reset
134
+ "\e[0m"
135
+ end
136
+
137
+ def setup_parser_options
138
+ @option_parser.banner = ""
139
+ format_desc = "The output formatter (Default: '#{@formatter.name}')"
140
+ @option_parser
141
+ .on("-f",
142
+ "--formatter VALUE",
143
+ format_desc) do |value|
144
+ @formatter = value
145
+ end
146
+ @option_parser
147
+ .on("-t", "--type VALUE", "The output format type") do |value|
148
+ @type = value
149
+ end
150
+ @option_parser
151
+ .on("-o", "--output VALUE", "The output path") do |value|
152
+ @output_path = Pathname.new(value)
153
+ end
154
+ @option_parser
155
+ .on("-i", "--input-format VALUE", "The input format") do |value|
156
+ @input_format = value
157
+ end
158
+ @option_parser
159
+ .on("-h", "--help", "Prints this help") do
160
+ print_help
161
+ exit
162
+ end
163
+ @option_parser.on("-g", "--graph VALUE") do |value|
164
+ Parsers::Attribute.parse(value).each do |key, attr_value|
165
+ @formatter.graph[key] = attr_value
166
+ end
167
+ end
168
+
169
+ @option_parser.on("-e", "--edge VALUE") do |value|
170
+ Parsers::Attribute.parse(value).each do |key, attr_value|
171
+ @formatter.edge[key] = attr_value
172
+ end
173
+ end
174
+
175
+ @option_parser.on("-n", "--node VALUE") do |value|
176
+ Parsers::Attribute.parse(value).each do |key, attr_value|
177
+ @formatter.node[key] = attr_value
178
+ end
179
+ end
180
+
181
+ @option_parser.on("-a", "--all VALUE") do |value|
182
+ Parsers::Attribute.parse(value).each do |key, attr_value|
183
+ @formatter.graph[key] = attr_value
184
+ @formatter.edge[key] = attr_value
185
+ @formatter.node[key] = attr_value
186
+ end
187
+ end
188
+ end
189
+
190
+ def print_help
191
+ @out_object.puts <<~HELP
192
+ #{text_bold('Usage:')} lutaml [options] PATHS
193
+
194
+ #{text_bold('Overview:')} Generate output from Supplied language files if supported
195
+
196
+ #{text_bold('Options:')}
197
+ #{@option_parser}
198
+ #{text_bold('Paths:')}
199
+
200
+ LUTAML can accept multiple paths for parsing for easier batch processing.
201
+
202
+ The location of the output by default is standard output.
203
+
204
+ The output can be directed to a path with #{text_bold_italic('--output')}, which can be a file or a directory.
205
+ If the output path is a directory, then the filename will be the same as the input filename,
206
+ with it's file extension substituted with the #{text_bold_italic('--type')}.
207
+
208
+ #{text_underline('Examples')}
209
+
210
+ `lutaml project.lutaml`
211
+
212
+ Produces DOT notation, sent to standard output
213
+
214
+ `lutaml -o . project.lutaml`
215
+
216
+ Produces DOT notation, written to #{text_italic('./project.dot')}
217
+
218
+ `lutaml -o ./diagram.dot project.lutaml`
219
+
220
+ Produces DOT notation, written to #{text_italic('./diagram.dot')}
221
+
222
+ `lutaml -o ./diagram.png project.lutaml`
223
+
224
+ Produces PNG image, written to #{text_italic('./diagram.png')}
225
+
226
+ `lutaml -t png -o . project.lutaml`
227
+
228
+ Produces PNG image, written to #{text_italic('./project.png')}
229
+
230
+ `lutaml -t png -o . project.lutaml core_ext.lutaml`
231
+
232
+ Produces PNG images, written to #{text_italic('./project.png')} and #{text_italic('./core_ext.png')}
233
+
234
+ #{text_bold('Inputs:')}
235
+
236
+ #{text_underline('Lutaml')}
237
+
238
+ Lutaml dsl syntax files, supports diagram generation(image or dot files) with Graphviz
239
+
240
+ #{text_bold('Formatters:')}
241
+
242
+ #{text_underline('Graphviz')}
243
+
244
+ Generates DOT notation and can use the DOT notation to generate any format Graphviz can produce.
245
+
246
+ The output format is based on #{text_bold_italic('--type')}, which by default is "dot".
247
+ If #{text_bold_italic('--type')} is not given and #{text_bold_italic('--output')} is, the file extension of the #{text_bold_italic('--output')} path will be used.
248
+
249
+ Valid types/extensions are: #{Lutaml::Formatter::Graphviz::VALID_TYPES.join(', ')}
250
+
251
+ #{text_bold('Options:')}
252
+
253
+ -g, --graph VALUE The graph attributes
254
+ -e, --edge VALUE The edge attributes
255
+ -n, --node VALUE The node attributes
256
+ -a, --all VALUE Set attributes for graph, edge, and node
257
+
258
+ HELP
259
+ end
260
+ end
261
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Formatter
5
+ class << self
6
+ def all
7
+ @all ||= []
8
+ end
9
+
10
+ def find_by_name(name)
11
+ name = name.to_sym
12
+
13
+ all.detect { |formatter_class| formatter_class.name == name }
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ require "lutaml/formatter/graphviz"
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/formatter"
4
+ require "lutaml/uml/has_attributes"
5
+
6
+ module Lutaml
7
+ module Formatter
8
+ class Base
9
+ class << self
10
+ def inherited(subclass)
11
+ Formatter.all << subclass
12
+ end
13
+
14
+ def format(node, attributes = {})
15
+ new(attributes).format(node)
16
+ end
17
+
18
+ def name
19
+ to_s.split("::").last.downcase.to_sym
20
+ end
21
+ end
22
+
23
+ include ::Lutaml::Uml::HasAttributes
24
+
25
+ # rubocop:disable Rails/ActiveRecordAliases
26
+ def initialize(attributes = {})
27
+ update_attributes(attributes)
28
+ end
29
+ # rubocop:enable Rails/ActiveRecordAliases
30
+
31
+ def name
32
+ self.class.name
33
+ end
34
+
35
+ attr_reader :type
36
+
37
+ def type=(value)
38
+ @type = value.to_s.strip.downcase.to_sym
39
+ end
40
+
41
+ def format(node)
42
+ case node
43
+ when ::Lutaml::Uml::Node::Field then format_field(node)
44
+ when ::Lutaml::Uml::Node::Method then format_method(node)
45
+ when ::Lutaml::Uml::Node::Relationship then format_relationship(node)
46
+ when ::Lutaml::Uml::Node::ClassRelationship
47
+ then format_class_relationship(node)
48
+ when ::Lutaml::Uml::Node::ClassNode then format_class(node)
49
+ when Lutaml::Uml::Document then format_document(node)
50
+ end
51
+ end
52
+
53
+ def format_field(_node); raise NotImplementedError; end
54
+
55
+ def format_method(_node); raise NotImplementedError; end
56
+
57
+ def format_relationship(_node); raise NotImplementedError; end
58
+
59
+ def format_class_relationship(_node); raise NotImplementedError; end
60
+
61
+ def format_class(_node); raise NotImplementedError; end
62
+
63
+ def format_document(_node); raise NotImplementedError; end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,332 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "open3"
4
+ require "lutaml/formatter/base"
5
+ require "lutaml/layout/graph_viz_engine"
6
+
7
+ module Lutaml
8
+ module Formatter
9
+ class Graphviz < Base
10
+ class Attributes < Hash
11
+ def to_s
12
+ to_a
13
+ .reject { |(_k, val)| val.nil? }
14
+ .map { |(a, b)| "#{a}=#{b.inspect}" }
15
+ .join(" ")
16
+ end
17
+ end
18
+
19
+ ACCESS_SYMBOLS = {
20
+ "public" => "+",
21
+ "protected" => "#",
22
+ "private" => "-",
23
+ }.freeze
24
+ DEFAULT_CLASS_FONT = "Helvetica".freeze
25
+
26
+ VALID_TYPES = %i[
27
+ dot
28
+ xdot
29
+ ps
30
+ pdf
31
+ svg
32
+ svgz
33
+ fig
34
+ png
35
+ gif
36
+ jpg
37
+ jpeg
38
+ json
39
+ imap
40
+ cmapx
41
+ ].freeze
42
+
43
+ def initialize(attributes = {})
44
+ super
45
+
46
+ @graph = Attributes.new
47
+ # Associations lines style, `true` gives curved lines
48
+ # https://graphviz.org/doc/info/attrs.html#d:splines
49
+ @graph["splines"] = "ortho"
50
+ # Padding between outside of picture and nodes
51
+ @graph["pad"] = 0.5
52
+ # Padding between levels
53
+ @graph["ranksep"] = "1.2.equally"
54
+ # Padding between nodes
55
+ @graph["nodesep"] = "1.2.equally"
56
+ # TODO: set rankdir
57
+ # @graph['rankdir'] = 'BT'
58
+
59
+ @edge = Attributes.new
60
+ @edge["color"] = "gray50"
61
+
62
+ @node = Attributes.new
63
+ @node["shape"] = "box"
64
+
65
+ @type = :dot
66
+ end
67
+
68
+ attr_reader :graph
69
+ attr_reader :edge
70
+ attr_reader :node
71
+
72
+ def type=(value)
73
+ super
74
+
75
+ @type = :dot unless VALID_TYPES.include?(@type)
76
+ end
77
+
78
+ def format(node)
79
+ dot = super.lines.map(&:rstrip).join("\n")
80
+
81
+ generate_from_dot(dot)
82
+ end
83
+
84
+ def escape_html_chars(text)
85
+ text
86
+ .gsub(/</, "&#60;")
87
+ .gsub(/>/, "&#62;")
88
+ .gsub(/\[/, "&#91;")
89
+ .gsub(/\]/, "&#93;")
90
+ end
91
+
92
+ def format_field(node)
93
+ symbol = ACCESS_SYMBOLS[node.visibility]
94
+ result = "#{symbol}#{node.name}"
95
+ if node.type
96
+ keyword = node.keyword ? "«#{node.keyword}»" : ""
97
+ result += " : #{keyword}#{node.type}"
98
+ end
99
+ if node.cardinality
100
+ result += "[#{node.cardinality[:min]}..#{node.cardinality[:max]}]"
101
+ end
102
+ result = escape_html_chars(result)
103
+ result = "<U>#{result}</U>" if node.static
104
+
105
+ result
106
+ end
107
+
108
+ def format_method(node)
109
+ symbol = ACCESS_SYMBOLS[node.access]
110
+ result = "#{symbol} #{node.name}"
111
+ if node.arguments
112
+ arguments = node.arguments.map do |argument|
113
+ "#{argument.name}#{" : #{argument.type}" if argument.type}"
114
+ end.join(", ")
115
+ end
116
+
117
+ result << "(#{arguments})"
118
+ result << " : #{node.type}" if node.type
119
+ result = "<U>#{result}</U>" if node.static
120
+ result = "<I>#{result}</I>" if node.abstract
121
+
122
+ result
123
+ end
124
+
125
+ def format_relationship(node)
126
+ graph_parent_name = generate_graph_name(node.owner_end)
127
+ graph_node_name = generate_graph_name(node.member_end)
128
+ attributes = generate_graph_relationship_attributes(node)
129
+ graph_attributes = " [#{attributes}]" unless attributes.empty?
130
+
131
+ %{#{graph_parent_name} -> #{graph_node_name}#{graph_attributes}}
132
+ end
133
+
134
+ def generate_graph_relationship_attributes(node)
135
+ attributes = Attributes.new
136
+ if %w[dependency realizes].include?(node.member_end_type)
137
+ attributes["style"] = "dashed"
138
+ end
139
+ attributes["dir"] = if node.owner_end_type && node.member_end_type
140
+ "both"
141
+ elsif node.owner_end_type
142
+ "back"
143
+ else
144
+ "direct"
145
+ end
146
+ attributes["label"] = node.action if node.action
147
+ if node.owner_end_attribute_name
148
+ attributes["headlabel"] = format_label(
149
+ node.owner_end_attribute_name,
150
+ node.owner_end_cardinality
151
+ )
152
+ end
153
+ if node.member_end_attribute_name
154
+ attributes["taillabel"] = format_label(
155
+ node.member_end_attribute_name,
156
+ node.member_end_cardinality
157
+ )
158
+ end
159
+
160
+ attributes["arrowtail"] = case node.owner_end_type
161
+ when "composition"
162
+ "diamond"
163
+ when "aggregation"
164
+ "odiamond"
165
+ when "direct"
166
+ "vee"
167
+ else
168
+ "onormal"
169
+ end
170
+
171
+ attributes["arrowhead"] = case node.member_end_type
172
+ when "composition"
173
+ "diamond"
174
+ when "aggregation"
175
+ "odiamond"
176
+ when "direct"
177
+ "vee"
178
+ else
179
+ "onormal"
180
+ end
181
+ # swap labels and arrows if `dir` eq to `back`
182
+ if attributes["dir"] == "back"
183
+ attributes["arrowhead"], attributes["arrowtail"] =
184
+ [attributes["arrowtail"], attributes["arrowhead"]]
185
+ attributes["headlabel"], attributes["taillabel"] =
186
+ [attributes["taillabel"], attributes["headlabel"]]
187
+ end
188
+ attributes
189
+ end
190
+
191
+ def format_label(name, cardinality = {})
192
+ res = "+#{name}"
193
+ if cardinality.nil? ||
194
+ (cardinality["min"].nil? || cardinality["max"].nil?)
195
+ return res
196
+ end
197
+
198
+ "#{res} #{cardinality['min']}..#{cardinality['max']}"
199
+ end
200
+
201
+ def format_member_rows(members, hide_members)
202
+ unless !hide_members && members && members.length.positive?
203
+ return <<~HEREDOC.chomp
204
+ <TABLE BORDER="0" CELLPADDING="0" CELLSPACING="0">
205
+ <TR><TD ALIGN="LEFT"></TD></TR>
206
+ </TABLE>
207
+ HEREDOC
208
+ end
209
+
210
+ field_rows = members.map do |field|
211
+ %{<TR><TD ALIGN="LEFT">#{format_field(field)}</TD></TR>}
212
+ end
213
+ field_table = <<~HEREDOC.chomp
214
+ <TABLE BORDER="0" CELLPADDING="0" CELLSPACING="0">
215
+ #{field_rows.map { |row| ' ' * 10 + row }.join("\n")}
216
+ </TABLE>
217
+ HEREDOC
218
+ field_table << "\n" << " " * 6
219
+ field_table
220
+ end
221
+
222
+ def format_class(node, hide_members)
223
+ name = ["<B>#{node.name}</B>"]
224
+ name.unshift("«#{node.keyword}»") if node.keyword
225
+ name_html = <<~HEREDOC
226
+ <TABLE BORDER="0" CELLPADDING="0" CELLSPACING="0">
227
+ #{name.map { |n| %(<TR><TD ALIGN="CENTER">#{n}</TD></TR>) }.join('\n')}
228
+ </TABLE>
229
+ HEREDOC
230
+
231
+ field_table = format_member_rows(node.attributes, hide_members)
232
+ method_table = format_member_rows(node.methods, hide_members)
233
+ table_body = [name_html, field_table, method_table].map do |type|
234
+ next if type.nil?
235
+
236
+ <<~TEXT
237
+ <TR>
238
+ <TD>#{type}</TD>
239
+ </TR>
240
+ TEXT
241
+ end
242
+
243
+ <<~HEREDOC.chomp
244
+ <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="10">
245
+ #{table_body.compact.join("\n")}
246
+ </TABLE>
247
+ HEREDOC
248
+ end
249
+
250
+ def format_document(node)
251
+ @fontname = node.fontname || DEFAULT_CLASS_FONT
252
+ @node["fontname"] = "#{@fontname}-bold"
253
+
254
+ if node.fidelity
255
+ hide_members = node.fidelity["hideMembers"]
256
+ hide_other_classes = node.fidelity["hideOtherClasses"]
257
+ end
258
+ classes = (node.classes +
259
+ node.enums +
260
+ node.data_types +
261
+ node.primitives).map do |class_node|
262
+ graph_node_name = generate_graph_name(class_node.name)
263
+
264
+ <<~HEREDOC
265
+ #{graph_node_name} [
266
+ shape="plain"
267
+ fontname="#{@fontname || DEFAULT_CLASS_FONT}"
268
+ label=<#{format_class(class_node, hide_members)}>]
269
+ HEREDOC
270
+ end.join("\n")
271
+ associations = node.classes.map(&:associations).compact.flatten +
272
+ node.associations
273
+ if node.groups
274
+ associations = sort_by_document_groupping(node.groups,
275
+ associations)
276
+ end
277
+ classes_names = node.classes.map(&:name)
278
+ associations = associations.map do |assoc_node|
279
+ if hide_other_classes &&
280
+ !classes_names.include?(assoc_node.member_end)
281
+ next
282
+ end
283
+
284
+ format_relationship(assoc_node)
285
+ end.join("\n")
286
+
287
+ classes = classes.lines.map { |line| " #{line}" }.join.chomp
288
+ associations = associations
289
+ .lines.map { |line| " #{line}" }.join.chomp
290
+
291
+ <<~HEREDOC
292
+ digraph G {
293
+ graph [#{@graph}]
294
+ edge [#{@edge}]
295
+ node [#{@node}]
296
+
297
+ #{classes}
298
+
299
+ #{associations}
300
+ }
301
+ HEREDOC
302
+ end
303
+
304
+ protected
305
+
306
+ def sort_by_document_groupping(groups, associations)
307
+ result = []
308
+ groups.each do |batch|
309
+ batch.each do |group_name|
310
+ associations
311
+ .select { |assc| assc.owner_end == group_name }
312
+ .each do |association|
313
+ result.push(association) unless result.include?(association)
314
+ end
315
+ end
316
+ end
317
+ associations.each do |association|
318
+ result.push(association) unless result.include?(association)
319
+ end
320
+ result
321
+ end
322
+
323
+ def generate_from_dot(input)
324
+ Lutaml::Layout::GraphVizEngine.new(input: input).render(@type)
325
+ end
326
+
327
+ def generate_graph_name(name)
328
+ name.gsub(/[^0-9a-zA-Z]/i, "")
329
+ end
330
+ end
331
+ end
332
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ class Engine
5
+ attr_accessor :input
6
+
7
+ def initialize(input:)
8
+ @input = input
9
+ end
10
+
11
+ def render(_type)
12
+ raise ArgumentError, "Implement render method"
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ruby-graphviz"
4
+ require "lutaml/layout/engine"
5
+
6
+ module Lutaml
7
+ module Layout
8
+ class GraphVizEngine < Engine
9
+ def render(type)
10
+ Open3.popen3("dot -T#{type}") do |stdin, stdout, _stderr, _wait|
11
+ stdin.puts(input)
12
+ stdin.close
13
+ stdout.read
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -31,7 +31,9 @@ module Lutaml
31
31
  object.instance_variables.each_with_object({}) do |var, res|
32
32
  variable = object.instance_variable_get(var)
33
33
  res[var.to_s.gsub("@", "")] = if variable.is_a?(Array)
34
- variable.map { |n| serialize_to_hash(n) }
34
+ variable.map do |n|
35
+ serialize_to_hash(n)
36
+ end
35
37
  else
36
38
  variable
37
39
  end
@@ -4,20 +4,50 @@ require "lutaml/uml/lutaml_path/document_wrapper"
4
4
  # require "lutaml/express/lutaml_path/document_wrapper"
5
5
 
6
6
  module Lutaml
7
- module Parser
8
- module_function
7
+ class Parser
8
+ attr_reader :parse_type, :file
9
9
 
10
- def parse(file)
11
- case File.extname(file.path)[1..-1]
10
+ class << self
11
+ def parse(file, input_type = nil)
12
+ new(file, input_type).parse
13
+ end
14
+
15
+ def parse_into_document(file, input_type = nil)
16
+ new(file, input_type).parse_into_document
17
+ end
18
+ end
19
+
20
+ def initialize(file, input_type)
21
+ @parse_type = input_type ? input_type : File.extname(file.path)[1..-1]
22
+ @file = file
23
+ end
24
+
25
+ def parse
26
+ document = parse_into_document
27
+ document_wrapper(document)
28
+ end
29
+
30
+ def parse_into_document
31
+ case parse_type
12
32
  # when "exp"
13
- # Lutaml::Express::LutamlPath::DocumentWrapper
14
- # .new(Lutaml::Express::Parsers::Exp.parse(file))
33
+ # Lutaml::Express::Parsers::Exp.parse(file)
15
34
  when "lutaml"
16
- Lutaml::Uml::LutamlPath::DocumentWrapper
17
- .new(Lutaml::Uml::Parsers::Dsl.parse(file))
35
+ Lutaml::Uml::Parsers::Dsl.parse(file)
36
+ when "yml"
37
+ Lutaml::Uml::Parsers::Yaml.parse(file.path)
18
38
  else
19
39
  raise ArgumentError, "Unsupported file format"
20
40
  end
21
41
  end
42
+
43
+ private
44
+
45
+ def document_wrapper(document)
46
+ if parse_type == "exp"
47
+ return Lutaml::Express::LutamlPath::DocumentWrapper.new(document)
48
+ end
49
+
50
+ Lutaml::Uml::LutamlPath::DocumentWrapper.new(document)
51
+ end
22
52
  end
23
53
  end
@@ -1,3 +1,3 @@
1
1
  module Lutaml
2
- VERSION = "0.3.3".freeze
2
+ VERSION = "0.4.0".freeze
3
3
  end
@@ -16,9 +16,12 @@ Gem::Specification.new do |spec|
16
16
  spec.metadata["changelog_uri"] = "https://github.com/lutaml/lutaml/releases"
17
17
 
18
18
  # Specify which files should be added to the gem when it is released.
19
- # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
19
+ # The `git ls-files -z` loads the files in the RubyGem
20
+ # that have been added into git.
20
21
  spec.files = Dir.chdir(File.expand_path(__dir__)) do
21
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
22
+ `git ls-files -z`
23
+ .split("\x0")
24
+ .reject { |f| f.match(%r{^(test|spec|features)/}) }
22
25
  end
23
26
  spec.bindir = "exe"
24
27
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lutaml
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.3
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-01-08 00:00:00.000000000 Z
11
+ date: 2021-01-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: lutaml-uml
@@ -139,7 +139,8 @@ dependencies:
139
139
  description: 'LutaML: data models in textual form'
140
140
  email:
141
141
  - open.source@ribose.com'
142
- executables: []
142
+ executables:
143
+ - lutaml
143
144
  extensions: []
144
145
  extra_rdoc_files: []
145
146
  files:
@@ -155,8 +156,15 @@ files:
155
156
  - Rakefile
156
157
  - bin/console
157
158
  - bin/setup
159
+ - exe/lutaml
158
160
  - lib/lutaml.rb
161
+ - lib/lutaml/command_line.rb
159
162
  - lib/lutaml/express/lutaml_path/document_wrapper.rb
163
+ - lib/lutaml/formatter.rb
164
+ - lib/lutaml/formatter/base.rb
165
+ - lib/lutaml/formatter/graphviz.rb
166
+ - lib/lutaml/layout/engine.rb
167
+ - lib/lutaml/layout/graph_viz_engine.rb
160
168
  - lib/lutaml/lutaml_path/document_wrapper.rb
161
169
  - lib/lutaml/parser.rb
162
170
  - lib/lutaml/uml/lutaml_path/document_wrapper.rb