functional-light-service 6.2.0 → 6.3.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.
- checksums.yaml +4 -4
- data/lib/functional-light-service.rb +6 -36
- metadata +14 -307
- data/.github/workflows/project-build.yml +0 -71
- data/.gitignore +0 -24
- data/.rspec +0 -3
- data/.rubocop.yml +0 -101
- data/.solargraph.yml +0 -11
- data/AUDIT-functional-light-service.md +0 -352
- data/Appraisals +0 -7
- data/CHANGELOG.md +0 -292
- data/CODE_OF_CONDUCT.md +0 -22
- data/Gemfile +0 -4
- data/LICENSE +0 -22
- data/README.md +0 -1663
- data/Rakefile +0 -12
- data/VERSION +0 -1
- data/audit/bench.rb +0 -99
- data/audit/verify_findings.rb +0 -172
- data/functional-light-service.gemspec +0 -35
- data/gemfiles/dry_inflector_0_2_1.gemfile +0 -5
- data/gemfiles/i18n_1_8_11.gemfile +0 -5
- data/lib/functional-light-service/action.rb +0 -145
- data/lib/functional-light-service/configuration.rb +0 -36
- data/lib/functional-light-service/context/key_verifier.rb +0 -124
- data/lib/functional-light-service/context.rb +0 -208
- data/lib/functional-light-service/deprecations.rb +0 -26
- data/lib/functional-light-service/errors.rb +0 -9
- data/lib/functional-light-service/functional/enum.rb +0 -286
- data/lib/functional-light-service/functional/maybe.rb +0 -21
- data/lib/functional-light-service/functional/monad.rb +0 -77
- data/lib/functional-light-service/functional/null.rb +0 -88
- data/lib/functional-light-service/functional/option.rb +0 -100
- data/lib/functional-light-service/functional/result.rb +0 -129
- data/lib/functional-light-service/functional/sequencer.rb +0 -144
- data/lib/functional-light-service/i18n/localization_adapter.rb +0 -50
- data/lib/functional-light-service/localization_adapter.rb +0 -39
- data/lib/functional-light-service/localization_map.rb +0 -9
- data/lib/functional-light-service/organizer/execute.rb +0 -16
- data/lib/functional-light-service/organizer/iterate.rb +0 -30
- data/lib/functional-light-service/organizer/reduce_case.rb +0 -50
- data/lib/functional-light-service/organizer/reduce_if.rb +0 -19
- data/lib/functional-light-service/organizer/reduce_if_else.rb +0 -23
- data/lib/functional-light-service/organizer/reduce_until.rb +0 -22
- data/lib/functional-light-service/organizer/reduce_while.rb +0 -31
- data/lib/functional-light-service/organizer/scoped_reducable.rb +0 -15
- data/lib/functional-light-service/organizer/with_callback.rb +0 -28
- data/lib/functional-light-service/organizer/with_reducer.rb +0 -81
- data/lib/functional-light-service/organizer/with_reducer_factory.rb +0 -20
- data/lib/functional-light-service/organizer/with_reducer_log_decorator.rb +0 -111
- data/lib/functional-light-service/organizer.rb +0 -129
- data/lib/functional-light-service/testing/context_factory.rb +0 -48
- data/lib/functional-light-service/testing.rb +0 -3
- data/lib/functional-light-service/version.rb +0 -5
- data/resources/fail_actions.png +0 -0
- data/resources/light-service.png +0 -0
- data/resources/organizer_and_actions.png +0 -0
- data/resources/skip_actions.png +0 -0
- data/spec/acceptance/add_numbers_spec.rb +0 -11
- data/spec/acceptance/after_actions_spec.rb +0 -87
- data/spec/acceptance/around_each_spec.rb +0 -19
- data/spec/acceptance/before_actions_spec.rb +0 -115
- data/spec/acceptance/custom_log_from_organizer_spec.rb +0 -61
- data/spec/acceptance/deprecation_warnings_spec.rb +0 -82
- data/spec/acceptance/fail_spec.rb +0 -52
- data/spec/acceptance/log_from_organizer_spec.rb +0 -154
- data/spec/acceptance/message_localization_spec.rb +0 -119
- data/spec/acceptance/organizer/add_aliases_spec.rb +0 -28
- data/spec/acceptance/organizer/add_to_context_spec.rb +0 -54
- data/spec/acceptance/organizer/around_each_with_reduce_if_spec.rb +0 -42
- data/spec/acceptance/organizer/context_failure_and_skipping_spec.rb +0 -90
- data/spec/acceptance/organizer/execute_spec.rb +0 -67
- data/spec/acceptance/organizer/iterate_spec.rb +0 -44
- data/spec/acceptance/organizer/reduce_case_spec.rb +0 -65
- data/spec/acceptance/organizer/reduce_if_else_spec.rb +0 -60
- data/spec/acceptance/organizer/reduce_if_spec.rb +0 -89
- data/spec/acceptance/organizer/reduce_until_spec.rb +0 -49
- data/spec/acceptance/organizer/reduce_while_spec.rb +0 -96
- data/spec/acceptance/organizer/with_callback_spec.rb +0 -113
- data/spec/acceptance/organizer_entry_point_spec.rb +0 -35
- data/spec/acceptance/rollback_spec.rb +0 -183
- data/spec/acceptance/skip_all_remaining_spec.rb +0 -139
- data/spec/acceptance/testing/context_factory_spec.rb +0 -54
- data/spec/action_expected_keys_spec.rb +0 -63
- data/spec/action_expects_and_promises_spec.rb +0 -97
- data/spec/action_optional_expected_keys_spec.rb +0 -107
- data/spec/action_promised_keys_spec.rb +0 -126
- data/spec/action_spec.rb +0 -97
- data/spec/context/inspect_spec.rb +0 -52
- data/spec/context_spec.rb +0 -301
- data/spec/examples/amount_spec.rb +0 -77
- data/spec/examples/controller_spec.rb +0 -63
- data/spec/examples/validate_address_spec.rb +0 -38
- data/spec/i18n_localization_adapter_spec.rb +0 -83
- data/spec/lib/deterministic/class_mixin_spec.rb +0 -24
- data/spec/lib/deterministic/currify_spec.rb +0 -90
- data/spec/lib/deterministic/monad_axioms.rb +0 -46
- data/spec/lib/deterministic/monad_spec.rb +0 -47
- data/spec/lib/deterministic/null_spec.rb +0 -65
- data/spec/lib/deterministic/option_spec.rb +0 -140
- data/spec/lib/deterministic/result/failure_spec.rb +0 -65
- data/spec/lib/deterministic/result/result_map_spec.rb +0 -155
- data/spec/lib/deterministic/result/result_shared.rb +0 -25
- data/spec/lib/deterministic/result/success_spec.rb +0 -41
- data/spec/lib/deterministic/result_spec.rb +0 -63
- data/spec/lib/deterministic/sequencer_spec.rb +0 -506
- data/spec/lib/edge_cases_spec.rb +0 -156
- data/spec/lib/enum_spec.rb +0 -114
- data/spec/lib/native_pattern_matching_spec.rb +0 -74
- data/spec/localization_adapter_spec.rb +0 -66
- data/spec/organizer/with_reducer_spec.rb +0 -56
- data/spec/organizer_key_aliases_spec.rb +0 -29
- data/spec/organizer_spec.rb +0 -115
- data/spec/readme_spec.rb +0 -45
- data/spec/sample/calculates_order_tax_action_spec.rb +0 -16
- data/spec/sample/calculates_tax_spec.rb +0 -30
- data/spec/sample/looks_up_tax_percentage_action_spec.rb +0 -55
- data/spec/sample/provides_free_shipping_action_spec.rb +0 -25
- data/spec/sample/tax/calculates_order_tax_action.rb +0 -10
- data/spec/sample/tax/calculates_tax.rb +0 -11
- data/spec/sample/tax/looks_up_tax_percentage_action.rb +0 -28
- data/spec/sample/tax/provides_free_shipping_action.rb +0 -11
- data/spec/spec_helper.rb +0 -32
- data/spec/support.rb +0 -1
- data/spec/test_doubles.rb +0 -656
- data/spec/testing/context_factory/iterate_spec.rb +0 -39
- data/spec/testing/context_factory/reduce_if_spec.rb +0 -40
- data/spec/testing/context_factory/reduce_until_spec.rb +0 -40
- data/spec/testing/context_factory/with_callback_spec.rb +0 -38
- data/spec/testing/context_factory_spec.rb +0 -76
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module FunctionalLightService
|
|
4
|
-
# rubocop:disable Metrics/ClassLength
|
|
5
|
-
class Context < Hash
|
|
6
|
-
include FunctionalLightService::Prelude::Option
|
|
7
|
-
include FunctionalLightService::Prelude::Result
|
|
8
|
-
|
|
9
|
-
attr_reader :outcome
|
|
10
|
-
attr_accessor :current_action, :organized_by
|
|
11
|
-
|
|
12
|
-
# rubocop:disable Lint/MissingSuper
|
|
13
|
-
def initialize(context = {},
|
|
14
|
-
outcome = Success(:message => '', :error => nil))
|
|
15
|
-
@outcome = outcome
|
|
16
|
-
@skip_remaining = false
|
|
17
|
-
@skip_all_remaining = false
|
|
18
|
-
context.to_hash.each { |k, v| self[k] = v }
|
|
19
|
-
end
|
|
20
|
-
# rubocop:enable Lint/MissingSuper
|
|
21
|
-
|
|
22
|
-
def self.make(context = {})
|
|
23
|
-
unless context.is_a?(Hash) || context.is_a?(FunctionalLightService::Context)
|
|
24
|
-
msg = 'Argument must be Hash or FunctionalLightService::Context'
|
|
25
|
-
raise ArgumentError, msg
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
context = new(context) unless context.is_a?(Context)
|
|
29
|
-
|
|
30
|
-
context.assign_aliases(context.delete(:_aliases)) if context[:_aliases]
|
|
31
|
-
context
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
def add_to_context(values)
|
|
35
|
-
merge! values
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def success?
|
|
39
|
-
@outcome.success?
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def failure?
|
|
43
|
-
@outcome.failure?
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def skip_remaining?
|
|
47
|
-
@skip_remaining
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def skip_all_remaining?
|
|
51
|
-
@skip_all_remaining
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def reset_skip_remaining!
|
|
55
|
-
# Resetta soltanto il flag: l'esito (e il suo messaggio) non vanno persi
|
|
56
|
-
@skip_remaining = false
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
def message
|
|
60
|
-
@outcome.value[:message]
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
def error_code
|
|
64
|
-
@outcome.value[:error]
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
def succeed!(message = nil, options = {})
|
|
68
|
-
message = Configuration.localization_adapter.success(message,
|
|
69
|
-
current_action,
|
|
70
|
-
options)
|
|
71
|
-
@outcome = Success(:message => message)
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
def fail!(message = nil, options_or_error_code = {})
|
|
75
|
-
options_or_error_code ||= {}
|
|
76
|
-
|
|
77
|
-
if options_or_error_code.is_a?(Hash)
|
|
78
|
-
# dup: l'hash di opzioni appartiene al chiamante e non va mutato
|
|
79
|
-
options = options_or_error_code.dup
|
|
80
|
-
error_code = options.delete(:error_code)
|
|
81
|
-
else
|
|
82
|
-
error_code = options_or_error_code
|
|
83
|
-
options = {}
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
message = Configuration.localization_adapter.failure(message,
|
|
87
|
-
current_action,
|
|
88
|
-
options)
|
|
89
|
-
|
|
90
|
-
@outcome = Failure(:message => message, :error => error_code)
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
def fail_and_return!(*args)
|
|
94
|
-
fail!(*args)
|
|
95
|
-
throw(:jump_when_failed)
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
def fail_with_rollback!(message = nil, error_code = nil)
|
|
99
|
-
fail!(message, error_code)
|
|
100
|
-
raise FailWithRollbackError
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
def skip_remaining!(message = nil)
|
|
104
|
-
@outcome = Success(:message => message)
|
|
105
|
-
@skip_remaining = true
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
# Variante non azzerabile di skip_remaining!: salta tutto il resto
|
|
109
|
-
# dell'organizer, scope annidati compresi (scoped_reduce non la resetta)
|
|
110
|
-
def skip_all_remaining!(message = nil)
|
|
111
|
-
@outcome = Success(:message => message)
|
|
112
|
-
@skip_all_remaining = true
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
def stop_processing?
|
|
116
|
-
failure? || skip_remaining? || skip_all_remaining?
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
# Registra le chiavi come accessor consentiti: la lettura/scrittura passa
|
|
120
|
-
# da method_missing con whitelist. Rispetto a define_singleton_method non
|
|
121
|
-
# materializza una singleton class per ogni context (audit, finding 3.3)
|
|
122
|
-
def define_accessor_methods_for_keys(keys)
|
|
123
|
-
return if keys.nil?
|
|
124
|
-
|
|
125
|
-
@accessor_methods ||= {}
|
|
126
|
-
keys.each do |key|
|
|
127
|
-
key = key.to_sym
|
|
128
|
-
next if @accessor_methods.key?(key)
|
|
129
|
-
|
|
130
|
-
# Prima il conflitto veniva saltato in silenzio e ctx.size (o :count,
|
|
131
|
-
# :message, ...) ritornava il metodo di Hash invece del valore
|
|
132
|
-
if respond_to?(key) || respond_to?("#{key}=")
|
|
133
|
-
raise ReservedKeysInContextError,
|
|
134
|
-
"expected or promised key :#{key} conflicts with an existing " \
|
|
135
|
-
"#{self.class.name} method: rename the key or access it via ctx[:#{key}]"
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
@accessor_methods[key] = [:reader, key]
|
|
139
|
-
@accessor_methods[:"#{key}="] = [:writer, key]
|
|
140
|
-
end
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
def method_missing(name, *args)
|
|
144
|
-
accessor = @accessor_methods && @accessor_methods[name]
|
|
145
|
-
return super unless accessor
|
|
146
|
-
|
|
147
|
-
accessor[0] == :reader ? fetch(accessor[1]) : self[accessor[1]] = args.first
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
def respond_to_missing?(name, _include_all = false)
|
|
151
|
-
(!@accessor_methods.nil? && @accessor_methods.key?(name)) || super
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
def assign_aliases(aliases)
|
|
155
|
-
@aliases = aliases
|
|
156
|
-
# Hash inverso precomputato: la risoluzione in lettura/scrittura
|
|
157
|
-
# resta O(1) invece del reverse-scan di Hash#key
|
|
158
|
-
@inverse_aliases = aliases.invert
|
|
159
|
-
|
|
160
|
-
self
|
|
161
|
-
end
|
|
162
|
-
|
|
163
|
-
def aliases
|
|
164
|
-
@aliases ||= {}
|
|
165
|
-
end
|
|
166
|
-
|
|
167
|
-
def [](key)
|
|
168
|
-
super(resolve_key(key))
|
|
169
|
-
end
|
|
170
|
-
|
|
171
|
-
def []=(key, value)
|
|
172
|
-
super(resolve_key(key), value)
|
|
173
|
-
end
|
|
174
|
-
|
|
175
|
-
def fetch(key, ...)
|
|
176
|
-
super(resolve_key(key), ...)
|
|
177
|
-
end
|
|
178
|
-
|
|
179
|
-
def key?(key)
|
|
180
|
-
super(resolve_key(key))
|
|
181
|
-
end
|
|
182
|
-
|
|
183
|
-
alias has_key? key?
|
|
184
|
-
alias member? key?
|
|
185
|
-
alias include? key?
|
|
186
|
-
|
|
187
|
-
def inspect
|
|
188
|
-
"#{self.class}(#{self}, success: #{success?}, message: #{check_nil(message)}, error_code: " \
|
|
189
|
-
"#{check_nil(error_code)}, skip_remaining: #{@skip_remaining}, " \
|
|
190
|
-
"skip_all_remaining: #{@skip_all_remaining}, aliases: #{aliases})"
|
|
191
|
-
end
|
|
192
|
-
|
|
193
|
-
private
|
|
194
|
-
|
|
195
|
-
def resolve_key(key)
|
|
196
|
-
return key unless @inverse_aliases
|
|
197
|
-
|
|
198
|
-
@inverse_aliases[key] || key
|
|
199
|
-
end
|
|
200
|
-
|
|
201
|
-
def check_nil(value)
|
|
202
|
-
return 'nil' unless value
|
|
203
|
-
|
|
204
|
-
"'#{value}'"
|
|
205
|
-
end
|
|
206
|
-
end
|
|
207
|
-
# rubocop:enable Metrics/ClassLength
|
|
208
|
-
end
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module FunctionalLightService
|
|
4
|
-
# Deprecation warnings non fatali: ogni messaggio viene emesso una sola
|
|
5
|
-
# volta per processo (su stderr) e puo' essere silenziato globalmente,
|
|
6
|
-
# ad esempio nelle suite di test.
|
|
7
|
-
module Deprecations
|
|
8
|
-
@emitted = {}
|
|
9
|
-
|
|
10
|
-
class << self
|
|
11
|
-
attr_accessor :silenced
|
|
12
|
-
|
|
13
|
-
def warn(message)
|
|
14
|
-
return if silenced
|
|
15
|
-
return if @emitted[message]
|
|
16
|
-
|
|
17
|
-
@emitted[message] = true
|
|
18
|
-
Kernel.warn("DEPRECATION WARNING: #{message}")
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def reset!
|
|
22
|
-
@emitted = {}
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
end
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module FunctionalLightService
|
|
4
|
-
class FailWithRollbackError < StandardError; end
|
|
5
|
-
class ExpectedKeysNotInContextError < StandardError; end
|
|
6
|
-
class PromisedKeysNotInContextError < StandardError; end
|
|
7
|
-
class ReservedKeysInContextError < StandardError; end
|
|
8
|
-
class UnusableExpectKeyDefaultError < StandardError; end
|
|
9
|
-
end
|
|
@@ -1,286 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module FunctionalLightService
|
|
4
|
-
module Enum
|
|
5
|
-
class MatchError < StandardError; end
|
|
6
|
-
end
|
|
7
|
-
|
|
8
|
-
class EnumBuilder
|
|
9
|
-
def initialize(parent)
|
|
10
|
-
@parent = parent
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
class DataType
|
|
14
|
-
module AnyEnum
|
|
15
|
-
include FunctionalLightService::Monad
|
|
16
|
-
|
|
17
|
-
def match(&)
|
|
18
|
-
parent.match(self, &)
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def to_s
|
|
22
|
-
value.to_s
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def name
|
|
26
|
-
self.class.name.split("::")[-1]
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
# Returns array. Will fail on Nullary objects.
|
|
30
|
-
# TODO: define a Unary module so we can define this method differently on Unary vs Binary
|
|
31
|
-
def wrapped_values
|
|
32
|
-
if is_a?(FunctionalLightService::EnumBuilder::DataType::Binary)
|
|
33
|
-
value.values
|
|
34
|
-
else
|
|
35
|
-
[value]
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
# Supporto al pattern matching nativo di Ruby (case/in):
|
|
40
|
-
# case result
|
|
41
|
-
# in FunctionalLightService::Result::Success(s) then ...
|
|
42
|
-
# in FunctionalLightService::Result::Failure(f) then ...
|
|
43
|
-
# end
|
|
44
|
-
def deconstruct
|
|
45
|
-
is_a?(FunctionalLightService::EnumBuilder::DataType::Nullary) ? [] : wrapped_values
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
def deconstruct_keys(_keys)
|
|
49
|
-
if is_a?(FunctionalLightService::EnumBuilder::DataType::Binary)
|
|
50
|
-
value.dup
|
|
51
|
-
elsif is_a?(FunctionalLightService::EnumBuilder::DataType::Nullary)
|
|
52
|
-
{}
|
|
53
|
-
else
|
|
54
|
-
{ args[0] => value }
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
module Nullary
|
|
60
|
-
def initialize(*_args)
|
|
61
|
-
@value = nil
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def inspect
|
|
65
|
-
name
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
# TODO: this should probably be named Multary
|
|
70
|
-
module Binary
|
|
71
|
-
def initialize(*init)
|
|
72
|
-
unless (init.one? && init[0].is_a?(Hash)) || init.count == args.count
|
|
73
|
-
raise ArgumentError, "Expected arguments for #{args}, got #{init}"
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
@value = if init.one? && init[0].is_a?(Hash)
|
|
77
|
-
args.zip(init[0].values).to_h
|
|
78
|
-
else
|
|
79
|
-
args.zip(init).to_h
|
|
80
|
-
end
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
def inspect
|
|
84
|
-
params = value.map { |k, v| "#{k}: #{v.inspect}" }
|
|
85
|
-
"#{name}(#{params.join(', ')})"
|
|
86
|
-
end
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
# rubocop:disable Metrics/MethodLength
|
|
90
|
-
def self.create(parent, args)
|
|
91
|
-
if args.include? :value
|
|
92
|
-
raise ArgumentError, "#{args} may not contain the reserved name :value"
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
dt = Class.new(parent)
|
|
96
|
-
|
|
97
|
-
dt.instance_eval do
|
|
98
|
-
public_class_method :new
|
|
99
|
-
include AnyEnum
|
|
100
|
-
|
|
101
|
-
define_method(:args) { args }
|
|
102
|
-
|
|
103
|
-
define_method(:parent) { parent }
|
|
104
|
-
private :parent
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
case args.count
|
|
108
|
-
when 0
|
|
109
|
-
dt.instance_eval do
|
|
110
|
-
include Nullary
|
|
111
|
-
|
|
112
|
-
private :value
|
|
113
|
-
end
|
|
114
|
-
when 1
|
|
115
|
-
dt.instance_eval do
|
|
116
|
-
define_method(args[0].to_sym) { value }
|
|
117
|
-
end
|
|
118
|
-
else
|
|
119
|
-
dt.instance_eval do
|
|
120
|
-
include Binary
|
|
121
|
-
|
|
122
|
-
args.each do |m|
|
|
123
|
-
define_method(m) do
|
|
124
|
-
@value[m]
|
|
125
|
-
end
|
|
126
|
-
end
|
|
127
|
-
end
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
dt
|
|
131
|
-
end
|
|
132
|
-
# rubocop:enable Metrics/MethodLength
|
|
133
|
-
|
|
134
|
-
class << self
|
|
135
|
-
public :new
|
|
136
|
-
end
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
def method_missing(m, *args)
|
|
140
|
-
if @parent.const_defined?(m)
|
|
141
|
-
raise ArgumentError, "variant #{m} is already defined for this enum"
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
@parent.const_set(m, DataType.create(@parent, args))
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
def respond_to_missing?(_m, _include_all = false)
|
|
148
|
-
true
|
|
149
|
-
end
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
module_function
|
|
153
|
-
|
|
154
|
-
# rubocop:disable Metrics/AbcSize
|
|
155
|
-
# rubocop:disable Metrics/CyclomaticComplexity
|
|
156
|
-
# rubocop:disable Metrics/MethodLength
|
|
157
|
-
# rubocop:disable Metrics/PerceivedComplexity
|
|
158
|
-
def enum(&block)
|
|
159
|
-
mod = Class.new do # the enum to be built
|
|
160
|
-
private_class_method :new
|
|
161
|
-
|
|
162
|
-
def self.match(obj, &block)
|
|
163
|
-
# Binding#receiver: stesso risultato di binding.eval('self') senza eval
|
|
164
|
-
caller_ctx = block.binding.receiver
|
|
165
|
-
|
|
166
|
-
matcher = self::Matcher.new(obj)
|
|
167
|
-
matcher.instance_eval(&block)
|
|
168
|
-
|
|
169
|
-
# exhaustiveness check su classi memoizzate: niente split/sort di
|
|
170
|
-
# stringhe per chiamata
|
|
171
|
-
covered = matcher.matches.map { |e| e[1] }
|
|
172
|
-
missing = variant_classes.reject { |klass| covered.include?(klass) }
|
|
173
|
-
unless missing.empty?
|
|
174
|
-
missing_names = missing.map { |klass| klass.name.split('::')[-1].to_sym }
|
|
175
|
-
raise Enum::MatchError, "Match is non-exhaustive, #{missing_names} not covered"
|
|
176
|
-
end
|
|
177
|
-
|
|
178
|
-
type_matches = matcher.matches.select { |r| r[0].is_a?(r[1]) }
|
|
179
|
-
|
|
180
|
-
type_matches.each do |match|
|
|
181
|
-
obj, _type, block, args, guard = match
|
|
182
|
-
|
|
183
|
-
return caller_ctx.instance_eval(&block) if args.empty?
|
|
184
|
-
|
|
185
|
-
if args.count != obj.args.count
|
|
186
|
-
msg = "Pattern (#{args.join(', ')}) must match (#{obj.args.join(', ')})"
|
|
187
|
-
raise Enum::MatchError, msg
|
|
188
|
-
end
|
|
189
|
-
|
|
190
|
-
guard_ctx = guard_context(obj, args)
|
|
191
|
-
return caller_ctx.instance_exec(* obj.wrapped_values, &block) unless guard
|
|
192
|
-
|
|
193
|
-
if guard && guard_ctx.instance_exec(obj, &guard)
|
|
194
|
-
return caller_ctx.instance_exec(* obj.wrapped_values, &block)
|
|
195
|
-
end
|
|
196
|
-
end
|
|
197
|
-
|
|
198
|
-
raise Enum::MatchError, "No match could be made"
|
|
199
|
-
end
|
|
200
|
-
|
|
201
|
-
def self.variants
|
|
202
|
-
constants - %i[Matcher MatchError]
|
|
203
|
-
end
|
|
204
|
-
|
|
205
|
-
def self.variant_classes
|
|
206
|
-
@variant_classes ||= variants.map { |v| const_get(v) }.freeze
|
|
207
|
-
end
|
|
208
|
-
|
|
209
|
-
def self.guard_context(obj, args)
|
|
210
|
-
# Struct.new definisce una classe: va fatto una volta per firma,
|
|
211
|
-
# non a ogni match con guard
|
|
212
|
-
@guard_structs ||= {}
|
|
213
|
-
struct = @guard_structs[args] ||= Struct.new(*args)
|
|
214
|
-
|
|
215
|
-
if obj.is_a?(FunctionalLightService::EnumBuilder::DataType::Binary)
|
|
216
|
-
struct.new(*obj.value.values)
|
|
217
|
-
else
|
|
218
|
-
struct.new(obj.value)
|
|
219
|
-
end
|
|
220
|
-
end
|
|
221
|
-
end
|
|
222
|
-
enum = EnumBuilder.new(mod)
|
|
223
|
-
enum.instance_eval(&block)
|
|
224
|
-
|
|
225
|
-
type_variants = mod.constants
|
|
226
|
-
|
|
227
|
-
matcher = Class.new do
|
|
228
|
-
def initialize(obj)
|
|
229
|
-
@obj = obj
|
|
230
|
-
@matches = []
|
|
231
|
-
@vars = []
|
|
232
|
-
end
|
|
233
|
-
|
|
234
|
-
attr_reader :matches, :vars
|
|
235
|
-
|
|
236
|
-
def where(&guard)
|
|
237
|
-
guard
|
|
238
|
-
end
|
|
239
|
-
|
|
240
|
-
type_variants.each do |m|
|
|
241
|
-
define_method(m) do |guard = nil, &inner_block|
|
|
242
|
-
raise ArgumentError, "No block given to `#{m}`" if inner_block.nil?
|
|
243
|
-
|
|
244
|
-
params_spec = inner_block.parameters
|
|
245
|
-
if params_spec.any? { |spec| spec.size < 2 }
|
|
246
|
-
msg = "Unnamed param found in block parameters: #{params_spec.inspect}"
|
|
247
|
-
raise ArgumentError, msg
|
|
248
|
-
end
|
|
249
|
-
if params_spec.any? { |spec| spec[0] != :req && spec[0] != :opt }
|
|
250
|
-
msg = "Only :req & :opt params allowed; parameters=#{params_spec.inspect}"
|
|
251
|
-
raise ArgumentError, msg
|
|
252
|
-
end
|
|
253
|
-
|
|
254
|
-
args = params_spec.map { |spec| spec[1] }
|
|
255
|
-
|
|
256
|
-
type = mod.const_get(m)
|
|
257
|
-
|
|
258
|
-
guard = nil if guard && !guard.is_a?(Proc)
|
|
259
|
-
|
|
260
|
-
@matches << [@obj, type, inner_block, args, guard]
|
|
261
|
-
end
|
|
262
|
-
end
|
|
263
|
-
end
|
|
264
|
-
|
|
265
|
-
mod.const_set(:Matcher, matcher)
|
|
266
|
-
|
|
267
|
-
type_variants.each do |variant|
|
|
268
|
-
mod.singleton_class.class_exec do
|
|
269
|
-
define_method(variant) do |*args|
|
|
270
|
-
const_get(variant).new(*args)
|
|
271
|
-
end
|
|
272
|
-
end
|
|
273
|
-
end
|
|
274
|
-
mod
|
|
275
|
-
end
|
|
276
|
-
# rubocop:enable Metrics/AbcSize
|
|
277
|
-
# rubocop:enable Metrics/CyclomaticComplexity
|
|
278
|
-
# rubocop:enable Metrics/MethodLength
|
|
279
|
-
# rubocop:enable Metrics/PerceivedComplexity
|
|
280
|
-
|
|
281
|
-
def impl(enum_type, &block)
|
|
282
|
-
enum_type.variants.each do |v|
|
|
283
|
-
enum_type.const_get(v).class_eval(&block)
|
|
284
|
-
end
|
|
285
|
-
end
|
|
286
|
-
end
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
class Object
|
|
4
|
-
def null?
|
|
5
|
-
false
|
|
6
|
-
end
|
|
7
|
-
|
|
8
|
-
def some?
|
|
9
|
-
true
|
|
10
|
-
end
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
# rubocop:disable Naming/MethodName
|
|
14
|
-
def Maybe(obj)
|
|
15
|
-
FunctionalLightService::Deprecations.warn(
|
|
16
|
-
"Maybe()/Null are deprecated and will be removed in a future release; " \
|
|
17
|
-
"use FunctionalLightService::Option (Some/None) instead"
|
|
18
|
-
)
|
|
19
|
-
obj.nil? ? Null.instance : obj
|
|
20
|
-
end
|
|
21
|
-
# rubocop:enable Naming/MethodName
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module FunctionalLightService
|
|
4
|
-
module Monad
|
|
5
|
-
class NotMonadError < StandardError; end
|
|
6
|
-
|
|
7
|
-
# Basicly the `pure` function
|
|
8
|
-
def initialize(init)
|
|
9
|
-
@value = join(init)
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
# If the passed value is monad already, get the value to avoid nesting
|
|
13
|
-
# M[M[A]] is equivalent to M[A]
|
|
14
|
-
def join(other)
|
|
15
|
-
if other.is_a? self.class
|
|
16
|
-
other.value
|
|
17
|
-
else
|
|
18
|
-
other
|
|
19
|
-
end
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
# The functor: takes a function (a -> b) and applies it to the inner value of the monad (Ma),
|
|
23
|
-
# boxes it back to the same monad (Mb)
|
|
24
|
-
# fmap :: (a -> b) -> M a -> M b
|
|
25
|
-
def fmap(proc = nil, &block)
|
|
26
|
-
result = (proc || block).call(value)
|
|
27
|
-
self.class.new(result)
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
# The monad: takes a function which returns a monad (of the same type), applies the function
|
|
31
|
-
# bind :: (a -> Mb) -> M a -> M b
|
|
32
|
-
# the self.class, i.e. the containing monad is passed as a second (optional) arg to the function
|
|
33
|
-
def bind(proc = nil, &block)
|
|
34
|
-
(proc || block).call(value).tap do |result|
|
|
35
|
-
# rubocop:disable Style/CaseEquality
|
|
36
|
-
parent = self.class.superclass === Object ? self.class : self.class.superclass
|
|
37
|
-
# rubocop:enable Style/CaseEquality
|
|
38
|
-
unless result.is_a? parent
|
|
39
|
-
raise NotMonadError, "Expected #{result.inspect} to be an #{parent}"
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
# rubocop:disable Naming/MethodName
|
|
44
|
-
alias :'>>=' :bind
|
|
45
|
-
# rubocop:enable Naming/MethodName
|
|
46
|
-
|
|
47
|
-
# Get the underlying value, return in Haskell
|
|
48
|
-
# return :: M a -> a
|
|
49
|
-
def value
|
|
50
|
-
@value
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def to_s
|
|
54
|
-
value.to_s
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
# Two monads are equivalent if they are of the same type and when their values are equal
|
|
58
|
-
def ==(other)
|
|
59
|
-
return false unless other.is_a? self.class
|
|
60
|
-
|
|
61
|
-
@value == other.monad_value
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
# Reader protetto: #value e' privato nelle varianti Nullary (es. None),
|
|
65
|
-
# ma il confronto tra monadi dello stesso tipo deve poter leggere il valore
|
|
66
|
-
def monad_value
|
|
67
|
-
@value
|
|
68
|
-
end
|
|
69
|
-
protected :monad_value
|
|
70
|
-
|
|
71
|
-
# Return the string representation of the Monad
|
|
72
|
-
def inspect
|
|
73
|
-
pretty_class_name = self.class.name.split('::')[-1]
|
|
74
|
-
"#{pretty_class_name}(#{value.inspect})"
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
end
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# The simplest NullObject there can be
|
|
4
|
-
class Null
|
|
5
|
-
class << self
|
|
6
|
-
def method_missing(m, *args)
|
|
7
|
-
if m == :new
|
|
8
|
-
super
|
|
9
|
-
else
|
|
10
|
-
Null.instance
|
|
11
|
-
end
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
def respond_to_missing?(m, _include_all = false)
|
|
15
|
-
m != :new || super
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def instance
|
|
19
|
-
FunctionalLightService::Deprecations.warn(
|
|
20
|
-
"Maybe()/Null are deprecated and will be removed in a future release; " \
|
|
21
|
-
"use FunctionalLightService::Option (Some/None) instead"
|
|
22
|
-
)
|
|
23
|
-
@instance ||= new([])
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def null?
|
|
27
|
-
true
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def some?
|
|
31
|
-
false
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
def mimic(klas)
|
|
35
|
-
FunctionalLightService::Deprecations.warn(
|
|
36
|
-
"Maybe()/Null are deprecated and will be removed in a future release; " \
|
|
37
|
-
"use FunctionalLightService::Option (Some/None) instead"
|
|
38
|
-
)
|
|
39
|
-
new(klas.instance_methods(false))
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def ==(other)
|
|
43
|
-
other.respond_to?(:null?) && other.null?
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
private_class_method :new
|
|
47
|
-
|
|
48
|
-
def initialize(methods)
|
|
49
|
-
@methods = methods
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
# implicit conversions
|
|
53
|
-
def to_str
|
|
54
|
-
''
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def to_ary
|
|
58
|
-
[]
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
def method_missing(m, *args)
|
|
62
|
-
return self if respond_to_missing?(m)
|
|
63
|
-
|
|
64
|
-
super
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
def null?
|
|
68
|
-
true
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
def some?
|
|
72
|
-
false
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
# Convenzione Ruby: si estende respond_to_missing?, mai respond_to?
|
|
76
|
-
# (il vecchio override aveva anche la firma sbagliata: mancava include_all)
|
|
77
|
-
def respond_to_missing?(m, _include_all = false)
|
|
78
|
-
@methods.empty? || @methods.include?(m) || super
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
def inspect
|
|
82
|
-
'Null'
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
def ==(other)
|
|
86
|
-
other.respond_to?(:null?) && other.null?
|
|
87
|
-
end
|
|
88
|
-
end
|