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 +7 -0
- data/lib/emanlib.rb +73 -0
- data/lib/patch/define.rb +198 -0
- data/lib/patch/foobar.rb +338 -0
- data/lib/patch/lambda.rb +149 -0
- metadata +44 -0
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
|
data/lib/patch/define.rb
ADDED
@@ -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
|
data/lib/patch/foobar.rb
ADDED
@@ -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
|
data/lib/patch/lambda.rb
ADDED
@@ -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: []
|