eco-helpers 0.6.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 (123) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +1 -0
  3. data/.rspec +3 -0
  4. data/README.md +20 -0
  5. data/eco-helpers.gemspec +34 -0
  6. data/lib/eco-helpers.rb +15 -0
  7. data/lib/eco/api.rb +13 -0
  8. data/lib/eco/api/common.rb +10 -0
  9. data/lib/eco/api/common/people.rb +17 -0
  10. data/lib/eco/api/common/people/base_parser.rb +16 -0
  11. data/lib/eco/api/common/people/default_parsers.rb +40 -0
  12. data/lib/eco/api/common/people/default_parsers/boolean_parser.rb +28 -0
  13. data/lib/eco/api/common/people/default_parsers/date_parser.rb +33 -0
  14. data/lib/eco/api/common/people/default_parsers/multi_parser.rb +33 -0
  15. data/lib/eco/api/common/people/default_parsers/numeric_parser.rb +23 -0
  16. data/lib/eco/api/common/people/default_parsers/select_parser.rb +29 -0
  17. data/lib/eco/api/common/people/entries.rb +120 -0
  18. data/lib/eco/api/common/people/person_entry.rb +380 -0
  19. data/lib/eco/api/common/people/person_factory.rb +114 -0
  20. data/lib/eco/api/common/people/person_modifier.rb +62 -0
  21. data/lib/eco/api/common/people/person_parser.rb +140 -0
  22. data/lib/eco/api/common/people/types.rb +47 -0
  23. data/lib/eco/api/common/session.rb +15 -0
  24. data/lib/eco/api/common/session/base_session.rb +46 -0
  25. data/lib/eco/api/common/session/environment.rb +47 -0
  26. data/lib/eco/api/common/session/file_manager.rb +90 -0
  27. data/lib/eco/api/common/session/logger.rb +105 -0
  28. data/lib/eco/api/common/session/mailer.rb +92 -0
  29. data/lib/eco/api/common/session/s3_uploader.rb +110 -0
  30. data/lib/eco/api/common/version_patches.rb +11 -0
  31. data/lib/eco/api/common/version_patches/external_person.rb +11 -0
  32. data/lib/eco/api/eco_faker.rb +59 -0
  33. data/lib/eco/api/organization.rb +13 -0
  34. data/lib/eco/api/organization/account.rb +23 -0
  35. data/lib/eco/api/organization/people.rb +118 -0
  36. data/lib/eco/api/organization/policy_groups.rb +51 -0
  37. data/lib/eco/api/organization/preferences.rb +28 -0
  38. data/lib/eco/api/organization/preferences_reference.json +23 -0
  39. data/lib/eco/api/organization/presets.rb +138 -0
  40. data/lib/eco/api/organization/presets_backup.rb +220 -0
  41. data/lib/eco/api/organization/presets_values.json +10 -0
  42. data/lib/eco/api/organization/tag_tree.rb +134 -0
  43. data/lib/eco/api/organization_old.rb +73 -0
  44. data/lib/eco/api/session.rb +180 -0
  45. data/lib/eco/api/session/batch.rb +132 -0
  46. data/lib/eco/api/session/batch_job.rb +152 -0
  47. data/lib/eco/api/session/batch_jobs.rb +131 -0
  48. data/lib/eco/api/session/batch_status.rb +138 -0
  49. data/lib/eco/api/session/task.rb +92 -0
  50. data/lib/eco/api/session_config.rb +179 -0
  51. data/lib/eco/api/session_config/api.rb +47 -0
  52. data/lib/eco/api/session_config/apis.rb +78 -0
  53. data/lib/eco/api/session_config/files.rb +30 -0
  54. data/lib/eco/api/session_config/logger.rb +54 -0
  55. data/lib/eco/api/session_config/mailer.rb +65 -0
  56. data/lib/eco/api/session_config/people.rb +89 -0
  57. data/lib/eco/api/session_config/s3_bucket.rb +62 -0
  58. data/lib/eco/api/session_config/use_cases.rb +30 -0
  59. data/lib/eco/api/usecases.rb +12 -0
  60. data/lib/eco/api/usecases/base_case.rb +14 -0
  61. data/lib/eco/api/usecases/case_data.rb +13 -0
  62. data/lib/eco/api/usecases/default_cases.rb +53 -0
  63. data/lib/eco/api/usecases/default_cases/change_email_case.rb +47 -0
  64. data/lib/eco/api/usecases/default_cases/create_details_case.rb +29 -0
  65. data/lib/eco/api/usecases/default_cases/create_details_with_supervisor_case.rb +49 -0
  66. data/lib/eco/api/usecases/default_cases/delete_case.rb +20 -0
  67. data/lib/eco/api/usecases/default_cases/email_as_id_case.rb +24 -0
  68. data/lib/eco/api/usecases/default_cases/hris_case.rb +67 -0
  69. data/lib/eco/api/usecases/default_cases/new_email_case.rb +26 -0
  70. data/lib/eco/api/usecases/default_cases/new_id_case.rb +26 -0
  71. data/lib/eco/api/usecases/default_cases/refresh_presets.rb +25 -0
  72. data/lib/eco/api/usecases/default_cases/reinvite_case.rb +22 -0
  73. data/lib/eco/api/usecases/default_cases/remove_account_case.rb +36 -0
  74. data/lib/eco/api/usecases/default_cases/reset_landing_page_case.rb +24 -0
  75. data/lib/eco/api/usecases/default_cases/set_default_tag_case.rb +44 -0
  76. data/lib/eco/api/usecases/default_cases/set_supervisor_case.rb +39 -0
  77. data/lib/eco/api/usecases/default_cases/to_csv_case.rb +36 -0
  78. data/lib/eco/api/usecases/default_cases/update_details_case.rb +30 -0
  79. data/lib/eco/api/usecases/default_cases/upsert_account_case.rb +35 -0
  80. data/lib/eco/api/usecases/use_case.rb +177 -0
  81. data/lib/eco/api/usecases/use_group.rb +104 -0
  82. data/lib/eco/cli.rb +9 -0
  83. data/lib/eco/cli/input.rb +109 -0
  84. data/lib/eco/cli/input_multi.rb +137 -0
  85. data/lib/eco/cli/root.rb +8 -0
  86. data/lib/eco/cli/session.rb +9 -0
  87. data/lib/eco/cli/session/batch.rb +9 -0
  88. data/lib/eco/common.rb +7 -0
  89. data/lib/eco/common/base_cli.rb +116 -0
  90. data/lib/eco/common/language.rb +9 -0
  91. data/lib/eco/data.rb +9 -0
  92. data/lib/eco/data/crypto.rb +7 -0
  93. data/lib/eco/data/crypto/encryption.rb +318 -0
  94. data/lib/eco/data/files.rb +10 -0
  95. data/lib/eco/data/files/directory.rb +93 -0
  96. data/lib/eco/data/files/file_pattern.rb +32 -0
  97. data/lib/eco/data/files/helpers.rb +90 -0
  98. data/lib/eco/data/mapper.rb +54 -0
  99. data/lib/eco/data/random.rb +10 -0
  100. data/lib/eco/data/random/distribution.rb +133 -0
  101. data/lib/eco/data/random/fake.rb +320 -0
  102. data/lib/eco/data/random/values.rb +80 -0
  103. data/lib/eco/language.rb +12 -0
  104. data/lib/eco/language/curry.rb +28 -0
  105. data/lib/eco/language/hash_transform.rb +68 -0
  106. data/lib/eco/language/hash_transform_modifier.rb +114 -0
  107. data/lib/eco/language/match.rb +30 -0
  108. data/lib/eco/language/match_modifier.rb +190 -0
  109. data/lib/eco/language/models.rb +11 -0
  110. data/lib/eco/language/models/attribute_parser.rb +38 -0
  111. data/lib/eco/language/models/collection.rb +181 -0
  112. data/lib/eco/language/models/modifier.rb +68 -0
  113. data/lib/eco/language/models/wrap.rb +114 -0
  114. data/lib/eco/language/values_at.rb +159 -0
  115. data/lib/eco/lexic/dictionary.rb +33 -0
  116. data/lib/eco/lexic/dictionary/dictionary.txt +355484 -0
  117. data/lib/eco/lexic/dictionary/tags.json +38 -0
  118. data/lib/eco/scripting.rb +30 -0
  119. data/lib/eco/scripting/README.md +11 -0
  120. data/lib/eco/scripting/arguments.rb +40 -0
  121. data/lib/eco/tester.rb +97 -0
  122. data/lib/eco/version.rb +3 -0
  123. metadata +325 -0
