exobasic 0.1.0

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