archimate 1.1.1 → 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,49 +0,0 @@
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
@@ -1,31 +0,0 @@
1
- # frozen_string_literal: true
2
- module Archimate
3
- module Diff
4
- class Conflict
5
- attr_reader :base_local_diffs
6
- attr_reader :base_remote_diffs
7
- attr_reader :reason
8
- attr_reader :diffs
9
-
10
- def initialize(base_local_diffs, base_remote_diffs, reason)
11
- @base_local_diffs = Array(base_local_diffs)
12
- @base_remote_diffs = Array(base_remote_diffs)
13
- @diffs = @base_local_diffs + @base_remote_diffs
14
- @reason = reason
15
- end
16
-
17
- def to_s
18
- "#{Color.color('CONFLICT:', [:black, :on_red])} #{reason}\n" \
19
- "\tBase->Local Diff(s):\n\t\t#{base_local_diffs.map(&:to_s).join("\n\t\t")}" \
20
- "\n\tBase->Remote Diffs(s):\n\t\t#{base_remote_diffs.map(&:to_s).join("\n\t\t")}"
21
- end
22
-
23
- def ==(other)
24
- other.is_a?(self.class) &&
25
- base_local_diffs == other.base_local_diffs &&
26
- base_remote_diffs == other.base_remote_diffs &&
27
- reason == other.reason
28
- end
29
- end
30
- end
31
- end
@@ -1,89 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'forwardable'
4
- require 'parallel'
5
- require 'archimate/diff/conflicts/base_conflict'
6
- require 'archimate/diff/conflicts/deleted_items_child_updated_conflict'
7
- require 'archimate/diff/conflicts/deleted_items_referenced_conflict'
8
- require 'archimate/diff/conflicts/path_conflict'
9
-
10
- module Archimate
11
- module Diff
12
- class Conflicts
13
- extend Forwardable
14
-
15
- attr_reader :base_local_diffs
16
- attr_reader :base_remote_diffs
17
-
18
- def_delegator :@conflicts, :empty?
19
- def_delegator :@conflicts, :size
20
- def_delegator :@conflicts, :first
21
- def_delegator :@conflicts, :map
22
- def_delegator :@conflicts, :each
23
-
24
- include Archimate::Logging
25
-
26
- def initialize(base_local_diffs, base_remote_diffs)
27
- @base_local_diffs = base_local_diffs
28
- @base_remote_diffs = base_remote_diffs
29
- @conflict_finders = [PathConflict, DeletedItemsChildUpdatedConflict, DeletedItemsReferencedConflict]
30
- @conflicts = nil
31
- @conflicting_diffs = nil
32
- @unconflicted_diffs = nil
33
- # TODO: consider making this an argument
34
- @conflict_resolver = Cli::ConflictResolver.new
35
- end
36
-
37
- # TODO: refactor this method elsewhere
38
- # resolve iterates through the set of conflicting diffs asking the user
39
- # (if running interactively) and return the set of diffs that can be applied.
40
- #
41
- # To keep diffs reasonably human readable in logs, the local diffs should
42
- # be applied first followed by the remote diffs.
43
- def resolve
44
- debug do
45
- <<~MSG
46
- Filtering out #{conflicts.size} conflicts from #{base_local_diffs.size + base_remote_diffs.size} diffs
47
- Remaining diffs #{unconflicted_diffs.size}
48
- MSG
49
- end
50
-
51
- conflicts.each_with_object(unconflicted_diffs) do |conflict, diffs|
52
- # TODO: this will result in diffs being out of order from their
53
- # original order. diffs should be flagged as conflicted and
54
- # this method should instead remove the conflicted flag.
55
- diffs.concat(@conflict_resolver.resolve(conflict))
56
- # TODO: if the conflict is resolved, it should be removed from the
57
- # @conflicts array.
58
- end
59
- end
60
-
61
- def conflicts
62
- @conflicts ||= find_conflicts
63
- end
64
-
65
- def conflicting_diffs
66
- @conflicting_diffs ||= conflicts.map(&:diffs).flatten
67
- end
68
-
69
- def unconflicted_diffs
70
- @unconflicted_diffs ||=
71
- (base_local_diffs + base_remote_diffs) - conflicting_diffs
72
- end
73
-
74
- def to_s
75
- "Conflicts:\n\n#{conflicts.map(&:to_s).join("\n\n")}\n"
76
- end
77
-
78
- private
79
-
80
- def find_conflicts
81
- # TODO: Running this in parallel breaks currently.
82
- # @conflicts = Parallel.map(@conflict_finders, in_processes: 3) { |cf_class|
83
- @conflicts = @conflict_finders.map { |cf_class|
84
- cf_class.new(base_local_diffs, base_remote_diffs).conflicts
85
- }.flatten
86
- end
87
- end
88
- end
89
- end
@@ -1,53 +0,0 @@
1
- # frozen_string_literal: true
2
- module Archimate
3
- module Diff
4
- class Conflicts
5
- # BaseConflict
6
- # @abstract
7
- class BaseConflict
8
- def initialize(base_local_diffs, base_remote_diffs)
9
- @base_local_diffs = base_local_diffs
10
- @base_remote_diffs = base_remote_diffs
11
- @associative = false
12
- @diff_iterations = nil
13
- end
14
-
15
- def filter1
16
- ->(_diff) { true }
17
- end
18
-
19
- def filter2
20
- ->(_diff) { true }
21
- end
22
-
23
- def conflicts
24
- progressbar = ProgressIndicator.new(total: diff_iterations.size, title: "Analyzing Conflicts")
25
- diff_iterations.each_with_object([]) do |(md1, md2), conflicts|
26
- progressbar.increment
27
- conflicts.concat(
28
- md1.map { |diff1| [diff1, md2.select(&method(:diff_conflicts).curry[diff1])] }
29
- .reject { |_diff1, diff2| diff2.empty? }
30
- .map { |diff1, diff2_ary| Conflict.new(diff1, diff2_ary, describe) }
31
- )
32
- end
33
- ensure
34
- progressbar.finish
35
- end
36
-
37
- def diff_combinations
38
- combos = [@base_local_diffs, @base_remote_diffs]
39
- @associative ? [combos] : combos.permutation(2)
40
- end
41
-
42
- # By default our conflict tests are not associative to we need to run
43
- # [local, remote] and [remote, local] through the tests.
44
- def diff_iterations
45
- @diff_iterations ||=
46
- diff_combinations.map do |local_diffs, remote_diffs|
47
- [local_diffs.select(&filter1), remote_diffs.select(&filter2)]
48
- end
49
- end
50
- end
51
- end
52
- end
53
- end
@@ -1,30 +0,0 @@
1
- # frozen_string_literal: true
2
- module Archimate
3
- module Diff
4
- class Conflicts
5
- class DeletedItemsChildUpdatedConflict < BaseConflict
6
- def describe
7
- "Checking for Deleted items in one change set have children that are inserted or changed in the other change set"
8
- end
9
-
10
- def filter1
11
- ->(diff) { diff.delete? }
12
- end
13
-
14
- def filter2
15
- ->(diff) { !diff.delete? }
16
- end
17
-
18
- # TODO: This is simple, but might be slow.
19
- def diff_conflicts(diff1, diff2)
20
- da1 = diff1.path.split("/")
21
- da2 = diff2.path.split("/")
22
-
23
- cmp_size = [da1, da2].map(&:size).min - 1
24
- return false if da2.size == cmp_size + 1
25
- da1[0..cmp_size] == da2[0..cmp_size]
26
- end
27
- end
28
- end
29
- end
30
- end
@@ -1,63 +0,0 @@
1
- # frozen_string_literal: true
2
- module Archimate
3
- module Diff
4
- class Conflicts
5
- # DeletedItemsReferencedConflict
6
- #
7
- # This sort of conflict occurs when one change set has deleted an element
8
- # that is referenced by id in the other change set.
9
- #
10
- # For example:
11
- #
12
- # In the local change set, an element with id "abc123" is deleted.
13
- # In the remote change set, a child is inserted into a diagram with
14
- # archimate_element = "abc123". These two changes are in conflict.
15
- class DeletedItemsReferencedConflict < BaseConflict
16
- using DataModel::DiffableArray
17
- using DataModel::DiffablePrimitive
18
-
19
- def describe
20
- "Checking for Deleted items in one change set are referenced in the other change set"
21
- end
22
-
23
- # Filters a changeset to potentially conflicting diffs (making the set
24
- # of combinations to check smaller)
25
- #
26
- # @return [lambda] a filter to limit diffs to Delete type
27
- def filter1
28
- ->(diff) { diff.delete? && diff.target.value.is_a?(DataModel::Referenceable) }
29
- end
30
-
31
- # Filters a changeset to potentially conflicting diffs (making the set
32
- # of combinations to check smaller)
33
- #
34
- # @return [lambda] a filter to limit diffs to other
35
- # than Delete type
36
- def filter2
37
- ->(diff) { !diff.delete? }
38
- end
39
-
40
- # Determine the set of conflicts between the given diffs
41
- # def conflicts
42
- # progressbar = ProgressIndicator.new(total: diff_iterations.size)
43
- # diff_iterations.each_with_object([]) do |(md1, md2), a|
44
- # progressbar.increment
45
- # a.concat(
46
- # md1.map { |diff1| [diff1, md2.select(&method(:diff_conflicts).curry[diff1])] }
47
- # .reject { |_diff1, diff2| diff2.empty? }
48
- # .map { |diff1, diff2_ary| Conflict.new(diff1, diff2_ary, describe) }
49
- # )
50
- # end
51
- # ensure
52
- # progressbar&.finish
53
- # end
54
-
55
- # TODO: This is simple, but might be slow. If it is, then override
56
- # the conflicts method to prevent calculating referenced_identified_nodes methods
57
- def diff_conflicts(diff1, diff2)
58
- diff2.target.value.referenced_identified_nodes.include?(diff1.target.value.id)
59
- end
60
- end
61
- end
62
- end
63
- end
@@ -1,51 +0,0 @@
1
- # frozen_string_literal: true
2
- module Archimate
3
- module Diff
4
- class Conflicts
5
- class PathConflict < BaseConflict
6
- def initialize(base_local_diffs, base_remote_diffs)
7
- super
8
- @associative = true
9
- end
10
-
11
- def describe
12
- "Checking for Differences in one change set that conflict with changes in other change set at the same path"
13
- end
14
-
15
- def diff_conflicts(diff1, diff2)
16
- same_path_but_diff(diff1, diff2) && in_conflict?(diff1, diff2)
17
- end
18
-
19
- private
20
-
21
- def same_path_but_diff(a, b)
22
- a.path == b.path && a != b
23
- end
24
-
25
- # I'm in conflict if:
26
- # 1. ld and rd are both changes to the same path (but not the same change)
27
- # 2. one is a change, the other a delete and changed_from is the same
28
- # 3. both are inserts of the different identifyable nodes with the same id
29
- #
30
- # If I'm not an identifyable node and my parent is an array, then two inserts are ok
31
- def in_conflict?(local_diff, remote_diff)
32
- return true if
33
- local_diff.target.parent.is_a?(Array) &&
34
- local_diff.target.value.is_a?(DataModel::Referenceable) &&
35
- local_diff.target.value.id == remote_diff.target.value.id &&
36
- local_diff != remote_diff
37
-
38
- case [local_diff, remote_diff].map { |d| d.class.name.split('::').last }.sort
39
- when %w(Change Change)
40
- local_diff.changed_from.value == remote_diff.changed_from.value &&
41
- local_diff.target.value != remote_diff.target.value
42
- when %w(Change Delete)
43
- true
44
- else
45
- false
46
- end
47
- end
48
- end
49
- end
50
- end
51
- end
@@ -1,41 +0,0 @@
1
- # frozen_string_literal: true
2
- module Archimate
3
- module Diff
4
- class Delete < Difference
5
- using DataModel::DiffablePrimitive
6
- using DataModel::DiffableArray
7
-
8
- # Create a new Delete difference
9
- #
10
- # @param target [ArchimateNodeReference] Element that was deleted
11
- def initialize(target)
12
- super
13
- end
14
-
15
- def to_s
16
- # Note - the explicit to_s is required to access the DiffableArray
17
- # implementation if the parent is an Array.
18
- "#{diff_type} #{target} from #{target.parent.to_s}"
19
- end
20
-
21
- def apply(el)
22
- target.delete(el)
23
- el
24
- end
25
-
26
- def delete?
27
- true
28
- end
29
-
30
- def kind
31
- "Delete"
32
- end
33
-
34
- private
35
-
36
- def diff_type
37
- Color.color('DELETE:', :delete)
38
- end
39
- end
40
- end
41
- end
@@ -1,113 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "forwardable"
4
-
5
- module Archimate
6
- module Diff
7
- class Difference
8
- using DataModel::DiffableArray
9
- using DataModel::DiffablePrimitive
10
- extend Forwardable
11
-
12
- ARRAY_RE = Regexp.compile(/\[(\d+)\]/)
13
- PATH_ROOT_SORT_ORDER = %w[elements relationships diagrams organizations].freeze
14
-
15
- # delete: something responds to parent, child attribute (which is thing deleted - and could be sym for archimate nodes or index for array), value
16
- # insert: something responds to parent, child attribute (or index), value, after value (to help with inserts)
17
- # change: something responds to parent, child attribute (or index), value, changed from value
18
- # move: something responds to parent, child index, value, after value) move after a particular value
19
- attr_reader :target
20
- attr_reader :changed_from
21
-
22
- def_delegator :@target, :path
23
-
24
- # Re-thinking.
25
- #
26
- # Requirements:
27
- #
28
- # 1. User friendly display of what is different in context
29
- # 2. Able to apply the diff to another model (which was based on the "base" of the diff)
30
- #
31
- # Delete: example
32
- # ArchimateNode child.bounds
33
- # ArchimateNode, attribute model, "name"
34
- # DiffableArray, ArchimateNode model.elements, element
35
- # bendpoint attributes under connection
36
- # documentation
37
- # properties
38
- # child/style/fill_color
39
- # child/style/font/name
40
- #
41
- # @param target [Dry::Struct with id attribute] the element operated on (why is array treated as a special case?)
42
- # @param changed_from [same class as target] (optional) for change this is the previous value
43
- # def initialize(changed_from, target)
44
- def initialize(target, changed_from = nil)
45
- # raise TypeError, "Expected target to be an ArchimateNodeReference" unless target.is_a?(ArchimateNodeReference)
46
- @target = target
47
- @changed_from = changed_from
48
- end
49
-
50
- def ==(other)
51
- other.is_a?(self.class) &&
52
- @target == other.target &&
53
- @changed_from == other.changed_from
54
- end
55
-
56
- # Difference sorting is based on the path.
57
- # Top level components are sorted in this order: (elements, relationships, diagrams, organizations)
58
- # Array entries are sorted by numeric order
59
- # Others are sorted alphabetically
60
- # TODO: this isn't complete
61
- def <=>(other)
62
- a = path_to_array
63
- b = other.path_to_array
64
-
65
- part_a = a.shift
66
- part_b = b.shift
67
- res = PATH_ROOT_SORT_ORDER.index(part_a) <=> PATH_ROOT_SORT_ORDER.index(part_b)
68
- return res unless res.zero?
69
-
70
- until a.empty? || b.empty?
71
- part_a = a.shift
72
- part_b = b.shift
73
-
74
- return part_a <=> part_b unless (part_a <=> part_b).zero?
75
- end
76
-
77
- return -1 if a.empty?
78
- return 1 if b.empty?
79
- part_a <=> part_b
80
- end
81
-
82
- def delete?
83
- false
84
- end
85
-
86
- def change?
87
- false
88
- end
89
-
90
- def insert?
91
- false
92
- end
93
-
94
- def move?
95
- false
96
- end
97
-
98
- def path_to_array
99
- path(force_array_index: :index).split("/").map do |p|
100
- md = ARRAY_RE.match(p)
101
- md ? md[1].to_i : p
102
- end
103
- end
104
-
105
- def summary_element
106
- summary_elements = [DataModel::Element, DataModel::Organization, DataModel::Relationship, DataModel::Diagram, DataModel::Model]
107
- se = target.value.primitive? ? target.parent : target.value
108
- se = se.parent while summary_elements.none? { |c| se.is_a?(c) }
109
- se
110
- end
111
- end
112
- end
113
- end