@@ -0,0 +1,51 @@
1
+ module Eco
2
+ module API
3
+ module Organization
4
+ class PolicyGroups < Eco::Language::Models::Collection
5
+ # build the shortcuts of Collection
6
+ attr_collection :id, :name
7
+
8
+ include Common::People
9
+
10
+ def initialize(policy_groups = [], klass: INTERNAL::PolicyGroup)
11
+ @klass = INTERNAL::PolicyGroup
12
+ @caches_init = false
13
+ super(policy_groups, klass: @klass)
14
+ init_caches
15
+ end
16
+
17
+ def to_id(name)
18
+ policy_group(name)&.id
19
+ end
20
+
21
+ def to_name(id)
22
+ policy_group(id)&.name
23
+ end
24
+
25
+ def policy_group(id_name)
26
+ @by_id.fetch(policy_group_id(id_name), nil)
27
+ end
28
+
29
+ private
30
+
31
+ def policy_group_name(id_name)
32
+ @by_id.fetch(id_name, nil)&.name&.downcase ||
33
+ @by_name.fetch(id_name&.downcase, nil)&.name&.downcase
34
+ end
35
+
36
+ def policy_group_id(id_name)
37
+ @by_name.fetch(id_name&.downcase, nil)&.id ||
38
+ @by_id.fetch(id_name, nil)&.id
39
+ end
40
+
41
+ def init_caches
42
+ return if @caches_init
43
+ @by_id = self.map { |pg| [pg.id, pg] }.to_h
44
+ @by_name = self.map { |pg| [pg.name&.downcase, pg] }.to_h
45
+ @caches_init = true
46
+ end
47
+
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,28 @@
1
+ module Eco
2
+ module API
3
+ module Organization
4
+ class Preferences
5
+
6
+ PREFERENCES_MODEL = {
7
+ show_sidebar: true,
8
+ show_shortcuts: true,
9
+ show_coming_soon: true,
10
+ show_recently_visited_forms: true,
11
+ show_tasks: true
12
+ }.to_json
13
+
14
+ PREFERENCES_REFERENCE = File.join(__dir__, 'preferences_reference.json')
15
+
16
+ attr_reader :preferences_model, :preferences_reference
17
+ attr_reader :featured_preferences
18
+
19
+ def initialize(init = {})
20
+ @preferences_model = JSON.parse(PREFERENCES_MODEL)
21
+ @preferences_reference = JSON.load(File.open(PREFERENCES_REFERENCE))
22
+ @featured_preferences = @preferences_reference.keys
23
+ end
24
+
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,23 @@
1
+ {
2
+ "contractor" : {
3
+ "show_sidebar" : "false",
4
+ "show_shortcuts": "true",
5
+ "show_coming_soon": "false",
6
+ "show_recently_visited_forms": "true",
7
+ "show_tasks" : "true"
8
+ },
9
+ "basic" : {
10
+ "show_sidebar" : "true",
11
+ "show_shortcuts": "true",
12
+ "show_coming_soon": "false",
13
+ "show_recently_visited_forms": "true",
14
+ "show_tasks" : "true"
15
+ },
16
+ "reviewer" : {
17
+ "show_sidebar" : "true",
18
+ "show_shortcuts": "true",
19
+ "show_coming_soon": "true",
20
+ "show_recently_visited_forms": "true",
21
+ "show_tasks" : "true"
22
+ }
23
+ }
@@ -0,0 +1,138 @@
1
+ module Eco
2
+ module API
3
+ module Organization
4
+
5
+ class PresetsFactory
6
+ ABILITIES = File.join(__dir__, 'presets_values.json')
7
+ DEFAULT_CUSTOM = 'presets_custom.json'
8
+ DEFAULT_MAP = 'presets_map.json'
9
+
10
+ def initialize(presets_custom: DEFAULT_CUSTOM, presets_map: DEFAULT_MAP, enviro: nil, policy_groups: nil)
11
+ @abilities = JSON.load(File.open(ABILITIES))
12
+ @habilities = @abilities.map do |key, values|
13
+ h_values = values.map { |v| [v, true] }.to_h
14
+ [key, h_values]
15
+ end.to_h
16
+
17
+ fatal("Expecting Environment object. Given: #{enviro}") if enviro && !enviro.is_a?(API::Common::Session::Environment)
18
+ @enviro = enviro
19
+
20
+ policy_groups = policy_groups || @enviro&.api&.policy_groups.to_a
21
+ if policy_groups.is_a?(Eco::API::Organization::PolicyGroups)
22
+ @policy_groups = policy_groups
23
+ else
24
+ @policy_groups = Eco::API::Organization::PolicyGroups.new(policy_groups)
25
+ end
26
+
27
+ init_custom(presets_custom)
28
+ init_map(presets_map)
29
+ end
30
+
31
+ def new(*policy_group_ids_or_names)
32
+
33
+ names = policy_group_ids_or_names.map do |id_name|
34
+ @policy_groups.to_name(id_name)&.downcase
35
+ end.compact
36
+
37
+ if @presets_map
38
+ preset_names = names.map { |name| @presets_map.fetch(name, nil) }
39
+ else # option to do not use preset mapping (so just the policy group name)
40
+ preset_names = names
41
+ end
42
+ compile(*preset_names)
43
+ end
44
+
45
+ private
46
+
47
+ def init_custom(file = DEFAULT_CUSTOM)
48
+ @presets_custom = nil
49
+
50
+ return if !file
51
+ file = File.expand_path(file)
52
+
53
+ if File.exists?(file)
54
+ @presets_custom = JSON.load(File.open(file))
55
+
56
+ errors = @presets_custom.map do |key, preset|
57
+ (err = preset_errors(preset)) ? "{ '#{key}' preset -> #{err}}": nil
58
+ end.compact
59
+
60
+ fatal("File '#{file}' contains invalid presets:\n #{errors.join("\n ")}") if errors.length > 0
61
+ end
62
+
63
+ end
64
+
65
+ def init_map(file = DEFAULT_MAP)
66
+ @presets_map = nil
67
+
68
+ return if !file
69
+ file = File.expand_path(file)
70
+
71
+ if File.exists?(file)
72
+ fatal("Maps file specified without custom presets file. Aborting!") if !@presets_custom
73
+ @presets_map = JSON.load(File.open(file))
74
+
75
+ errors = []
76
+ if @policy_groups.length > 0
77
+ errors = @policy_groups.map do |pg|
78
+ exists = @presets_map[pg.name.downcase] || @presets_custom[pg.name.downcase]
79
+ exists ? nil : "'#{pg.name}'"
80
+ end.compact
81
+
82
+ warn("No maps or no preset for policy group(s): #{errors.join(", ")}") if errors.length > 0
83
+ end
84
+
85
+ errors = @presets_map.map do |source, dest|
86
+ @presets_custom[dest] ? nil : "'#{dest}'"
87
+ end.compact
88
+
89
+ warn("Unexisting mapped preset(s): #{errors.uniq.join(", ")}") if errors.length > 0
90
+ end
91
+
92
+ end
93
+
94
+ def fatal(msg)
95
+ raise msg if !@enviro
96
+ @enviro.logger.fatal(msg)
97
+ exit
98
+ end
99
+
100
+ def warn(msg)
101
+ raise msg if !@enviro
102
+ @enviro.logger.warn(msg)
103
+ end
104
+
105
+ def compile(*preset_names)
106
+ fatal("You need to specify an existing file for the custom presets.") if !@presets_custom
107
+ @presets_custom.values_at(*preset_names).compact.reduce(empty_model) do |p1, p2|
108
+ merge(p1, p2)
109
+ end
110
+ end
111
+
112
+ def merge(preset1, preset2)
113
+ @abilities.map do |key, values|
114
+ idx = [
115
+ values.index(preset1[key]),
116
+ values.index(preset2[key])
117
+ ].compact.max
118
+ [key, idx && values[idx]]
119
+ end.to_h
120
+ end
121
+
122
+ def empty_model
123
+ JSON.parse(@abilities.to_json).transform_values {|v| nil }
124
+ end
125
+
126
+ def preset_errors(preset)
127
+ return "No preset given" if !preset
128
+ errors = preset.map do |k, v|
129
+ @habilities.dig(k, v) ? nil : "#{k}:#{v}"
130
+ end.compact
131
+ return " unknown: {#{errors.join(", ")}}" if errors.length > 0
132
+ nil
133
+ end
134
+ end
135
+
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,220 @@
1
+ module Eco
2
+ module API
3
+ module Account
4
+
5
+ #TODO set rules, make Presets immutable, and init from config
6
+ class PresetsFactory
7
+ ABILITIES = File.join(__dir__, 'presets_values.json')
8
+ DEFAULT_CUSTOM = 'presets_custom.json'
9
+ DEFAULT_MAPS = 'presets_map.json'
10
+
11
+ def initialize(init = {}, policy_groups: [])
12
+ @abilities = JSON.load(File.open(ABILITIES))
13
+
14
+ init_custom(init)
15
+ init_maps(init)
16
+
17
+ @policy_groups = policy_groups
18
+ @policy_groups_by_id = policy_groups.map{ |pg| [pg.id, pg] }.to_h
19
+ @policy_groups_by_name = policy_groups.map { |pg| [pg.name&.downcase, pg] }.to_h
20
+ end
21
+
22
+ def new(*policy_group_ids_or_names)
23
+ names = policy_group_ids_or_names.map { |id_name| policy_group_name(id_name) }
24
+ preset_names = names.map { |name| @presets_map.fetch(name, nil) }
25
+ # validate
26
+ compile(*preset_names)
27
+ end
28
+
29
+ private
30
+
31
+ def policy_group_name(id_name)
32
+ @policy_groups_by_id.fetch(id_name, nil)&.name&.downcase ||
33
+ @policy_groups_by_name.fetch(id_name&.downcase, nil)&.name&.downcase
34
+ end
35
+
36
+ def init_custom(init = {})
37
+ file = File.expand_path(init.fetch('presets_custom', DEFAULT_CUSTOM))
38
+ @presets_custom = JSON.load(File.open(file))
39
+ end
40
+
41
+ def init_maps(init = {})
42
+ file = File.expand_path(init.fetch('presets_map', DEFAULT_MAPS))
43
+ @presets_map = JSON.load(File.open(file))
44
+ end
45
+
46
+ def compile(*preset_names)
47
+ @presets_custom.values_at(*preset_names).reduce(empty_model) do |p1, p2|
48
+ merge(p1, p2)
49
+ end
50
+ end
51
+
52
+ def merge(preset1, preset2)
53
+ @abilities.map do |key, values|
54
+ idx = [
55
+ values.index(preset1[key]),
56
+ values.index(preset2[key])
57
+ ].compact.max
58
+ [key, idx && values[idx]]
59
+ end.to_h
60
+ end
61
+
62
+ def empty_model
63
+ JSON.parse(@abilities.to_json).transform_values {|v| nil }
64
+ end
65
+
66
+ end
67
+
68
+ class Presets
69
+ DEFAULT_PRESET = "basic"
70
+ PRESETS_VALUES = File.join(__dir__, 'presets_values.json')
71
+ PRESETS_REFERENCE = File.join(__dir__, 'presets_reference.json')
72
+
73
+ attr_reader :default
74
+ attr_reader :presets_model, :presets_values, :presets_reference
75
+ attr_reader :native_presets, :featured_presets, :all_presets
76
+ attr_reader :rules
77
+ def initialize(init = {})
78
+ init = {} if !init || !init.is_a?(Hash)
79
+
80
+ @presets_values = JSON.load(File.open(PRESETS_VALUES))
81
+ # get the index of each value of each ability (so we can compare for highest and lowest)
82
+ @presets_values_hashed = JSON.parse(@presets_values.to_json) \
83
+ .transform_values {|v| Hash[v.map.with_index.to_a] }
84
+
85
+ @presets_model = self.empty_model
86
+ @presets_reference = init.fetch('reference', JSON.load(File.open(PRESETS_REFERENCE)))
87
+ @native_presets = ["read_only", "forms", "editor", "administrator", "custom"]
88
+ @all_presets = @presets_reference.keys
89
+ @featured_presets = @all_presets - @native_presets
90
+ @default = init.fetch("default", DEFAULT_PRESET)
91
+
92
+ self.set_rules(init.fetch('rules', nil))
93
+ end
94
+
95
+ def set_rules(rules)
96
+ @rules = rules
97
+ end
98
+
99
+ def empty_model
100
+ JSON.parse(@presets_values.to_json).transform_values {|v| nil }
101
+ end
102
+
103
+ def is_custom?(preset_name)
104
+ return (!preset_name || preset_name == "custom")
105
+ end
106
+
107
+ def find_preset(preset)
108
+ found = @all_presets.select { |ref|
109
+ preset_ref = @presets_reference.fetch(ref, nil)
110
+ presets_equal?(preset, preset_ref)
111
+ }
112
+ return found.switch()
113
+ end
114
+
115
+ # returns a new hash with presets defaulting to a preset profile
116
+ def new_model(default = nil)
117
+ presets_sample = @presets_reference.fetch(default, @presets_model)
118
+ JSON.parse(presets_sample.to_json)
119
+ end
120
+
121
+ def default_model
122
+ self.new_model(@default)
123
+ end
124
+
125
+ def get_models(presets_list = [])
126
+ presets_list.map {|v| self.new_model(v) }
127
+ end
128
+
129
+ def presets_equal?(preset1, preset2)
130
+ return false if !preset1 || !preset2
131
+ all_keys = self.new_model.keys
132
+ # "no access" can / should be nil; or not specified (notice that it is not so for updates)
133
+ return !all_keys.empty? && all_keys.all? { |key| preset1.fetch(key, nil) == preset2.fetch(key, nil) }
134
+ end
135
+
136
+ # among a set of presets, it returns the presets doc
137
+ # for each flag, it contains the highest (or lowest) of all the input presets
138
+ # by giving at least the abilities of the preset min: to each flag
139
+ # and / or the max: abilities one if specified
140
+ def merge (presets, highest: true, min: nil, max: nil)
141
+ max_preset_model = max ? self.new_model(max) : nil
142
+ min = self.default if min == "default"
143
+
144
+ return self.new_model(min).map do |k_flag, min_ability|
145
+ levels = presets.map {|pre| ability_level(k_flag, pre[k_flag]) }
146
+
147
+ min_lev = ability_level(k_flag, min_ability)
148
+ max_lev = Float::INFINITY
149
+ if max
150
+ max_ability = max_preset_model.fetch(k_flag, nil)
151
+ max_lev = ability_level(k_flag, max_ability)
152
+ end
153
+ if highest
154
+ level = highest(levels, flag: k_flag, min: min_lev, max: max_lev)
155
+ else
156
+ level = lowest(levels, flag: k_flag, min: min_lev, max: max_lev)
157
+ end
158
+
159
+ [k_flag, level_ability(k_flag, level)]
160
+ end.to_h
161
+ end
162
+
163
+ def self.test(num = 2, min: "random", max: "random")
164
+ account_presets = self.new
165
+ all_presets = account_presets.all_presets
166
+
167
+ num = rand(2..5) if num == "random"
168
+ list_types = all_presets.sample(num)
169
+
170
+ min = rand > 0.5 if min == "random"
171
+ max = rand > 0.5 if max == "random"
172
+
173
+ min = all_presets.sample(1).first if (!!min === min) && min
174
+ max = all_presets.sample(1).first if (!!max === max) && max
175
+
176
+ presets = account_presets.get_models(list_types)
177
+
178
+ puts "min: #{min}; max: #{max}" if min || max
179
+ puts "chosen presets: #{list_types}"
180
+ puts "pre-merge models: #{presets}"
181
+ pp account_presets.merge(presets) unless min || max
182
+ pp account_presets.merge(presets, min: min, max: max) if min && max
183
+ pp account_presets.merge(presets, min: min) if min && !max
184
+ pp account_presets.merge(presets, max: max) if !min && max
185
+ end
186
+
187
+ private
188
+
189
+ # given an array list of values for a flag
190
+ # it returns the highest of all the abilities
191
+ def highest(list, flag:, min: 0, max: Float::INFINITY)
192
+ m = (list + [min]).max
193
+ return (max < m)? max : m
194
+ end
195
+
196
+ # it returns the highest of all the values
197
+ def lowest(list, flag:, min: 0, max: Float::INFINITY)
198
+ m = (list + [max]).min
199
+ return (min > m)? min : m if min
200
+ return m
201
+ end
202
+
203
+ # given an ability of a flag
204
+ # it returns the level of that flag
205
+ def ability_level(flag, ability)
206
+ return 0 unless ability
207
+ return @presets_values_hashed.dig(flag, ability)
208
+ end
209
+
210
+ # given a level of one flag
211
+ # it returns the ability of that flag
212
+ def level_ability(flag, level)
213
+ return nil unless level && level > 0
214
+ return @presets_values[flag][level]
215
+ end
216
+
217
+ end
218
+ end
219
+ end
220
+ end