archimate-diff 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/archimate-diff.gemspec +51 -0
- data/exe/archidiff +7 -0
- data/exe/archidiff-summary +7 -0
- data/exe/archimerge +7 -0
- data/exe/fmtxml +7 -0
- data/lib/archimate-diff.rb +33 -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/cli/conflict_resolver.rb +41 -0
- data/lib/archimate/diff/cli/diff.rb +33 -0
- data/lib/archimate/diff/cli/diff_summary.rb +103 -0
- data/lib/archimate/diff/cli/merge.rb +51 -0
- data/lib/archimate/diff/cli/merger.rb +111 -0
- data/lib/archimate/diff/conflict.rb +31 -0
- data/lib/archimate/diff/conflicts.rb +89 -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/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/version.rb +6 -0
- metadata +453 -0
@@ -0,0 +1,51 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
lib = File.expand_path('../lib', __FILE__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'archimate-diff'
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = "archimate-diff"
|
9
|
+
spec.version = Archimate::Diff::VERSION
|
10
|
+
spec.authors = ["Mark Morga"]
|
11
|
+
spec.email = ["markmorga@gmail.com", "mmorga@rackspace.com"]
|
12
|
+
|
13
|
+
spec.summary = "Archimate-Diff Tools"
|
14
|
+
spec.description = "A collection of tools to diff and merge ArchiMate files"
|
15
|
+
spec.homepage = "http://github.com/mmorga/archimate-diff"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
|
+
spec.bindir = "exe"
|
19
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
spec.metadata["yard.run"] = "yri" # use "yard" to build full HTML docs.
|
22
|
+
spec.required_ruby_version = '>= 2.3.0'
|
23
|
+
|
24
|
+
spec.add_runtime_dependency "archimate", "~> 1.1"
|
25
|
+
spec.add_runtime_dependency "nokogiri", "~> 1.6"
|
26
|
+
spec.add_runtime_dependency "thor", "~> 0.19"
|
27
|
+
spec.add_runtime_dependency "highline", "~> 1.7"
|
28
|
+
spec.add_runtime_dependency "ruby-progressbar", "~>1.8.1"
|
29
|
+
spec.add_runtime_dependency "parallel", "~> 1.11"
|
30
|
+
|
31
|
+
spec.add_development_dependency "bundler"
|
32
|
+
spec.add_development_dependency "rake"
|
33
|
+
spec.add_development_dependency "minitest"
|
34
|
+
spec.add_development_dependency "minitest-matchers"
|
35
|
+
spec.add_development_dependency "minitest-color"
|
36
|
+
spec.add_development_dependency "minitest-profile"
|
37
|
+
spec.add_development_dependency "pry"
|
38
|
+
spec.add_development_dependency "pry-byebug"
|
39
|
+
spec.add_development_dependency "guard"
|
40
|
+
spec.add_development_dependency "guard-minitest"
|
41
|
+
spec.add_development_dependency "guard-bundler"
|
42
|
+
spec.add_development_dependency "ruby-prof"
|
43
|
+
spec.add_development_dependency "simplecov"
|
44
|
+
spec.add_development_dependency "simplecov-json"
|
45
|
+
spec.add_development_dependency "kramdown"
|
46
|
+
spec.add_development_dependency "yard"
|
47
|
+
spec.add_development_dependency "guard-ctags-bundler"
|
48
|
+
spec.add_development_dependency "faker"
|
49
|
+
spec.add_development_dependency "rsense"
|
50
|
+
spec.add_development_dependency "awesome_print"
|
51
|
+
end
|
data/exe/archidiff
ADDED
data/exe/archimerge
ADDED
data/exe/fmtxml
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "archimate"
|
4
|
+
require 'archimate/diff/difference'
|
5
|
+
require 'archimate/diff/archimate_node_reference'
|
6
|
+
require 'archimate/diff/archimate_identified_node_reference'
|
7
|
+
require 'archimate/diff/archimate_array_reference'
|
8
|
+
require 'archimate/diff/archimate_node_attribute_reference'
|
9
|
+
require 'archimate/diff/change'
|
10
|
+
require 'archimate/diff/conflict'
|
11
|
+
require 'archimate/diff/conflicts'
|
12
|
+
require 'archimate/diff/delete'
|
13
|
+
require 'archimate/diff/insert'
|
14
|
+
require 'archimate/diff/merge'
|
15
|
+
require 'archimate/diff/move'
|
16
|
+
require "archimate/diff/version"
|
17
|
+
|
18
|
+
module Archimate
|
19
|
+
module Diff
|
20
|
+
module Cli
|
21
|
+
autoload :ConflictResolver, 'archimate/diff/cli/conflict_resolver'
|
22
|
+
autoload :Diff, 'archimate/diff/cli/diff'
|
23
|
+
autoload :DiffSummary, 'archimate/diff/cli/diff_summary'
|
24
|
+
autoload :Merge, 'archimate/diff/cli/merge'
|
25
|
+
autoload :Merger, 'archimate/diff/cli/merger'
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Computes the set of differences between base and remote models
|
30
|
+
def self.diff(base, remote)
|
31
|
+
base.diff(remote)
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Archimate
|
4
|
+
module Diff
|
5
|
+
class ArchimateArrayReference < ArchimateNodeReference
|
6
|
+
using DataModel::DiffableArray
|
7
|
+
using DataModel::DiffablePrimitive
|
8
|
+
|
9
|
+
attr_reader :array_index
|
10
|
+
|
11
|
+
def initialize(array, array_index)
|
12
|
+
unless array.is_a?(Array)
|
13
|
+
raise(
|
14
|
+
TypeError,
|
15
|
+
"array argument must be an Array, was #{array.class}"
|
16
|
+
)
|
17
|
+
end
|
18
|
+
unless array_index.is_a?(Integer)
|
19
|
+
raise(
|
20
|
+
TypeError,
|
21
|
+
"array_index argument must be a Integer, was #{array_index.class} #{array_index.inspect}"
|
22
|
+
)
|
23
|
+
end
|
24
|
+
unless array_index >= 0 && array_index < array.size
|
25
|
+
raise(
|
26
|
+
ArgumentError,
|
27
|
+
"array_index argument a valid index for array #{array_index.inspect}"
|
28
|
+
)
|
29
|
+
end
|
30
|
+
super(array)
|
31
|
+
@array_index = array_index
|
32
|
+
end
|
33
|
+
|
34
|
+
def value
|
35
|
+
archimate_node[array_index]
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_s
|
39
|
+
value.to_s
|
40
|
+
end
|
41
|
+
|
42
|
+
def lookup_in_model(model)
|
43
|
+
result = lookup_parent_in_model(model)
|
44
|
+
raise TypeError, "result was #{result.class} expected Array" unless result.is_a?(Array)
|
45
|
+
result[array_index]
|
46
|
+
end
|
47
|
+
|
48
|
+
def path(options = {})
|
49
|
+
@array_ref_path ||= [
|
50
|
+
super,
|
51
|
+
case value
|
52
|
+
when DataModel::Referenceable
|
53
|
+
value.id
|
54
|
+
else
|
55
|
+
array_index
|
56
|
+
end
|
57
|
+
].map(&:to_s).reject(&:empty?).join("/")
|
58
|
+
end
|
59
|
+
|
60
|
+
# For inserts - we can't be sure of what is available (without an expensive sort)
|
61
|
+
# So lookup the first previous value that exists in to_model and insert it after that
|
62
|
+
# value instead of a fixed index.
|
63
|
+
def find_insert_index_in_ary(ary)
|
64
|
+
return -1 if array_index.zero?
|
65
|
+
my_idx = (array_index - 1).downto(0).find(-1) do |idx|
|
66
|
+
ary.smart_include?(archimate_node[idx])
|
67
|
+
end
|
68
|
+
ary.smart_find(archimate_node[my_idx])
|
69
|
+
end
|
70
|
+
|
71
|
+
def insert(to_model)
|
72
|
+
ary_in_model = lookup_parent_in_model(to_model)
|
73
|
+
insert_idx = find_insert_index_in_ary(ary_in_model) + 1
|
74
|
+
ary_in_model.insert(insert_idx, value)
|
75
|
+
end
|
76
|
+
|
77
|
+
def delete(to_model)
|
78
|
+
ary_in_model = lookup_parent_in_model(to_model)
|
79
|
+
if ary_in_model.nil?
|
80
|
+
$stderr.puts "lookup parent in model failed for path #{path}"
|
81
|
+
return nil
|
82
|
+
end
|
83
|
+
idx = ary_in_model.smart_find(value)
|
84
|
+
if idx
|
85
|
+
ary_in_model.delete_at(idx)
|
86
|
+
else
|
87
|
+
$stderr.puts "Couldn't find item #{value.inspect} in path #{path} to delete in to_model"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def change(to_model, from_value)
|
92
|
+
ary_in_model = lookup_parent_in_model(to_model)
|
93
|
+
idx = ary_in_model.smart_find(from_value)
|
94
|
+
if idx.nil?
|
95
|
+
$stderr.puts "Couldn't find value #{from_value.inspect} in path #{path} to change in to_model, adding to end of list"
|
96
|
+
idx = ary_in_model.size
|
97
|
+
end
|
98
|
+
ary_in_model[idx] = value
|
99
|
+
end
|
100
|
+
|
101
|
+
def move(to_model, _from_ref)
|
102
|
+
ary_in_model = lookup_parent_in_model(to_model)
|
103
|
+
insert_idx = parent.previous_item_index(ary_in_model, value) + 1
|
104
|
+
current_idx = ary_in_model.smart_find(value)
|
105
|
+
deleted_value = ary_in_model.delete_at(current_idx)
|
106
|
+
ary_in_model.insert(
|
107
|
+
insert_idx,
|
108
|
+
deleted_value
|
109
|
+
)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Archimate
|
4
|
+
module Diff
|
5
|
+
class ArchimateReferenceableReference < ArchimateNodeReference
|
6
|
+
using DataModel::DiffableArray
|
7
|
+
|
8
|
+
def initialize(archimate_node)
|
9
|
+
unless archimate_node.is_a?(DataModel::Referenceable)
|
10
|
+
raise(
|
11
|
+
TypeError,
|
12
|
+
"archimate_node is a #{archimate_node.class}, Referenceable was expected"
|
13
|
+
)
|
14
|
+
end
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
def lookup_in_model(model)
|
19
|
+
raise TypeError unless model.is_a?(DataModel::Model)
|
20
|
+
# There can be only one Model so return the model argument if
|
21
|
+
# this node reference is a Model. This escape is required in case
|
22
|
+
# the Model this is being applied to has a different id than the
|
23
|
+
# model of the attribute this reference refers to.
|
24
|
+
return model if archimate_node.is_a?(DataModel::Model)
|
25
|
+
model.lookup(archimate_node.id)
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_s
|
29
|
+
archimate_node.to_s
|
30
|
+
end
|
31
|
+
|
32
|
+
def value
|
33
|
+
archimate_node
|
34
|
+
end
|
35
|
+
|
36
|
+
def parent
|
37
|
+
archimate_node.parent
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Archimate
|
4
|
+
module Diff
|
5
|
+
class ArchimateNodeAttributeReference < ArchimateNodeReference
|
6
|
+
attr_reader :attribute
|
7
|
+
|
8
|
+
def initialize(archimate_node, attribute)
|
9
|
+
unless archimate_node.is_a?(DataModel::ArchimateNode)
|
10
|
+
raise(
|
11
|
+
TypeError,
|
12
|
+
"archimate_node must be an ArchimateNode, was #{archimate_node.class}"
|
13
|
+
)
|
14
|
+
end
|
15
|
+
unless attribute.is_a?(Symbol)
|
16
|
+
raise(
|
17
|
+
TypeError,
|
18
|
+
"Node #{archimate_node.class} attribute must be a sym, was a #{attribute.class} value #{attribute.inspect}"
|
19
|
+
)
|
20
|
+
end
|
21
|
+
unless archimate_node.class.schema.keys.include?(attribute)
|
22
|
+
raise(
|
23
|
+
ArgumentError,
|
24
|
+
"Attribute #{attribute} invalid for class #{archimate_node.class}"
|
25
|
+
)
|
26
|
+
end
|
27
|
+
super(archimate_node)
|
28
|
+
@attribute = attribute
|
29
|
+
end
|
30
|
+
|
31
|
+
def ==(other)
|
32
|
+
super && attribute == other.attribute
|
33
|
+
end
|
34
|
+
|
35
|
+
def lookup_in_model(model)
|
36
|
+
recurse_lookup_in_model(archimate_node, model)[attribute]
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_s
|
40
|
+
attribute.to_s
|
41
|
+
end
|
42
|
+
|
43
|
+
def value
|
44
|
+
archimate_node[attribute]
|
45
|
+
end
|
46
|
+
|
47
|
+
def path(options = {})
|
48
|
+
@node_attribute_ref_path ||= [
|
49
|
+
super, @attribute
|
50
|
+
].map(&:to_s).reject(&:empty?).join("/")
|
51
|
+
end
|
52
|
+
|
53
|
+
def insert(to_model)
|
54
|
+
lookup_parent_in_model(to_model).set(attribute, value)
|
55
|
+
end
|
56
|
+
|
57
|
+
def delete(to_model)
|
58
|
+
lookup_parent_in_model(to_model).delete(attribute)
|
59
|
+
end
|
60
|
+
|
61
|
+
def change(to_model, _from_value)
|
62
|
+
lookup_parent_in_model(to_model).set(attribute, value)
|
63
|
+
end
|
64
|
+
|
65
|
+
def move(_to_model)
|
66
|
+
raise "Move is not valid for ArchimateNodes"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Archimate
|
4
|
+
module Diff
|
5
|
+
class ArchimateNodeReference
|
6
|
+
using DataModel::DiffablePrimitive
|
7
|
+
using DataModel::DiffableArray
|
8
|
+
|
9
|
+
attr_reader :archimate_node
|
10
|
+
|
11
|
+
# There should be only a few things that are valid here:
|
12
|
+
# 1. An archimate node and a attribute name sym
|
13
|
+
# 2. An array and index
|
14
|
+
# Produces a NodeReference instance for the given parameters
|
15
|
+
def self.for_node(node, child_node)
|
16
|
+
case node
|
17
|
+
when DataModel::ArchimateNode
|
18
|
+
ArchimateNodeAttributeReference.new(node, child_node)
|
19
|
+
when Array
|
20
|
+
ArchimateArrayReference.new(node, child_node)
|
21
|
+
else
|
22
|
+
raise TypeError, "Node references need to be either an ArchimateNode or an Array"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize(archimate_node)
|
27
|
+
unless archimate_node.is_a?(DataModel::ArchimateNode) || archimate_node.is_a?(Array)
|
28
|
+
raise(
|
29
|
+
TypeError,
|
30
|
+
"archimate_node must be an ArchimateNode or Array, was #{archimate_node.class}"
|
31
|
+
)
|
32
|
+
end
|
33
|
+
raise "new WTF? parent at path #{archimate_node.path} is a #{archimate_node.class} but isn't assigned a model" if archimate_node.in_model.nil? && !archimate_node.is_a?(DataModel::Model)
|
34
|
+
@archimate_node = archimate_node
|
35
|
+
end
|
36
|
+
|
37
|
+
def ==(other)
|
38
|
+
other.is_a?(self.class) &&
|
39
|
+
value == other.value
|
40
|
+
end
|
41
|
+
|
42
|
+
def to_s
|
43
|
+
value.to_s
|
44
|
+
end
|
45
|
+
|
46
|
+
def lookup_in_model(model)
|
47
|
+
recurse_lookup_in_model(archimate_node, model)
|
48
|
+
end
|
49
|
+
|
50
|
+
def recurse_lookup_in_model(node, model)
|
51
|
+
return nil if node.nil?
|
52
|
+
raise TypeError, "node argument must be ArchimateNode or Array, was a #{node.class}" unless node.is_a?(Array) || node.is_a?(DataModel::ArchimateNode)
|
53
|
+
raise TypeError, "model argument must be a Model, was a #{model.class}" unless model.is_a?(DataModel::Model)
|
54
|
+
if node.is_a?(DataModel::Model)
|
55
|
+
return model
|
56
|
+
elsif node.is_a?(DataModel::Referenceable)
|
57
|
+
return model.lookup(node.id)
|
58
|
+
else
|
59
|
+
node_parent_in_model = recurse_lookup_in_model(node.parent, model)
|
60
|
+
node_parent_in_model[node.parent_attribute_name] unless node_parent_in_model.nil?
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def lookup_parent_in_model(model)
|
65
|
+
raise "WTF? parent at path #{path} is a #{parent.class} but isn't assigned a model" if parent.in_model.nil? && !parent.is_a?(DataModel::Model)
|
66
|
+
result = recurse_lookup_in_model(parent, model)
|
67
|
+
$stderr.puts "Unable to lookup parent with path #{path}" if result.nil?
|
68
|
+
result
|
69
|
+
end
|
70
|
+
|
71
|
+
def parent
|
72
|
+
archimate_node
|
73
|
+
end
|
74
|
+
|
75
|
+
def path(options = {})
|
76
|
+
@node_ref_path ||= archimate_node.path(options)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Archimate
|
3
|
+
module Diff
|
4
|
+
class Change < Difference
|
5
|
+
using DataModel::DiffableArray
|
6
|
+
|
7
|
+
# Create a new Change difference
|
8
|
+
#
|
9
|
+
# @param target [Archimate::Diff::ArchimateNodeReference] reference to
|
10
|
+
# ArchimateNode that was changed
|
11
|
+
# @param changed_from [Archimate::Diff::ArchimateNodeReference] Element
|
12
|
+
# that was changed
|
13
|
+
def initialize(target, changed_from)
|
14
|
+
super(target, changed_from)
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_s
|
18
|
+
# Note - the explicit to_s is required to access the DiffableArray
|
19
|
+
# implementation if the parent is an Array.
|
20
|
+
"#{diff_type} #{changed_from.parent&.to_s} #{Color.color(target.to_s, :change)} changed to #{target.value}"
|
21
|
+
end
|
22
|
+
|
23
|
+
def apply(to_model)
|
24
|
+
unless to_model.is_a?(DataModel::Model)
|
25
|
+
throw(
|
26
|
+
TypeError,
|
27
|
+
"Expected a Archimate::DataModel::Model, was a #{to_model.class}"
|
28
|
+
)
|
29
|
+
end
|
30
|
+
target.change(to_model, changed_from.value)
|
31
|
+
to_model
|
32
|
+
end
|
33
|
+
|
34
|
+
def change?
|
35
|
+
true
|
36
|
+
end
|
37
|
+
|
38
|
+
def kind
|
39
|
+
"Change"
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def diff_type
|
45
|
+
Color.color('CHANGE:', :change)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|