archimate 1.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.
Files changed (211) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +22 -0
  3. data/.projectile +18 -0
  4. data/.rubocop.yml +13 -0
  5. data/.travis.yml +4 -0
  6. data/.yardocs +15 -0
  7. data/Gemfile +5 -0
  8. data/Guardfile +27 -0
  9. data/LICENSE +201 -0
  10. data/README.md +94 -0
  11. data/Rakefile +29 -0
  12. data/TODOs.org +286 -0
  13. data/archimate.gemspec +51 -0
  14. data/bin/archimate +17 -0
  15. data/bin/console +13 -0
  16. data/bin/setup +8 -0
  17. data/exe/archidiff +7 -0
  18. data/exe/archidiff-summary +7 -0
  19. data/exe/archimate +7 -0
  20. data/exe/archimerge +7 -0
  21. data/exe/fmtxml +7 -0
  22. data/lib/archimate/cli/archi.rb +189 -0
  23. data/lib/archimate/cli/cleanup.rb +54 -0
  24. data/lib/archimate/cli/conflict_resolver.rb +39 -0
  25. data/lib/archimate/cli/convert.rb +39 -0
  26. data/lib/archimate/cli/diff.rb +31 -0
  27. data/lib/archimate/cli/diff_summary.rb +101 -0
  28. data/lib/archimate/cli/duper.rb +85 -0
  29. data/lib/archimate/cli/lint.rb +16 -0
  30. data/lib/archimate/cli/mapper.rb +82 -0
  31. data/lib/archimate/cli/merge.rb +49 -0
  32. data/lib/archimate/cli/merger.rb +109 -0
  33. data/lib/archimate/cli/stats.rb +42 -0
  34. data/lib/archimate/cli/svger.rb +40 -0
  35. data/lib/archimate/color.rb +53 -0
  36. data/lib/archimate/config.rb +35 -0
  37. data/lib/archimate/data_model/any_attribute.rb +13 -0
  38. data/lib/archimate/data_model/any_element.rb +15 -0
  39. data/lib/archimate/data_model/archimate_node.rb +181 -0
  40. data/lib/archimate/data_model/bounds.rb +72 -0
  41. data/lib/archimate/data_model/color.rb +47 -0
  42. data/lib/archimate/data_model/concept.rb +14 -0
  43. data/lib/archimate/data_model/concern.rb +17 -0
  44. data/lib/archimate/data_model/connection.rb +107 -0
  45. data/lib/archimate/data_model/constants.rb +82 -0
  46. data/lib/archimate/data_model/container.rb +17 -0
  47. data/lib/archimate/data_model/diagram.rb +40 -0
  48. data/lib/archimate/data_model/diffable_array.rb +213 -0
  49. data/lib/archimate/data_model/diffable_primitive.rb +83 -0
  50. data/lib/archimate/data_model/documentation.rb +14 -0
  51. data/lib/archimate/data_model/element.rb +87 -0
  52. data/lib/archimate/data_model/font.rb +54 -0
  53. data/lib/archimate/data_model/label.rb +19 -0
  54. data/lib/archimate/data_model/lang_string.rb +41 -0
  55. data/lib/archimate/data_model/location.rb +31 -0
  56. data/lib/archimate/data_model/metadata.rb +14 -0
  57. data/lib/archimate/data_model/model.rb +217 -0
  58. data/lib/archimate/data_model/modeling_note.rb +13 -0
  59. data/lib/archimate/data_model/named_referenceable.rb +14 -0
  60. data/lib/archimate/data_model/organization.rb +40 -0
  61. data/lib/archimate/data_model/property.rb +25 -0
  62. data/lib/archimate/data_model/property_definition.rb +16 -0
  63. data/lib/archimate/data_model/referenceable.rb +29 -0
  64. data/lib/archimate/data_model/relationship.rb +85 -0
  65. data/lib/archimate/data_model/schema_info.rb +18 -0
  66. data/lib/archimate/data_model/style.rb +32 -0
  67. data/lib/archimate/data_model/view.rb +12 -0
  68. data/lib/archimate/data_model/view_concept.rb +18 -0
  69. data/lib/archimate/data_model/view_node.rb +114 -0
  70. data/lib/archimate/data_model/viewpoint.rb +87 -0
  71. data/lib/archimate/data_model.rb +54 -0
  72. data/lib/archimate/diff/archimate_array_reference.rb +113 -0
  73. data/lib/archimate/diff/archimate_identified_node_reference.rb +41 -0
  74. data/lib/archimate/diff/archimate_node_attribute_reference.rb +70 -0
  75. data/lib/archimate/diff/archimate_node_reference.rb +80 -0
  76. data/lib/archimate/diff/change.rb +49 -0
  77. data/lib/archimate/diff/conflict.rb +31 -0
  78. data/lib/archimate/diff/conflicts/base_conflict.rb +53 -0
  79. data/lib/archimate/diff/conflicts/deleted_items_child_updated_conflict.rb +30 -0
  80. data/lib/archimate/diff/conflicts/deleted_items_referenced_conflict.rb +63 -0
  81. data/lib/archimate/diff/conflicts/path_conflict.rb +51 -0
  82. data/lib/archimate/diff/conflicts.rb +89 -0
  83. data/lib/archimate/diff/delete.rb +41 -0
  84. data/lib/archimate/diff/difference.rb +113 -0
  85. data/lib/archimate/diff/insert.rb +43 -0
  86. data/lib/archimate/diff/merge.rb +70 -0
  87. data/lib/archimate/diff/move.rb +51 -0
  88. data/lib/archimate/diff.rb +17 -0
  89. data/lib/archimate/export/csv_export.rb +32 -0
  90. data/lib/archimate/export/cypher.rb +171 -0
  91. data/lib/archimate/export/graph_ml.rb +131 -0
  92. data/lib/archimate/export/n_quads.rb +142 -0
  93. data/lib/archimate/file_format.rb +30 -0
  94. data/lib/archimate/file_formats/archi_file_format.rb +151 -0
  95. data/lib/archimate/file_formats/archi_file_reader.rb +252 -0
  96. data/lib/archimate/file_formats/archi_file_writer.rb +230 -0
  97. data/lib/archimate/file_formats/archimate_v2.rb +461 -0
  98. data/lib/archimate/file_formats/model_exchange_file/xml_lang_string.rb +35 -0
  99. data/lib/archimate/file_formats/model_exchange_file/xml_metadata.rb +115 -0
  100. data/lib/archimate/file_formats/model_exchange_file/xml_property_definitions.rb +28 -0
  101. data/lib/archimate/file_formats/model_exchange_file/xml_property_defs.rb +27 -0
  102. data/lib/archimate/file_formats/model_exchange_file_reader.rb +237 -0
  103. data/lib/archimate/file_formats/model_exchange_file_reader_21.rb +73 -0
  104. data/lib/archimate/file_formats/model_exchange_file_reader_30.rb +134 -0
  105. data/lib/archimate/file_formats/model_exchange_file_writer.rb +157 -0
  106. data/lib/archimate/file_formats/model_exchange_file_writer_21.rb +143 -0
  107. data/lib/archimate/file_formats/model_exchange_file_writer_30.rb +153 -0
  108. data/lib/archimate/file_formats/writer.rb +56 -0
  109. data/lib/archimate/lint/duplicate_entities.rb +121 -0
  110. data/lib/archimate/lint/linter.rb +146 -0
  111. data/lib/archimate/logging.rb +55 -0
  112. data/lib/archimate/maybe_io.rb +34 -0
  113. data/lib/archimate/progress_indicator.rb +29 -0
  114. data/lib/archimate/svg/archimate.css +232 -0
  115. data/lib/archimate/svg/child.rb +29 -0
  116. data/lib/archimate/svg/connection.rb +184 -0
  117. data/lib/archimate/svg/css_style.rb +31 -0
  118. data/lib/archimate/svg/diagram.rb +87 -0
  119. data/lib/archimate/svg/entity/and_junction.rb +14 -0
  120. data/lib/archimate/svg/entity/application_collaboration.rb +14 -0
  121. data/lib/archimate/svg/entity/application_component.rb +41 -0
  122. data/lib/archimate/svg/entity/application_event.rb +13 -0
  123. data/lib/archimate/svg/entity/application_function.rb +13 -0
  124. data/lib/archimate/svg/entity/application_interaction.rb +13 -0
  125. data/lib/archimate/svg/entity/application_interface.rb +13 -0
  126. data/lib/archimate/svg/entity/application_process.rb +13 -0
  127. data/lib/archimate/svg/entity/application_service.rb +13 -0
  128. data/lib/archimate/svg/entity/artifact.rb +39 -0
  129. data/lib/archimate/svg/entity/assessment.rb +14 -0
  130. data/lib/archimate/svg/entity/base_entity.rb +128 -0
  131. data/lib/archimate/svg/entity/business_actor.rb +14 -0
  132. data/lib/archimate/svg/entity/business_collaboration.rb +14 -0
  133. data/lib/archimate/svg/entity/business_event.rb +10 -0
  134. data/lib/archimate/svg/entity/business_function.rb +13 -0
  135. data/lib/archimate/svg/entity/business_interaction.rb +13 -0
  136. data/lib/archimate/svg/entity/business_interface.rb +13 -0
  137. data/lib/archimate/svg/entity/business_object.rb +13 -0
  138. data/lib/archimate/svg/entity/business_process.rb +13 -0
  139. data/lib/archimate/svg/entity/business_role.rb +14 -0
  140. data/lib/archimate/svg/entity/business_service.rb +13 -0
  141. data/lib/archimate/svg/entity/capability.rb +14 -0
  142. data/lib/archimate/svg/entity/communication_network.rb +14 -0
  143. data/lib/archimate/svg/entity/communication_path.rb +14 -0
  144. data/lib/archimate/svg/entity/constraint.rb +15 -0
  145. data/lib/archimate/svg/entity/contract.rb +13 -0
  146. data/lib/archimate/svg/entity/course_of_action.rb +14 -0
  147. data/lib/archimate/svg/entity/data_entity.rb +29 -0
  148. data/lib/archimate/svg/entity/data_object.rb +13 -0
  149. data/lib/archimate/svg/entity/deliverable.rb +13 -0
  150. data/lib/archimate/svg/entity/device.rb +48 -0
  151. data/lib/archimate/svg/entity/diagram_model_reference.rb +22 -0
  152. data/lib/archimate/svg/entity/diagram_object.rb +30 -0
  153. data/lib/archimate/svg/entity/distribution_network.rb +14 -0
  154. data/lib/archimate/svg/entity/driver.rb +14 -0
  155. data/lib/archimate/svg/entity/equipment.rb +16 -0
  156. data/lib/archimate/svg/entity/event_entity.rb +36 -0
  157. data/lib/archimate/svg/entity/facility.rb +16 -0
  158. data/lib/archimate/svg/entity/function_entity.rb +14 -0
  159. data/lib/archimate/svg/entity/gap.rb +21 -0
  160. data/lib/archimate/svg/entity/goal.rb +14 -0
  161. data/lib/archimate/svg/entity/group.rb +24 -0
  162. data/lib/archimate/svg/entity/implementation_event.rb +13 -0
  163. data/lib/archimate/svg/entity/infrastructure_function.rb +13 -0
  164. data/lib/archimate/svg/entity/infrastructure_interface.rb +13 -0
  165. data/lib/archimate/svg/entity/infrastructure_service.rb +13 -0
  166. data/lib/archimate/svg/entity/interaction_entity.rb +14 -0
  167. data/lib/archimate/svg/entity/interface_entity.rb +35 -0
  168. data/lib/archimate/svg/entity/junction.rb +19 -0
  169. data/lib/archimate/svg/entity/location.rb +14 -0
  170. data/lib/archimate/svg/entity/material.rb +14 -0
  171. data/lib/archimate/svg/entity/meaning.rb +40 -0
  172. data/lib/archimate/svg/entity/motivation_entity.rb +40 -0
  173. data/lib/archimate/svg/entity/network.rb +14 -0
  174. data/lib/archimate/svg/entity/node.rb +33 -0
  175. data/lib/archimate/svg/entity/node_shape.rb +64 -0
  176. data/lib/archimate/svg/entity/note.rb +30 -0
  177. data/lib/archimate/svg/entity/or_junction.rb +14 -0
  178. data/lib/archimate/svg/entity/outcome.rb +15 -0
  179. data/lib/archimate/svg/entity/path.rb +14 -0
  180. data/lib/archimate/svg/entity/plateau.rb +15 -0
  181. data/lib/archimate/svg/entity/principle.rb +15 -0
  182. data/lib/archimate/svg/entity/process_entity.rb +63 -0
  183. data/lib/archimate/svg/entity/product.rb +20 -0
  184. data/lib/archimate/svg/entity/rect.rb +13 -0
  185. data/lib/archimate/svg/entity/rect_entity.rb +25 -0
  186. data/lib/archimate/svg/entity/representation.rb +31 -0
  187. data/lib/archimate/svg/entity/requirement.rb +15 -0
  188. data/lib/archimate/svg/entity/resource.rb +14 -0
  189. data/lib/archimate/svg/entity/rounded_rect_entity.rb +23 -0
  190. data/lib/archimate/svg/entity/service_entity.rb +51 -0
  191. data/lib/archimate/svg/entity/sketch_model_sticky.rb +14 -0
  192. data/lib/archimate/svg/entity/stakeholder.rb +15 -0
  193. data/lib/archimate/svg/entity/system_software.rb +14 -0
  194. data/lib/archimate/svg/entity/technology_collaboration.rb +14 -0
  195. data/lib/archimate/svg/entity/technology_event.rb +13 -0
  196. data/lib/archimate/svg/entity/technology_function.rb +13 -0
  197. data/lib/archimate/svg/entity/technology_interaction.rb +13 -0
  198. data/lib/archimate/svg/entity/technology_interface.rb +13 -0
  199. data/lib/archimate/svg/entity/technology_process.rb +13 -0
  200. data/lib/archimate/svg/entity/technology_service.rb +13 -0
  201. data/lib/archimate/svg/entity/value.rb +27 -0
  202. data/lib/archimate/svg/entity/work_package.rb +14 -0
  203. data/lib/archimate/svg/entity.rb +93 -0
  204. data/lib/archimate/svg/entity_factory.rb +17 -0
  205. data/lib/archimate/svg/extents.rb +27 -0
  206. data/lib/archimate/svg/point.rb +7 -0
  207. data/lib/archimate/svg/svg_template.rb +27 -0
  208. data/lib/archimate/svg/svg_template.svg.erb +169 -0
  209. data/lib/archimate/version.rb +4 -0
  210. data/lib/archimate.rb +114 -0
  211. metadata +623 -0
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "highline"
4
+
5
+ module Archimate
6
+ module Cli
7
+ class ConflictResolver
8
+ def initialize
9
+ @config = Config.instance
10
+ # TODO: pull the stdin/stdout from the app config
11
+ @hl = HighLine.new(STDIN, STDOUT)
12
+ end
13
+
14
+ # TODO: this implementation has much to be written
15
+ def resolve(conflict)
16
+ return [] unless @config.interactive
17
+ base_local_diffs = conflict.base_local_diffs
18
+ base_remote_diffs = conflict.base_remote_diffs
19
+ choice = @hl.choose do |menu|
20
+ menu.prompt = conflict
21
+ menu.choice(:local, text: base_local_diffs.map(&:to_s).join("\n\t\t"))
22
+ menu.choice(:remote, text: base_remote_diffs.map(&:to_s).join("\n\t\t"))
23
+ # menu.choice(:neither, help: "Don't choose either set of diffs")
24
+ # menu.choice(:edit, help: "Edit the diffs (coming soon)")
25
+ # menu.choice(:quit, help: "I'm in over my head. Just stop!")
26
+ menu.select_by = :index_or_name
27
+ end
28
+ case choice
29
+ when :local
30
+ base_local_diffs
31
+ when :remote
32
+ base_remote_diffs
33
+ else
34
+ error "Unexpected choice #{choice.inspect}."
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Archimate
4
+ module Cli
5
+ class Convert
6
+ include Archimate::Logging
7
+
8
+ SUPPORTED_FORMATS = %w[meff2.1 meff3.0 archi nquads graphml csv cypher].freeze
9
+
10
+ attr_reader :model
11
+
12
+ def initialize(model)
13
+ @model = model
14
+ end
15
+
16
+ def convert(export_format, output_io, output_dir)
17
+ return unless output_io && model
18
+ case export_format
19
+ when "archi"
20
+ Archimate::FileFormats::ArchiFileWriter.new(model).write(output_io)
21
+ when "meff2.1"
22
+ Archimate::FileFormats::ModelExchangeFileWriter21.new(model).write(output_io)
23
+ when "meff3.0"
24
+ Archimate::FileFormats::ModelExchangeFileWriter30.new(model).write(output_io)
25
+ when "nquads"
26
+ output_io.write(Archimate::Export::NQuads.new(model).to_nq)
27
+ when "graphml"
28
+ output_io.write(Archimate::Export::GraphML.new(model).to_graphml)
29
+ when "csv"
30
+ Archimate::Export::CSVExport.new(model).to_csv(output_dir: output_dir)
31
+ when "cypher"
32
+ Archimate::Export::Cypher.new(output_io).to_cypher(model)
33
+ else
34
+ error { "Export to '#{export_format}' is not supported yet." }
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+ module Archimate
3
+ module Cli
4
+ class Diff
5
+ attr_reader :local, :remote
6
+
7
+ def self.diff(local_file, remote_file)
8
+ local = Archimate.read(local_file)
9
+ remote = Archimate.read(remote_file)
10
+
11
+ my_diff = Diff.new(local, remote)
12
+ my_diff.diff
13
+ end
14
+
15
+ def initialize(local, remote)
16
+ @local = local
17
+ @remote = remote
18
+ end
19
+
20
+ def diff
21
+ diffs = Archimate.diff(local, remote)
22
+
23
+ diffs.each { |d| puts d }
24
+
25
+ puts "\n\n#{diffs.size} Differences"
26
+
27
+ diffs
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+ require 'forwardable'
3
+
4
+ module Archimate
5
+ module Cli
6
+ class DiffSummary
7
+ using DataModel::DiffableArray
8
+ using DataModel::DiffablePrimitive
9
+
10
+ DIFF_KINDS = %w(Delete Change Insert).freeze
11
+
12
+ attr_reader :local, :remote
13
+
14
+ def self.diff(local_file, remote_file, options = { verbose: true })
15
+ logger.info "Reading #{local_file}"
16
+ local = Archimate.read(local_file)
17
+ logger.info "Reading #{remote_file}"
18
+ remote = Archimate.read(remote_file)
19
+
20
+ my_diff = DiffSummary.new(local, remote)
21
+ my_diff.diff
22
+ end
23
+
24
+ def initialize(local, remote)
25
+ @local = local
26
+ @remote = remote
27
+ @summary = Hash.new { |hash, key| hash[key] = Hash.new { |k_hash, k_key| k_hash[k_key] = 0 } }
28
+ end
29
+
30
+ def diff
31
+ logger.info "Calculating differences"
32
+ diffs = Archimate.diff(local, remote)
33
+
34
+ puts Color.color("Summary of differences", :headline)
35
+ puts "\n"
36
+
37
+ summary_element_diffs = diffs.group_by { |diff| diff.summary_element.class.to_s.split("::").last }
38
+ summarize_elements summary_element_diffs["Element"]
39
+ summarize "Organization", summary_element_diffs["Organization"]
40
+ summarize "Relationship", summary_element_diffs["Relationship"]
41
+ summarize_diagrams summary_element_diffs["Diagram"]
42
+
43
+ puts "Total Diffs: #{diffs.size}"
44
+ end
45
+
46
+ def summarize(title, diffs)
47
+ return if diffs.nil? || diffs.empty?
48
+ by_kind = diffs_by_kind(diffs)
49
+
50
+ puts color(title)
51
+ DIFF_KINDS.each do |kind|
52
+ puts format(" #{color(kind)}: #{by_kind[kind]&.size}") if by_kind.key?(kind)
53
+ end
54
+ end
55
+
56
+ def diffs_by_kind(diffs)
57
+ diffs
58
+ .group_by(&:summary_element)
59
+ .each_with_object(Hash.new { |hash, key| hash[key] = [] }) do |(summary_element, element_diffs), a|
60
+ top_level_diff = element_diffs.find { |diff| summary_element == diff.target.value }
61
+ if top_level_diff
62
+ a[top_level_diff.kind] << summary_element
63
+ else
64
+ a["Change"] << summary_element
65
+ end
66
+ end
67
+ end
68
+
69
+ def summarize_elements(diffs)
70
+ return if diffs.nil? || diffs.empty?
71
+ puts Color.color("Elements", :headline)
72
+ by_layer = diffs.group_by { |diff| diff.summary_element.layer }
73
+ summarize "Business", by_layer["Business"]
74
+ summarize "Application", by_layer["Application"]
75
+ summarize "Technology", by_layer["Technology"]
76
+ summarize "Motivation", by_layer["Motivation"]
77
+ summarize "Implementation and Migration", by_layer["Implementation and Migration"]
78
+ summarize "Connectors", by_layer["Connectors"]
79
+ end
80
+
81
+ def summarize_diagrams(diffs)
82
+ return if diffs.nil? || diffs.empty?
83
+ puts Color.color("Diagrams", :headline)
84
+
85
+ by_kind = diffs_by_kind(diffs)
86
+ %w(Delete Change Insert).each do |kind|
87
+ next unless by_kind.key?(kind)
88
+ diagram_names = by_kind[kind].uniq.map(&:name)
89
+ puts " #{color(kind)}"
90
+ # TODO: make this magic number an option
91
+ diagram_names[0..14].each { |diagram_name| puts " #{diagram_name}" }
92
+ puts " ... and #{diagram_names.size - 15} more" if diagram_names.size > 15
93
+ end
94
+ end
95
+
96
+ def color(kind)
97
+ Color.color(kind, kind)
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "highline"
4
+
5
+ module Archimate
6
+ module Cli
7
+ class Duper
8
+ def initialize(model, output, mergeall = false)
9
+ @model = model
10
+ @output = output
11
+ @mergeall = mergeall
12
+ @cli = HighLine.new
13
+ @skipall = false
14
+ end
15
+
16
+ def list
17
+ dupes = Archimate::Lint::DuplicateEntities.new(@model)
18
+
19
+ dupes.each do |element_type, _name, entities|
20
+ @output.puts "#{element_type} has potential duplicates: \n\t#{entities.join(",\n\t")}\n"
21
+ end
22
+ @output.puts "Total Possible Duplicates: #{dupes.count}"
23
+ end
24
+
25
+ def merge
26
+ dupes = Archimate::Lint::DuplicateEntities.new(@model)
27
+ if dupes.empty?
28
+ @output.puts "No potential duplicates detected"
29
+ return
30
+ end
31
+
32
+ dupes.each do |element_type, name, entities|
33
+ handle_duplicate(element_type, name, entities)
34
+ end
35
+
36
+ # TODO: make the output writer be an argument
37
+ Archimate::FileFormats::ArchiFileWriter.new(@model).write(@output)
38
+ end
39
+
40
+ # Belongs to merge
41
+ # 1. Determine which one is the *original*
42
+ protected def handle_duplicate(element_type, name, entities)
43
+ return if @skipall
44
+ first_entity = entities.first
45
+ choice = @mergeall ? first_entity : choices(element_type, name, entities)
46
+
47
+ case choice
48
+ when :mergeall
49
+ @mergeall = true
50
+ choice = first_entity
51
+ when :skip
52
+ choice = nil
53
+ @cli.say("Skipping")
54
+ when :skipall
55
+ @skipall = true
56
+ choice = nil
57
+ @cli.say("Skipping")
58
+ end
59
+
60
+ return unless choice
61
+ @model.merge_entities(choice, entities)
62
+ end
63
+
64
+ # Belongs to handle_duplicate
65
+ protected def choices(element_type, name, entities)
66
+ @cli.choose do |menu|
67
+ # TODO: set up a layout that permits showing a repr of the copies
68
+ # to permit making the choice of the original
69
+ menu.header = "There are #{entities.size} #{element_type}s that are potentially duplicate"
70
+ menu.prompt = "What to do with potential duplicates?"
71
+ entities.each_with_index do |entity, idx|
72
+ menu.choice(entity, help: "Merge entities into this entity")
73
+ end
74
+ # menu.choice(:merge, help: "Merge elements into a single element", text: "Merge elements")
75
+ menu.choice(:mergeall, help: "Merge all elements from here on", text: "Merge all elements")
76
+ # menu.choices(:rename, "Rename to eliminate duplicates", "Rename element(s)")
77
+ menu.choice(:skip, help: "Don't change the elements", text: "Skip")
78
+ menu.choice(:skipall, help: "Skip the rest of the duplicates", text: "Skip the rest")
79
+ menu.select_by = :index_or_name
80
+ menu.help("Help", "don't panic")
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Archimate
4
+ module Cli
5
+ class Lint
6
+ def initialize(model, output_io)
7
+ @model = model
8
+ @output_io = output_io
9
+ end
10
+
11
+ def lint
12
+ Archimate::Lint::Linter.new(@model).report(@output_io)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Archimate
4
+ module Cli
5
+ class Mapper
6
+ HEADERS = %w[id name viewpoint].freeze
7
+ COL_DIVIDER = " | "
8
+
9
+ attr_reader :model
10
+
11
+ def initialize(model, output_io)
12
+ @model = model
13
+ @output = output_io
14
+ end
15
+
16
+ def header_row(widths, headers)
17
+ titles = []
18
+ widths.each_with_index { |w, i| titles << format("%-#{w}s", headers[i]) }
19
+ @output.puts titles.map { |t| Color.color(t.capitalize, %i[bold blue]) }.join(Color.color(COL_DIVIDER, :light_black))
20
+ @output.puts Color.color(widths.map { |w| "-" * w }.join("-+-"), :light_black)
21
+ end
22
+
23
+ def process_diagrams(diagrams)
24
+ diagrams.map { |e| [e.id, e.name, e.viewpoint, e.type] }.map do |row|
25
+ row[2] = case row[3]
26
+ when "canvas:CanvasModel"
27
+ ["Canvas", row[4]].compact.join(": ")
28
+ when "archimate:SketchModel"
29
+ "Sketch"
30
+ when "archimate:ArchimateDiagramModel"
31
+ DataModel::Constants::VIEWPOINTS[(row[2] || 0).to_i]
32
+ else
33
+ row[3]
34
+ end
35
+ row[0] = Color.color("#{row[0]}.png", :underline)
36
+ row
37
+ end
38
+ end
39
+
40
+ def compute_column_widths(diagrams, headers)
41
+ initial_widths = headers.map(&:size)
42
+ diagrams.each_with_object(initial_widths) do |diagram, memo|
43
+ diagram.slice(0, headers.size).each_with_index do |o, i|
44
+ memo[i] = !o.nil? && Color.uncolor(o).size > memo[i] ? Color.uncolor(o).size : memo[i]
45
+ end
46
+ memo
47
+ end
48
+ end
49
+
50
+ def output_diagrams(diagrams, widths)
51
+ diagrams.sort_by { |a| a[1] }.each do |m|
52
+ row = []
53
+ m.slice(0, widths.size).each_with_index { |c, i| row << format("%-#{widths[i]}s", c) }
54
+ @output.puts row.join(Color.color(COL_DIVIDER, :light_black))
55
+ end
56
+ end
57
+
58
+ def build_organization_hash(organizations, parent = "", hash = {})
59
+ organizations.each_with_object(hash) do |i, a|
60
+ organization_path = [parent, i.name].join("/")
61
+ a[organization_path] = i
62
+ build_organization_hash(i.organizations, organization_path, a)
63
+ end
64
+ end
65
+
66
+ def map
67
+ widths = compute_column_widths(process_diagrams(model.diagrams), HEADERS)
68
+ adjusted_widths = widths.inject(COL_DIVIDER.size * (HEADERS.size - 1), &:+)
69
+ header_row(widths, HEADERS)
70
+ organization_paths = build_organization_hash(model.organizations)
71
+ organization_paths.keys.sort.each do |organization_name|
72
+ diagrams = organization_paths[organization_name].items.map { |i| model.lookup(i) }.select { |i| i.is_a?(DataModel::Diagram) }
73
+ next if diagrams.empty?
74
+ @output.puts(Color.color(format("%-#{adjusted_widths}s", organization_name), %i[bold green on_light_black]))
75
+ output_diagrams(process_diagrams(diagrams), widths)
76
+ end
77
+
78
+ @output.puts "\n#{model.diagrams.size} Diagrams"
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "parallel"
4
+
5
+ module Archimate
6
+ module Cli
7
+ class Merge
8
+ include Logging
9
+
10
+ attr_reader :base, :local, :remote, :merged_file
11
+
12
+ def self.merge(base_file, remote_file, local_file, merged_file)
13
+ Logging.debug { "Reading base file: #{base_file}, local file: #{local_file}, remote file: #{remote_file}" }
14
+ base, local, remote = Parallel.map([base_file, local_file, remote_file], in_processes: 3) do |file|
15
+ Archimate.read(file)
16
+ end
17
+ Logging.debug { "Merged file is #{merged_file}" }
18
+
19
+ Merge.new(base, local, remote, merged_file).run_merge
20
+ end
21
+
22
+ def initialize(base, local, remote, merged_file)
23
+ @base = base
24
+ @local = local
25
+ @remote = remote
26
+ @merged_file = merged_file
27
+ @merge = Archimate::Diff::Merge.new
28
+ end
29
+
30
+ def run_merge
31
+ debug { "Starting merging" }
32
+ merged, conflicts = @merge.three_way(base, local, remote)
33
+ # TODO: there should be no conflicts here
34
+ debug do
35
+ <<~MSG
36
+ Done merging
37
+ #{conflicts}
38
+ MSG
39
+ end
40
+
41
+ File.open(merged_file, "w") do |file|
42
+ # TODO: this should be controlled by the options and the defaulted to the read format
43
+ debug { "Serializing" }
44
+ Archimate::FileFormats::ArchiFileWriter.write(merged, file)
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Archimate
4
+ module Cli
5
+ # Merger is a class that is a decorator on Archimate::DataModel::Model
6
+ # to provide the capability to merge another model into itself
7
+ #
8
+ # TODO: provide for a conflict resolver instance
9
+ # TODO: provide an option to determine if potential matches are merged
10
+ # or if the conflict resolver should be asked.
11
+ class Merger # < SimpleDelegator
12
+ # def initialize(primary_model, conflict_resolver)
13
+ # super(primary_model)
14
+ # @resolver = conflict_resolver
15
+ # end
16
+
17
+ # What merge does:
18
+ # For all entities: (other than Model...):
19
+ # - PropertyDefinition
20
+ # - View
21
+ # - Viewpoint
22
+ # Entity:
23
+ # look for a matching entity: with result
24
+ # 1. Found a matching entity: goto entity merge
25
+ # 2. Found no matching entity, but id conflicts: gen new id, goto add entity
26
+ # 3. Found no matching entity: goto add entity
27
+ # entity merge:
28
+ # 1. merge (with func from deduper)
29
+ # add entity:
30
+ # 1. add entity to model
31
+ # add remapping entry to map from entities other model id to id in this model
32
+ # Relationship:
33
+ # def merge(other_model)
34
+ # other_model.entities.each do |entity|
35
+ # # TODO: matching entity should use the same criteria that DuplicateEntities uses.
36
+ # my_entity = find_matching_entity(entity)
37
+ # if my_entity
38
+ # end
39
+ # end
40
+
41
+ # TODO: handle inner text of elements
42
+ # TODO: handle merging by element type
43
+
44
+ def hash_to_attr(h)
45
+ h.map { |k, v| "#{k}=\"#{v}\"" }.join(" ")
46
+ end
47
+
48
+ def e_to_s(e)
49
+ "#{e.name} #{hash_to_attr(e.attributes)}"
50
+ end
51
+
52
+ # Merge node1, node2
53
+ # For node
54
+ # For each child
55
+ # If has a matching child
56
+ def merge(doc1, doc2)
57
+ doc2.children.each do |e|
58
+ next if e.name == "text" && e.text.strip.empty?
59
+ # p = e.path
60
+ # if p =~ /\[\d+\]$/
61
+ # p = p.gsub(/\[\d+\]$/, "[@name=\"#{e.attr("name")}\"]")
62
+ # end
63
+ # puts "Looking for #{p}"``
64
+ # matches = doc1.xpath(p)
65
+ css = ">#{e.name}"
66
+ # puts css
67
+ css += "[name=\"#{e.attr('name')}\"]" if e.attributes.include?("name")
68
+ css += "[xsi|type=\"#{e.attr('xsi:type')}\"]" if e.attributes.include?("xsi:type")
69
+ matches = doc1.css(css)
70
+ if !matches.empty? # We have a potential match
71
+ # puts "Match?"
72
+ # puts " Doc2: #{e_to_s(e)}"
73
+ # matches.each do |e1|
74
+ # puts " Doc1: #{e_to_s(e1)}"
75
+ # end
76
+ merge(matches[0], e) unless matches.size > 1
77
+ else # No match insert the node into the tree TODO: handle id conflicts
78
+ doc1.add_child(e)
79
+ end
80
+ end
81
+ doc1
82
+ end
83
+
84
+ def id_hash_for(doc)
85
+ doc.css("[id]").each_with_object({}) do |obj, memo|
86
+ memo[obj["id"]] = obj
87
+ memo
88
+ end
89
+ end
90
+
91
+ def conflicting_ids(doc1, doc2)
92
+ doc_id_hash1 = id_hash_for(doc1)
93
+ doc_id_hash2 = id_hash_for(doc2)
94
+ cids = Set.new(doc_id_hash1.keys) & doc_id_hash2.keys
95
+ # puts "ID Conflicts:"
96
+ # puts cids.to_a.join(",")
97
+ cids
98
+ end
99
+
100
+ def merge_files(file1, file2)
101
+ doc1 = Nokogiri::XML(File.open(file1))
102
+ doc2 = Nokogiri::XML(File.open(file2))
103
+
104
+ # cids = conflicting_ids(doc1, doc2)
105
+ merge(doc1.root, doc2.root).document
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Archimate
4
+ module Cli
5
+ class Stats
6
+ attr_reader :output_io
7
+ attr_reader :model
8
+
9
+ def initialize(model, output_io)
10
+ @model = model
11
+ @output_io = output_io
12
+ end
13
+
14
+ def statistics
15
+ output_io.puts Color.color("#{model.name} ArchiMate Model Statistics\n", :headline)
16
+
17
+ output_io.puts "Elements:"
18
+ elements_by_layer.each do |layer, elements|
19
+ output_io.puts row(layer, elements.size, layer)
20
+ end
21
+ output_io.puts row("Total Elements", model.elements.size, :horizontal_line)
22
+ output_io.puts row("Relationships", model.relationships.size, :Relationship)
23
+ output_io.puts row("Diagrams", model.diagrams.size, :Diagram)
24
+ end
25
+
26
+ def elements_by_layer
27
+ @elements_by_layer ||= model.elements.group_by(&:layer)
28
+ end
29
+
30
+ def title_width
31
+ @title_width ||= (elements_by_layer.keys + ["Total Elements"]).map(&:size).max
32
+ end
33
+
34
+ def row(title, size, color)
35
+ @title_format_str ||= "%#{title_width}s"
36
+ @count_format_str ||= "%7d"
37
+
38
+ Color.color(format(@title_format_str, title), color) + format(@count_format_str, size)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Archimate
4
+ module Cli
5
+ # This class is used to export SVG diagrams as defined in the given model
6
+ class Svger
7
+ def self.export_svgs(archi_file, output_dir)
8
+ new(Archimate.read(archi_file).diagrams, output_dir).export_svgs
9
+ end
10
+
11
+ def initialize(diagrams, output_dir)
12
+ @diagrams = diagrams
13
+ @output_dir = output_dir
14
+ end
15
+
16
+ def export_svgs
17
+ progress = ProgressIndicator.new(total: @diagrams.size, title: "Writing SVGs")
18
+ @diagrams.each do |diagram|
19
+ export(diagram)
20
+ progress.increment
21
+ end
22
+ ensure
23
+ progress.finish
24
+ end
25
+
26
+ def export(diagram, file_name = nil)
27
+ file_name = Cli.process_svg_filename(file_name || diagram.id)
28
+ File.open(File.join(@output_dir, file_name), "wb") do |svg_file|
29
+ svg_file.write(Svg::Diagram.new(diagram).to_svg)
30
+ end
31
+ end
32
+ end
33
+
34
+ def self.process_svg_filename(name)
35
+ file_name = name.strip.tr("/", "-")
36
+ file_name += ".svg" unless file_name =~ /\.svg$/
37
+ file_name
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "highline"
4
+
5
+ HighLine.color_scheme = HighLine::ColorScheme.new do |cs|
6
+ cs[:headline] = %i[underline bold yellow on_black]
7
+ cs[:horizontal_line] = %i[bold white]
8
+ cs[:even_row] = [:green]
9
+ cs[:odd_row] = [:magenta]
10
+ cs[:error] = %i[bold red]
11
+ cs[:warning] = %i[bold yellow]
12
+ cs[:debug] = [:gray]
13
+ cs[:insert] = %i[bold green]
14
+ cs[:change] = %i[bold yellow]
15
+ cs[:move] = %i[bold yellow]
16
+ cs[:delete] = %i[bold red]
17
+ cs[:Business] = %i[black on_light_yellow]
18
+ cs[:Application] = %i[black on_light_blue]
19
+ cs[:Technology] = %i[black on_light_green]
20
+ cs[:Motivation] = %i[black on_light_magenta]
21
+ cs[:"Implementation and Migration"] = %i[black on_light_red]
22
+ cs[:Physical] = %i[black on_light_green]
23
+ cs[:Connectors] = %i[black on_light_gray]
24
+ cs[:unknown_layer] = %i[black on_gray]
25
+ cs[:Model] = [:cyan]
26
+ cs[:Connection] = [:blue]
27
+ cs[:Organization] = [:cyan]
28
+ cs[:Relationship] = %i[black on_light_gray]
29
+ cs[:Diagram] = %i[black on_cyan]
30
+ cs[:path] = [:light_blue]
31
+ end
32
+
33
+ module Archimate
34
+ class Color
35
+ def self.layer_color(layer, str)
36
+ layer_sym = layer.to_sym
37
+ sym = HighLine.color_scheme.include?(layer_sym) ? layer_sym : :unknown_layer
38
+ color(str, sym)
39
+ end
40
+
41
+ def self.data_model(str)
42
+ layer_color(str, str)
43
+ end
44
+
45
+ def self.color(str, args)
46
+ HighLine.color(str, args)
47
+ end
48
+
49
+ def self.uncolor(str)
50
+ HighLine.uncolor(str)
51
+ end
52
+ end
53
+ end