archimate 1.1.1 → 1.2.1

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.
@@ -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