emanlib 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 1325271b5892d5c0195b9169f09416790ea4887f680e194afb2445f33bb57b75
4
+ data.tar.gz: effa861c1070d969e2ab86287c101ba90bcd52ad9f82cceb87616d79f3b5a554
5
+ SHA512:
6
+ metadata.gz: 2a5248da79b6d73c42c62932c9a2473d10dee603aa9c40ba880cbe708f5ae450d1fa0861805c507051fa5dd3d777031dbfecd6a2120ac0fe688bc5743ee0eaf5
7
+ data.tar.gz: 77a89f7f87e7003134b581f272c4892f61c3cc861371cc7758b21a022463caa8468064b16adafd88b0d70f7b0bf1d431290b90bfe2dda22c16d6ff619d3d0389
data/lib/emanlib.rb ADDED
@@ -0,0 +1,73 @@
1
+ require_relative "patch/define"
2
+ require_relative "patch/foobar"
3
+ require_relative "patch/lambda"
4
+
5
+ module EmanLib
6
+ # The identity Lambda object (`_`).
7
+ # Store in a short variable, and use it as a building block for anonymous functions.
8
+ #
9
+ # _ = EmanLib.LAMBDA
10
+ # [1, 2, 3].map(&_.succ) => [2, 3, 4]
11
+ # [[1, 2], [3, 4]].map(&(_ + _).lift) => [3, 7]
12
+ LAMBDA = Lambda.new
13
+
14
+ # Helper method to create definitions.
15
+ # A convenient shorthand for `Object.new.define(...)`.
16
+ #
17
+ # @param args [Array<Hash, Array>] A list of Hashes or "hashy" Arrays.
18
+ # @param block [Proc] If provided, its local variables are used to define methods.
19
+ # @return [Object] A new object with dynamically defined methods.
20
+ #
21
+ # @see [Object#define]
22
+ #
23
+ # @example
24
+ # point = let(x: 10, y: 20)
25
+ # puts point.x # => 10
26
+ #
27
+ # settings = let do
28
+ # theme = "dark"
29
+ # font_size = 12
30
+ # binding
31
+ # end
32
+ # puts settings.theme # => "dark"
33
+ #
34
+ # complex_data = let([[:id, "item1"]], name: "Test Item") do
35
+ # details = { color: "red", size: "large" }
36
+ # binding
37
+ # end
38
+ #
39
+ # puts complex_data.id # => "item1"
40
+ # puts complex_data.name # => "Test Item"
41
+ # puts complex_data.details.color # => "red"
42
+ def let(*args, &block)
43
+ Object.new.define(*args, &block)
44
+ end
45
+
46
+ module_function :let
47
+
48
+ # Support for using a `_` as the second operand with operators.
49
+ # WARN: This method will MODIFY the standard library classes.
50
+ # In particular, the operators: `- * / % ** & | ^ << >> <=> == === != > < >= <=`
51
+ # in the classes: `Integer, Float, Rational, Complex, Array, String, Hash, Range, Set`
52
+ def support_lambda
53
+ [[Integer, Float, Rational, Complex, Array, String, Hash, Range, Set],
54
+ %i[- * / % ** & | ^ << >> <=> == === != > < >= <=]].op(:product)
55
+ .each do |klass, op|
56
+ next unless klass.instance_methods(false).include?(op)
57
+
58
+ original = klass.instance_method(op)
59
+ klass.define_method(op) do |other|
60
+ if other.is_a?(Lambda)
61
+ repr = [self]
62
+ repr.concat(other.repr)
63
+ repr << Lambda::Function.new(op)
64
+ Lambda.new(repr)
65
+ else
66
+ original.bind(self).call(other)
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
+ module_function :support_lambda
73
+ end
@@ -0,0 +1,198 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Enhances the Binding class to easily extract local variables into a Hash.
4
+ class Binding
5
+ # Converts the local variables accessible from this binding into a Hash.
6
+ # The keys of the hash are the variable names (as Symbols), and the values
7
+ # are the corresponding variable values.
8
+ #
9
+ # @return [Hash<Symbol, Object>] A hash mapping local variable names to their values.
10
+ #
11
+ # @example
12
+ # def my_method
13
+ # a = 10
14
+ # b = "hello"
15
+ # binding.variables # => {:a=>10, :b=>"hello"}
16
+ # end
17
+ def variables
18
+ Hash[
19
+ local_variables.map do |var|
20
+ [var, local_variable_get(var)]
21
+ end
22
+ ]
23
+ end
24
+ end
25
+
26
+ # Enhances the Array class with a utility method to check its structure.
27
+ class Array
28
+ # Checks if the array is "hashy", meaning it consists entirely of
29
+ # two-element arrays. This structure is suitable for conversion to a Hash
30
+ # using `to_h`.
31
+ #
32
+ # @return [Boolean] `true` if the array is "hashy", `false` otherwise.
33
+ #
34
+ # @example
35
+ # [[:a, 1], [:b, 2]].hashy? # => true
36
+ # [["key1", "value1"]].hashy? # => true
37
+ # [[1, 2, 3], [:b, 2]].hashy? # => false (first item has 3 elements)
38
+ # [1, 2, 3].hashy? # => false (items are not arrays)
39
+ # [[], [:a, 1]].hashy? # => false (first item is not a 2-element array)
40
+ def hashy?
41
+ all? { |item| item.is_a?(Array) && item.size == 2 }
42
+ end
43
+ end
44
+
45
+ # Enhances the String class with validation for method and variable names.
46
+ class String
47
+ # Checks if the string is a valid Ruby method or variable name.
48
+ #
49
+ # Valid method names can include letters, numbers, underscores, and may
50
+ # end with `!`, `=`, or `?`.
51
+ # Valid variable names can include letters, numbers, and underscores but
52
+ # cannot end with `!`, `=`, or `?`.
53
+ #
54
+ # @param target [:method, :variable] Specifies whether to validate as a method
55
+ # name or a variable name. Defaults to `:method`.
56
+ # @return [Boolean] `true` if the string is a valid name for the specified target,
57
+ # `false` otherwise.
58
+ #
59
+ # @example
60
+ # "my_method".valid_name? # => true
61
+ # "my_method?".valid_name? # => true
62
+ # "setter=".valid_name? # => true
63
+ # "_private_method!".valid_name? # => true
64
+ # "ConstantLike".valid_name? # => true
65
+ # "1invalid".valid_name? # => false (starts with a number)
66
+ # "invalid-name".valid_name? # => false (contains hyphen)
67
+ #
68
+ # "my_variable".valid_name?(target: :variable) # => true
69
+ # "_var".valid_name?(target: :variable) # => true
70
+ # "my_variable?".valid_name?(target: :variable)# => false (ends with ?)
71
+ # "A_CONSTANT".valid_name?(target: :variable) # => true
72
+ def valid_name?(target: :method)
73
+ case target
74
+ when :method
75
+ self =~ /\A[a-zA-Z_]\w*[!?=]?\z/
76
+ when :variable
77
+ self =~ /\A[a-zA-Z_]\w*\z/
78
+ else
79
+ false
80
+ end
81
+ end
82
+ end
83
+
84
+ # Enhances the base Object class to allow dynamic definition of properties
85
+ class Object
86
+ # Dynamically defines properties on `self`,
87
+ # based on the provided arguments and/or block.
88
+ #
89
+ # Arguments can be Hashes or "hashy" Arrays (arrays of `[key, value]` pairs).
90
+ # If a block is given, its local variables are also used to define methods.
91
+ # Keys are converted to symbols and validated as method names.
92
+ #
93
+ # If a value is a Hash or a "hashy" Array, it's recursively
94
+ # used to define nested properties.
95
+ #
96
+ # @param `args` [Array<Hash, Array>] A list of Hashes or "hashy" Arrays.
97
+ # Each key-value pair will result in a getter and setter method.
98
+ # @param `block` [Proc] If provided, `block.call` is expected to return a `Binding`
99
+ # (i.e. last expression in the block must be `binding`).
100
+ # Local variables from this binding will be used to define methods.
101
+ #
102
+ # @return [self] The object itself, now with the newly defined methods.
103
+ #
104
+ # @raise [ArgumentError] If an argument is not a Hash or a "hashy" Array.
105
+ # @raise [ArgumentError] If a key is not a valid method name.
106
+ #
107
+ # @example Defining with a Hash
108
+ # # let(...) === Object.new.define(...)
109
+ #
110
+ # person = let(name: "Alice", age: 30)
111
+ # person.name # => "Alice"
112
+ # person.age = 31
113
+ # person.age # => 31
114
+ #
115
+ # @example Defining with a "hashy" Array
116
+ # config = let([[:host, "localhost"], [:port, 8080]])
117
+ # config.host # => "localhost"
118
+ #
119
+ # @example Defining with a block
120
+ # user = let do
121
+ # username = "bob"
122
+ # active = true
123
+ # binding # Important: makes local variables available
124
+ # end
125
+ #
126
+ # user.username # => "bob"
127
+ # user.active? # This won't define active? automatically, but user.active
128
+ #
129
+ # @example Nested definitions
130
+ # settings = let(
131
+ # database: { adapter: "sqlite3", pool: 5 },
132
+ # logging: [[:level, "info"], [:file, "/var/log/app.log"]]
133
+ # )
134
+ # settings.database.adapter # => "sqlite3"
135
+ # settings.logging.level # => "info"
136
+ #
137
+ # @example Combining arguments
138
+ # complex = let({id: 1}, [[:type, "example"]]) do
139
+ # description = "A complex object"
140
+ # status = :new
141
+ # binding
142
+ # end
143
+ #
144
+ # complex.id # => 1
145
+ # complex.type # => "example"
146
+ # complex.description # => "A complex object"
147
+ def define(*args, &block)
148
+ # Stores all key-value pairs to be defined
149
+ variable = {}
150
+
151
+ # Process Hashes and "hashy" Arrays first
152
+ args.each do |arg|
153
+ case arg
154
+ when Hash
155
+ variable.merge!(arg)
156
+ when Array
157
+ raise ArgumentError, "Array should be Hash like." unless arg.hashy?
158
+ variable.merge!(arg.to_h)
159
+ else
160
+ raise ArgumentError, "Invalid argument type: #{arg.class}"
161
+ end
162
+ end
163
+
164
+ # Process local variables from the block
165
+ if block_given? # If provided
166
+ binding = block.call # The block is expected to return its binding.
167
+ raise ArgumentError, "Block must return a Binding object." unless binding.is_a?(Binding)
168
+
169
+ variable.merge!(binding.variables)
170
+ end
171
+
172
+ # Define getters and setters and store values
173
+ variable.each do |name, value|
174
+ name = name.to_s.to_sym
175
+ raise ArgumentError, "Invalid name: #{name}" unless name.to_s.valid_name?(target: :method)
176
+
177
+ # Recursively define for nested Hashes or "hashy" Arrays
178
+ if value.is_a? Hash
179
+ value = Object.new.define(value)
180
+ elsif value.is_a?(Array) && value.hashy?
181
+ value = Object.new.define(value.to_h)
182
+ end
183
+
184
+ # Store the original value in an instance variable
185
+ instance_variable_set("@#{name}", value)
186
+
187
+ define_singleton_method(name) do
188
+ instance_variable_get("@#{name}")
189
+ end
190
+
191
+ define_singleton_method("#{name}=") do |value|
192
+ instance_variable_set("@#{name}", value)
193
+ end
194
+ end
195
+
196
+ self
197
+ end
198
+ end
@@ -0,0 +1,338 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Extends the base Object class with utility methods for control flow,
4
+ # introspection, and data manipulation.
5
+ class Object
6
+ # If the object is truthy (neither `nil` nor `false`), yields the object
7
+ # itself to the given block.
8
+ #
9
+ # @example
10
+ # 123.if { |x| puts "Number is #{x}" } # Output: Number is 123
11
+ # nil.if { |x| puts "This won't print: #{x}" } # Nothing
12
+ # false.if { |x| puts "This also won't print: #{x}" } # Nothing
13
+ # "hello".if { |s| s.upcase } # => "HELLO"
14
+ #
15
+ # @yield [self] Gives `self` to the block if `self` is truthy.
16
+ # @return [Object, nil] The result of the block if executed, otherwise `nil`.
17
+ def if
18
+ self && yield(self)
19
+ end
20
+
21
+ # If the object is truthy, yields the object to the block and then returns `self`.
22
+ # This is similar to {Object#if}, but always returns `self`, making it useful
23
+ # for chaining or conditional side effects.
24
+ #
25
+ # @example
26
+ # value = "test".tif { |s| puts "Processing #{s}" } # Output: Processing test
27
+ # puts value # Output: test
28
+ #
29
+ # data = [1, 2, 3]
30
+ # data.tif { |arr| arr.pop if arr.size > 2 }
31
+ # puts data.inspect # Output: [1, 2] (if data was not empty)
32
+ #
33
+ # @yield [self] Gives `self` to the block if `self` is truthy.
34
+ # @return [self] The original object.
35
+ def tif
36
+ tap { self && yield(self) }
37
+ end
38
+
39
+ # If the object is falsy (`nil` or `false`), yields the object itself to the
40
+ # given block.
41
+ #
42
+ # @example
43
+ # nil.or { |x| puts "Object was nil" } # Output: Object was nil
44
+ # false.or { |x| puts "Object was false" } # Output: Object was false
45
+ # 123.or { |x| puts "This won't print: #{x}" } # Output: (nothing)
46
+ # "".or { |s| s + "default" } # => "" (empty string is truthy)
47
+ #
48
+ # @yield [self] Gives `self` to the block if `self` is falsy.
49
+ # @return [Object, nil] The result of the block if executed, otherwise `self`.
50
+ # Note that `self || yield(self)` returns `self` if truthy, and `yield(self)` if falsy.
51
+ def or
52
+ self || yield(self)
53
+ end
54
+
55
+ # If the object is falsy, yields the object to the block and then returns `self`.
56
+ # This is similar to {#or}, but always returns the original object.
57
+ #
58
+ # @see Object#default An alias for this method.
59
+ #
60
+ # @example
61
+ # result = nil.tor { |x| puts "Handled nil case" } # Output: Handled nil case
62
+ # puts result.inspect # Output: nil
63
+ #
64
+ # user_input = "".empty? && nil # user_input is nil
65
+ # user_input.tor { puts "This will print" }
66
+ #
67
+ # @yield [self] Gives `self` to the block if `self` is falsy.
68
+ # @return [self] The original object.
69
+ def tor
70
+ tap { self || yield(self) }
71
+ end
72
+
73
+ # If `x` is case-equal (`===`) to `self`, yields to the block and returns `self`.
74
+ # This is useful for chaining checks, mimicking a case-like structure.
75
+ #
76
+ # @param x [Object] The object to compare against `self` using `===`. Defaults to `self`,
77
+ # which means `self === self` is checked if no argument is passed (effectively
78
+ # yielding if `self` is matched by itself, which is always true for typical objects).
79
+ # @yield If `x === self` is true.
80
+ # @return [self] The original object.
81
+ #
82
+ # @example
83
+ # status_code = 200
84
+ # status_code
85
+ # .when(200..299) { puts "Yay!" } # Only one to run
86
+ # .when(400..499) { puts "Client Error!" }
87
+ # .when(500..599) { puts "Server Error!" }
88
+ #
89
+ # # Only `Array`'s block runs here
90
+ # [1,2,3,4,5,6]
91
+ # .when(Array) { puts "It's an array" }
92
+ # .when(String) { puts "It's a string" }
93
+ #
94
+ # # Chaining `default` (alias for `tor`)
95
+ # "hello"
96
+ # .when(Integer) { |n| puts "#{n} + 1 = #{n + 1}" }
97
+ # .default { puts "This won't run either" }
98
+ #
99
+ # # If value was nil:
100
+ # # nil.when(Integer){}.default { puts "this will print" }
101
+ def when(x = self)
102
+ tap { yield(self) if x === self }
103
+ end
104
+
105
+ # Just like [Object#when], but propagates the values of the blocks that execute.
106
+ # This method can be chained because it returns `self` if no block is executed.
107
+ #
108
+ # @param x [Object] The object to compare against `self` using `===`. Default is `self`.
109
+ # @yield If `x === self` is true.
110
+ # @return [Object | self] The result of the block if executed, otherwise `self`.
111
+ #
112
+ # @example
113
+ # 42.iff(Integer) { |x| x * 2 } # => 84
114
+ # "foo".iff(Float) { |n| n / 2 }.iff(String) { |s| s.upcase } # => "FOO"
115
+ def iff(x = self)
116
+ if x === self
117
+ yield(self)
118
+ else
119
+ self
120
+ end
121
+ end
122
+
123
+ # Asserts a condition about `self`.
124
+ # If a block is given, it asserts that the block, when called with `self`, returns a truthy value.
125
+ # If no block is given, it asserts that `n === self` is true.
126
+ # If the assertion fails, it performs a non-local exit by `raise`.
127
+ #
128
+ # @param `n` ([Object]) The object to compare with `self` if no block is given. Defaults to `self`.
129
+ # @param `error` ([Class]) The class of error to raise if the assertion fails.
130
+ # Defaults to `StandardError`.
131
+ # @yield [self] Optional block whose truthiness is asserted.
132
+ # @return [self] The original object if assertion passes.
133
+ # @throw `error` (or a related symbol) if the assertion fails.
134
+ #
135
+ # @example
136
+ # 5.assert(Integer) # Passes
137
+ # "string".assert(error: ArgumentError) { |s| s.length > 5 } # Passes
138
+ def assert(n = self, error: StandardError)
139
+ tap { (block_given? ? yield(self) : (n === self)) || raise(error) }
140
+ end
141
+
142
+ # Prints the `inspect` representation of `self` to standard output.
143
+ # Returns `self`, allowing for chaining.
144
+ #
145
+ # @example
146
+ # [1, 2, 3]
147
+ # .show # prints [1, 2, 3]
148
+ # .map { |x| x * 2 }
149
+ # .show # print [2, 4, 6]
150
+ def show
151
+ tap { puts inspect }
152
+ end
153
+ end
154
+
155
+ alias m method
156
+ alias default tor
157
+ alias env binding
158
+
159
+ # Extends the Integer class with methods for time durations and comparisons.
160
+ class Integer
161
+ # Represents the integer as a duration in seconds.
162
+ # @return [Integer] self
163
+ def second; self end
164
+
165
+ # Converts the integer (assumed to be minutes) into seconds.
166
+ # @return [Integer] Total seconds.
167
+ def minute; self * 60.second end
168
+
169
+ # Converts the integer (assumed to be hours) into seconds.
170
+ # @return [Integer] Total seconds.
171
+ def hour; self * 60.minute end
172
+
173
+ # Converts the integer (assumed to be days) into seconds.
174
+ # @return [Integer] Total seconds.
175
+ def day; self * 24.hour end
176
+
177
+ # Converts the integer (assumed to be weeks) into seconds.
178
+ # @return [Integer] Total seconds.
179
+ def week; self * 7.day end
180
+
181
+ # Converts the integer (assumed to be months, approximated as 4 weeks) into seconds.
182
+ # @return [Integer] Total seconds.
183
+ def month; self * 4.week end
184
+
185
+ # Converts the integer (assumed to be years) into seconds.
186
+ # @return [Integer] Total seconds.
187
+ def year; self * 12.month end
188
+
189
+ # Returns the minimum of `self` and the given numeric values.
190
+ #
191
+ # @param `xs` [Array<Numeric>] Zero or more numeric values to compare against.
192
+ # @return [Numeric] The smallest value among `self` and `xs`.
193
+ # @example
194
+ # 5.min(3, 4, 2) # => 2
195
+ # -1.min(0, 1) # => -1
196
+ # 10.min(20) # => 10
197
+ def min(*xs)
198
+ xs.push(self)
199
+ xs.min
200
+ end
201
+
202
+ # Returns the maximum of `self` and the given numeric values.
203
+ #
204
+ # @param `xs` [Array<Numeric>] Zero or more numeric values to compare against.
205
+ # @return [Numeric] The largest value among `self` and `xs`.
206
+ # @example
207
+ # 5.max(3, 4, 11) # => 11
208
+ # -1.max(-5, 0) # => 0
209
+ # 10.max(2) # => 10
210
+ def max(*xs)
211
+ xs.push(self)
212
+ xs.max
213
+ end
214
+ end
215
+
216
+ # Extends the String class with methods for system execution and output.
217
+ class String
218
+ # Executes the string as a system command.
219
+ # Allows for substituting arguments into the string using `sprintf` format.
220
+ #
221
+ # @param args [Hash] A hash of arguments to be interpolated into the command string.
222
+ # The keys are used in `sprintf`-style formatting (e.g., `%{key}`).
223
+ # @return [Boolean, nil] Returns `true` if the command was found and ran successfully (exit status 0),
224
+ # `false` if the command returned a non-zero exit status, and `nil` if command execution failed
225
+ # (e.g., command not found).
226
+ # @example
227
+ # "ls -l %{dir}".exec(dir: "/tmp")
228
+ # "echo 'Hello World'".exec # => true (prints "Hello World")
229
+ # "ruby -e 'exit 1'".exec # => false
230
+ def exec(args = {})
231
+ system(self % args)
232
+ end
233
+
234
+ # Prints the string to the console, followed by a newline.
235
+ def echo
236
+ tap { puts self }
237
+ end
238
+ end
239
+
240
+ # Extends the Array class with methods for accessing elements.
241
+ class Array
242
+ # Returns the rest of the array (all elements except the first).
243
+ # Returns an empty array if the original array has 0 or 1 element.
244
+ #
245
+ # @return [Array] A new array containing all elements except the first.
246
+ # @example
247
+ # [1, 2, 3, 4].rest # => [2, 3, 4]
248
+ # [1].rest # => []
249
+ # [].rest # => []
250
+ def rest
251
+ drop 1
252
+ end
253
+
254
+ # Simplifies the array: if it contains exactly one element, returns that element.
255
+ # Otherwise, returns the array itself.
256
+ #
257
+ # @return [Object, Array] The single element if array size is 1, otherwise `self`.
258
+ # @example
259
+ # [42].simplify # => 42
260
+ # ["hello"].simplify # => "hello"
261
+ # [1, 2].simplify # => [1, 2]
262
+ # [].simplify # => []
263
+ def simplify
264
+ size == 1 ? fetch(0) : self
265
+ end
266
+
267
+ # Defines methods `second`, `third`, ..., `tenth` for accessing elements or sub-arrays.
268
+ # Each method `arr.Nth(n=1)` (e.g., `arr.third` or `arr.third(2)`):
269
+ # - Skips `(index_of_Nth - 1)` elements (e.g., for `third`, skips 2 elements).
270
+ # - Takes `n` elements from that point.
271
+ # - Simplifies the result using {#simplify}.
272
+ #
273
+ # @example
274
+ # arr = [10, 20, 30, 40, 50, 60]
275
+ # arr.second # => 20 (drops 1, takes 1, simplifies)
276
+ # arr.third # => 30 (drops 2, takes 1, simplifies)
277
+ # arr.second(2) # => [20, 30] (drops 1, takes 2, simplifies)
278
+ # arr.fourth(3) # => [40, 50, 60] (drops 3, takes 3, simplifies)
279
+ # arr.sixth # => 60
280
+ # arr.seventh # => nil (if out of bounds and simplified from empty array or single nil)
281
+ # ["a","b"].third # => nil
282
+ %i[second third fourth fifth sixth seventh eighth ninth tenth]
283
+ .zip(1..) # 1-based index for human-readable Nth
284
+ .each do |method_name, index|
285
+ define_method(method_name) do |n = 1|
286
+ # `index` is 1 for 'second', 2 for 'third', etc.
287
+ # So, for 'second' (index 1), drop 1. For 'third' (index 2), drop 2.
288
+ drop(index).take(n).simplify
289
+ end
290
+ end
291
+ end
292
+
293
+ # Extends the Symbol class for functional programming patterns.
294
+ class Symbol
295
+ # Creates a Proc that functions as a curried instance method for objects
296
+ # It is possible to supply a block (to the instance method) for later use.
297
+ # This method allows for partial application of instance methods.
298
+ #
299
+ # @param `args` [Array<Object>] Arguments to be pre-supplied to the method.
300
+ # @param `block` [Proc] A block to be pre-supplied to the method.
301
+ # @return [Proc] A lambda that expects a receiver object and any further arguments.
302
+ #
303
+ # @example
304
+ # plus_2 = :+.with(2)
305
+ # plus_2.call(5) # => 7
306
+ #
307
+ # mapper = :map.with { |x| x * x }
308
+ # data = [1, 2, 3]
309
+ # mapper.call(data) # => [1, 4, 9]
310
+ #
311
+ # # Combining with map
312
+ # [1, 2, 3].map(&:+.with(10)) # => [11, 12, 13]
313
+ def with(*args, &block)
314
+ ->(o, *more) { o.send(self, *args, *more, &block) }
315
+ end
316
+ end
317
+
318
+ # Extends the Thread class.
319
+ class Thread
320
+ # Returns an array of all live threads except the current thread.
321
+ # A static class method.
322
+ #
323
+ # @return [Array<Thread>] An array of other live Thread objects.
324
+ #
325
+ # @example
326
+ # thread1 = Thread.new { sleep 1 }
327
+ # thread2 = Thread.new { sleep 1 }
328
+ # # In the main thread:
329
+ # Thread.others # => might include thread1, thread2 (depending on timing)
330
+ def self.others
331
+ Thread.list.reject { |x| x == Thread.current }
332
+ end
333
+ end
334
+
335
+ # Extends the Hash class with an alias
336
+ class Hash
337
+ alias flip invert
338
+ end
@@ -0,0 +1,149 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Monkey patches for use in `Lambda`
4
+ class Array
5
+ # Allows for this:
6
+ # - `[1, 2, 3].op(:+) # => 6`
7
+ # - `[1, 2, 3].op(:-) # => -4`
8
+ #
9
+ # Not the same as [reduce] or [inject]
10
+ # The `method` argument is supposed to be an instance method
11
+ # for the first element of the array.
12
+ def op(method)
13
+ first.send(method, slice(1..))
14
+ end
15
+
16
+ # Same as [Array#pop] but returns `default` if the array is empty.
17
+ def qoq(default = nil)
18
+ empty? ? default : pop
19
+ end
20
+ end
21
+
22
+ # A Lambda object (`_`) is building block for anonymous functions.
23
+ # For instance, (`_ + _`) represents f(x,y) = x + y
24
+ # (`_ * 2`) represents f(x) = 2x.
25
+ # You can use any method or operator on a `_`, and it will work:
26
+ # - `[1, 2].map(&_.succ ** 3) => [8, 27]`
27
+ # - `[[1,2], [3,4]].map(&_.sum / 2) => [1.5, 3.5]`
28
+ #
29
+ # The `lift` method allows a `_` to be used like so:
30
+ # - `[[1, 2], [3, 4]].map(&(_ + _).lift) => [3, 7]`
31
+ #
32
+ # i.e. it treats the first arg (that is an array) as the actual arguments to be used
33
+ # WARN: "lift" state is contagious (e.g. `(_ + _.lift) <=> (_ + _).lift`).
34
+ #
35
+ # You can similarly use [unlift] to convert a lifted `_` back to a normal.
36
+ # [support_lambda] will allow for a _ to be used as the second operand (e.g. `2 - _`)
37
+ #
38
+ class Lambda < BasicObject
39
+ class Arg; end
40
+
41
+ class Block
42
+ def initialize(block)
43
+ @proc = block
44
+ end
45
+
46
+ def to_proc
47
+ @proc
48
+ end
49
+ end
50
+
51
+ class Function
52
+ def initialize(method)
53
+ @method = method
54
+ end
55
+
56
+ def to_proc
57
+ @method.to_proc
58
+ end
59
+ end
60
+
61
+ def __repr__; @__repr__ end
62
+ def __tuply__; @__tuply__ end
63
+
64
+ def initialize(repr = [Arg.new], *args)
65
+ @__tuply__ = args.include?(:lift)
66
+ @__repr__ = repr
67
+
68
+ return if repr != :lift
69
+
70
+ @__repr__ = [Arg.new]
71
+ @__tuply__ = true
72
+ end
73
+
74
+ def to_proc
75
+ ::Proc.new do |*args|
76
+ args = args.first if @__tuply__ && args.first.is_a?(::Array)
77
+ stack = []
78
+ index = 0
79
+
80
+ @__repr__.each do |element|
81
+ case element
82
+ when Arg
83
+ stack << args[index]
84
+ index += 1
85
+ when Function
86
+ f = element.to_proc
87
+ operands, block = stack.partition { |e| !e.is_a?(Block) }
88
+ empty = ::Object.new
89
+ xy = [operands.qoq(empty), operands.qoq(empty)]
90
+ .reject { |x| x.equal?(empty) }.reverse
91
+ stack = operands
92
+
93
+ if block.empty?
94
+ stack << f.call(*xy)
95
+ else
96
+ stack << f.call(*xy, &block[0].to_proc)
97
+ end
98
+ else
99
+ stack << element
100
+ end
101
+ end
102
+
103
+ stack.first
104
+ end
105
+ end
106
+
107
+ # Temporary wrapper for @__repr__
108
+ class Repr
109
+ attr_reader :repr
110
+
111
+ def initialize(repr)
112
+ @repr = repr
113
+ end
114
+ end
115
+
116
+ def method_missing(method, *args, &block)
117
+ extra = [Function.new(method)]
118
+ extra.unshift Block.new(block) if block
119
+ tuply = @__tuply__
120
+
121
+ args.map do |arg|
122
+ if arg.is_a?(::Lambda)
123
+ tuply ||= arg.__tuply__
124
+ Repr.new(arg.__repr__)
125
+ else
126
+ arg
127
+ end
128
+ end.each_with_index do |arg, i|
129
+ if arg.is_a? Repr
130
+ args.insert(i, *arg.repr)
131
+ args.delete_at(i + arg.repr.size)
132
+ end
133
+ end
134
+
135
+ if tuply
136
+ ::Lambda.new(@__repr__ + args + extra, :lift)
137
+ else
138
+ ::Lambda.new(@__repr__ + args + extra)
139
+ end
140
+ end
141
+
142
+ def lift
143
+ ::Lambda.new(@__repr__, :lift)
144
+ end
145
+
146
+ def unlift
147
+ @__tuply__ ? ::Lambda.new(@__repr__) : self
148
+ end
149
+ end
metadata ADDED
@@ -0,0 +1,44 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: emanlib
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - emanrdesu
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: Convenience methods and features, mostly done through monkey patching.
13
+ email:
14
+ - janitor@waifu.club
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - lib/emanlib.rb
20
+ - lib/patch/define.rb
21
+ - lib/patch/foobar.rb
22
+ - lib/patch/lambda.rb
23
+ homepage: https://github.com/emanrdesu/lib
24
+ licenses:
25
+ - GPL-3.0-only
26
+ metadata: {}
27
+ rdoc_options: []
28
+ require_paths:
29
+ - lib
30
+ required_ruby_version: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: 3.0.0
35
+ required_rubygems_version: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ requirements: []
41
+ rubygems_version: 3.6.7
42
+ specification_version: 4
43
+ summary: emanrdesu's personal library
44
+ test_files: []