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.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/.projectile +18 -0
- data/.rubocop.yml +13 -0
- data/.travis.yml +4 -0
- data/.yardocs +15 -0
- data/Gemfile +5 -0
- data/Guardfile +27 -0
- data/LICENSE +201 -0
- data/README.md +94 -0
- data/Rakefile +29 -0
- data/TODOs.org +286 -0
- data/archimate.gemspec +51 -0
- data/bin/archimate +17 -0
- data/bin/console +13 -0
- data/bin/setup +8 -0
- data/exe/archidiff +7 -0
- data/exe/archidiff-summary +7 -0
- data/exe/archimate +7 -0
- data/exe/archimerge +7 -0
- data/exe/fmtxml +7 -0
- data/lib/archimate/cli/archi.rb +189 -0
- data/lib/archimate/cli/cleanup.rb +54 -0
- data/lib/archimate/cli/conflict_resolver.rb +39 -0
- data/lib/archimate/cli/convert.rb +39 -0
- data/lib/archimate/cli/diff.rb +31 -0
- data/lib/archimate/cli/diff_summary.rb +101 -0
- data/lib/archimate/cli/duper.rb +85 -0
- data/lib/archimate/cli/lint.rb +16 -0
- data/lib/archimate/cli/mapper.rb +82 -0
- data/lib/archimate/cli/merge.rb +49 -0
- data/lib/archimate/cli/merger.rb +109 -0
- data/lib/archimate/cli/stats.rb +42 -0
- data/lib/archimate/cli/svger.rb +40 -0
- data/lib/archimate/color.rb +53 -0
- data/lib/archimate/config.rb +35 -0
- data/lib/archimate/data_model/any_attribute.rb +13 -0
- data/lib/archimate/data_model/any_element.rb +15 -0
- data/lib/archimate/data_model/archimate_node.rb +181 -0
- data/lib/archimate/data_model/bounds.rb +72 -0
- data/lib/archimate/data_model/color.rb +47 -0
- data/lib/archimate/data_model/concept.rb +14 -0
- data/lib/archimate/data_model/concern.rb +17 -0
- data/lib/archimate/data_model/connection.rb +107 -0
- data/lib/archimate/data_model/constants.rb +82 -0
- data/lib/archimate/data_model/container.rb +17 -0
- data/lib/archimate/data_model/diagram.rb +40 -0
- data/lib/archimate/data_model/diffable_array.rb +213 -0
- data/lib/archimate/data_model/diffable_primitive.rb +83 -0
- data/lib/archimate/data_model/documentation.rb +14 -0
- data/lib/archimate/data_model/element.rb +87 -0
- data/lib/archimate/data_model/font.rb +54 -0
- data/lib/archimate/data_model/label.rb +19 -0
- data/lib/archimate/data_model/lang_string.rb +41 -0
- data/lib/archimate/data_model/location.rb +31 -0
- data/lib/archimate/data_model/metadata.rb +14 -0
- data/lib/archimate/data_model/model.rb +217 -0
- data/lib/archimate/data_model/modeling_note.rb +13 -0
- data/lib/archimate/data_model/named_referenceable.rb +14 -0
- data/lib/archimate/data_model/organization.rb +40 -0
- data/lib/archimate/data_model/property.rb +25 -0
- data/lib/archimate/data_model/property_definition.rb +16 -0
- data/lib/archimate/data_model/referenceable.rb +29 -0
- data/lib/archimate/data_model/relationship.rb +85 -0
- data/lib/archimate/data_model/schema_info.rb +18 -0
- data/lib/archimate/data_model/style.rb +32 -0
- data/lib/archimate/data_model/view.rb +12 -0
- data/lib/archimate/data_model/view_concept.rb +18 -0
- data/lib/archimate/data_model/view_node.rb +114 -0
- data/lib/archimate/data_model/viewpoint.rb +87 -0
- data/lib/archimate/data_model.rb +54 -0
- data/lib/archimate/diff/archimate_array_reference.rb +113 -0
- data/lib/archimate/diff/archimate_identified_node_reference.rb +41 -0
- data/lib/archimate/diff/archimate_node_attribute_reference.rb +70 -0
- data/lib/archimate/diff/archimate_node_reference.rb +80 -0
- data/lib/archimate/diff/change.rb +49 -0
- data/lib/archimate/diff/conflict.rb +31 -0
- data/lib/archimate/diff/conflicts/base_conflict.rb +53 -0
- data/lib/archimate/diff/conflicts/deleted_items_child_updated_conflict.rb +30 -0
- data/lib/archimate/diff/conflicts/deleted_items_referenced_conflict.rb +63 -0
- data/lib/archimate/diff/conflicts/path_conflict.rb +51 -0
- data/lib/archimate/diff/conflicts.rb +89 -0
- data/lib/archimate/diff/delete.rb +41 -0
- data/lib/archimate/diff/difference.rb +113 -0
- data/lib/archimate/diff/insert.rb +43 -0
- data/lib/archimate/diff/merge.rb +70 -0
- data/lib/archimate/diff/move.rb +51 -0
- data/lib/archimate/diff.rb +17 -0
- data/lib/archimate/export/csv_export.rb +32 -0
- data/lib/archimate/export/cypher.rb +171 -0
- data/lib/archimate/export/graph_ml.rb +131 -0
- data/lib/archimate/export/n_quads.rb +142 -0
- data/lib/archimate/file_format.rb +30 -0
- data/lib/archimate/file_formats/archi_file_format.rb +151 -0
- data/lib/archimate/file_formats/archi_file_reader.rb +252 -0
- data/lib/archimate/file_formats/archi_file_writer.rb +230 -0
- data/lib/archimate/file_formats/archimate_v2.rb +461 -0
- data/lib/archimate/file_formats/model_exchange_file/xml_lang_string.rb +35 -0
- data/lib/archimate/file_formats/model_exchange_file/xml_metadata.rb +115 -0
- data/lib/archimate/file_formats/model_exchange_file/xml_property_definitions.rb +28 -0
- data/lib/archimate/file_formats/model_exchange_file/xml_property_defs.rb +27 -0
- data/lib/archimate/file_formats/model_exchange_file_reader.rb +237 -0
- data/lib/archimate/file_formats/model_exchange_file_reader_21.rb +73 -0
- data/lib/archimate/file_formats/model_exchange_file_reader_30.rb +134 -0
- data/lib/archimate/file_formats/model_exchange_file_writer.rb +157 -0
- data/lib/archimate/file_formats/model_exchange_file_writer_21.rb +143 -0
- data/lib/archimate/file_formats/model_exchange_file_writer_30.rb +153 -0
- data/lib/archimate/file_formats/writer.rb +56 -0
- data/lib/archimate/lint/duplicate_entities.rb +121 -0
- data/lib/archimate/lint/linter.rb +146 -0
- data/lib/archimate/logging.rb +55 -0
- data/lib/archimate/maybe_io.rb +34 -0
- data/lib/archimate/progress_indicator.rb +29 -0
- data/lib/archimate/svg/archimate.css +232 -0
- data/lib/archimate/svg/child.rb +29 -0
- data/lib/archimate/svg/connection.rb +184 -0
- data/lib/archimate/svg/css_style.rb +31 -0
- data/lib/archimate/svg/diagram.rb +87 -0
- data/lib/archimate/svg/entity/and_junction.rb +14 -0
- data/lib/archimate/svg/entity/application_collaboration.rb +14 -0
- data/lib/archimate/svg/entity/application_component.rb +41 -0
- data/lib/archimate/svg/entity/application_event.rb +13 -0
- data/lib/archimate/svg/entity/application_function.rb +13 -0
- data/lib/archimate/svg/entity/application_interaction.rb +13 -0
- data/lib/archimate/svg/entity/application_interface.rb +13 -0
- data/lib/archimate/svg/entity/application_process.rb +13 -0
- data/lib/archimate/svg/entity/application_service.rb +13 -0
- data/lib/archimate/svg/entity/artifact.rb +39 -0
- data/lib/archimate/svg/entity/assessment.rb +14 -0
- data/lib/archimate/svg/entity/base_entity.rb +128 -0
- data/lib/archimate/svg/entity/business_actor.rb +14 -0
- data/lib/archimate/svg/entity/business_collaboration.rb +14 -0
- data/lib/archimate/svg/entity/business_event.rb +10 -0
- data/lib/archimate/svg/entity/business_function.rb +13 -0
- data/lib/archimate/svg/entity/business_interaction.rb +13 -0
- data/lib/archimate/svg/entity/business_interface.rb +13 -0
- data/lib/archimate/svg/entity/business_object.rb +13 -0
- data/lib/archimate/svg/entity/business_process.rb +13 -0
- data/lib/archimate/svg/entity/business_role.rb +14 -0
- data/lib/archimate/svg/entity/business_service.rb +13 -0
- data/lib/archimate/svg/entity/capability.rb +14 -0
- data/lib/archimate/svg/entity/communication_network.rb +14 -0
- data/lib/archimate/svg/entity/communication_path.rb +14 -0
- data/lib/archimate/svg/entity/constraint.rb +15 -0
- data/lib/archimate/svg/entity/contract.rb +13 -0
- data/lib/archimate/svg/entity/course_of_action.rb +14 -0
- data/lib/archimate/svg/entity/data_entity.rb +29 -0
- data/lib/archimate/svg/entity/data_object.rb +13 -0
- data/lib/archimate/svg/entity/deliverable.rb +13 -0
- data/lib/archimate/svg/entity/device.rb +48 -0
- data/lib/archimate/svg/entity/diagram_model_reference.rb +22 -0
- data/lib/archimate/svg/entity/diagram_object.rb +30 -0
- data/lib/archimate/svg/entity/distribution_network.rb +14 -0
- data/lib/archimate/svg/entity/driver.rb +14 -0
- data/lib/archimate/svg/entity/equipment.rb +16 -0
- data/lib/archimate/svg/entity/event_entity.rb +36 -0
- data/lib/archimate/svg/entity/facility.rb +16 -0
- data/lib/archimate/svg/entity/function_entity.rb +14 -0
- data/lib/archimate/svg/entity/gap.rb +21 -0
- data/lib/archimate/svg/entity/goal.rb +14 -0
- data/lib/archimate/svg/entity/group.rb +24 -0
- data/lib/archimate/svg/entity/implementation_event.rb +13 -0
- data/lib/archimate/svg/entity/infrastructure_function.rb +13 -0
- data/lib/archimate/svg/entity/infrastructure_interface.rb +13 -0
- data/lib/archimate/svg/entity/infrastructure_service.rb +13 -0
- data/lib/archimate/svg/entity/interaction_entity.rb +14 -0
- data/lib/archimate/svg/entity/interface_entity.rb +35 -0
- data/lib/archimate/svg/entity/junction.rb +19 -0
- data/lib/archimate/svg/entity/location.rb +14 -0
- data/lib/archimate/svg/entity/material.rb +14 -0
- data/lib/archimate/svg/entity/meaning.rb +40 -0
- data/lib/archimate/svg/entity/motivation_entity.rb +40 -0
- data/lib/archimate/svg/entity/network.rb +14 -0
- data/lib/archimate/svg/entity/node.rb +33 -0
- data/lib/archimate/svg/entity/node_shape.rb +64 -0
- data/lib/archimate/svg/entity/note.rb +30 -0
- data/lib/archimate/svg/entity/or_junction.rb +14 -0
- data/lib/archimate/svg/entity/outcome.rb +15 -0
- data/lib/archimate/svg/entity/path.rb +14 -0
- data/lib/archimate/svg/entity/plateau.rb +15 -0
- data/lib/archimate/svg/entity/principle.rb +15 -0
- data/lib/archimate/svg/entity/process_entity.rb +63 -0
- data/lib/archimate/svg/entity/product.rb +20 -0
- data/lib/archimate/svg/entity/rect.rb +13 -0
- data/lib/archimate/svg/entity/rect_entity.rb +25 -0
- data/lib/archimate/svg/entity/representation.rb +31 -0
- data/lib/archimate/svg/entity/requirement.rb +15 -0
- data/lib/archimate/svg/entity/resource.rb +14 -0
- data/lib/archimate/svg/entity/rounded_rect_entity.rb +23 -0
- data/lib/archimate/svg/entity/service_entity.rb +51 -0
- data/lib/archimate/svg/entity/sketch_model_sticky.rb +14 -0
- data/lib/archimate/svg/entity/stakeholder.rb +15 -0
- data/lib/archimate/svg/entity/system_software.rb +14 -0
- data/lib/archimate/svg/entity/technology_collaboration.rb +14 -0
- data/lib/archimate/svg/entity/technology_event.rb +13 -0
- data/lib/archimate/svg/entity/technology_function.rb +13 -0
- data/lib/archimate/svg/entity/technology_interaction.rb +13 -0
- data/lib/archimate/svg/entity/technology_interface.rb +13 -0
- data/lib/archimate/svg/entity/technology_process.rb +13 -0
- data/lib/archimate/svg/entity/technology_service.rb +13 -0
- data/lib/archimate/svg/entity/value.rb +27 -0
- data/lib/archimate/svg/entity/work_package.rb +14 -0
- data/lib/archimate/svg/entity.rb +93 -0
- data/lib/archimate/svg/entity_factory.rb +17 -0
- data/lib/archimate/svg/extents.rb +27 -0
- data/lib/archimate/svg/point.rb +7 -0
- data/lib/archimate/svg/svg_template.rb +27 -0
- data/lib/archimate/svg/svg_template.svg.erb +169 -0
- data/lib/archimate/version.rb +4 -0
- data/lib/archimate.rb +114 -0
- 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
|