lutaml 0.3.3 → 0.4.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.
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