cotcube-helpers 0.2.2.3 → 0.2.4
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 +4 -4
- data/CHANGELOG.md +25 -0
- data/Gemfile +1 -0
- data/VERSION +1 -1
- data/bin/gitlog +4 -0
- data/bin/uncommitted +28 -0
- data/cotcube-helpers.gemspec +1 -1
- data/lib/cotcube-helpers/array_ext.rb +52 -27
- data/lib/cotcube-helpers/cache_client.rb +52 -0
- data/lib/cotcube-helpers/constants.rb +2 -0
- data/lib/cotcube-helpers/data_client.rb +2 -2
- data/lib/cotcube-helpers/datetime_ext.rb +13 -5
- data/lib/cotcube-helpers/deep_decode_datetime.rb +25 -0
- data/lib/cotcube-helpers/hash_ext.rb +31 -5
- data/lib/cotcube-helpers/init.rb +5 -3
- data/lib/cotcube-helpers/josch_client.rb +115 -0
- data/lib/cotcube-helpers/order_client.rb +131 -0
- data/lib/cotcube-helpers/string_ext.rb +4 -0
- data/lib/cotcube-helpers.rb +4 -1
- data/scripts/cron_ruby_wrapper.sh +2 -1
- metadata +10 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 569d3d98802f31608b3525e0b0692a19a67b65a05036f7b9c04cd4d8985017d3
|
4
|
+
data.tar.gz: ad92e036d65e317ab94f179020b4d131ebb085e8ff87395f17ac5c65b0ec02a2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ca30750b5450a285efc24f7d8498919b329fddc848e8eaee24990632701e529ab967aaf4c94d6718858f921297674adaaee36025a2fce2664d11e370e0003a86
|
7
|
+
data.tar.gz: 7b85764c5630f909f87c0987e662b6801e20e22de850945342dddc839a67c7636abe284463f4e5fba1c952aec80e51dce25f24a1795e9c9a6cdb96e8b29907e6
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,28 @@
|
|
1
|
+
## 0.2.4 (January 13, 2022)
|
2
|
+
- minor changes to init and bin/uncommitted
|
3
|
+
- datetime_ext: added #seconds_until_next_minute
|
4
|
+
- hash: added #deep_dup and #reduce_group; array: added #deep_dup
|
5
|
+
- bin/gitlog: pretty formatted git log
|
6
|
+
- string_ext: escape_regex escapes characters that otherwise have functional meanings in a regex
|
7
|
+
- array_ext: elem_raises? checks for each elem if raises with block, return raising elems or false
|
8
|
+
- Merge branch 'main' of github.com:donkeybridge/cotcube-helpers into main
|
9
|
+
- merging conflict
|
10
|
+
|
11
|
+
## 0.2.3 (December 30, 2021)
|
12
|
+
- merging conflict
|
13
|
+
- added bare josch_ and order_client s
|
14
|
+
- added bare js_ and order_client s
|
15
|
+
|
16
|
+
## 0.2.2.5 (December 23, 2021)
|
17
|
+
- hash_ext: reworked keys_to_sym!
|
18
|
+
- minor changes
|
19
|
+
- deep_decode_datetime: added helper for conversion of timestrings in nested arrays and hashed
|
20
|
+
- cache_client: rewritten as class
|
21
|
+
- gemspec: raised activesupport to version 7
|
22
|
+
|
23
|
+
## 0.2.2.4 (December 07, 2021)
|
24
|
+
- introduced cache_client as client to readcache
|
25
|
+
|
1
26
|
## 0.2.2.3 (November 28, 2021)
|
2
27
|
- data_client: fixing troublesome output in .command
|
3
28
|
|
data/Gemfile
CHANGED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.2.
|
1
|
+
0.2.4
|
data/bin/gitlog
ADDED
data/bin/uncommitted
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
ROOT=${1:-$HOME/GEMS}
|
3
|
+
|
4
|
+
|
5
|
+
check_dir () {
|
6
|
+
CYAN="\033[1;36m"
|
7
|
+
RESET="\033[0m"
|
8
|
+
VGREP="grep --color=always -v"
|
9
|
+
dir=$1
|
10
|
+
if [ -d "${dir}/.git" ]; then
|
11
|
+
echo -e ${CYAN}${dir}${RESET}
|
12
|
+
pushd ${dir} 2>&1 >/dev/null
|
13
|
+
git status 2>&1 | $VGREP 'no changes added to commit' |\
|
14
|
+
$VGREP '^$' |\
|
15
|
+
$VGREP 'to include in what will' |\
|
16
|
+
$VGREP '(use "git ' |\
|
17
|
+
$VGREP 'On branch main' |\
|
18
|
+
$VGREP 'On branch master' |\
|
19
|
+
$VGREP 'nothing to commit' |\
|
20
|
+
$VGREP 'Your branch is up to date'
|
21
|
+
popd 2>&1 >/dev/null
|
22
|
+
fi
|
23
|
+
}
|
24
|
+
|
25
|
+
|
26
|
+
export -f check_dir
|
27
|
+
|
28
|
+
find $ROOT -type d -maxdepth 1 2>/dev/null | xargs -n 1 bash -c 'check_dir "$1"' _
|
data/cotcube-helpers.gemspec
CHANGED
@@ -26,7 +26,7 @@ Gem::Specification.new do |spec|
|
|
26
26
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
27
27
|
spec.require_paths = ['lib']
|
28
28
|
|
29
|
-
spec.add_dependency 'activesupport', '~>
|
29
|
+
spec.add_dependency 'activesupport', '~> 7'
|
30
30
|
spec.add_dependency 'colorize', '~> 0.8'
|
31
31
|
|
32
32
|
|
@@ -64,38 +64,63 @@ class Array
|
|
64
64
|
def select_within(ranges:, attr: nil, &block)
|
65
65
|
unless attr.nil? || first[attr]
|
66
66
|
raise ArgumentError,
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
67
|
+
"At least first element of Array '#{first}' does not contain attr '#{attr}'!"
|
68
|
+
end
|
69
|
+
raise ArgumentError, 'Ranges should be an Array or, more precisely, respond_to :map' unless ranges.respond_to? :map
|
70
|
+
raise ArgumentError, 'Each range in :ranges should respond to .include!' unless ranges.map do |x|
|
71
|
+
x.respond_to? :include?
|
72
|
+
end.reduce(:&)
|
73
|
+
|
74
|
+
select do |el|
|
75
|
+
value = attr.nil? ? el : el[attr]
|
76
|
+
ranges.map do |range|
|
77
|
+
range.include?(block.nil? ? value : block.call(value))
|
78
|
+
end.reduce(:|)
|
79
|
+
end
|
79
80
|
end
|
80
|
-
end
|
81
81
|
|
82
|
-
def select_right_by(inclusive: false, exclusive: false, initial: [], &block)
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
82
|
+
def select_right_by(inclusive: false, exclusive: false, initial: [], &block)
|
83
|
+
# unless range.is_a? Range and
|
84
|
+
# (range.begin.nil? or range.begin.is_a?(Integer)) and
|
85
|
+
# (range.end.nil? or range.end.is_a?(Integer))
|
86
|
+
# raise ArgumentError, ":range, if given, must be a range of ( nil|Integer..nil|Integer), got '#{range}'"
|
87
|
+
# end
|
88
|
+
|
89
|
+
raise ArgumentError, 'No block given.' unless block.is_a? Proc
|
90
|
+
|
91
|
+
inclusive = true unless exclusive
|
92
|
+
if inclusive && exclusive
|
93
|
+
raise ArgumentError,
|
94
|
+
"Either :inclusive or :exclusive must remain falsey, got '#{inclusive}' and '#{exclusive}'"
|
95
|
+
end
|
96
|
+
|
97
|
+
index = find_index { |obj| block.call(obj) }
|
88
98
|
|
89
|
-
|
99
|
+
self[((inclusive ? index : index + 1)..)]
|
100
|
+
end
|
90
101
|
|
91
|
-
|
92
|
-
|
93
|
-
raise ArgumentError,
|
94
|
-
|
102
|
+
def elem_raises?(&block)
|
103
|
+
raise ArgumentError, "Must provide a block." unless block_given?
|
104
|
+
raise ArgumentError, "Block must have arity of 1." unless block.arity == 1
|
105
|
+
map do |elem|
|
106
|
+
begin
|
107
|
+
block.call(elem)
|
108
|
+
false
|
109
|
+
rescue
|
110
|
+
elem
|
111
|
+
end
|
112
|
+
end.reject{|z| z.is_a? FalseClass }.tap{|z| z.empty? ? (return false) : (return z)}
|
95
113
|
end
|
96
114
|
|
97
|
-
|
115
|
+
def deep_dup
|
116
|
+
map do |el|
|
117
|
+
case el
|
118
|
+
when Hash, Array
|
119
|
+
el.deep_dup
|
120
|
+
else
|
121
|
+
el.dup
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
98
125
|
|
99
|
-
self[((inclusive ? index : index + 1)..)]
|
100
|
-
end
|
101
126
|
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Cotcube
|
5
|
+
module Helpers
|
6
|
+
class CacheClient
|
7
|
+
|
8
|
+
def initialize(query='keys', timezone: Cotcube::Helpers::CHICAGO, debug: false, deflate: false, update: false)
|
9
|
+
raise ArgumentError, "Query must not be empty." if [ nil, '' ].include? query
|
10
|
+
raise ArgumentError, "Query '#{query}' is garbage." if query.split('/').size > 2 or not query.match? /\A[a-zA-Z0-9?=\/]+\Z/
|
11
|
+
@update = update ? '?update=true' : ''
|
12
|
+
@request_headers = {}
|
13
|
+
@request_headers['Accept-Encoding'] = 'deflate' if deflate
|
14
|
+
@query = query
|
15
|
+
@result = JSON.parse(HTTParty.get("http://100.100.0.14:8081/#{query}#{@update}").body, headers: @request_headers, symbolize_names: true) rescue { error: 1, msg: "Could not parse response for query '#{query}'." }
|
16
|
+
retry_once if has_errors?
|
17
|
+
end
|
18
|
+
|
19
|
+
def retry_once
|
20
|
+
sleep 2
|
21
|
+
raw = HTTParty.get("http://100.100.0.14:8081/#{query}#{update}")
|
22
|
+
@result = JSON.parse(raw.body, symbolize_names: true) rescue { error: 1, msg: "Could not parse response for query '#{query}'." }
|
23
|
+
if has_errors?
|
24
|
+
puts "ERROR in parsing response: #{raw[..300]}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def has_errors?
|
29
|
+
result[:error].nil? or result[:error] > 0
|
30
|
+
end
|
31
|
+
|
32
|
+
def warnings
|
33
|
+
result[:warnings]
|
34
|
+
end
|
35
|
+
|
36
|
+
def payload
|
37
|
+
has_errors? ? false : @result[:payload]
|
38
|
+
end
|
39
|
+
|
40
|
+
def entity
|
41
|
+
query.split('/').first
|
42
|
+
end
|
43
|
+
|
44
|
+
def asset
|
45
|
+
entity, asset = query.split('/')
|
46
|
+
asset
|
47
|
+
end
|
48
|
+
|
49
|
+
attr_reader :query, :result, :update
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -44,7 +44,7 @@ module Cotcube
|
|
44
44
|
# otherwise times out --- the counterpart here is the subscription within
|
45
45
|
# setup_reply_queue
|
46
46
|
#
|
47
|
-
def command(command, timeout:
|
47
|
+
def command(command, timeout: 10)
|
48
48
|
command = { command: command.to_s } unless command.is_a? Hash
|
49
49
|
command[:timestamp] ||= (Time.now.to_f * 1000).to_i
|
50
50
|
request_id = Digest::SHA256.hexdigest(command.to_json)[..6]
|
@@ -227,4 +227,4 @@ begin
|
|
227
227
|
}
|
228
228
|
ensure
|
229
229
|
client.stop
|
230
|
-
|
230
|
+
end
|
@@ -12,6 +12,14 @@ class DateTime
|
|
12
12
|
end
|
13
13
|
|
14
14
|
alias to_sssm to_seconds_since_sunday_morning
|
15
|
+
|
16
|
+
def seconds_until_next_minute(offset: 60)
|
17
|
+
offset = offset % 60
|
18
|
+
offset = 60 if offset.zero?
|
19
|
+
seconds = (self + offset - (self.to_f % 60).round(3) - self).to_f
|
20
|
+
seconds + (seconds.negative? ? 60 : 0)
|
21
|
+
end
|
22
|
+
|
15
23
|
end
|
16
24
|
|
17
25
|
class Date
|
@@ -21,12 +29,12 @@ class Date
|
|
21
29
|
form = '%Y %W %w'
|
22
30
|
build_range = lambda {|w|
|
23
31
|
begin
|
24
|
-
|
25
|
-
|
32
|
+
( DateTime.strptime("#{year} #{w} 1", form).to_date..
|
33
|
+
DateTime.strptime("#{year} #{w} 0", form).to_date)
|
26
34
|
rescue
|
27
|
-
|
28
|
-
|
29
|
-
|
35
|
+
# beyond Dec 31st #strptime must be called with cw:0 to keep it working
|
36
|
+
( DateTime.strptime("#{year} #{w} 1", form).to_date..
|
37
|
+
DateTime.strptime("#{year+1} 0 0", form).to_date)
|
30
38
|
end
|
31
39
|
}
|
32
40
|
case week
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Cotcube
|
2
|
+
module Helpers
|
3
|
+
VALID_DATETIME_STRING = lambda {|str| str.is_a?(String) and [10,25,29].include?(str.length) and str.count("^0-9:TZ+-= ").zero? }
|
4
|
+
|
5
|
+
def deep_decode_datetime(data, zone: DateTime)
|
6
|
+
case data
|
7
|
+
when nil; nil
|
8
|
+
when VALID_DATETIME_STRING
|
9
|
+
res = nil
|
10
|
+
begin
|
11
|
+
res = zone.parse(data)
|
12
|
+
rescue ArgumentError
|
13
|
+
data
|
14
|
+
end
|
15
|
+
[ DateTime, ActiveSupport::TimeWithZone ].include?(res.class) ? res : data
|
16
|
+
when Array; data.map! { |d| deep_decode_datetime(d, zone: zone) }
|
17
|
+
when Hash; data.transform_values! { |v| deep_decode_datetime(v, zone: zone) }
|
18
|
+
else; data
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
module_function :deep_decode_datetime
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
@@ -2,16 +2,42 @@
|
|
2
2
|
|
3
3
|
# Monkey patching the Ruby Core class Hash
|
4
4
|
class Hash
|
5
|
-
def keys_to_sym
|
6
|
-
|
5
|
+
def keys_to_sym!
|
6
|
+
self.keys.each do |key|
|
7
7
|
case self[key].class.to_s
|
8
8
|
when 'Hash'
|
9
|
-
self[key].keys_to_sym
|
9
|
+
self[key].keys_to_sym!
|
10
10
|
when 'Array'
|
11
|
-
self[key].map { |el| el.is_a?(Hash) ? el.keys_to_sym : el }
|
11
|
+
self[key].map { |el| el.is_a?(Hash) ? el.keys_to_sym! : el }
|
12
12
|
end
|
13
|
-
self[key.to_sym] = delete(key)
|
13
|
+
self["#{key}".to_sym] = delete(key)
|
14
14
|
end
|
15
15
|
self
|
16
16
|
end
|
17
|
+
|
18
|
+
# a group_hash was created from an array by running group_by
|
19
|
+
# to reduce a group_hash, the given block is applied to each array of the hash
|
20
|
+
# if its not an array value, the block will auto-yield nil
|
21
|
+
def reduce_group(&block)
|
22
|
+
raise ArgumentError, 'No block given' unless block_given?
|
23
|
+
map do |key,value|
|
24
|
+
case value
|
25
|
+
when Array
|
26
|
+
[key, (block.call(value) rescue nil) ]
|
27
|
+
else
|
28
|
+
[key, nil]
|
29
|
+
end
|
30
|
+
end.to_h
|
31
|
+
end
|
32
|
+
|
33
|
+
def deep_dup
|
34
|
+
map do |k,v|
|
35
|
+
case v
|
36
|
+
when Hash, Array
|
37
|
+
[k, v.deep_dup]
|
38
|
+
else
|
39
|
+
[k, v.dup]
|
40
|
+
end
|
41
|
+
end.to_h
|
42
|
+
end
|
17
43
|
end
|
data/lib/cotcube-helpers/init.rb
CHANGED
@@ -22,9 +22,10 @@ module Cotcube
|
|
22
22
|
def init(config_file_name: nil,
|
23
23
|
gem_name: nil,
|
24
24
|
debug: false)
|
25
|
-
gem_name
|
26
|
-
|
27
|
-
|
25
|
+
gem_name ||= self.ancestors.first.to_s
|
26
|
+
name = gem_name.split('::').last.downcase
|
27
|
+
config_file_name = "#{name}.yml"
|
28
|
+
config_file = config_path + "/#{config_file_name}"
|
28
29
|
|
29
30
|
if File.exist?(config_file)
|
30
31
|
require 'yaml'
|
@@ -35,6 +36,7 @@ module Cotcube
|
|
35
36
|
|
36
37
|
defaults = {
|
37
38
|
data_path: '/var/cotcube/' + name,
|
39
|
+
pid_file: "/var/run/cotcube/#{name}.pid"
|
38
40
|
}
|
39
41
|
|
40
42
|
config = defaults.merge(config)
|
@@ -0,0 +1,115 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'bunny'
|
5
|
+
require 'json'
|
6
|
+
|
7
|
+
module Cotcube
|
8
|
+
module Helpers
|
9
|
+
class JoSchClient
|
10
|
+
SECRETS_DEFAULT = {
|
11
|
+
'josch_mq_proto' => 'http',
|
12
|
+
'josch_mq_user' => 'guest',
|
13
|
+
'josch_mq_password' => 'guest',
|
14
|
+
'josch_mq_host' => 'localhost',
|
15
|
+
'josch_mq_port' => '15672',
|
16
|
+
'josch_mq_vhost' => '%2F'
|
17
|
+
}.freeze
|
18
|
+
|
19
|
+
SECRETS = SECRETS_DEFAULT.merge(
|
20
|
+
lambda {
|
21
|
+
begin
|
22
|
+
YAML.safe_load(File.read(Cotcube::Helpers.init[:secrets_file]))
|
23
|
+
rescue StandardError
|
24
|
+
{}
|
25
|
+
end
|
26
|
+
}.call
|
27
|
+
)
|
28
|
+
|
29
|
+
def initialize
|
30
|
+
@connection = Bunny.new(user: SECRETS['josch_mq_user'],
|
31
|
+
password: SECRETS['josch_mq_password'],
|
32
|
+
vhost: SECRETS['josch_mq_vhost'])
|
33
|
+
@connection.start
|
34
|
+
|
35
|
+
@commands = connection.create_channel
|
36
|
+
@exchange = commands.direct('josch_commands')
|
37
|
+
@requests = {}
|
38
|
+
@debug = false
|
39
|
+
setup_reply_queue
|
40
|
+
end
|
41
|
+
|
42
|
+
# command acts a synchronizer: it sends the command and waits for the response
|
43
|
+
# otherwise times out --- the counterpart here is the subscription within
|
44
|
+
# setup_reply_queue
|
45
|
+
#
|
46
|
+
def command(command, timeout: 10)
|
47
|
+
command = { command: command.to_s } unless command.is_a? Hash
|
48
|
+
command[:timestamp] ||= (Time.now.to_f * 1000).to_i
|
49
|
+
request_id = Digest::SHA256.hexdigest(command.to_json)[..6]
|
50
|
+
requests[request_id] = {
|
51
|
+
request: command,
|
52
|
+
id: request_id,
|
53
|
+
lock: Mutex.new,
|
54
|
+
condition: ConditionVariable.new
|
55
|
+
}
|
56
|
+
|
57
|
+
exchange.publish(command.to_json,
|
58
|
+
routing_key: 'josch_commands',
|
59
|
+
correlation_id: request_id,
|
60
|
+
reply_to: reply_queue.name)
|
61
|
+
|
62
|
+
# wait for the signal to continue the execution
|
63
|
+
#
|
64
|
+
requests[request_id][:lock].synchronize do
|
65
|
+
requests[request_id][:condition].wait(requests[request_id][:lock], timeout)
|
66
|
+
end
|
67
|
+
|
68
|
+
# if we reached timeout, we will return nil, just for explicity
|
69
|
+
response = requests[request_id][:response].dup
|
70
|
+
requests.delete(request_id)
|
71
|
+
response
|
72
|
+
end
|
73
|
+
|
74
|
+
alias_method :send_command, :command
|
75
|
+
|
76
|
+
attr_accessor :response
|
77
|
+
attr_reader :lock, :condition
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
attr_reader :call_id, :connection, :requests, :persistent,
|
82
|
+
:commands, :server_queue_name, :reply_queue, :exchange
|
83
|
+
|
84
|
+
def setup_reply_queue
|
85
|
+
@reply_queue = commands.queue('', exclusive: true, auto_delete: true)
|
86
|
+
@reply_queue.bind(commands.exchange('josch_replies'), routing_key: @reply_queue.name)
|
87
|
+
|
88
|
+
reply_queue.subscribe do |delivery_info, properties, payload|
|
89
|
+
__id__ = properties[:correlation_id]
|
90
|
+
|
91
|
+
if __id__.nil?
|
92
|
+
puts "Received without __id__: #{delivery_info.map { |k, v| "#{k}\t#{v}" }.join("\n")
|
93
|
+
}\n\n#{properties.map { |k, v| "#{k}\t#{v}" }.join("\n")
|
94
|
+
}\n\n#{JSON.parse(payload).map { |k, v| "#{k}\t#{v}" }.join("\n")}" if @debug
|
95
|
+
|
96
|
+
elsif requests[__id__].nil?
|
97
|
+
puts "Received non-matching response, maybe previously timed out: \n\n#{delivery_info}\n\n#{properties}\n\n#{payload}\n."[..620].scan(/.{1,120}/).join(' '*30 + "\n") if @debug
|
98
|
+
else
|
99
|
+
# save the payload and send the signal to continue the execution of #command
|
100
|
+
# need to rescue the rare case, where lock and condition are destroyed right in parallel by timeout
|
101
|
+
begin
|
102
|
+
puts "Received result for #{__id__}" if @debug
|
103
|
+
requests[__id__][:response] = payload
|
104
|
+
requests[__id__][:lock].synchronize { requests[__id__][:condition].signal }
|
105
|
+
rescue nil
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
JoschClient = JoSchClient
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
__END__
|
@@ -0,0 +1,131 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'bunny'
|
5
|
+
require 'json'
|
6
|
+
|
7
|
+
module Cotcube
|
8
|
+
module Helpers
|
9
|
+
class OrderClient
|
10
|
+
SECRETS_DEFAULT = {
|
11
|
+
'orderproxy_mq_proto' => 'http',
|
12
|
+
'orderproxy_mq_user' => 'guest',
|
13
|
+
'orderproxy_mq_password' => 'guest',
|
14
|
+
'orderproxy_mq_host' => 'localhost',
|
15
|
+
'orderproxy_mq_port' => '15672',
|
16
|
+
'orderproxy_mq_vhost' => '%2F'
|
17
|
+
}.freeze
|
18
|
+
|
19
|
+
SECRETS = SECRETS_DEFAULT.merge(
|
20
|
+
lambda {
|
21
|
+
begin
|
22
|
+
YAML.safe_load(File.read(Cotcube::Helpers.init[:secrets_file]))
|
23
|
+
rescue StandardError
|
24
|
+
{}
|
25
|
+
end
|
26
|
+
}.call
|
27
|
+
)
|
28
|
+
|
29
|
+
def initialize
|
30
|
+
@connection = Bunny.new(user: SECRETS['orderproxy_mq_user'],
|
31
|
+
password: SECRETS['orderproxy_mq_password'],
|
32
|
+
vhost: SECRETS['orderproxy_mq_vhost'])
|
33
|
+
@connection.start
|
34
|
+
|
35
|
+
@commands = connection.create_channel
|
36
|
+
@exchange = commands.direct('orderproxy_commands')
|
37
|
+
@requests = {}
|
38
|
+
@persistent = { depth: {}, realtimebars: {}, ticks: {} }
|
39
|
+
@debug = false
|
40
|
+
setup_reply_queue
|
41
|
+
end
|
42
|
+
|
43
|
+
# command acts a synchronizer: it sends the command and waits for the response
|
44
|
+
# otherwise times out --- the counterpart here is the subscription within
|
45
|
+
# setup_reply_queue
|
46
|
+
#
|
47
|
+
def command(command, timeout: 10)
|
48
|
+
command = { command: command.to_s } unless command.is_a? Hash
|
49
|
+
command[:timestamp] ||= (Time.now.to_f * 1000).to_i
|
50
|
+
request_id = Digest::SHA256.hexdigest(command.to_json)[..6]
|
51
|
+
requests[request_id] = {
|
52
|
+
request: command,
|
53
|
+
id: request_id,
|
54
|
+
lock: Mutex.new,
|
55
|
+
condition: ConditionVariable.new
|
56
|
+
}
|
57
|
+
|
58
|
+
exchange.publish(command.to_json,
|
59
|
+
routing_key: 'orderproxy_commands',
|
60
|
+
correlation_id: request_id,
|
61
|
+
reply_to: reply_queue.name)
|
62
|
+
|
63
|
+
# wait for the signal to continue the execution
|
64
|
+
#
|
65
|
+
requests[request_id][:lock].synchronize do
|
66
|
+
requests[request_id][:condition].wait(requests[request_id][:lock], timeout)
|
67
|
+
end
|
68
|
+
|
69
|
+
# if we reached timeout, we will return nil, just for explicity
|
70
|
+
response = requests[request_id][:response].dup
|
71
|
+
requests.delete(request_id)
|
72
|
+
response
|
73
|
+
end
|
74
|
+
|
75
|
+
alias_method :send_command, :command
|
76
|
+
|
77
|
+
def stop
|
78
|
+
commands.close
|
79
|
+
connection.close
|
80
|
+
end
|
81
|
+
|
82
|
+
def get_contracts(symbol:)
|
83
|
+
send_command({ command: :get_contracts, symbol: symbol })
|
84
|
+
end
|
85
|
+
|
86
|
+
attr_accessor :response
|
87
|
+
attr_reader :lock, :condition
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
attr_reader :call_id, :connection, :requests, :persistent,
|
92
|
+
:commands, :server_queue_name, :reply_queue, :exchange
|
93
|
+
|
94
|
+
def setup_reply_queue
|
95
|
+
@reply_queue = commands.queue('', exclusive: true, auto_delete: true)
|
96
|
+
@reply_queue.bind(commands.exchange('orderproxy_replies'), routing_key: @reply_queue.name)
|
97
|
+
|
98
|
+
reply_queue.subscribe do |delivery_info, properties, payload|
|
99
|
+
__id__ = properties[:correlation_id]
|
100
|
+
|
101
|
+
if __id__.nil?
|
102
|
+
puts "Received without __id__: #{delivery_info.map { |k, v| "#{k}\t#{v}" }.join("\n")
|
103
|
+
}\n\n#{properties.map { |k, v| "#{k}\t#{v}" }.join("\n")
|
104
|
+
}\n\n#{JSON.parse(payload).map { |k, v| "#{k}\t#{v}" }.join("\n")}" if @debug
|
105
|
+
|
106
|
+
elsif requests[__id__].nil?
|
107
|
+
puts "Received non-matching response, maybe previously timed out: \n\n#{delivery_info}\n\n#{properties}\n\n#{payload}\n."[..620].scan(/.{1,120}/).join(' '*30 + "\n") if @debug
|
108
|
+
else
|
109
|
+
# save the payload and send the signal to continue the execution of #command
|
110
|
+
# need to rescue the rare case, where lock and condition are destroyed right in parallel by timeout
|
111
|
+
begin
|
112
|
+
puts "Received result for #{__id__}" if @debug
|
113
|
+
requests[__id__][:response] = payload
|
114
|
+
requests[__id__][:lock].synchronize { requests[__id__][:condition].signal }
|
115
|
+
rescue nil
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
__END__
|
125
|
+
begin
|
126
|
+
client = OrderClient.new
|
127
|
+
reply = client.send_command( { command: 'ping' } )
|
128
|
+
puts reply.nil? ? 'nil' : JSON.parse(reply)
|
129
|
+
ensure
|
130
|
+
client.stop
|
131
|
+
end
|
data/lib/cotcube-helpers.rb
CHANGED
@@ -21,6 +21,7 @@ require_relative 'cotcube-helpers/subpattern'
|
|
21
21
|
require_relative 'cotcube-helpers/parallelize'
|
22
22
|
require_relative 'cotcube-helpers/simple_output'
|
23
23
|
require_relative 'cotcube-helpers/simple_series_stats'
|
24
|
+
require_relative 'cotcube-helpers/deep_decode_datetime'
|
24
25
|
require_relative 'cotcube-helpers/constants'
|
25
26
|
require_relative 'cotcube-helpers/input'
|
26
27
|
require_relative 'cotcube-helpers/output'
|
@@ -52,4 +53,6 @@ module Cotcube
|
|
52
53
|
end
|
53
54
|
end
|
54
55
|
|
55
|
-
|
56
|
+
%w[ data cache order josch ].each do |part|
|
57
|
+
require_relative "cotcube-helpers/#{part}_client"
|
58
|
+
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
#!/bin/bash
|
2
2
|
|
3
|
-
export rubyenv=/home/pepe/.rvm/environments/
|
3
|
+
export rubyenv=/home/pepe/.rvm/environments/ruby-2.7.5
|
4
4
|
|
5
5
|
. $rubyenv
|
6
6
|
cd /home/pepe/GEMS/${1}
|
@@ -10,6 +10,7 @@ ruby ${2} ${3} ${4} ${5} ${6}
|
|
10
10
|
|
11
11
|
|
12
12
|
exit
|
13
|
+
|
13
14
|
for testing run
|
14
15
|
env - `cat /home/pepe/bin/cron_ruby_wrapper.sh | tail -n 6` /bin/bash
|
15
16
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cotcube-helpers
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Benjamin L. Tischendorf
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-01-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '7'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '7'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: colorize
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -93,12 +93,16 @@ files:
|
|
93
93
|
- LICENSE.txt
|
94
94
|
- README.md
|
95
95
|
- VERSION
|
96
|
+
- bin/gitlog
|
97
|
+
- bin/uncommitted
|
96
98
|
- cotcube-helpers.gemspec
|
97
99
|
- lib/cotcube-helpers.rb
|
98
100
|
- lib/cotcube-helpers/array_ext.rb
|
101
|
+
- lib/cotcube-helpers/cache_client.rb
|
99
102
|
- lib/cotcube-helpers/constants.rb
|
100
103
|
- lib/cotcube-helpers/data_client.rb
|
101
104
|
- lib/cotcube-helpers/datetime_ext.rb
|
105
|
+
- lib/cotcube-helpers/deep_decode_datetime.rb
|
102
106
|
- lib/cotcube-helpers/enum_ext.rb
|
103
107
|
- lib/cotcube-helpers/expiration.rb
|
104
108
|
- lib/cotcube-helpers/get_id_set.rb
|
@@ -106,7 +110,9 @@ files:
|
|
106
110
|
- lib/cotcube-helpers/ib_contracts.rb
|
107
111
|
- lib/cotcube-helpers/init.rb
|
108
112
|
- lib/cotcube-helpers/input.rb
|
113
|
+
- lib/cotcube-helpers/josch_client.rb
|
109
114
|
- lib/cotcube-helpers/numeric_ext.rb
|
115
|
+
- lib/cotcube-helpers/order_client.rb
|
110
116
|
- lib/cotcube-helpers/orderclient.rb
|
111
117
|
- lib/cotcube-helpers/output.rb
|
112
118
|
- lib/cotcube-helpers/parallelize.rb
|