exobasic 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: 804b273f3d2c35dbf8f0ff3cde48e05830bd6baf75bc1e448b60b433b30daa29
4
+ data.tar.gz: 46a64b7e4b1dce72249e72e1992f673a577e096b8d568b8eb5696087a8cdceae
5
+ SHA512:
6
+ metadata.gz: 66a06c15c4ea3c3f1862134f3e4a76ab8bf800002d2e35f97a297d66e88e82498b59070e4153001f16afcf4e8de030f3c682768fecfdbd01049552ed5d19401e
7
+ data.tar.gz: c5b9e1be628bb51eedf24fa6374eef6301a82549fa389339ef70c4bbe7749ff1168bd95f7a942945d02b3c4be884231fc7e33a89de9e030c7eb6a7eeee893604
data/README.md ADDED
@@ -0,0 +1,23 @@
1
+ # ExoBasic
2
+
3
+ BasicHelpers: This gem provides basic helpers for a Ruby project.
4
+
5
+ - data: DFS
6
+ - encryption: ECDSA, RSA, HMAC, Password
7
+ - http: JSON HTTP client
8
+ - stats: Average counters
9
+ - time: Timer
10
+ - Likert value
11
+ - Settings
12
+ - String helpers
13
+
14
+ [reference for gem creation: [newgem-template](https://github.com/wycats/newgem-template)]
15
+
16
+ [reference for git init: [github init](https://gist.github.com/seankross/8830032)]
17
+
18
+
19
+ ## Getting started
20
+
21
+ ### Requirements
22
+
23
+ - Ruby 2.5+
@@ -0,0 +1,83 @@
1
+ require 'set'
2
+
3
+ module ExoBasic
4
+ class DFS
5
+ def self.search_helper!(target, curr, path, visited, edges_map)
6
+ children = edges_map[curr]
7
+
8
+ visited.add(curr)
9
+ path.push(curr)
10
+
11
+ if target == curr
12
+ path
13
+ else
14
+ found = false
15
+ if !children.nil?
16
+ for i in children
17
+ if !visited.include?(i)
18
+ found = DFS.search_helper!(target, i, path, visited, edges_map)
19
+ if found
20
+ break
21
+ end
22
+ end
23
+ end
24
+ end
25
+ if !found
26
+ path.pop
27
+ end
28
+ found
29
+ end
30
+ end
31
+
32
+ def self.search(to, from, edges_map)
33
+ DFS.search_helper!(to, from, [], Set.new, edges_map)
34
+ end
35
+
36
+ def self.has_loop_helper(node, edges_map)
37
+ children = edges_map[node]
38
+ visited = Set.new
39
+ found = false
40
+ found_path = []
41
+ if children.nil?
42
+ []
43
+ else
44
+ for i in children
45
+ if !visited.include?(i)
46
+ found = DFS.search_helper!(node, i, [node], visited, edges_map)
47
+ if found
48
+ found_path = found
49
+ break
50
+ end
51
+ end
52
+ end
53
+ found_path
54
+ end
55
+ end
56
+
57
+ def self.has_loop(node, edges_map)
58
+ !DFS.has_loop_helper(node, edges_map).empty?
59
+ end
60
+
61
+ def self.loop_map(edges_map)
62
+ edges_map.keys.map do |node|
63
+ [node, DFS.has_loop_helper(node, edges_map)]
64
+ end
65
+ .to_h
66
+ end
67
+
68
+ def self.get_available(node, others, edges_map)
69
+ to_search = Set.new(others)
70
+ for i in to_search.to_a
71
+ if to_search.include?(i)
72
+ found = DFS.search_helper!(node, i, [], Set.new, edges_map)
73
+ if found
74
+ to_remove = Set.new(found)
75
+ to_search -= to_remove
76
+ end
77
+ end
78
+ end
79
+ to_search
80
+ end
81
+
82
+ end
83
+ end
@@ -0,0 +1,125 @@
1
+ require 'openssl'
2
+ require 'digest'
3
+ require 'base64'
4
+
5
+ #
6
+ # see: https://gist.github.com/ostinelli/1770a93d5c01376728c9
7
+ # https://stackoverflow.com/questions/50077065/
8
+ # get-elliptic-curve-public-key-from-private-key-using-ruby-openssl
9
+ # https://stackoverflow.com/questions/38934615/
10
+ # not-sure-how-to-generate-an-ecdsa-signature-given-a-private-key-and-a-message
11
+ #
12
+
13
+ module ExoBasic
14
+ class ECDSAKeys
15
+ CURVE = 'secp256k1'
16
+
17
+ def self.private_pem_to_s(pem)
18
+ pem.gsub("-----BEGIN EC PRIVATE KEY-----\n", '')
19
+ .gsub("\n-----END EC PRIVATE KEY-----\n", '')
20
+ .gsub("\n", '')
21
+ end
22
+
23
+ def self.s_to_private_pem(b64)
24
+ "-----BEGIN EC PRIVATE KEY-----\n#{b64}-----END EC PRIVATE KEY-----\n"
25
+ end
26
+
27
+ def self.public_pem_to_s(pem)
28
+ pem.gsub("-----BEGIN PUBLIC KEY-----\n", '')
29
+ .gsub("\n-----END PUBLIC KEY-----\n", '')
30
+ .gsub("\n", '')
31
+ end
32
+
33
+ def self.s_to_public_pem(b64)
34
+ "-----BEGIN PUBLIC KEY-----\n#{b64}-----END PUBLIC KEY-----\n"
35
+ end
36
+
37
+ def self.get_key_details(private_key)
38
+ private_hex = private_key.to_der.unpack('H*').first
39
+ private_pem = private_key.to_pem
40
+
41
+ key2 = OpenSSL::PKey::EC.new(private_key.public_key.group)
42
+ key2.public_key = private_key.public_key
43
+
44
+ public_hex = private_key.public_key.to_bn.to_s(16).downcase
45
+ public_pem = key2.to_pem
46
+
47
+ {
48
+ :private_hex => private_hex,
49
+ :private_pem => private_pem,
50
+ :public_hex => public_hex,
51
+ :public_pem => public_pem
52
+ }
53
+ end
54
+
55
+ def self.to_pem(key)
56
+ key.to_pem
57
+ end
58
+
59
+ def self.from_pem(key_pem)
60
+ OpenSSL::PKey::EC.new(key_pem)
61
+ end
62
+
63
+ def self.private_from_hex(private_hex)
64
+ der = [private_hex].pack('H*')
65
+ b64 = Base64.encode64(der)
66
+ pem = ECDSAKeys.s_to_private_pem(b64)
67
+
68
+ ECDSAKeys.from_pem(pem)
69
+ end
70
+
71
+ def self.public_from_hex(public_hex, curve=nil)
72
+ # rebuild key
73
+ c = curve.nil? ? ECDSAKeys::CURVE : curve
74
+ group = OpenSSL::PKey::EC::Group.new(c)
75
+ key = OpenSSL::PKey::EC.new(group)
76
+
77
+ # create point from hex key
78
+ public_key_bn = OpenSSL::BN.new(public_hex, 16)
79
+ public_key = OpenSSL::PKey::EC::Point.new(group, public_key_bn)
80
+ key.public_key = public_key
81
+
82
+ key
83
+ end
84
+
85
+ def self.gen_key(curve=nil)
86
+ c = curve.nil? ? ECDSAKeys::CURVE : curve
87
+
88
+ ECDSAKeys.get_key_details(OpenSSL::PKey::EC.new(c).generate_key!)
89
+ end
90
+
91
+ def self.digest(data)
92
+ OpenSSL::Digest::SHA256.digest(data)
93
+ end
94
+
95
+ def self.sign_message(private_key_pem, data)
96
+ key = ECDSAKeys.from_pem(private_key_pem)
97
+
98
+ digested = ECDSAKeys.digest(data)
99
+ digested_base64 = Base64.encode64(digested).gsub("\n", '')
100
+ signature = key.dsa_sign_asn1(digested)
101
+ signature_base64 = Base64.encode64(signature).gsub("\n", '')
102
+
103
+ signature_base64
104
+ end
105
+
106
+ def self.verify_message(public_key_pem, signature_base64, data)
107
+ key = OpenSSL::PKey::EC.new(public_key_pem)
108
+ signature = Base64.decode64(signature_base64)
109
+ digested_data = ECDSAKeys.digest(data)
110
+
111
+ key.dsa_verify_asn1(digested_data, signature)
112
+ end
113
+
114
+ def self.verify_digested_message(public_key_hex, signature_base64, encoded_digested_data,
115
+ curve=nil)
116
+
117
+ key = ECDSAKeys.public_from_hex(public_key_hex, curve)
118
+ signature = Base64.decode64(signature_base64)
119
+ digested_data = Base64.decode64(encoded_digested_data)
120
+
121
+ key.dsa_verify_asn1(digested_data, signature)
122
+ end
123
+
124
+ end
125
+ end
@@ -0,0 +1,35 @@
1
+ require 'securerandom'
2
+ require 'openssl'
3
+ require 'digest'
4
+ require 'base64'
5
+
6
+ #
7
+ # see: https://docs.github.com/en/developers/webhooks-and-events/securing-your-webhooks
8
+ # https://github.com/square/connect-api-examples/blob/master/connect-examples/v1/ruby/webhooks.rb
9
+ #
10
+
11
+ module ExoBasic
12
+ class HMACKeys
13
+ SIZE = 20
14
+
15
+ def self.gen_key(size=nil)
16
+ s = size.nil? ? HMACKeys::SIZE : s
17
+
18
+ SecureRandom.hex(s)
19
+ end
20
+
21
+ def self.sign_message(key, data)
22
+ signature = OpenSSL::HMAC.hexdigest('SHA256', key, data)
23
+ signature_base64 = Base64.encode64(signature).gsub("\n", '')
24
+
25
+ signature_base64
26
+ end
27
+
28
+ def self.verify_message(key, signature_base64, data)
29
+ # Hash the signatures a second time (to protect against timing attacks) and compare them
30
+ Digest::SHA256.base64digest(HMACKeys.sign_message(key, data)) ==
31
+ Digest::SHA256.base64digest(signature_base64)
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,14 @@
1
+ require 'bcrypt'
2
+
3
+ module ExoBasic
4
+ class PasswordEncryption
5
+ def self.encrypt(str)
6
+ BCrypt::Password.create(str)
7
+ end
8
+
9
+ def self.check_encrypted(encrypted, str)
10
+ BCrypt::Password.new(encrypted) == str
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,123 @@
1
+ require 'openssl'
2
+ require 'base64'
3
+
4
+ module ExoBasic
5
+ class RSAKeys
6
+ BITS = 2048
7
+
8
+ def self.private_pem_to_s(pem)
9
+ pem.gsub("-----BEGIN RSA PRIVATE KEY-----\n", '')
10
+ .gsub("\n-----END RSA PRIVATE KEY-----\n", '')
11
+ .gsub("\n", '')
12
+ end
13
+
14
+ def self.s_to_private_pem(b64)
15
+ "-----BEGIN RSA PRIVATE KEY-----\n#{b64}-----END RSA PRIVATE KEY-----\n"
16
+ end
17
+
18
+ def self.public_pem_to_s(pem)
19
+ pem.gsub("-----BEGIN PUBLIC KEY-----\n", '')
20
+ .gsub("\n-----END PUBLIC KEY-----\n", '')
21
+ .gsub("\n", '')
22
+ end
23
+
24
+ def self.s_to_public_pem(b64)
25
+ "-----BEGIN PUBLIC KEY-----\n#{b64}-----END PUBLIC KEY-----\n"
26
+ end
27
+
28
+ def self.get_key_details(private_key)
29
+ private_key_pem = private_key.to_pem
30
+ private_key_hex = private_key.to_der.unpack('H*').first
31
+
32
+ public_key = private_key.public_key
33
+ public_key_pem = public_key.to_pem
34
+ public_key_hex = public_key.to_der.unpack('H*').first
35
+
36
+ {
37
+ :private_pem => private_key_pem,
38
+ :private_hex => private_key_hex,
39
+ :public_pem => public_key_pem,
40
+ :public_hex => public_key_hex
41
+ }
42
+ end
43
+
44
+ def self.to_pem(key)
45
+ key.to_pem
46
+ end
47
+
48
+ def self.from_pem(key_pem)
49
+ OpenSSL::PKey::RSA.new(key_pem)
50
+ end
51
+
52
+ def self.private_from_hex(private_hex)
53
+ der = [private_hex].pack('H*')
54
+ b64 = Base64.encode64(der)
55
+ pem = RSAKeys.s_to_private_pem(b64)
56
+
57
+ RSAKeys.from_pem(pem)
58
+ end
59
+
60
+ def self.public_from_hex(public_hex)
61
+ der = [public_hex].pack('H*')
62
+ b64 = Base64.encode64(der)
63
+ pem = RSAKeys.s_to_public_pem(b64)
64
+
65
+ RSAKeys.from_pem(pem)
66
+ end
67
+
68
+ def self.gen_key(bits=nil)
69
+ b = bits.nil? ? RSAKeys::BITS : bits
70
+
71
+ RSAKeys.get_key_details(OpenSSL::PKey::RSA.generate(b))
72
+ end
73
+
74
+ def self.sign_message(private_key_pem, data)
75
+ pkey = RSAKeys.from_pem(private_key_pem)
76
+
77
+ signature = pkey.sign_pss('SHA256', data, salt_length: :max, mgf1_hash: 'SHA256')
78
+ signature_base64 = Base64.encode64(signature).gsub("\n", '')
79
+
80
+ signature_base64
81
+ end
82
+
83
+ def self.verify_message(public_key_pem, signature_base64, data)
84
+ pub_key = RSAKeys.from_pem(public_key_pem)
85
+
86
+ signature = Base64.decode64(signature_base64)
87
+ pub_key.verify_pss('SHA256', signature, data, salt_length: :auto, mgf1_hash: 'SHA256')
88
+ end
89
+
90
+ def self.private_encrypt(private_key_pem, data)
91
+ pkey = RSAKeys.from_pem(private_key_pem)
92
+
93
+ encrypted = pkey.private_encrypt(data)
94
+ encrypted_base64 = Base64.encode64(encrypted).gsub("\n", '')
95
+
96
+ encrypted_base64
97
+ end
98
+
99
+ def self.public_encrypt(public_key_pem, data)
100
+ pub_key = RSAKeys.from_pem(public_key_pem)
101
+
102
+ encrypted = pub_key.public_encrypt(data)
103
+ encrypted_base64 = Base64.encode64(encrypted).gsub("\n", '')
104
+
105
+ encrypted_base64
106
+ end
107
+
108
+ def self.private_decrypt(private_key_pem, encrypted_base64)
109
+ pkey = RSAKeys.from_pem(private_key_pem)
110
+
111
+ encrypted = Base64.decode64(encrypted_base64)
112
+ pkey.private_decrypt(encrypted)
113
+ end
114
+
115
+ def self.public_decrypt(public_key_pem, encrypted_base64)
116
+ pub_key = RSAKeys.from_pem(public_key_pem)
117
+
118
+ encrypted = Base64.decode64(encrypted_base64)
119
+ pub_key.public_decrypt(encrypted)
120
+ end
121
+
122
+ end
123
+ end
@@ -0,0 +1,90 @@
1
+ module ExoBasic
2
+ class JsonHttp
3
+ def self.is_success_raw?(code)
4
+ code >= 200 && code < 300
5
+ end
6
+
7
+ def self.is_success?(code)
8
+ begin
9
+ JsonHttp.is_success_raw?(code.to_i)
10
+ rescue
11
+ false
12
+ end
13
+ end
14
+
15
+ def self.mk_http_return
16
+ {
17
+ :http_return => 0,
18
+ :warnings => [],
19
+ :errors => [],
20
+ :body => {}
21
+ }
22
+ end
23
+
24
+ def self.append_http_error(result, code, err)
25
+ result[:http_return] = code
26
+ result[:errors].push(err)
27
+
28
+ true
29
+ end
30
+
31
+ def self.append_http_errors(result, code, errs)
32
+ result[:http_return] = code
33
+ result[:errors] += errs
34
+
35
+ true
36
+ end
37
+
38
+ def self.append_http_body(result, k, v)
39
+ result[:http_return] = 200
40
+ result[:body][k] = v
41
+
42
+ false
43
+ end
44
+
45
+ def self.set_http_body(result, h)
46
+ result[:http_return] = 200
47
+ result[:body] = h
48
+
49
+ false
50
+ end
51
+
52
+ def self.mk_result
53
+ {
54
+ :success => true,
55
+ :errors => [],
56
+ :warnings => [],
57
+ :body => {}
58
+ }
59
+ end
60
+
61
+ def self.append_result_error(result, err)
62
+ result[:success] = false
63
+ result[:errors].push(err)
64
+
65
+ true
66
+ end
67
+
68
+ def self.append_result_errors(result, errs)
69
+ result[:success] = false
70
+ result[:errors] += errs
71
+
72
+ true
73
+ end
74
+
75
+ def self.append_result_body(result, k, v)
76
+ result[:success] = true
77
+ result[:body][k] = v
78
+
79
+ false
80
+ end
81
+
82
+ def self.set_result_body(result, h)
83
+ result[:success] = true
84
+ result[:body] = h
85
+
86
+ false
87
+ end
88
+
89
+ end
90
+ end
@@ -0,0 +1,79 @@
1
+ require 'uri'
2
+ require 'net/https'
3
+ require 'json'
4
+
5
+ module ExoBasic
6
+ class JsonHttpClient
7
+ def self.prepare_http(url)
8
+ if !url.start_with?('http') && !url.start_with?('https')
9
+ url = "https://#{url}"
10
+ end
11
+ uri = URI.parse(url)
12
+ http = Net::HTTP.new(uri.host, uri.port)
13
+ if url.start_with?('https')
14
+ http.use_ssl = true
15
+ end
16
+
17
+ [uri, http]
18
+ end
19
+
20
+ def self.url_with_parameters(url, parameters)
21
+ parameters_prime = parameters.map { |k, v| "#{k}=#{v}" }.join('&')
22
+ if !parameters_prime.empty?
23
+ "#{url}?#{parameters_prime}"
24
+ else
25
+ url
26
+ end
27
+ end
28
+
29
+ def self.do_get_request(url, headers={})
30
+ uri, http = JsonHttpClient.prepare_http(url)
31
+ req = Net::HTTP::Get.new(uri.request_uri)
32
+ headers.each { |k, v| req[k] = v }
33
+
34
+ resp = http.request(req)
35
+
36
+ [resp.code, JSON.load(resp.body)]
37
+ end
38
+
39
+ def self.do_post_request(url, headers={}, payload)
40
+ uri, http = JsonHttpClient.prepare_http(url)
41
+ req = Net::HTTP::Post.new(uri.request_uri)
42
+ headers.each { |k, v| req[k] = v }
43
+ req['Content-Type'] = 'application/json'
44
+ if !payload.nil?
45
+ req.body = payload.to_json
46
+ end
47
+
48
+ resp = http.request(req)
49
+
50
+ [resp.code, JSON.load(resp.body)]
51
+ end
52
+
53
+ def self.do_delete_request(url, headers={})
54
+ uri, http = JsonHttpClient.prepare_http(url)
55
+ req = Net::HTTP::Delete.new(uri.request_uri)
56
+ headers.each { |k, v| req[k] = v }
57
+
58
+ resp = http.request(req)
59
+
60
+ [resp.code, resp.message]
61
+ end
62
+
63
+ def self.get_response_errors(response)
64
+ if response[1].is_a?(Hash) && response[1].key?('errors')
65
+ errs = response[1]['errors']
66
+ if response[1]['errors'].is_a?(Array)
67
+ errs
68
+ elsif response[1]['errors'].is_a?(String)
69
+ [errs]
70
+ else
71
+ [errs.to_s]
72
+ end
73
+ else
74
+ []
75
+ end
76
+ end
77
+
78
+ end
79
+ end
@@ -0,0 +1,42 @@
1
+ module ExoBasic
2
+ class LikertValue
3
+ attr_reader :v, :maximum
4
+
5
+ UNKNOWN = 0
6
+ MINIMUM = 1
7
+
8
+ def initialize(v, maximum)
9
+ @v = [LikertValue::UNKNOWN, [v, maximum].min].max
10
+ @maximum = maximum
11
+ end
12
+
13
+ def get_range
14
+ LikertValue::MINIMUM..@maximum
15
+ end
16
+
17
+ def get_extended_range
18
+ LikertValue::UNKNOWN..@maximum
19
+ end
20
+
21
+ def normalize
22
+ if @v != LikertValue::UNKNOWN
23
+ [LikertValue::MINIMUM, [@v, @maximum].min].max
24
+ else
25
+ LikertValue::UNKNOWN
26
+ end
27
+ end
28
+
29
+ def normalize_known
30
+ [LikertValue::MINIMUM, [@v, @maximum].min].max
31
+ end
32
+
33
+ def self.real_to_likert(v, maximum)
34
+ LikertValue.new(v.round(), maximum).normalize_known
35
+ end
36
+
37
+ def self.likert_avg(total_likert, n, maximum)
38
+ LikertValue.real_to_likert(1.0 * total_likert / (1.0 * n), maximum)
39
+ end
40
+
41
+ end
42
+ end
@@ -0,0 +1,112 @@
1
+ require 'logger'
2
+ require 'json'
3
+
4
+ module ExoBasic
5
+ class Settings
6
+ DEFAULT_FILENAME = './settings.json'
7
+
8
+ @@app_name = 'Untitled'
9
+ @@load_observers = []
10
+
11
+ def self.get_app_name
12
+ @@app_name
13
+ end
14
+ def self.set_app_name(appname)
15
+ @@app_name = appname
16
+ end
17
+
18
+ def self.add_on_load_observer(observer)
19
+ @@load_observers.push(observer)
20
+ end
21
+
22
+ def self.post_load(js)
23
+ merged = {}
24
+ if js.key?('_include')
25
+ to_include = []
26
+ if js['_include'].is_a?(Array)
27
+ to_include = js['_include']
28
+ else
29
+ to_include = [js['_include']]
30
+ end
31
+
32
+ to_include.each do |i|
33
+ h = Settings.get_conf_with_rem(i)
34
+ merged = merged.deep_merge(h)
35
+ end
36
+ merged = merged.deep_merge(js)
37
+ else
38
+ merged = js
39
+ end
40
+
41
+ merged
42
+ end
43
+
44
+ def self.get_conf_with_rem(filename=nil)
45
+ f = filename
46
+ if f.nil?
47
+ f = Settings::DEFAULT_FILENAME
48
+ end
49
+
50
+ if File.exists?(f)
51
+ js = JSON.load(
52
+ File.readlines(f)
53
+ .select { |line| !line.strip.start_with?('//') }
54
+ .join())
55
+ Settings.post_load(js)
56
+ else
57
+ {}
58
+ end
59
+ end
60
+
61
+ def self.get_conf(filename=nil)
62
+ f = filename
63
+ if f.nil?
64
+ f = Settings::DEFAULT_FILENAME
65
+ end
66
+
67
+ js = JSON.load(File.read(f))
68
+ Settings.post_load(js)
69
+ end
70
+
71
+ def self.try_get_key(conf, default_value=false, k)
72
+ conf.key?(k) ? conf[k] : default_value
73
+ end
74
+
75
+ def self.try_get_nested_key(conf, default_value=false, ks)
76
+ if ks.empty?
77
+ default_value
78
+ else
79
+ v = Settings.try_get_key(conf, default_value, ks[0])
80
+ if v != default_value && ks.length > 1
81
+ Settings.try_get_nested_key(v, default_value, ks[1..-1])
82
+ else
83
+ v
84
+ end
85
+ end
86
+ end
87
+
88
+ def self.load
89
+ Settings.get_conf_with_rem
90
+ end
91
+
92
+ @@loaded = Settings.load
93
+
94
+ def self.loaded
95
+ @@loaded
96
+ end
97
+
98
+ def self.reload(filename=nil)
99
+ @@loaded = Settings.get_conf_with_rem(filename)
100
+ @@load_observers.each { |observer| observer.settings_reloaded }
101
+ end
102
+
103
+ def self.get_app_conf
104
+ @@loaded[@@app_name]
105
+ end
106
+
107
+ def self.get_logger
108
+ Logger.new(STDOUT)
109
+ end
110
+
111
+ end
112
+ end
@@ -0,0 +1,141 @@
1
+ module ExoBasic
2
+ class AvgMeta
3
+ attr_reader :n,
4
+ :change,
5
+ :averageChange,
6
+ :squareOfChange,
7
+ :variance,
8
+ :averageDirectional,
9
+ :minPossibility,
10
+ :maxPossibility
11
+
12
+ def initialize
13
+ @n = 0
14
+ @change = 0.0
15
+ @averageChange = 0.0
16
+ @squareOfChange = 0.0
17
+ @variance = 0.0
18
+ @averageDirectional = 0.0
19
+ @minPossibility = Float::MAX
20
+ @maxPossibility = Float::MIN
21
+ end
22
+
23
+ def from_hash(h)
24
+ @n = h[:n]
25
+ @change = h[:change]
26
+ @averageChange = h[:averageChange]
27
+ @squareOfChange = h[:squareOfChange]
28
+ @variance = h[:variance]
29
+ @averageDirectional = h[:averageDirectional]
30
+ @minPossibility = h[:minPossibility]
31
+ @maxPossibility = h[:maxPossibility]
32
+ end
33
+
34
+ def self.mk(h)
35
+ x = AvgMeta.new
36
+ x.from_hash(h)
37
+
38
+ x
39
+ end
40
+
41
+ def empty?
42
+ @n == 0
43
+ end
44
+
45
+ def offer(x, prev_mean, d)
46
+ delta = x - prev_mean
47
+ d2 = d <= 0 ? 1.0 : d.to_f
48
+ change_prime = delta.abs
49
+ square_of_change_prime = delta * delta
50
+
51
+ AvgMeta.mk({
52
+ :n => @n + 1,
53
+ :change => change_prime,
54
+ :averageChange => change_prime / d2 + (d2 - 1.0) * @averageChange / d2,
55
+ :squareOfChange => square_of_change_prime,
56
+ :variance => square_of_change_prime / d2 + (d2 - 1.0) * @variance / d2,
57
+ :averageDirectional => delta,
58
+ :minPossibility => [x, @minPossibility].min,
59
+ :maxPossibility => [x, @maxPossibility].max
60
+ })
61
+ end
62
+
63
+ # Copy
64
+ def deep_copy
65
+ Marshal.load(Marshal.dump(self))
66
+ end
67
+
68
+ # Equal
69
+ def ==(other)
70
+ StatsHelpers.double_equals(@change, other.change) &&
71
+ StatsHelpers.double_equals(@variance, other.variance) &&
72
+ StatsHelpers.double_equals(@averageDirectional, other.averageDirectional) &&
73
+ StatsHelpers.double_equals(@minPossibility, other.minPossibility) &&
74
+ StatsHelpers.double_equals(@maxPossibility, other.maxPossibility)
75
+ end
76
+
77
+ # Monoid
78
+ def self.zero
79
+ AvgMeta.new
80
+ end
81
+
82
+ def self.append_helper(n1, d1, n2, d2, nAll)
83
+ d1 * n1 / nAll + d2 * n2 / nAll
84
+ end
85
+
86
+ def +(other)
87
+ n_prime = @n + other.n
88
+
89
+ nd = n_prime.to_f
90
+ n1 = @n.to_f
91
+ n2 = other.n.to_f
92
+
93
+ x = {
94
+ :n => n_prime
95
+ }
96
+ if n1 <= 0
97
+ x[:change] = other.change
98
+ x[:averageChange] = other.averageChange
99
+ x[:squareOfChange] = other.squareOfChange
100
+ x[:variance] = other.variance
101
+ x[:averageDirectional] = other.averageDirectional
102
+ elsif n2 <= 0
103
+ x[:change] = @change
104
+ x[:averageChange] = @averageChange
105
+ x[:squareOfChange] = @squareOfChange
106
+ x[:variance] = @variance
107
+ x[:averageDirectional] = @averageDirectional
108
+ else
109
+ x[:change] = AvgMeta.append_helper(n1, @change,
110
+ n2, other.change,
111
+ nd),
112
+ x[:averageChange] = AvgMeta.append_helper(n1, @averageChange,
113
+ n2, other.averageChange,
114
+ nd),
115
+ x[:squareOfChange] = AvgMeta.append_helper(n1, @squareOfChange,
116
+ n2, other.squareOfChange,
117
+ nd),
118
+ x[:variance] = AvgMeta.append_helper(n1, @variance,
119
+ n2, other.variance,
120
+ nd),
121
+ x[:averageDirectional] = AvgMeta.append_helper(n1, @averageDirectional,
122
+ n2, other.averageDirectional,
123
+ nd)
124
+ end
125
+
126
+ x[:minPossibility] = [@minPossibility, other.minPossibility].min
127
+ x[:maxPossibility] = [@maxPossibility, other.maxPossibility].max
128
+
129
+ AvgMeta.mk(x)
130
+ end
131
+
132
+ def standardDeviation
133
+ Math.sqrt(@variance)
134
+ end
135
+
136
+ def possibilityRange
137
+ [@minPossibility, @maxPossibility]
138
+ end
139
+
140
+ end
141
+ end
@@ -0,0 +1,34 @@
1
+ module ExoBasic
2
+ class StatsHelpers
3
+ def self.double_equals(a1, a2)
4
+ (a1 - a2).abs < 1e-4
5
+ end
6
+ end
7
+
8
+ module AvgTraits
9
+ def count
10
+ self.meta.n
11
+ end
12
+
13
+ def minimum
14
+ self.meta.possibilityRange[0]
15
+ end
16
+
17
+ def maximum
18
+ self.meta.possibilityRange[1]
19
+ end
20
+
21
+ def approx=(other)
22
+ StatsHelpers.double_equals(self.avg, other.avg)
23
+ end
24
+
25
+ def deep_copy
26
+ Marshal.load(Marshal.dump(self))
27
+ end
28
+
29
+ def offer_many(xs)
30
+ xs.each { |x| self.offer(x) }
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,33 @@
1
+ module ExoBasic
2
+ class DecayingAvg
3
+ include AvgTraits
4
+
5
+ attr_reader :d, :avg, :meta
6
+
7
+ DEFAULT_D = 12
8
+
9
+ def initialize(d=DEFAULT_D)
10
+ @d = d <= 0 ? DEFAULT_D : d
11
+ @avg = 0.0
12
+ @meta = AvgMeta.new
13
+ end
14
+
15
+ def d=(d)
16
+ @d = d <= 0 ? DecayingAvg::DEFAULT_D : d
17
+ end
18
+
19
+ def offer(x)
20
+ @meta = @meta.offer(x, @avg, d)
21
+
22
+ d_prime = @d.to_f
23
+ @avg = x / d_prime + (d_prime - 1.0) * @avg / d_prime
24
+ end
25
+
26
+ def ==(other)
27
+ @d == other.d &&
28
+ StatsHelpers.double_equals(@avg, other.avg) &&
29
+ @meta == other.meta
30
+ end
31
+
32
+ end
33
+ end
@@ -0,0 +1,47 @@
1
+ module ExoBasic
2
+ class MeanAvg
3
+ include AvgTraits
4
+
5
+ attr_reader :avg, :meta
6
+
7
+ def initialize
8
+ @avg = 0.0
9
+ @meta = AvgMeta.new
10
+ end
11
+
12
+ def from_hash(h)
13
+ @avg = h[:avg]
14
+ @meta = h[:meta]
15
+ end
16
+
17
+ def self.mk(h)
18
+ x = MeanAvg.new
19
+ x.from_hash(h)
20
+
21
+ x
22
+ end
23
+
24
+ def offer(x)
25
+ n = @meta.n + 1
26
+ @meta = @meta.offer(x, @avg, n)
27
+
28
+ n_prime = n.to_f
29
+ @avg = x / n_prime + (n_prime - 1.0) * @avg / n_prime
30
+ end
31
+
32
+ def ==(other)
33
+ StatsHelpers.double_equals(@avg, other.avg) &&
34
+ @meta == other.meta
35
+ end
36
+
37
+ def +(other)
38
+ n_prime = @meta.n + other.meta.n
39
+
40
+ MeanAvg.mk({
41
+ :avg => n_prime == 0 ? 0.0 : (@avg * @meta.n + other.avg * other.meta.n) / n_prime.to_f,
42
+ :meta => @meta + other.meta
43
+ })
44
+ end
45
+
46
+ end
47
+ end
@@ -0,0 +1,42 @@
1
+ module ExoBasic
2
+ class ShortTermAvg
3
+ include AvgTraits
4
+
5
+ attr_reader :d, :history, :meta
6
+
7
+ DEFAULT_D = 12
8
+
9
+ def initialize(d=DEFAULT_D)
10
+ @d = d <= 0 ? DEFAULT_D : d
11
+ @history = []
12
+ @meta = AvgMeta.new
13
+ end
14
+
15
+ def d=(d)
16
+ @d = d <= 0 ? ShortTermAvg::DEFAULT_D : d
17
+ @history = @history[0..@d - 1]
18
+ end
19
+
20
+ def avg
21
+ history.empty? ? 0.0 : history.sum / history.length.to_f
22
+ end
23
+
24
+ def offer(x)
25
+ siz = history.length
26
+ if siz < @d
27
+ history.push(x)
28
+ else
29
+ history = history[1..-2].push(x)
30
+ end
31
+
32
+ @meta = @meta.offer(x, self.avg, siz)
33
+ end
34
+
35
+ def ==(other)
36
+ @d == other.d &&
37
+ (@history <=> other.history) == 0 &&
38
+ @meta == other.meta
39
+ end
40
+
41
+ end
42
+ end
@@ -0,0 +1,12 @@
1
+ module ExoBasic
2
+ class StrHelpers
3
+ def self.normalize_name(name)
4
+ name.strip.gsub(/[^[:print:]]/, '.')
5
+ end
6
+
7
+ def self.is_name_valid?(name)
8
+ !name.nil? && !name.empty? && StrHelpers.normalize_name(name) == name
9
+ end
10
+
11
+ end
12
+ end
@@ -0,0 +1,34 @@
1
+ module ExoBasic
2
+ class DateTimer
3
+ def self.date_time_now
4
+ DateTime.now
5
+ end
6
+
7
+ def self.get(date_time)
8
+ if date_time.nil?
9
+ DateTimer.date_time_now
10
+ elsif date_time.is_a?(String)
11
+ DateTime.parse(date_time)
12
+ else
13
+ date_time
14
+ end
15
+ end
16
+
17
+ def self.duration_in_days(from, to)
18
+ (to - from).to_i
19
+ end
20
+
21
+ def self.duration_in_sec(from, to)
22
+ to.to_i - from.to_i
23
+ end
24
+
25
+ def self.add_days(t, days)
26
+ t + days
27
+ end
28
+
29
+ def self.add_sec(t, sec)
30
+ t + Rational(sec, 86400)
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,166 @@
1
+ module ExoBasic
2
+ class Timer
3
+ attr_reader :name, :start, :prev, :now, :samples
4
+
5
+ EPOCH = Time.utc(1970, 1, 1)
6
+ END_OF_ALL_DAYS = Time.utc(3000, 1, 1)
7
+
8
+ def initialize(name=nil)
9
+ @name = name
10
+ @start = Time.now
11
+ @prev = @start
12
+ @now = @start
13
+ @samples = 0
14
+ end
15
+
16
+ def sample
17
+ t = Time.now
18
+ @prev = @now
19
+ @now = t
20
+ @samples += 1
21
+ end
22
+
23
+ def tick
24
+ @now = Time.now
25
+ end
26
+
27
+ def warp
28
+ @start = @prev
29
+ @samples = 1
30
+ end
31
+
32
+ def delta
33
+ Time.now - @now
34
+ end
35
+
36
+ def lifetime
37
+ Time.now - @start
38
+ end
39
+
40
+ def diff_from(that)
41
+ @now - that.now
42
+ end
43
+
44
+ def merge_with(that)
45
+ if that.start < @start
46
+ @start = that.start
47
+ @samples += 1
48
+ end
49
+ if that.now > @now
50
+ @now = that.now
51
+ @samples += 1
52
+ end
53
+ end
54
+
55
+ def show(msg=nil)
56
+ "-- TIME_MEASUREMENT #{@name}::#{msg}::#{@samples}\tfrom_prev = #{@now - @prev}\tfrom_start = #{@now - @start}"
57
+ end
58
+
59
+ def checkpoint(msg=nil)
60
+ self.sample
61
+ STDERR.puts self.show(msg)
62
+ end
63
+
64
+ def self.time_now
65
+ Time.now
66
+ end
67
+
68
+ def self.get(date_time)
69
+ if date_time.nil?
70
+ Timer.time_now
71
+ elsif date_time.is_a?(String)
72
+ Time.parse(date_time)
73
+ else
74
+ date_time
75
+ end
76
+ end
77
+
78
+ def self.duration_in_days(from, to)
79
+ ((to - from) / 86400).to_i
80
+ end
81
+
82
+ def self.duration_in_sec(from, to)
83
+ to - from
84
+ end
85
+
86
+ def self.add_days(t, days)
87
+ t + days * 86400
88
+ end
89
+
90
+ def self.add_sec(t, sec)
91
+ t + sec
92
+ end
93
+
94
+ # sec
95
+ def self.from_epoch(epoch)
96
+ Time.at(epoch)
97
+ end
98
+
99
+ # sec
100
+ def self.to_epoch(t)
101
+ t.strftime('%s').to_i
102
+ end
103
+
104
+ def self.to_iso8601(t)
105
+ t.iso8601
106
+ end
107
+
108
+ def self.year(t)
109
+ t.year
110
+ end
111
+
112
+ # [YYYY, (1..366)]
113
+ def self.date_by_day(t)
114
+ [t.year, t.strftime('%-j').to_i]
115
+ end
116
+
117
+ def self.date_by_day_to_epoch(tuple)
118
+ Timer.to_epoch(Time.strptime("#{tuple[0]} #{tuple[1]}", '%Y %j'))
119
+ end
120
+
121
+ def self.day_of_year(t)
122
+ Timer.date_by_day(t)[1]
123
+ end
124
+
125
+ # [YYYY, (1..54), (1..7)]
126
+ # week starts with Monday
127
+ def self.date_by_week(t)
128
+ [t.year, t.strftime('%W').to_i + 1, t.strftime('%u').to_i]
129
+ end
130
+
131
+ def self.date_by_week_to_epoch(tuple)
132
+ Timer.to_epoch(Time.strptime("#{tuple[0]} #{tuple[1] - 1} #{tuple[2]}", '%Y %W %u'))
133
+ end
134
+
135
+ def self.week_of_year(t)
136
+ Timer.date_by_week(t)[1]
137
+ end
138
+
139
+ def self.day_of_week_of_year(t)
140
+ Timer.date_by_week(t)[2]
141
+ end
142
+
143
+ # [YYYY, (1..12), (1..31)]
144
+ def self.date_by_month(t)
145
+ [t.year, t.month, t.strftime('%-d').to_i]
146
+ end
147
+
148
+ def self.date_by_month_to_epoch(tuple)
149
+ Timer.to_epoch(Time.strptime("#{tuple[0]} #{tuple[1]} #{tuple[2]}", '%Y %m %d'))
150
+ end
151
+
152
+ def self.month(t)
153
+ Timer.date_by_month(t)[1]
154
+ end
155
+
156
+ def self.day_of_month(t)
157
+ Timer.date_by_month(t)[2]
158
+ end
159
+
160
+ # (+, -)HH:MM
161
+ def self.tz_of(t)
162
+ t.strftime('%:z')
163
+ end
164
+
165
+ end
166
+ end
@@ -0,0 +1,3 @@
1
+ module ExoBasic
2
+ VERSION = "0.1.0"
3
+ end
data/lib/exobasic.rb ADDED
@@ -0,0 +1,22 @@
1
+ require 'exobasic/likert_value'
2
+ require 'exobasic/settings'
3
+ require 'exobasic/str_helpers'
4
+
5
+ require 'exobasic/data/dfs'
6
+
7
+ require 'exobasic/encrypt/ecdsa_keys'
8
+ require 'exobasic/encrypt/hmac_keys'
9
+ require 'exobasic/encrypt/password_encryption'
10
+ require 'exobasic/encrypt/rsa_keys'
11
+
12
+ require 'exobasic/http/json_http_client'
13
+ require 'exobasic/http/json_http'
14
+
15
+ require 'exobasic/stats/avg_meta'
16
+ require 'exobasic/stats/avg_traits'
17
+ require 'exobasic/stats/decaying_avg'
18
+ require 'exobasic/stats/mean_avg'
19
+ require 'exobasic/stats/short_term_avg'
20
+
21
+ require 'exobasic/time/date_timer'
22
+ require 'exobasic/time/timer'
metadata ADDED
@@ -0,0 +1,77 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: exobasic
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Dionysios Kakolyris
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-10-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bcrypt
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 3.1.12
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 3.1.12
27
+ description: Exotic Basic Helpers
28
+ email:
29
+ - contact@exotic.industries
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - README.md
35
+ - lib/exobasic.rb
36
+ - lib/exobasic/data/dfs.rb
37
+ - lib/exobasic/encrypt/ecdsa_keys.rb
38
+ - lib/exobasic/encrypt/hmac_keys.rb
39
+ - lib/exobasic/encrypt/password_encryption.rb
40
+ - lib/exobasic/encrypt/rsa_keys.rb
41
+ - lib/exobasic/http/json_http.rb
42
+ - lib/exobasic/http/json_http_client.rb
43
+ - lib/exobasic/likert_value.rb
44
+ - lib/exobasic/settings.rb
45
+ - lib/exobasic/stats/avg_meta.rb
46
+ - lib/exobasic/stats/avg_traits.rb
47
+ - lib/exobasic/stats/decaying_avg.rb
48
+ - lib/exobasic/stats/mean_avg.rb
49
+ - lib/exobasic/stats/short_term_avg.rb
50
+ - lib/exobasic/str_helpers.rb
51
+ - lib/exobasic/time/date_timer.rb
52
+ - lib/exobasic/time/timer.rb
53
+ - lib/exobasic/version.rb
54
+ homepage: https://bitbucket.org/vertigoindustries/exotic-basic
55
+ licenses:
56
+ - MIT
57
+ metadata: {}
58
+ post_install_message:
59
+ rdoc_options: []
60
+ require_paths:
61
+ - lib
62
+ required_ruby_version: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: '0'
67
+ required_rubygems_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: 1.3.6
72
+ requirements: []
73
+ rubygems_version: 3.0.6
74
+ signing_key:
75
+ specification_version: 4
76
+ summary: BasicHelpers
77
+ test_files: []