eco-helpers 2.6.4 → 2.7.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rubocop.yml +95 -0
- data/CHANGELOG.md +139 -2
- data/Rakefile +13 -7
- data/eco-helpers.gemspec +2 -2
- data/lib/eco/api/common/loaders/base.rb +2 -2
- data/lib/eco/api/common/loaders/case_base.rb +1 -1
- data/lib/eco/api/common/loaders/config/workflow/mailer.rb +5 -5
- data/lib/eco/api/common/loaders/error_handler.rb +8 -5
- data/lib/eco/api/common/loaders/parser.rb +44 -22
- data/lib/eco/api/common/loaders/policy.rb +6 -4
- data/lib/eco/api/common/loaders/use_case.rb +13 -7
- data/lib/eco/api/common/people/base_parser.rb +0 -2
- data/lib/eco/api/common/people/default_parsers/boolean_parser.rb +0 -1
- data/lib/eco/api/common/people/default_parsers/csv_parser.rb +1 -1
- data/lib/eco/api/common/people/default_parsers/date_parser.rb +64 -12
- data/lib/eco/api/common/people/default_parsers/freemium_parser.rb +0 -1
- data/lib/eco/api/common/people/default_parsers/login_providers_parser.rb +13 -5
- data/lib/eco/api/common/people/default_parsers/multi_parser.rb +0 -1
- data/lib/eco/api/common/people/default_parsers/numeric_parser.rb +18 -5
- data/lib/eco/api/common/people/default_parsers/policy_groups_parser.rb +8 -8
- data/lib/eco/api/common/people/default_parsers/select_parser.rb +50 -26
- data/lib/eco/api/common/people/default_parsers/send_invites_parser.rb +6 -6
- data/lib/eco/api/common/people/default_parsers/xls_parser.rb +9 -12
- data/lib/eco/api/common/people/default_parsers.rb +1 -12
- data/lib/eco/api/common/people/entries.rb +13 -13
- data/lib/eco/api/common/people/entry_factory.rb +76 -45
- data/lib/eco/api/common/people/person_attribute_parser.rb +8 -12
- data/lib/eco/api/common/people/person_entry.rb +86 -75
- data/lib/eco/api/common/people/person_entry_attribute_mapper.rb +60 -44
- data/lib/eco/api/common/people/person_factory.rb +30 -22
- data/lib/eco/api/common/people/person_modifier.rb +11 -13
- data/lib/eco/api/common/people/person_parser.rb +101 -39
- data/lib/eco/api/common/people/supervisor_helpers.rb +25 -26
- data/lib/eco/api/common/session/base_session.rb +9 -9
- data/lib/eco/api/common/session/environment.rb +7 -5
- data/lib/eco/api/common/session/sftp.rb +59 -32
- data/lib/eco/api/common/version_patches/exception.rb +11 -13
- data/lib/eco/api/error.rb +32 -20
- data/lib/eco/api/organization/node_classifications.rb +82 -0
- data/lib/eco/api/organization/policy_groups.rb +4 -6
- data/lib/eco/api/organization/tag_tree.rb +169 -93
- data/lib/eco/api/organization.rb +1 -0
- data/lib/eco/api/session/batch/job.rb +1 -1
- data/lib/eco/api/session/config/tagtree.rb +41 -23
- data/lib/eco/api/session/config/workflow.rb +113 -88
- data/lib/eco/api/session/config.rb +6 -0
- data/lib/eco/api/session.rb +51 -29
- data/lib/eco/api/usecases/base_io.rb +28 -25
- data/lib/eco/api/usecases/default/locations/cli/tagtree_extract_cli.rb +7 -2
- data/lib/eco/api/usecases/default/locations/cli/tagtree_upload_cli.rb +21 -0
- data/lib/eco/api/usecases/default/locations/csv_to_tree_case.rb +3 -3
- data/lib/eco/api/usecases/default/locations/tagtree_extract_case.rb +54 -23
- data/lib/eco/api/usecases/default/locations/tagtree_upload_case.rb +87 -0
- data/lib/eco/api/usecases/default/locations.rb +1 -0
- data/lib/eco/api/usecases/default/people/analyse_people_case.rb +60 -56
- data/lib/eco/api/usecases/default/people/change_email_case.rb +8 -9
- data/lib/eco/api/usecases/default/people/clean_unknown_tags_case.rb +13 -11
- data/lib/eco/api/usecases/default/people/clear_abilities_case.rb +2 -2
- data/lib/eco/api/usecases/default/people/org_data_convert_case.rb +25 -27
- data/lib/eco/api/usecases/default/people/refresh_case.rb +2 -2
- data/lib/eco/api/usecases/default/people/reinvite_trans_case.rb +1 -1
- data/lib/eco/api/usecases/default/people/reinvite_trans_cli.rb +0 -1
- data/lib/eco/api/usecases/default/people/restore_db_case.rb +39 -34
- data/lib/eco/api/usecases/default/people/set_default_tag_case.rb +19 -15
- data/lib/eco/api/usecases/default/people/supers_cyclic_identify_case.rb +16 -12
- data/lib/eco/api/usecases/default_cases/hris_case.rb +17 -15
- data/lib/eco/api/usecases/default_cases/samples/sftp_case.rb +30 -16
- data/lib/eco/api/usecases/graphql/base.rb +5 -3
- data/lib/eco/api/usecases/graphql/helpers/base/case_env.rb +4 -1
- data/lib/eco/api/usecases/graphql/helpers/base/graphql_env.rb +14 -0
- data/lib/eco/api/usecases/graphql/helpers/base.rb +5 -4
- data/lib/eco/api/usecases/graphql/helpers/location/base/classifications_parser.rb +60 -0
- data/lib/eco/api/usecases/graphql/helpers/location/base/tree_tracking.rb +72 -0
- data/lib/eco/api/usecases/graphql/helpers/location/base.rb +25 -59
- data/lib/eco/api/usecases/graphql/helpers/location/command/diff/as_update.rb +59 -0
- data/lib/eco/api/usecases/graphql/helpers/location/command/diff/compare.rb +49 -0
- data/lib/eco/api/usecases/graphql/helpers/location/command/diff.rb +11 -0
- data/lib/eco/api/usecases/graphql/helpers/location/command/diffs/stages/commandable.rb +46 -0
- data/lib/eco/api/usecases/graphql/helpers/location/command/diffs/stages/diff_sortable/for_archive.rb +23 -0
- data/lib/eco/api/usecases/graphql/helpers/location/command/diffs/stages/diff_sortable/for_unarchive.rb +65 -0
- data/lib/eco/api/usecases/graphql/helpers/location/command/diffs/stages/diff_sortable.rb +49 -0
- data/lib/eco/api/usecases/graphql/helpers/location/command/diffs/stages/sortable/relation_safe_sort.rb +119 -0
- data/lib/eco/api/usecases/graphql/helpers/location/command/diffs/stages/sortable.rb +59 -0
- data/lib/eco/api/usecases/graphql/helpers/location/command/diffs/stages.rb +82 -0
- data/lib/eco/api/usecases/graphql/helpers/location/command/diffs.rb +20 -0
- data/lib/eco/api/usecases/graphql/helpers/location/command/optimizations.rb +84 -0
- data/lib/eco/api/usecases/graphql/helpers/location/command/result.rb +4 -4
- data/lib/eco/api/usecases/graphql/helpers/location/command/results.rb +24 -12
- data/lib/eco/api/usecases/graphql/helpers/location/command.rb +21 -24
- data/lib/eco/api/usecases/graphql/helpers/location/tags_remap/tags_map.rb +1 -1
- data/lib/eco/api/usecases/graphql/helpers/location/tags_remap/tags_set.rb +10 -11
- data/lib/eco/api/usecases/graphql/helpers/location/tags_remap.rb +8 -9
- data/lib/eco/api/usecases/graphql/samples/location/command/dsl.rb +41 -12
- data/lib/eco/api/usecases/graphql/samples/location/command/results.rb +11 -80
- data/lib/eco/api/usecases/graphql/samples/location/command/service/tree_update.rb +89 -0
- data/lib/eco/api/usecases/graphql/samples/location/command/service.rb +6 -0
- data/lib/eco/api/usecases/graphql/samples/location/command/track_changed_ids.rb +89 -0
- data/lib/eco/api/usecases/graphql/samples/location/command.rb +3 -0
- data/lib/eco/api/usecases/graphql/samples/location/service/base.rb +9 -0
- data/lib/eco/api/usecases/graphql/samples/location/service/tree_diff/convertible/heading.rb +18 -0
- data/lib/eco/api/usecases/graphql/samples/location/service/tree_diff/convertible/inputable.rb +53 -0
- data/lib/eco/api/usecases/graphql/samples/location/service/tree_diff/convertible/parsing/classifications.rb +34 -0
- data/lib/eco/api/usecases/graphql/samples/location/service/tree_diff/convertible/parsing/helpers.rb +28 -0
- data/lib/eco/api/usecases/graphql/samples/location/service/tree_diff/convertible/parsing.rb +46 -0
- data/lib/eco/api/usecases/graphql/samples/location/service/tree_diff/convertible.rb +38 -0
- data/lib/eco/api/usecases/graphql/samples/location/service/tree_diff.rb +105 -0
- data/lib/eco/api/usecases/graphql/samples/location/service/tree_to_list/converter/discarded.rb +16 -0
- data/lib/eco/api/usecases/graphql/samples/location/service/tree_to_list/converter/input.rb +15 -0
- data/lib/eco/api/usecases/graphql/samples/location/service/tree_to_list/converter/node_attr_maps.rb +22 -0
- data/lib/eco/api/usecases/graphql/samples/location/service/tree_to_list/converter/parser.rb +45 -0
- data/lib/eco/api/usecases/graphql/samples/location/service/tree_to_list/converter.rb +36 -0
- data/lib/eco/api/usecases/graphql/samples/location/service/tree_to_list/output.rb +56 -0
- data/lib/eco/api/usecases/graphql/samples/location/service/tree_to_list.rb +41 -0
- data/lib/eco/api/usecases/graphql/samples/location/service.rb +8 -0
- data/lib/eco/api/usecases/graphql/samples/location.rb +1 -0
- data/lib/eco/api/usecases/graphql/utils/sftp.rb +96 -36
- data/lib/eco/api/usecases/ooze_cases/export_register_case.rb +8 -6
- data/lib/eco/api/usecases/ooze_samples/helpers/creatable.rb +4 -3
- data/lib/eco/api/usecases/ooze_samples/helpers/exportable_ooze.rb +39 -25
- data/lib/eco/api/usecases/ooze_samples/helpers/exportable_register.rb +13 -15
- data/lib/eco/api/usecases/ooze_samples/helpers/filters.rb +50 -21
- data/lib/eco/api/usecases/ooze_samples/helpers/ooze_handlers.rb +21 -11
- data/lib/eco/api/usecases/ooze_samples/helpers/rescuable.rb +2 -0
- data/lib/eco/api/usecases/ooze_samples/helpers/shortcuts.rb +49 -43
- data/lib/eco/api/usecases/ooze_samples/ooze_base_case.rb +17 -19
- data/lib/eco/api/usecases/ooze_samples/register_export_case.rb +48 -43
- data/lib/eco/api/usecases/ooze_samples/register_update_case.rb +33 -34
- data/lib/eco/api/usecases/ooze_samples/target_oozes_update_case.rb +8 -10
- data/lib/eco/api/usecases.rb +0 -1
- data/lib/eco/cli/config/use_cases.rb +31 -29
- data/lib/eco/cli_default/workflow.rb +13 -14
- data/lib/eco/csv/table.rb +34 -25
- data/lib/eco/data/hashes/array_diff.rb +24 -35
- data/lib/eco/data/hashes/diff_result/meta.rb +131 -0
- data/lib/eco/data/hashes/diff_result.rb +65 -57
- data/lib/eco/data/hashes/sanke_camel_indifferent_access.rb +278 -0
- data/lib/eco/data/hashes.rb +1 -1
- data/lib/eco/data/locations/convert.rb +1 -1
- data/lib/eco/data/locations/node_base/csv_convert.rb +19 -9
- data/lib/eco/data/locations/node_base/parsing.rb +4 -2
- data/lib/eco/data/locations/node_base/treeify.rb +149 -132
- data/lib/eco/data/locations/node_base.rb +15 -4
- data/lib/eco/data/locations/node_diff/accessors.rb +13 -5
- data/lib/eco/data/locations/node_diff/nodes_diff/clustered_treeify.rb +101 -0
- data/lib/eco/data/locations/node_diff/nodes_diff/diffs_tree.rb +99 -0
- data/lib/eco/data/locations/node_diff/{selectors.rb → nodes_diff/selectors.rb} +1 -1
- data/lib/eco/data/locations/node_diff/nodes_diff.rb +50 -35
- data/lib/eco/data/locations/node_diff.rb +45 -17
- data/lib/eco/data/locations/node_level/parsing.rb +15 -21
- data/lib/eco/data/locations/node_level.rb +66 -22
- data/lib/eco/data/locations/node_plain/parsing.rb +1 -1
- data/lib/eco/data/locations/node_plain.rb +60 -7
- data/lib/eco/data/strings/camel_case.rb +35 -0
- data/lib/eco/data/strings/snake_case.rb +18 -0
- data/lib/eco/data/strings.rb +8 -0
- data/lib/eco/data.rb +1 -0
- data/lib/eco/language/auxiliar_logger.rb +7 -5
- data/lib/eco/language/methods/call_detector.rb +11 -0
- data/lib/eco/language/methods/dsl_able.rb +7 -1
- data/lib/eco/language/methods.rb +2 -1
- data/lib/eco/language/models/collection.rb +23 -25
- data/lib/eco/language/models/parser_serializer.rb +24 -5
- data/lib/eco/version.rb +1 -1
- data/lib/eco-helpers.rb +0 -1
- metadata +52 -7
- data/lib/eco/data/hashes/diff_meta.rb +0 -52
@@ -0,0 +1,131 @@
|
|
1
|
+
module Eco
|
2
|
+
module Data
|
3
|
+
module Hashes
|
4
|
+
class DiffResult
|
5
|
+
module Meta
|
6
|
+
class << self
|
7
|
+
def included(base)
|
8
|
+
super(base)
|
9
|
+
base.extend Eco::Language::Models::ClassHelpers
|
10
|
+
base.extend ClassMethods
|
11
|
+
base.inheritable_class_vars :key, :compared_attrs, :case_sensitive
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
# @param value [String, NilClass]
|
17
|
+
# @return [String] the attribute that is key of the node diff elements.
|
18
|
+
def key(value = nil)
|
19
|
+
return @key unless value
|
20
|
+
@key = value.to_s
|
21
|
+
end
|
22
|
+
|
23
|
+
# @return [Boolean] is there a `key` attribute defined?
|
24
|
+
def key?
|
25
|
+
!!@key
|
26
|
+
end
|
27
|
+
|
28
|
+
# @param attrs [Array<Symbol>, Array<String>]
|
29
|
+
# @return [Array<String>] the comparable attributes
|
30
|
+
def compare(*attrs, when_present: false)
|
31
|
+
if when_present
|
32
|
+
compared_attrs_when_present.push(*attrs.map(&:to_s)).uniq!
|
33
|
+
compared_attrs_when_present
|
34
|
+
else
|
35
|
+
compared_attrs.push(*attrs.map(&:to_s)).uniq!
|
36
|
+
compared_attrs
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Whether or not the diff calc of a node should be done case sensitive or insensitive.
|
41
|
+
def case_sensitive(value = nil)
|
42
|
+
@case_sensitive = false unless instance_variable_defined?(:@case_sensitive)
|
43
|
+
return @case_sensitive unless value
|
44
|
+
@case_sensitive = !!value
|
45
|
+
end
|
46
|
+
|
47
|
+
# @return [Boolean] are comparisons of values done case sensitive?
|
48
|
+
def case_sensitive?
|
49
|
+
!!@case_sensitive
|
50
|
+
end
|
51
|
+
|
52
|
+
# @return [Array<String>] the comparable attributes
|
53
|
+
def compared_attrs
|
54
|
+
@compared_attrs ||= []
|
55
|
+
end
|
56
|
+
|
57
|
+
# Same as `compared_attrs` but they only get compared when present
|
58
|
+
# in both data sources
|
59
|
+
# @note if present in one source but not in another,
|
60
|
+
# that attribute will NOT generate a diff.
|
61
|
+
def compared_attrs_when_present
|
62
|
+
@compared_attrs_when_present ||= []
|
63
|
+
end
|
64
|
+
|
65
|
+
def all_compared_attrs
|
66
|
+
compared_attrs | compared_attrs_when_present
|
67
|
+
end
|
68
|
+
|
69
|
+
def compared_attr_when_present?(attr)
|
70
|
+
compared_attrs_when_present.include?(attr)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def key
|
75
|
+
self.class.key.dup
|
76
|
+
end
|
77
|
+
|
78
|
+
def case_sensitive?
|
79
|
+
self.class.case_sensitive?
|
80
|
+
end
|
81
|
+
|
82
|
+
def all_compared_attrs
|
83
|
+
self.class.all_compared_attrs.dup.compact.map(&:to_s)
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
# @return [Boolean] whether `val1` is equal to `val2`
|
89
|
+
def eq?(val_1, val_2)
|
90
|
+
return eq_ary?(val_1, val_2) if [val_1, val_2].any? {|v| v.is_a?(Array)}
|
91
|
+
return true if val_1 == val_2
|
92
|
+
return false if case_sensitive?
|
93
|
+
return false unless val_2 && val_1
|
94
|
+
val_1.upcase == val_2.upcase
|
95
|
+
end
|
96
|
+
|
97
|
+
# Compares two arrays
|
98
|
+
# @note
|
99
|
+
# 1. Order does NOT matter
|
100
|
+
# 2. Repeated elements are treated as one
|
101
|
+
def eq_ary?(val_1, val_2)
|
102
|
+
return false unless [val_1, val_2].all? {|v| v.is_a?(Array)}
|
103
|
+
return true if val_1 == val_2
|
104
|
+
v_1 = case_sensitive?? val_1 : val_1.map {|v| v&.upcase}
|
105
|
+
v_2 = case_sensitive?? val_2 : val_2.map {|v| v&.upcase}
|
106
|
+
joined = (v_1 | v_2)
|
107
|
+
(joined & v_1) == (joined & v_2)
|
108
|
+
end
|
109
|
+
|
110
|
+
# Hash access
|
111
|
+
# @note DSL to be able to modify behaviour
|
112
|
+
# (i.e. indifferent access for camel and snake case keys)
|
113
|
+
def get_attr(src, key)
|
114
|
+
return nil unless src
|
115
|
+
src[key.to_s]
|
116
|
+
end
|
117
|
+
|
118
|
+
def slice_attrs(src, *keys)
|
119
|
+
return nil unless src
|
120
|
+
src.slice(*keys)
|
121
|
+
end
|
122
|
+
|
123
|
+
def key_present?(src, key)
|
124
|
+
return false unless src
|
125
|
+
src.key?(key)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -1,53 +1,68 @@
|
|
1
|
+
require_relative 'diff_result/meta'
|
2
|
+
|
1
3
|
module Eco
|
2
4
|
module Data
|
3
5
|
module Hashes
|
4
6
|
class DiffResult
|
5
|
-
|
6
|
-
include Eco::Data::Hashes::DiffMeta
|
7
|
-
|
8
|
-
inheritable_class_vars :key, :compared_attrs, :case_sensitive
|
7
|
+
include Eco::Data::Hashes::DiffResult::Meta
|
9
8
|
|
10
|
-
attr_reader :
|
9
|
+
attr_reader :src_1, :src_2
|
11
10
|
|
12
|
-
def initialize(
|
13
|
-
@
|
14
|
-
@
|
11
|
+
def initialize(src_1, src_2)
|
12
|
+
@src_1 = src_1
|
13
|
+
@src_2 = src_2
|
15
14
|
end
|
16
15
|
|
17
|
-
|
18
|
-
|
16
|
+
# @return [Value] the current value of `attr` (in `src_2`)
|
17
|
+
def attr(attr)
|
18
|
+
get_attr(src_2, attr)
|
19
19
|
end
|
20
20
|
|
21
|
-
|
22
|
-
|
21
|
+
# @return [Value] the previous value of `attr` (in `src_1`)
|
22
|
+
def attr_prev(attr)
|
23
|
+
get_attr(src_1, attr)
|
23
24
|
end
|
24
25
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
self.
|
26
|
+
# Deduplication to prevent mutability when manipulation is required.
|
27
|
+
def dup(src_1: nil, src_2: nil)
|
28
|
+
src_1 ||= self.src_1
|
29
|
+
src_2 ||= self.src_2
|
30
|
+
self.class.new(src_1.dup, src_2.dup)
|
29
31
|
end
|
30
32
|
|
31
33
|
def new?
|
32
|
-
|
34
|
+
src_2 && !src_1
|
33
35
|
end
|
34
36
|
|
35
37
|
def del?
|
36
|
-
|
38
|
+
src_1 && !src_2
|
37
39
|
end
|
38
40
|
|
39
41
|
def update?
|
40
42
|
!new? && !del? && diff?
|
41
43
|
end
|
42
44
|
|
45
|
+
# Uniq access point to identify attrs that have changed.
|
46
|
+
# @note when is `new?` or to be deleted (`del?`), there's nothing to compare.
|
47
|
+
# @return [Array<Symbol>] hash with the list of attrs that are different
|
48
|
+
# between `src_1` and `src_2`.
|
49
|
+
def diff_attrs
|
50
|
+
return (@diff_attrs = []) if new? || del?
|
51
|
+
@diff_attrs ||= all_compared_attrs.each_with_object([]) do |kattr, out|
|
52
|
+
next unless comparable_attr?(kattr)
|
53
|
+
out << kattr unless eq?(attr_prev(kattr), attr(kattr))
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
43
57
|
# @note `diff_attrs` may not include the `key` attribute
|
44
58
|
# This is always included via `new?` (new key value) and `del?` (missing key value)
|
45
59
|
# @return [Boolean] was there any change?
|
46
60
|
def diff?
|
47
|
-
new? || del? ||
|
61
|
+
new? || del? || diff_attrs.any?
|
48
62
|
end
|
49
63
|
|
50
|
-
# Is the `key` attr value
|
64
|
+
# Is the `key` attr value updated?
|
65
|
+
# @note that `new?` and `del?` won't be considered as key's change
|
51
66
|
def key?
|
52
67
|
!(new? || del?) && diff_attr?(key)
|
53
68
|
end
|
@@ -59,54 +74,47 @@ module Eco
|
|
59
74
|
diff_attrs.include?(attr.to_s)
|
60
75
|
end
|
61
76
|
|
62
|
-
# @return [Value] the current value of `attr` (in `src2`)
|
63
|
-
def attr(attr)
|
64
|
-
return nil unless src2
|
65
|
-
src2[attr.to_s]
|
66
|
-
end
|
67
|
-
|
68
|
-
# @return [Value] the previous value of `attr` (in `src1`)
|
69
|
-
def attr_prev(attr)
|
70
|
-
return nil unless src1
|
71
|
-
src1[attr.to_s]
|
72
|
-
end
|
73
|
-
|
74
77
|
# @note the `key` attribute will always be added (even if there's no change)
|
75
|
-
# @return [Hash] hash with the differences as per `
|
78
|
+
# @return [Hash] hash with the differences as per `src_2`
|
76
79
|
def diff_hash
|
77
|
-
target_attrs = [key] |
|
78
|
-
return
|
79
|
-
return
|
80
|
-
|
80
|
+
target_attrs = [key] | all_compared_attrs
|
81
|
+
return slice_attrs(src_2, *target_attrs) if new?
|
82
|
+
return slice_attrs(src_1, key) if del?
|
83
|
+
slice_attrs(src_2, key, *diff_attrs)
|
81
84
|
end
|
82
85
|
|
83
|
-
#
|
84
|
-
|
85
|
-
|
86
|
-
|
86
|
+
# Uniq access point to scope if an `attr` is in the scope of the diff compare.
|
87
|
+
# Set of attributes that are general target to identify differences
|
88
|
+
# between both sources.
|
89
|
+
# @note When the class `all_compared_attrs` has not been deefined,
|
90
|
+
# it uses `all_source_keys`
|
91
|
+
# @return [Array<String>] the set of attributes that are comparable in this class.
|
92
|
+
def all_compared_attrs
|
93
|
+
super().map(&:to_s).uniq.tap do |attrs|
|
94
|
+
return all_source_keys unless attrs.any?
|
87
95
|
end
|
88
96
|
end
|
89
97
|
|
90
|
-
#
|
91
|
-
def
|
92
|
-
|
93
|
-
return false if case_sensitive?
|
94
|
-
return false if !val2 || !val1
|
95
|
-
val1.upcase == val2.upcase
|
98
|
+
# All the keys that the data comes with
|
99
|
+
def all_source_keys
|
100
|
+
(src_1&.keys || []) & (src_2&.keys || [])
|
96
101
|
end
|
97
102
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
+
private
|
104
|
+
|
105
|
+
# Refinement over `all_compared_attrs`, provided that those that must
|
106
|
+
# be comparable only **when present** in both, are filtered out when
|
107
|
+
# that is not the case.
|
108
|
+
def comparable_attr?(attr)
|
109
|
+
attr = attr.to_s
|
110
|
+
return false unless all_compared_attrs.include?(attr)
|
111
|
+
return true unless self.class.compared_attr_when_present?(attr)
|
112
|
+
key_present_in_both?(attr)
|
103
113
|
end
|
104
114
|
|
105
|
-
#
|
106
|
-
def
|
107
|
-
|
108
|
-
return comp_attrs unless comp_attrs.empty?
|
109
|
-
(src1&.keys || []) & (src2&.keys || [])
|
115
|
+
# Is the key `attr` present in both sources?
|
116
|
+
def key_present_in_both?(attr)
|
117
|
+
key_present?(src_1, attr) && key_present?(src_2, attr)
|
110
118
|
end
|
111
119
|
end
|
112
120
|
end
|
@@ -0,0 +1,278 @@
|
|
1
|
+
module Eco
|
2
|
+
module Data
|
3
|
+
module Hashes
|
4
|
+
module SnakeCamelIndifferentAccess
|
5
|
+
include Eco::Data::Strings::SnakeCase
|
6
|
+
include Eco::Data::Strings::CamelCase
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
# options are: `:snake_case` and `:camel_case`
|
11
|
+
def hash_isca_preferred
|
12
|
+
@hash_isca_preferred ||= :snake_case
|
13
|
+
end
|
14
|
+
|
15
|
+
# Sets `value` for `key`. The end result is that get on `key` gives `value`,
|
16
|
+
# no matter the original case form of `key`. This might entail to resolve
|
17
|
+
# the `key` if its in its multiple case forms.
|
18
|
+
# @note if an internal merge resolve of the `key` happens, it's done in the
|
19
|
+
# preferred case form.
|
20
|
+
# @note if once resolved the internal merge, only the preferred case form of
|
21
|
+
# the key remains, that key form will be used to do the `set` operation,
|
22
|
+
# no matter the original case form of the `key` argument.
|
23
|
+
# @param keep_case [Boolean] whether only the preferred case form of key
|
24
|
+
# should be used or rather try to keep its case.
|
25
|
+
def hash_isca_set(hash, key, value, prefer: nil, keep_case: false, residual: {})
|
26
|
+
# (pp hash) if key == 'barCamel'
|
27
|
+
hash_isca_resolve_key!(hash, key, prefer: prefer, residual: residual)
|
28
|
+
# (pp hash) if key == 'barCamel'
|
29
|
+
|
30
|
+
target_key = hash_isca_to_correct_key(key, keep_case: keep_case, prefer: prefer)
|
31
|
+
|
32
|
+
# puts "potential_target_key: '#{key}' => '#{target_key}'" if key == 'barCamel'
|
33
|
+
|
34
|
+
if keep_case
|
35
|
+
# Spot the existing target key
|
36
|
+
ext_keys = hash_isca_existing_correct_keys(hash, key, prefer: prefer)
|
37
|
+
target_key = ext_keys.first unless ext_keys.empty? || ext_keys.include?(target_key)
|
38
|
+
end
|
39
|
+
|
40
|
+
puts "final target_key: '#{key}' => '#{target_key}'" if key == 'barCamel'
|
41
|
+
|
42
|
+
hash[target_key] = value
|
43
|
+
hash_isca_resolve_key!(hash, key, prefer: prefer, residual: residual)
|
44
|
+
|
45
|
+
pp hash if key == 'barCamel'
|
46
|
+
puts "*" * 20 if key == 'barCamel'
|
47
|
+
|
48
|
+
value
|
49
|
+
end
|
50
|
+
|
51
|
+
# Retrieves the value of key `key`
|
52
|
+
# @note
|
53
|
+
# 1. when both forms of the key exist, it uses the preferred case form !!
|
54
|
+
# 2. when the key exists only in its form it uses it
|
55
|
+
# 3. when only the other form of the key is present it uses it
|
56
|
+
def hash_isca_get(value, key, prefer: nil)
|
57
|
+
hash = value.to_h
|
58
|
+
keys = hash_isca_double_key(key, prefer: prefer)
|
59
|
+
keys = keys.find_all {|k| hash.key?(k)}
|
60
|
+
return nil unless keys.any?
|
61
|
+
hash[keys.first]
|
62
|
+
end
|
63
|
+
|
64
|
+
# Slice generates a hash with `keys`, regardless the original case form
|
65
|
+
# of those keys in the source hash (value).
|
66
|
+
# @note values are retrieved as per `hash_isca_get` rules.
|
67
|
+
def hash_isca_slice(value, *keys, prefer: nil)
|
68
|
+
hash = value.to_h
|
69
|
+
values = keys.map do |key|
|
70
|
+
hash_isca_get(hash, key, prefer: prefer)
|
71
|
+
end
|
72
|
+
keys.zip(values).to_h
|
73
|
+
end
|
74
|
+
|
75
|
+
# Delegates to `hash_isca_get`
|
76
|
+
def hash_isca_values_at(value, *keys, prefer: nil)
|
77
|
+
hash = value.to_h
|
78
|
+
keys.map do |key|
|
79
|
+
hash_isca_get(hash, key, prefer: prefer)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# The values of the uniq keys, where among keys with multiple case form
|
84
|
+
# the preferred form case is used to retrieve the value.
|
85
|
+
# @note residual named arg was not added just to keep consistent its hash type.
|
86
|
+
def hash_isca_values(value, prefer: nil)
|
87
|
+
hash = value.to_h
|
88
|
+
hash.values_at(*hash_isca_uniq_keys(hash, prefer: prefer))
|
89
|
+
end
|
90
|
+
|
91
|
+
# Enumerator
|
92
|
+
def hash_isca_each(value, prefer: nil, residual: {}, &block)
|
93
|
+
return to_enum(:hash_isca_each, value, prefer: prefer, residual: residual) unless block_given?
|
94
|
+
hash = value.to_h
|
95
|
+
keys = hash_isca_uniq_keys(hash, prefer: prefer)
|
96
|
+
residual.merge!(hash.slice(*(hash.keys - keys)))
|
97
|
+
hash.slice(*keys).each(&block)
|
98
|
+
end
|
99
|
+
|
100
|
+
# @note due to simplicity and consistency, for matching merging keys
|
101
|
+
# it resolves internal key conflicts on both (source and other).
|
102
|
+
# This internal resolve is ONLY applied to common keys (between source and other).
|
103
|
+
def hash_isca_merge!(hash_1, value_2, prefer: nil, residual: {})
|
104
|
+
hash_1.tap do
|
105
|
+
hash_2 = hash_isca_internal_merge_resolve!(value_2.to_h.dup, residual: (res_2 = {}))
|
106
|
+
hash_isca_each(hash_2, prefer: prefer, residual: residual) do |key, value|
|
107
|
+
hash_isca_set(hash_1, key, value, prefer: prefer, keep_case: true, residual: residual)
|
108
|
+
end
|
109
|
+
residual.merge!(res_2)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def hash_isca_merge(value_1, value_2, prefer: nil, residual: {})
|
114
|
+
hash_isca_merge!(value_1.to_h.dup, value_2, prefer: prefer, residual: residual)
|
115
|
+
end
|
116
|
+
|
117
|
+
# Keys may not necessarily come in its camel or snake case version.
|
118
|
+
# This method ensures they are.
|
119
|
+
# @note modus operandi:
|
120
|
+
# 1. will preserve all the keys that were in a correct case form.
|
121
|
+
# 2. where it can't preserve, the preferred form will be used instead.
|
122
|
+
# 3. where merge conflicts exist, the value associatd with the key that
|
123
|
+
# was originally in the correc case form will prevail.
|
124
|
+
def hash_isca_correct_keys!(hash, prefer: nil, residual: {})
|
125
|
+
hash.dup.each do |key, _val|
|
126
|
+
ckey = hash_isca_to_correct_key(key, keep_case: true, prefer: prefer)
|
127
|
+
next if ckey == key
|
128
|
+
residual[key] = hash.delete(key) if hash.key?(ckey)
|
129
|
+
end
|
130
|
+
hash
|
131
|
+
end
|
132
|
+
|
133
|
+
# Same as `hash_isca_correct_keys!` but only for one single key.
|
134
|
+
def hash_isca_correct_key!(hash, key, prefer: nil, residual: {})
|
135
|
+
tkey = hash_isca_to_correct_key(key, keep_case: true, prefer: prefer)
|
136
|
+
|
137
|
+
hash.dup.each do |k_c, _val|
|
138
|
+
ckey = hash_isca_to_correct_key(k_c, keep_case: true, prefer: prefer)
|
139
|
+
next unless ckey == tkey
|
140
|
+
next if ckey == k_c
|
141
|
+
residual[k_c] = hash.delete(k_c) if hash.key?(ckey)
|
142
|
+
end
|
143
|
+
hash
|
144
|
+
end
|
145
|
+
|
146
|
+
# internal merge
|
147
|
+
def hash_isca_internal_merge_resolve!(hash, prefer: nil, residual: {})
|
148
|
+
hash_isca_correct_keys!(hash, prefer: prefer, residual: residual)
|
149
|
+
keep_keys = hash_isca_uniq_keys(hash, prefer: prefer)
|
150
|
+
hash.dup.each do |key, _v|
|
151
|
+
residual[key] = hash.delete(key) unless keep_keys.include?(key)
|
152
|
+
end
|
153
|
+
hash
|
154
|
+
end
|
155
|
+
|
156
|
+
# internal merge
|
157
|
+
def hash_isca_internal_merge_resolve(value, prefer: nil, residual: {})
|
158
|
+
hash_isca_internal_merge_resolve!(value.to_h.dup, prefer: prefer, residual: residual)
|
159
|
+
end
|
160
|
+
|
161
|
+
# it resolves a single key
|
162
|
+
def hash_isca_resolve_key!(hash, key, prefer: nil, residual: {})
|
163
|
+
hash_isca_correct_key!(hash, key, prefer: prefer, residual: residual)
|
164
|
+
return hash if hash_isca_uniq_key?(hash, key)
|
165
|
+
_pkey, rkey = hash_isca_double_key(key, prefer: prefer)
|
166
|
+
residual[rkey] = hash.delete(rkey)
|
167
|
+
hash
|
168
|
+
end
|
169
|
+
|
170
|
+
# all keys to camel case
|
171
|
+
def hash_isca_camelize_keys(value)
|
172
|
+
hash_isca_with_preferred_keys(value, prefer: :camel_case)
|
173
|
+
end
|
174
|
+
|
175
|
+
def hash_isca_camelize_keys!(hash)
|
176
|
+
hash_isca_with_preferred_keys!(hash, prefer: :camel_case)
|
177
|
+
end
|
178
|
+
|
179
|
+
# all keys to snake case
|
180
|
+
def hash_isca_snakeize_keys(value)
|
181
|
+
hash_isca_with_preferred_keys(value, prefer: :snake_case)
|
182
|
+
end
|
183
|
+
|
184
|
+
def hash_isca_snakeize_keys!(hash)
|
185
|
+
hash_isca_with_preferred_keys!(hash, prefer: :snake_case)
|
186
|
+
end
|
187
|
+
|
188
|
+
# Conerts keys to the `prefer` case form
|
189
|
+
# @note it first resolves with the default/general `hash_isca_preferred`,
|
190
|
+
# as all operations have been done up to now this way.
|
191
|
+
def hash_isca_with_preferred_keys!(hash, prefer: nil, residual: {})
|
192
|
+
hash_isca_internal_merge_resolve!(hash, residual: residual)
|
193
|
+
hash.dup.each do |k, _v|
|
194
|
+
key = hash_isca_to_preferred_key(k, prefer: prefer)
|
195
|
+
hash[key] = hash.delete(k)
|
196
|
+
end
|
197
|
+
hash
|
198
|
+
end
|
199
|
+
|
200
|
+
def hash_isca_with_preferred_keys(value, prefer: nil)
|
201
|
+
hash_isca_with_preferred_keys!(value.to_h.dup, prefer: prefer)
|
202
|
+
end
|
203
|
+
|
204
|
+
# @return [Array<String>] the resolved keys
|
205
|
+
def hash_isca_uniq_keys(value, prefer: nil)
|
206
|
+
hash = value.to_h
|
207
|
+
all_keys = hash.keys
|
208
|
+
dup_keys = all_keys.each_with_object({}) do |key, res|
|
209
|
+
keys = hash_isca_double_key(key, prefer: prefer)
|
210
|
+
next unless keys.all? {|k| hash.key?(k)}
|
211
|
+
next if res.key?(keys.first)
|
212
|
+
res[keys.first] = keys.last
|
213
|
+
end
|
214
|
+
all_keys - dup_keys.values
|
215
|
+
end
|
216
|
+
|
217
|
+
# @note if among the raw existing keys there were multiple keys for a given
|
218
|
+
# correct case version of `key`, they would be considered as one.
|
219
|
+
# @return [Boolean] `true` if the key is only in one form or not present.
|
220
|
+
# `false` if the key is in **multiple** forms
|
221
|
+
def hash_isca_uniq_key?(value, key)
|
222
|
+
hash_isca_existing_correct_keys(value, key).length <= 1
|
223
|
+
end
|
224
|
+
|
225
|
+
# For a given `key` it finds out what keys exist in its correct case form.
|
226
|
+
# @return [Array<String>] the existing correct keys.
|
227
|
+
def hash_isca_existing_correct_keys(value, key, prefer: nil)
|
228
|
+
hash = value.to_h
|
229
|
+
hash_isca_double_key(key, prefer: prefer).find_all do |k|
|
230
|
+
hash.key?(k)
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
def hash_isca_key?(value, key)
|
235
|
+
hash = value.to_h
|
236
|
+
hash_isca_double_key(key).any? {|k| hash.key?(k)}
|
237
|
+
end
|
238
|
+
|
239
|
+
def hash_isca_same_key?(k_1, k_2)
|
240
|
+
hash_isca_double_key(k_1) == hash_isca_double_key(k_2)
|
241
|
+
end
|
242
|
+
|
243
|
+
# @return [String] the preferred case form of the key
|
244
|
+
def hash_isca_to_preferred_key(key, prefer: nil)
|
245
|
+
hash_isca_double_key(key, prefer: prefer).first
|
246
|
+
end
|
247
|
+
|
248
|
+
# Spot the correct key. Ensures keys are in camel or snake case.
|
249
|
+
# @param keep_case [Boolean] whether only the preferred case form of key
|
250
|
+
# should be used or rather try to keep its case.
|
251
|
+
# @return [String] the target key
|
252
|
+
def hash_isca_to_correct_key(key, keep_case: true, prefer: nil)
|
253
|
+
pkey, okey = hash_isca_double_key(key, prefer: prefer)
|
254
|
+
return pkey unless keep_case
|
255
|
+
return pkey unless [pkey, okey].include?(key)
|
256
|
+
key
|
257
|
+
end
|
258
|
+
|
259
|
+
# @return [Boolean] whether `key` is already in snake or camel case form
|
260
|
+
def hash_isca_correct_key?(key)
|
261
|
+
hash_isca_double_key(key).any? {|k| k == key}
|
262
|
+
end
|
263
|
+
|
264
|
+
def hash_isca_double_key(key, prefer: nil)
|
265
|
+
return [key, key] unless key.is_a?(String)
|
266
|
+
return [snake_case(key), camel_case(key)] if hash_isca_prefer(prefer) == :snake_case
|
267
|
+
[camel_case(key), snake_case(key)]
|
268
|
+
end
|
269
|
+
|
270
|
+
def hash_isca_prefer(prefer = nil)
|
271
|
+
return hash_isca_preferred || :snake_case if prefer.nil?
|
272
|
+
return :camel_case unless prefer == :snake_case
|
273
|
+
:snake_case
|
274
|
+
end
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
data/lib/eco/data/hashes.rb
CHANGED
@@ -76,7 +76,7 @@ module Eco::Data::Locations
|
|
76
76
|
end
|
77
77
|
|
78
78
|
# It logs a message from `yield` and appends a `pretty_inspect` on object.
|
79
|
-
# @note it only works where `object` is `
|
79
|
+
# @note it only works where `object` is `Enumerable`
|
80
80
|
def log_pretty_inspect(object, lev = :info)
|
81
81
|
return unless object.is_a?(Enumerable)
|
82
82
|
return if object.empty?
|
@@ -6,17 +6,21 @@ module Eco::Data::Locations::NodeBase
|
|
6
6
|
Eco::API::Organization::TagTree
|
7
7
|
end
|
8
8
|
|
9
|
-
# @yield [
|
9
|
+
# @yield [node, json] optional custom serializer
|
10
|
+
# @yieldparam node [Node] the node that is being serialized
|
11
|
+
# @yieldparam json [Hash] the default serialization
|
10
12
|
# @yieldreturn [Hash] the serialized Node
|
11
13
|
# @param value [CSV::Table, Eco::API::Organization::TagTree]
|
12
14
|
# @return [Array<Hash>] a plain list of hash nodes
|
13
15
|
def hash_list(value, &block)
|
14
|
-
return hash_list(org_tree(value)) if value.is_a?(::CSV::Table)
|
15
|
-
return value.as_nodes_json if value.is_a?(tree_class)
|
16
|
+
return hash_list(org_tree(value), &block) if value.is_a?(::CSV::Table)
|
17
|
+
return value.as_nodes_json(&block) if value.is_a?(tree_class)
|
16
18
|
raise ArgumentError, "Expecting Eco::API::Organization::TagTree or CSV::Table. Given: #{value.class}"
|
17
19
|
end
|
18
20
|
|
19
|
-
# @yield [
|
21
|
+
# @yield [node, json] optional custom serializer
|
22
|
+
# @yieldparam node [Node] the node that is being serialized
|
23
|
+
# @yieldparam json [Hash] the default serialization
|
20
24
|
# @yieldreturn [Hash] the serialized Node
|
21
25
|
# @param value [CSV::Table, Eco::API::Organization::TagTree]
|
22
26
|
# @return [Array<Hash>] a hierarchical tree of hash nodes,
|
@@ -27,7 +31,9 @@ module Eco::Data::Locations::NodeBase
|
|
27
31
|
raise ArgumentError, "Expecting Eco::API::Organization::TagTree or CSV::Table. Given: #{value.class}"
|
28
32
|
end
|
29
33
|
|
30
|
-
# @yield [
|
34
|
+
# @yield [node, json] optional custom serializer
|
35
|
+
# @yieldparam node [Node] the node that is being serialized
|
36
|
+
# @yieldparam json [Hash] the default serialization
|
31
37
|
# @yieldreturn [Hash] the serialized Node
|
32
38
|
# @param value [CSV::Table, Eco::API::Organization::TagTree]
|
33
39
|
# @return [Eco::API::Organization::TagTree]
|
@@ -37,21 +43,25 @@ module Eco::Data::Locations::NodeBase
|
|
37
43
|
raise ArgumentError, "Expecting Eco::API::Organization::TagTree or CSV::Table. Given: #{value.class}"
|
38
44
|
end
|
39
45
|
|
40
|
-
# @yield [
|
46
|
+
# @yield [node, json] optional custom serializer
|
47
|
+
# @yieldparam node [Node] the node that is being serialized
|
48
|
+
# @yieldparam json [Hash] the default serialization
|
41
49
|
# @yieldreturn [Hash] the serialized Node
|
42
50
|
# @return [CSV::Table] a table with L1 to Ln columns ready for dump to csv
|
43
|
-
def csv_tree(value, encoding: 'utf-8', attrs: [:id], &block)
|
51
|
+
def csv_tree(value, encoding: 'utf-8', attrs: [:id], &block) # rubocop:disable Lint/UnusedMethodArgument
|
44
52
|
Eco::CSV::Table.new(hash_tree_to_tree_csv(hash_tree(value, &block), attrs: attrs))
|
45
53
|
end
|
46
54
|
|
47
55
|
# @note it just converts to an organizational tagtree and uses a helper method.
|
48
|
-
# @yield [
|
56
|
+
# @yield [node, json] optional custom serializer
|
57
|
+
# @yieldparam node [Node] the node that is being serialized
|
58
|
+
# @yieldparam json [Hash] the default serialization
|
49
59
|
# @yieldreturn [Hash] the serialized Node
|
50
60
|
# @param value [CSV::Table, Eco::API::Organization::TagTree]
|
51
61
|
# @return [CSV::Table] a table with a list of nodes and their parents
|
52
62
|
def csv_list(value, &block)
|
53
63
|
value = org_tree(value, &block) unless value.is_a?(tree_class)
|
54
|
-
Eco::CSV::Table.new(hash_list(value))
|
64
|
+
Eco::CSV::Table.new(hash_list(value, &block))
|
55
65
|
end
|
56
66
|
end
|
57
67
|
end
|