funcml-core 0.9.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/LICENSE.txt +21 -0
- data/README.md +101 -0
- data/Rakefile +12 -0
- data/lib/funcml-core/patch/array.rb +9 -0
- data/lib/funcml-core/patch/false.rb +7 -0
- data/lib/funcml-core/patch/float.rb +7 -0
- data/lib/funcml-core/patch/funcs/cast.rb +21 -0
- data/lib/funcml-core/patch/funcs/cryptography.rb +49 -0
- data/lib/funcml-core/patch/funcs/dictionary.rb +27 -0
- data/lib/funcml-core/patch/funcs/encoding.rb +16 -0
- data/lib/funcml-core/patch/funcs/list.rb +57 -0
- data/lib/funcml-core/patch/funcs/math.rb +64 -0
- data/lib/funcml-core/patch/funcs/time.rb +28 -0
- data/lib/funcml-core/patch/hash.rb +165 -0
- data/lib/funcml-core/patch/integer.rb +7 -0
- data/lib/funcml-core/patch/nil.rb +7 -0
- data/lib/funcml-core/patch/string.rb +25 -0
- data/lib/funcml-core/patch/symbol.rb +7 -0
- data/lib/funcml-core/patch/true.rb +7 -0
- data/lib/funcml-core/version.rb +5 -0
- data/lib/funcml-core.rb +30 -0
- data/sig/funcml.rbs +4 -0
- metadata +69 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 229952b1c47eb4c17e0cc4209f1e0226306010425713c4d1ed9d1b6a4bd0adfb
|
4
|
+
data.tar.gz: 7435707d489decc839bad4c3697c72534fdc7fa465b44049c9e52bd4a1899e47
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 68458c9df2ec863e3030c35b082405e39c71dba66576f33f5306f0046aedd674eae5720e491ad331880f7e89e04eded00e6fc9d77b51db6af86ea8f678a2be4d
|
7
|
+
data.tar.gz: '08d321d32f188aad017170d3df8c8c42b45b3ad40939a3ec20294464679ecee0b8875bfde73f496ff7bac543fb6c536c1c327c24bcbeeebdfa92aa925525c6c2'
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2024 Mimopotato
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
# funcml-core
|
2
|
+
|
3
|
+
Funcml-core extends the standard Ruby classes (String, Integer, Hash, Array, etc.) to provide a framework for data structures. This framework makes it possible to perform mutations on data structures and is at the heart of the funcml-cli project.
|
4
|
+
|
5
|
+
Connected to funcml-cli, funcml-core can ingest JSON and YAML files and apply prepared mutations. Funcml-cli is part of a wider project, Karist, which aims to simplify the management of Kubernetes manifests using YAML.
|
6
|
+
|
7
|
+
However, funcml-core can be used in just about any project thanks to its super-simple, documented AP
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Ruby superior to >=2.7 is required due to pattern-matching usage.
|
12
|
+
|
13
|
+
```bash
|
14
|
+
gem install funcml-core
|
15
|
+
```
|
16
|
+
|
17
|
+
## Usage
|
18
|
+
|
19
|
+
Data structures are mutated using the #mutate function, which is loaded with the funcml-core library. It is then possible to launch a mutation on any object by specifying the mutation functions as arguments to the #mutate method.
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
require "funcml-core"
|
23
|
+
|
24
|
+
simple_hash = {key: "$value"}
|
25
|
+
simple_hash.mutate(value: ["a", "great", "example"])
|
26
|
+
|
27
|
+
# { key: ["a", "great", "example"] }
|
28
|
+
```
|
29
|
+
|
30
|
+
Many mutations are supported. Funcml-core includes:
|
31
|
+
|
32
|
+
* variables support
|
33
|
+
* Time functions
|
34
|
+
* Mathematics
|
35
|
+
* Hash/dictionaries manipulations
|
36
|
+
* Cryptography (encryption/decryption with AES)
|
37
|
+
* Encoding (base64, SHA1, SHA256, MD5...)
|
38
|
+
* External files inclusion
|
39
|
+
* Arrays/lists manipulations
|
40
|
+
* And many others !
|
41
|
+
|
42
|
+
Read the doc at [Funcml.org](https://funcml.org) to learn more about what funcml-core supports.
|
43
|
+
|
44
|
+
## How it works ?
|
45
|
+
|
46
|
+
A #mutate method is implemented for each Ruby object. This method recursively applies the various mutations specified as arguments to the first call.
|
47
|
+
|
48
|
+
Funcml-core exposes mutation functions in two ways: either through additional hash keys, determined by the prefix "_", or through strings beginning with "$".
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
struct = {
|
52
|
+
mutations: {
|
53
|
+
first: "hello",
|
54
|
+
second: "world",
|
55
|
+
third: "!"
|
56
|
+
},
|
57
|
+
key: {
|
58
|
+
value: {
|
59
|
+
_if: [
|
60
|
+
{eq: [{_last: '$mutations'}, "!"]}
|
61
|
+
],
|
62
|
+
_concat: {
|
63
|
+
items: [
|
64
|
+
'$mutations.first',
|
65
|
+
'$mutations.second',
|
66
|
+
'$mutations.third'
|
67
|
+
],
|
68
|
+
sep: " "
|
69
|
+
}
|
70
|
+
}
|
71
|
+
}
|
72
|
+
}
|
73
|
+
|
74
|
+
struct[:key].mutate(struct)
|
75
|
+
# {value: "hello world !"}
|
76
|
+
```
|
77
|
+
|
78
|
+
The implementation of funcml-core is somewhat similar in idea to LISP and you will no doubt be able to find some similarities.
|
79
|
+
|
80
|
+
The functions are applied recursively: when _concat is called, all items are first mutated before _concat's own evaluation. This is relatively similar to LISP languages where "inside the code is the first part executed".
|
81
|
+
|
82
|
+
## Development
|
83
|
+
|
84
|
+
All functions are tested using original Ruby data structures. Take a look in the test folder to see all the funcml-core possibilities.
|
85
|
+
|
86
|
+
|
87
|
+
You can run the tests with the following command (test-unit).
|
88
|
+
|
89
|
+
```
|
90
|
+
TESTOPTS="-v" rake test
|
91
|
+
```
|
92
|
+
|
93
|
+
Funcml-core only extends Ruby's native classes. Under no circumstances does funcml-core handle marshal, unmarshal or file read/write operations (see funcml-cli for these parts).
|
94
|
+
|
95
|
+
## Contributing
|
96
|
+
|
97
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/mimopotato/funcml-core.
|
98
|
+
|
99
|
+
## License
|
100
|
+
|
101
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Hash
|
4
|
+
def _string(mutations)
|
5
|
+
self.fetch(:_string).then do |obj|
|
6
|
+
obj.mutate(mutations).to_s
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def _int(mutations)
|
11
|
+
self.fetch(:_int).then do |obj|
|
12
|
+
obj.mutate(mutations).to_i
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def _float(mutations)
|
17
|
+
self.fetch(:_float).then do |obj|
|
18
|
+
obj.mutate(mutations).to_f
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "digest"
|
3
|
+
require "openssl"
|
4
|
+
|
5
|
+
class Hash
|
6
|
+
include Funcml
|
7
|
+
|
8
|
+
def _sha1sum(mutations)
|
9
|
+
self.fetch(:_sha1sum).then do |value|
|
10
|
+
return Digest::SHA1.hexdigest(value.mutate(mutations))
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def _sha256sum(mutations)
|
15
|
+
self.fetch(:_sha256sum).then do |value|
|
16
|
+
return Digest::SHA256.hexdigest(value.mutate(mutations))
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# source https://gist.github.com/wteuber/5318013
|
21
|
+
def _encryptAES(mutations)
|
22
|
+
self.fetch(:_encryptAES).then do |blk|
|
23
|
+
case blk
|
24
|
+
in {data: data, encryptionKey: encryption_key}
|
25
|
+
cipher = OpenSSL::Cipher.new("aes-256-cbc").encrypt
|
26
|
+
cipher.key = Digest::MD5.hexdigest(encryption_key.mutate(mutations))
|
27
|
+
s = cipher.update(data.mutate(mutations)) + cipher.final
|
28
|
+
s.unpack('H*')[0].upcase
|
29
|
+
else
|
30
|
+
raise MissingEncryptionKeyException, "encryptionKey field missing in #{self.to_s}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# source https://gist.github.com/wteuber/5318013
|
36
|
+
def _decryptAES(mutations)
|
37
|
+
self.fetch(:_decryptAES).then do |blk|
|
38
|
+
case blk
|
39
|
+
in {data: data, encryptionKey: encryption_key}
|
40
|
+
cipher = OpenSSL::Cipher.new("aes-256-cbc").decrypt
|
41
|
+
cipher.key = Digest::MD5.hexdigest(encryption_key.mutate(mutations))
|
42
|
+
s = [data.mutate(mutations)].pack('H*').unpack('C*').pack('c*')
|
43
|
+
cipher.update(s) + cipher.final
|
44
|
+
else
|
45
|
+
raise MissingEncryptionKeyException, "encryptionKey field missing in #{self.to_s}"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Hash
|
4
|
+
def _keys(mutations)
|
5
|
+
self.fetch(:_keys).mutate(mutations).then do |elems|
|
6
|
+
return elems.map do |elem|
|
7
|
+
if elem.is_a?(Hash)
|
8
|
+
elem.keys
|
9
|
+
else
|
10
|
+
elem
|
11
|
+
end
|
12
|
+
end.flatten
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def _values(mutations)
|
17
|
+
self.fetch(:_values).mutate(mutations).then do |elems|
|
18
|
+
return elems.map do |elem|
|
19
|
+
if elem.is_a?(Hash)
|
20
|
+
elem.values
|
21
|
+
else
|
22
|
+
elem
|
23
|
+
end
|
24
|
+
end.flatten
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "base64"
|
3
|
+
|
4
|
+
class Hash
|
5
|
+
def _base64encode(mutations)
|
6
|
+
self.fetch(:_base64encode, "").then do |value|
|
7
|
+
return Base64.encode64(value.mutate(mutations)).strip
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def _base64decode(mutations)
|
12
|
+
self.fetch(:_base64decode, "").then do |value|
|
13
|
+
return Base64.decode64(value.mutate(mutations)).strip
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Hash
|
4
|
+
include Funcml
|
5
|
+
|
6
|
+
def _first(mutations)
|
7
|
+
self.fetch(:_first).mutate(mutations).then do |elems|
|
8
|
+
return elems.first
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def _last(mutations)
|
13
|
+
self.fetch(:_last).mutate(mutations).then do |elems|
|
14
|
+
if elems.is_a?(Hash)
|
15
|
+
return elems.values.last
|
16
|
+
else
|
17
|
+
return elems.last
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def _tail(mutations)
|
23
|
+
self.fetch(:_tail).mutate(mutations).then do |elems|
|
24
|
+
return elems[0].last(elems[1])
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def _head(mutations)
|
29
|
+
self.fetch(:_head).mutate(mutations).then do |elems|
|
30
|
+
return elems[0][0..(elems[1]-1)]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def _reverse(mutations)
|
35
|
+
self.fetch(:_reverse).mutate(mutations).then do |elems|
|
36
|
+
return elems.reverse
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def _uniq(mutations)
|
41
|
+
self.fetch(:_uniq).mutate(mutations).then do |elems|
|
42
|
+
return elems.uniq
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def _index(mutations)
|
47
|
+
self.fetch(:_index).mutate(mutations).then do |elems|
|
48
|
+
return elems[0].index(elems[1])
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def _len(mutations)
|
53
|
+
self.fetch(:_len).then do |elems|
|
54
|
+
elems.mutate(mutations).length
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Hash
|
4
|
+
def _add(mutations)
|
5
|
+
self.fetch(:_add, []).then do |numbers|
|
6
|
+
numbers.mutate(mutations).reduce(:+)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def _sub(mutations)
|
11
|
+
self.fetch(:_sub, []).then do |numbers|
|
12
|
+
numbers.mutate(mutations).reduce(:-)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def _div(mutations)
|
17
|
+
self.fetch(:_div, []).then do |numbers|
|
18
|
+
numbers.mutate(mutations).reduce(:/)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def _mod(mutations)
|
23
|
+
self.fetch(:_mod).then do |numbers|
|
24
|
+
first, second = numbers.mutate(mutations)
|
25
|
+
first % second
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def _mul(mutations)
|
30
|
+
self.fetch(:_mul, []).then do |numbers|
|
31
|
+
numbers.mutate(mutations).reduce(:*)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def _min(mutations)
|
36
|
+
self.fetch(:_min, []).then do |numbers|
|
37
|
+
numbers.mutate(mutations).min
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def _max(mutations)
|
42
|
+
self.fetch(:_max, []).then do |numbers|
|
43
|
+
numbers.mutate(mutations).max
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def _floor(mutations)
|
48
|
+
self.fetch(:_floor).then do |number|
|
49
|
+
number.mutate(mutations).floor
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def _ceil(mutations)
|
54
|
+
self.fetch(:_ceil).then do |number|
|
55
|
+
number.mutate(mutations).ceil
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def _round(mutations)
|
60
|
+
self.fetch(:_round).then do |number|
|
61
|
+
number.mutate(mutations).round
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "time"
|
4
|
+
|
5
|
+
class Hash
|
6
|
+
include Funcml
|
7
|
+
|
8
|
+
def _time(mutations)
|
9
|
+
self.fetch(:_time).then do |blk|
|
10
|
+
_value = blk.fetch(:value)
|
11
|
+
_format = blk.fetch(:format, "%d/%m/%Y %H:%M:%S")
|
12
|
+
|
13
|
+
_value.mutate(mutations).then do |value|
|
14
|
+
return Time.parse(value).strftime(_format.mutate(mutations))
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def _ago(mutations)
|
20
|
+
self.fetch(:_ago).then do |blk|
|
21
|
+
unless blk.is_a?(Integer)
|
22
|
+
raise IncorrectSecondsException, "_ago only supports seconds as value in #{self.to_s}"
|
23
|
+
end
|
24
|
+
|
25
|
+
(Time.now - blk).to_s
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,165 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Hash
|
4
|
+
include Funcml
|
5
|
+
|
6
|
+
def mutate(mutations = {})
|
7
|
+
# dup is here to avoid iteration error on #delete/#merge
|
8
|
+
self.dup.each do |key, value|
|
9
|
+
case key
|
10
|
+
in /^_.*$/
|
11
|
+
# recursive mutation of result from pattern-matched _method
|
12
|
+
# once recursively mutated, we return the final object by guard
|
13
|
+
# clause instead of mutating the current object.
|
14
|
+
return self.send(key, mutations)
|
15
|
+
.mutate(mutations)
|
16
|
+
else
|
17
|
+
# no monkey-patch method found (_ prefix), we mutate anyway
|
18
|
+
# in case of variables or sub-functions.
|
19
|
+
self[key] = value.mutate(mutations)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
self
|
24
|
+
end
|
25
|
+
|
26
|
+
def _if(mutations)
|
27
|
+
_runcond = Proc.new do |cond, mutations|
|
28
|
+
case cond
|
29
|
+
in {in: [needle, haystack]}
|
30
|
+
haystack.mutate(mutations).include?(needle.mutate(mutations))
|
31
|
+
|
32
|
+
in {null: vars}
|
33
|
+
vars.all? {|x| x.mutate(mutations).nil? }
|
34
|
+
|
35
|
+
in {present: vars}
|
36
|
+
vars.all? {|x| !x.mutate(mutations).nil? }
|
37
|
+
|
38
|
+
in {eq: [first, second]}
|
39
|
+
first.mutate(mutations).eql?(second.mutate(mutations))
|
40
|
+
|
41
|
+
in {ne: [first, second]}
|
42
|
+
!first.mutate(mutations).eql?(second.mutate(mutations))
|
43
|
+
|
44
|
+
in {gt: [first, second]}
|
45
|
+
first.mutate(mutations) > second.mutate(mutations)
|
46
|
+
|
47
|
+
in {lt: [first, second]}
|
48
|
+
first.mutate(mutations) < second.mutate(mutations)
|
49
|
+
|
50
|
+
in {ge: [first, second]}
|
51
|
+
first.mutate(mutations) >= second.mutate(mutations)
|
52
|
+
|
53
|
+
in {le: [first, second]}
|
54
|
+
first.mutate(mutations) <= second.mutate(mutations)
|
55
|
+
|
56
|
+
# or should evaluated by top-level if as true or false.
|
57
|
+
in {or: or_conds}
|
58
|
+
or_conds.any? do |cond|
|
59
|
+
_runcond.call(cond, mutations)
|
60
|
+
end
|
61
|
+
|
62
|
+
else
|
63
|
+
raise UnknownConditionException, "#{cond.to_s} is not a valid condition"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
self.fetch(:_if).then do |conditions|
|
68
|
+
all_conditions_state = conditions.all? do |cond|
|
69
|
+
_runcond.call(cond, mutations)
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
if all_conditions_state
|
74
|
+
self.delete(:_if)
|
75
|
+
self.delete(:_else)
|
76
|
+
return self
|
77
|
+
else
|
78
|
+
return self[:_else] # returns nil if nothing has been set.
|
79
|
+
end
|
80
|
+
|
81
|
+
nil
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def _loop(mutations)
|
86
|
+
self.fetch(:_loop).then do |_loop|
|
87
|
+
items = _loop.fetch(:items, []).mutate(mutations)
|
88
|
+
_results = []
|
89
|
+
|
90
|
+
# guard clause related to allowed types
|
91
|
+
unless items.is_a?(Array)
|
92
|
+
raise LoopTypeException, "_loop only supports Array as items"
|
93
|
+
end
|
94
|
+
|
95
|
+
# always mutate all items as they could be variable calling hashes.
|
96
|
+
# when caller is a string, will be callable as $item for String
|
97
|
+
# when caller is a Hash, will be callable as $item.path.to.value for String
|
98
|
+
items.each do |item|
|
99
|
+
_results << _loop[:block]
|
100
|
+
.dup # required to avoid duplicates in code
|
101
|
+
.mutate(mutations.merge(item: item))
|
102
|
+
end
|
103
|
+
|
104
|
+
return _results
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def _until(mutations)
|
109
|
+
_results = []
|
110
|
+
amount = self.fetch(:_until)
|
111
|
+
amount.times do |i|
|
112
|
+
_results << self.dup.select{|k, v| k != :_until}
|
113
|
+
.mutate(mutations.merge(item: i))
|
114
|
+
end
|
115
|
+
|
116
|
+
return _results
|
117
|
+
end
|
118
|
+
|
119
|
+
# _sum takes and mutates all elements in array and
|
120
|
+
# sums them.
|
121
|
+
# eg: {_sum: [1, 2, 3]} = 6
|
122
|
+
def _sum(mutations)
|
123
|
+
self.fetch(:_sum).map do |item|
|
124
|
+
item.mutate(mutations)
|
125
|
+
end.sum
|
126
|
+
end
|
127
|
+
|
128
|
+
# _merge merges a hash found at mutation path with caller hash
|
129
|
+
# and removes the _merge reference.
|
130
|
+
# eg: {key: value, _merge: path.to_mutation } = {key: value, imported_key: value}
|
131
|
+
def _merge(mutations)
|
132
|
+
self.fetch(:_merge).mutate(mutations).then do |result|
|
133
|
+
self.merge!(result).then do
|
134
|
+
self.delete(:_merge)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
self
|
139
|
+
end
|
140
|
+
|
141
|
+
# _concat concatenates items elements as a string with sep as separator.
|
142
|
+
# eg: {_concat: {items: ["a", "b", "c"], sep: " "}} = "a b c"
|
143
|
+
def _concat(mutations)
|
144
|
+
self.fetch(:_concat).then do |concat|
|
145
|
+
return concat.fetch(:items, [])
|
146
|
+
.mutate(mutations)
|
147
|
+
.join(concat.fetch(:sep, "").mutate(mutations))
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def _readfile(mutations)
|
152
|
+
self.fetch(:_readfile).then do |path|
|
153
|
+
File.read(path)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def dig_from_str(path, mutations)
|
158
|
+
path_array_sym = path.split('.').map do |sub|
|
159
|
+
sub.to_sym
|
160
|
+
end
|
161
|
+
|
162
|
+
self.dig(*path_array_sym).mutate(mutations)
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "securerandom"
|
3
|
+
|
4
|
+
class String
|
5
|
+
include Funcml
|
6
|
+
|
7
|
+
def mutate(mutations)
|
8
|
+
if self.start_with?('$')
|
9
|
+
|
10
|
+
return Time.now.to_s if self.eql?('$now')
|
11
|
+
return SecureRandom.uuid if self.eql?('$uuidv4')
|
12
|
+
|
13
|
+
mutations.dig_from_str(self.gsub('$', ''), mutations).then do |value|
|
14
|
+
raise MutationException, "`#{self}` not found in mutations, available mutations: #{mutations.to_s}" if value.nil?
|
15
|
+
return value.mutate(mutations)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
def dig(_)
|
23
|
+
self
|
24
|
+
end
|
25
|
+
end
|
data/lib/funcml-core.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "funcml-core/version"
|
4
|
+
require_relative "funcml-core/patch/hash"
|
5
|
+
require_relative "funcml-core/patch/string"
|
6
|
+
require_relative "funcml-core/patch/nil"
|
7
|
+
require_relative "funcml-core/patch/false"
|
8
|
+
require_relative "funcml-core/patch/true"
|
9
|
+
require_relative "funcml-core/patch/array"
|
10
|
+
require_relative "funcml-core/patch/integer"
|
11
|
+
require_relative "funcml-core/patch/symbol"
|
12
|
+
require_relative "funcml-core/patch/float"
|
13
|
+
|
14
|
+
require_relative "funcml-core/patch/funcs/encoding"
|
15
|
+
require_relative "funcml-core/patch/funcs/cryptography"
|
16
|
+
require_relative "funcml-core/patch/funcs/time"
|
17
|
+
require_relative "funcml-core/patch/funcs/dictionary"
|
18
|
+
require_relative "funcml-core/patch/funcs/list"
|
19
|
+
require_relative "funcml-core/patch/funcs/math"
|
20
|
+
require_relative "funcml-core/patch/funcs/cast"
|
21
|
+
|
22
|
+
module Funcml
|
23
|
+
class Error < StandardError; end
|
24
|
+
class MutationException < Error; end
|
25
|
+
class LoopTypeException < Error; end
|
26
|
+
class UnknownConditionException < Error; end
|
27
|
+
class MissingEncryptionKeyException < Error; end
|
28
|
+
class IncorrectSecondsException < Error; end
|
29
|
+
# Your code goes here...
|
30
|
+
end
|
data/sig/funcml.rbs
ADDED
metadata
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: funcml-core
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.9.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Mimopotato
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-07-14 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: A library that implements mutations on data-structures
|
14
|
+
email:
|
15
|
+
- 173955441+mimopotato@users.noreply.github.com
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- LICENSE.txt
|
21
|
+
- README.md
|
22
|
+
- Rakefile
|
23
|
+
- lib/funcml-core.rb
|
24
|
+
- lib/funcml-core/patch/array.rb
|
25
|
+
- lib/funcml-core/patch/false.rb
|
26
|
+
- lib/funcml-core/patch/float.rb
|
27
|
+
- lib/funcml-core/patch/funcs/cast.rb
|
28
|
+
- lib/funcml-core/patch/funcs/cryptography.rb
|
29
|
+
- lib/funcml-core/patch/funcs/dictionary.rb
|
30
|
+
- lib/funcml-core/patch/funcs/encoding.rb
|
31
|
+
- lib/funcml-core/patch/funcs/list.rb
|
32
|
+
- lib/funcml-core/patch/funcs/math.rb
|
33
|
+
- lib/funcml-core/patch/funcs/time.rb
|
34
|
+
- lib/funcml-core/patch/hash.rb
|
35
|
+
- lib/funcml-core/patch/integer.rb
|
36
|
+
- lib/funcml-core/patch/nil.rb
|
37
|
+
- lib/funcml-core/patch/string.rb
|
38
|
+
- lib/funcml-core/patch/symbol.rb
|
39
|
+
- lib/funcml-core/patch/true.rb
|
40
|
+
- lib/funcml-core/version.rb
|
41
|
+
- sig/funcml.rbs
|
42
|
+
homepage: https://github.com/mimopotato/funcml-lang
|
43
|
+
licenses:
|
44
|
+
- MIT
|
45
|
+
metadata:
|
46
|
+
allowed_push_host: https://rubygems.org
|
47
|
+
homepage_uri: https://github.com/mimopotato/funcml-lang
|
48
|
+
source_code_uri: https://github.com/mimopotato/funcml-core
|
49
|
+
changelog_uri: https://github.com/mimopotato/funcml-core
|
50
|
+
post_install_message:
|
51
|
+
rdoc_options: []
|
52
|
+
require_paths:
|
53
|
+
- lib
|
54
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: 2.7.0
|
59
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0'
|
64
|
+
requirements: []
|
65
|
+
rubygems_version: 3.3.3
|
66
|
+
signing_key:
|
67
|
+
specification_version: 4
|
68
|
+
summary: funcml-core
|
69
|
+
test_files: []
|