funcml-core 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|