exobasic 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +23 -0
- data/lib/exobasic/data/dfs.rb +83 -0
- data/lib/exobasic/encrypt/ecdsa_keys.rb +125 -0
- data/lib/exobasic/encrypt/hmac_keys.rb +35 -0
- data/lib/exobasic/encrypt/password_encryption.rb +14 -0
- data/lib/exobasic/encrypt/rsa_keys.rb +123 -0
- data/lib/exobasic/http/json_http.rb +90 -0
- data/lib/exobasic/http/json_http_client.rb +79 -0
- data/lib/exobasic/likert_value.rb +42 -0
- data/lib/exobasic/settings.rb +112 -0
- data/lib/exobasic/stats/avg_meta.rb +141 -0
- data/lib/exobasic/stats/avg_traits.rb +34 -0
- data/lib/exobasic/stats/decaying_avg.rb +33 -0
- data/lib/exobasic/stats/mean_avg.rb +47 -0
- data/lib/exobasic/stats/short_term_avg.rb +42 -0
- data/lib/exobasic/str_helpers.rb +12 -0
- data/lib/exobasic/time/date_timer.rb +34 -0
- data/lib/exobasic/time/timer.rb +166 -0
- data/lib/exobasic/version.rb +3 -0
- data/lib/exobasic.rb +22 -0
- metadata +77 -0
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,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,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
|
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: []
|