eco-helpers 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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