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.
Files changed (40) 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/archimate-diff.gemspec +51 -0
  13. data/exe/archidiff +7 -0
  14. data/exe/archidiff-summary +7 -0
  15. data/exe/archimerge +7 -0
  16. data/exe/fmtxml +7 -0
  17. data/lib/archimate-diff.rb +33 -0
  18. data/lib/archimate/diff/archimate_array_reference.rb +113 -0
  19. data/lib/archimate/diff/archimate_identified_node_reference.rb +41 -0
  20. data/lib/archimate/diff/archimate_node_attribute_reference.rb +70 -0
  21. data/lib/archimate/diff/archimate_node_reference.rb +80 -0
  22. data/lib/archimate/diff/change.rb +49 -0
  23. data/lib/archimate/diff/cli/conflict_resolver.rb +41 -0
  24. data/lib/archimate/diff/cli/diff.rb +33 -0
  25. data/lib/archimate/diff/cli/diff_summary.rb +103 -0
  26. data/lib/archimate/diff/cli/merge.rb +51 -0
  27. data/lib/archimate/diff/cli/merger.rb +111 -0
  28. data/lib/archimate/diff/conflict.rb +31 -0
  29. data/lib/archimate/diff/conflicts.rb +89 -0
  30. data/lib/archimate/diff/conflicts/base_conflict.rb +53 -0
  31. data/lib/archimate/diff/conflicts/deleted_items_child_updated_conflict.rb +30 -0
  32. data/lib/archimate/diff/conflicts/deleted_items_referenced_conflict.rb +63 -0
  33. data/lib/archimate/diff/conflicts/path_conflict.rb +51 -0
  34. data/lib/archimate/diff/delete.rb +41 -0
  35. data/lib/archimate/diff/difference.rb +113 -0
  36. data/lib/archimate/diff/insert.rb +43 -0
  37. data/lib/archimate/diff/merge.rb +70 -0
  38. data/lib/archimate/diff/move.rb +51 -0
  39. data/lib/archimate/diff/version.rb +6 -0
  40. 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
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby -w
2
+ # frozen_string_literal: true
3
+
4
+ require "rubygems"
5
+ require "archimate-diff"
6
+
7
+ Archimate::Cli::Diff.diff(ARGV[0], ARGV[1], Archimate::AIO.new)
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby -w
2
+ # frozen_string_literal: true
3
+
4
+ require "rubygems"
5
+ require "archimate-diff"
6
+
7
+ Archimate::Cli::DiffSummary.diff(ARGV[0], ARGV[1])
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby -w
2
+ # frozen_string_literal: true
3
+
4
+ require "rubygems"
5
+ require "archimate-diff"
6
+
7
+ Archimate::Cli::Merge.merge(ARGV[0], ARGV[1], ARGV[2], ARGV[3])
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "archimate-diff"
6
+
7
+ Archimate::Cli::XmlTextconv.new(ARGV[0])
@@ -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