whirled_peas 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 85262725d00d4055dceb88c2672afc6a7f5027626a3038998706b5d8e2d1a3aa
4
- data.tar.gz: 15bc9278fd2945d0ea5cf6a5436ba11e00724e71f128ec487f5569c3689bfaeb
3
+ metadata.gz: 6608e1e6c9505eefea5409bcdc0f8af117b5bcf390564d163315f05b9fe73323
4
+ data.tar.gz: 713b7ff1c83c462b137e1a9203282ad74f8dc71e660237da35b40d472ae1d486
5
5
  SHA512:
6
- metadata.gz: 7270704f5ff2adc5d225dd7c612f489a968c9d5efc2fef399b65a8ad7320fcad2920ec355645f961744577df740efbfcf4947f477e13224702764bf64931a52b
7
- data.tar.gz: a46f83016c3038275fccc907894c0ea7fc3ce0553c5da343d77fbfbbdf9f619ca169346e11b5862cf502e7785372a750a0ab668bfffb3198b1d529a75cd198a5
6
+ metadata.gz: 81d5ae8c86907fa5a32025be54fd4649d1c75c93726d0f581f0f63cd7182f8381285451cef1c2350b223c378ba98c00682b2a96ac65b31cabc05b5871cbaf1fc
7
+ data.tar.gz: 52cdda8685d24cd1ad89c5a2acc70b7fb77c22cf6c21cb3d3eb20dce5df2660bc2e9d285e04fbb13018189f9b2f9700203e3915c13f678ecb4d92f2ce173bfab
@@ -0,0 +1,9 @@
1
+ # Changelog
2
+
3
+ ## v0.1.1 - 2021-01-20
4
+
5
+ - [3852c8c](https://github.com/tcollier/whirled_peas/tree/3852c8c700c2e8fb92e65bbca1c99be74304c6d0): Improve error handling
6
+
7
+ ## v0.1.0 - 2021-01-20
8
+
9
+ - [f343434](https://github.com/tcollier/whirled_peas/tree/f34343458097da04d5846ab13533e8226ba04d75): Inaugural release
data/README.md CHANGED
@@ -122,6 +122,36 @@ A `ComposableElement` provides the following methods to add child elements
122
122
  - `add_grid` - yields a `ComposableElement` and a `GridSettings`, which will be added to the parent's children
123
123
  - `add_text` - yields `nil` and a `TextSettings`, which will be added to the parent's children
124
124
 
125
+ E.g.
126
+
127
+ ```ruby
128
+ WhirledPeas.template do |template, template_settings|
129
+ template_settings.bg_color = :blue
130
+ template.add_grid do |grid, grid_settings|
131
+ grid_settings.num_cols = 10
132
+ 100.times do |i|
133
+ grid.add_text { i.to_s }
134
+ end
135
+ end
136
+ end
137
+ ```
138
+
139
+ The above template can also be broken down into more manageable methods, e.g.
140
+
141
+ ```ruby
142
+ def number_grid(grid, settings)
143
+ settings.num_cols = 10
144
+ 100.times do |i|
145
+ grid.add_text { i.to_s }
146
+ end
147
+ end
148
+
149
+ WhirledPeas.template do |template, settings|
150
+ settings.bg_color = :blue
151
+ template.add_grid(&method(:number_grid))
152
+ end
153
+ ```
154
+
125
155
  Additionally, if no child element is explicitly added to a `GridElement`, but the block returns an array of strings or numbers, those will be converted to `TextElements` and added as children to the `GridElement`. For example, these are identical ways to create a grid of strings
126
156
 
127
157
  ```ruby
@@ -276,39 +306,51 @@ Many of these also have a "bright" option:
276
306
 
277
307
  ```ruby
278
308
  class TemplateFactory
279
- def initialize
280
- @numbers = []
309
+ def build(frame, args)
310
+ set_state(frame, args)
311
+ WhirledPeas.template do |t|
312
+ t.add_box(&method(:body))
313
+ end
281
314
  end
282
315
 
283
- def build(name, args)
284
- @numbers = args['numbers'] if args.key?('numbers')
316
+ private
285
317
 
286
- WhirledPeas.template do |t|
287
- t.add_box do |body, settings|
288
- settings.flow = :l2r
289
- settings.auto_margin = true
290
- body.add_box do |title, settings|
291
- settings.underline = true
292
- "Pair Finder"
293
- end
294
- body.add_box do |_, settings|
295
- settings.color = name == 'found-pair' ? :green : :red
296
- args.key?('sum') ? "Sum: #{args['sum']}" : 'N/A'
297
- end
298
- body.add_grid do |g, settings|
299
- settings.full_border
300
- @numbers.each.with_index do |num, index|
301
- is_low = args.key?('low') && args['low'] == index
302
- is_high = args.key?('high') && args['high'] == index
303
- g.add_text do |_, settings|
304
- settings.bg_color = (is_low || is_high) ? :cyan : :white
305
- num.to_s
306
- end
307
- end
308
- end
318
+ def set_state(frame, args)
319
+ @frame = frame
320
+ @numbers = args.key?('numbers') ? args['numbers'] || []
321
+ @sum = args['sum'] if args.key?('sum')
322
+ @low = args['low'] if args.key?('low')
323
+ @high = args['high'] if args.key?('high')
324
+ end
325
+
326
+ def title(_elem, settings)
327
+ settings.underline = true
328
+ "Pair Finder"
329
+ end
330
+
331
+ def sum(_elem, settings)
332
+ settings.color = @frame == 'found-pair' ? :green : :red
333
+ @sum ? "Sum: #{@sum}" : 'N/A'
334
+ end
335
+
336
+ def number_grid(elem, settings)
337
+ settings.full_border
338
+ @numbers.each.with_index do |num, index|
339
+ g.add_text do |_, settings|
340
+ settings.bg_color = (@low == index || @high == index) ? :cyan : :white
341
+ num.to_s
309
342
  end
310
343
  end
311
344
  end
345
+
346
+ def body(elem, settings)
347
+ settings.flow = :l2r
348
+ settings.auto_margin = true
349
+
350
+ elem.add_box(&method(:title))
351
+ elem.add_box(&method(:sum))
352
+ elem.add_grid(&method(:number_grid))
353
+ end
312
354
  end
313
355
  ```
314
356
 
@@ -11,6 +11,7 @@ module WhirledPeas
11
11
  DEFAULT_PORT = 8765
12
12
  DEFAULT_REFRESH_RATE = 30
13
13
 
14
+ LOGGER_ID = 'MAIN'
14
15
 
15
16
  def self.start(driver, template_factory, log_level: Logger::INFO, refresh_rate: DEFAULT_REFRESH_RATE, host: DEFAULT_HOST, port: DEFAULT_PORT)
16
17
  logger = Logger.new(File.open('whirled_peas.log', 'a'))
@@ -20,16 +21,19 @@ module WhirledPeas
20
21
  end
21
22
 
22
23
  consumer = Frame::Consumer.new(template_factory, refresh_rate, logger)
23
- consumer_thread = Thread.new { consumer.start(host: host, port: port) }
24
+ consumer_thread = Thread.new do
25
+ Thread.current.report_on_exception = false
26
+ consumer.start(host: host, port: port)
27
+ end
24
28
 
25
29
  Frame::Producer.start(logger: logger, host: host, port: port) do |producer|
26
30
  begin
27
31
  driver.start(producer)
28
32
  producer.stop
29
33
  rescue => e
30
- logger.warn('MAIN') { "Driver exited with error, terminating producer..." }
31
- logger.error('MAIN') { e }
32
- logger.error('MAIN') { e.backtrace.join("\n") }
34
+ logger.warn(LOGGER_ID) { 'Driver exited with error, terminating producer...' }
35
+ logger.error(LOGGER_ID) { e }
36
+ logger.error(LOGGER_ID) { e.backtrace.join("\n") }
33
37
  producer.terminate
34
38
  raise
35
39
  end
@@ -1,13 +1,15 @@
1
1
  require 'socket'
2
2
  require 'json'
3
3
 
4
- require_relative 'loop'
4
+ require_relative 'event_loop'
5
5
 
6
6
  module WhirledPeas
7
7
  module Frame
8
8
  class Consumer
9
+ LOGGER_ID = 'CONSUMER'
10
+
9
11
  def initialize(template_factory, refresh_rate, logger=NullLogger.new)
10
- @loop = Loop.new(template_factory, refresh_rate, logger)
12
+ @event_loop = EventLoop.new(template_factory, refresh_rate, logger)
11
13
  @logger = logger
12
14
  @running = false
13
15
  @mutex = Mutex.new
@@ -15,10 +17,13 @@ module WhirledPeas
15
17
 
16
18
  def start(host:, port:)
17
19
  mutex.synchronize { @running = true }
18
- loop_thread = Thread.new { loop.start }
20
+ loop_thread = Thread.new do
21
+ Thread.current.report_on_exception = false
22
+ event_loop.start
23
+ end
19
24
  socket = TCPSocket.new(host, port)
20
- logger.info('CONSUMER') { "Connected to #{host}:#{port}" }
21
- while @running
25
+ logger.info(LOGGER_ID) { "Connected to #{host}:#{port}" }
26
+ while @running && event_loop.running?
22
27
  line = socket.gets
23
28
  if line.nil?
24
29
  sleep(0.001)
@@ -27,35 +32,36 @@ module WhirledPeas
27
32
  args = JSON.parse(line)
28
33
  name = args.delete('name')
29
34
  if [Frame::EOF, Frame::TERMINATE].include?(name)
30
- logger.info('CONSUMER') { "Received #{name} event, stopping..." }
31
- loop.stop if name == Frame::TERMINATE
35
+ logger.info(LOGGER_ID) { "Received #{name} event, stopping..." }
36
+ event_loop.stop if name == Frame::TERMINATE
32
37
  @running = false
33
38
  else
34
39
  duration = args.delete('duration')
35
- loop.enqueue(name, duration, args)
40
+ event_loop.enqueue(name, duration, args)
36
41
  end
37
42
  end
38
- logger.info('CONSUMER') { "Exited normally" }
43
+ logger.info(LOGGER_ID) { 'Exited normally' }
44
+ logger.info(LOGGER_ID) { 'Waiting for loop thread to exit' }
45
+ loop_thread.join
39
46
  rescue => e
40
- logger.warn('CONSUMER') { "Exited with error" }
41
- logger.error('CONSUMER') { e.message }
42
- logger.error('CONSUMER') { e.backtrace.join("\n") }
43
- loop.stop
47
+ event_loop.stop if event_loop.running?
48
+ logger.warn(LOGGER_ID) { 'Exited with error' }
49
+ logger.error(LOGGER_ID) { e.message }
50
+ logger.error(LOGGER_ID) { e.backtrace.join("\n") }
51
+ raise
44
52
  ensure
45
- logger.info('CONSUMER') { "Waiting for loop thread to exit" }
46
- loop_thread.join
47
- logger.info('CONSUMER') { "Closing socket" }
53
+ logger.info(LOGGER_ID) { 'Closing socket' }
48
54
  socket.close if socket
49
55
  end
50
56
 
51
57
  def stop
52
- logger.info('CONSUMER') { "Stopping..." }
58
+ logger.info(LOGGER_ID) { 'Stopping...' }
53
59
  mutex.synchronize { @running = false }
54
60
  end
55
61
 
56
62
  private
57
63
 
58
- attr_reader :loop, :logger, :mutex
64
+ attr_reader :event_loop, :logger, :mutex
59
65
  end
60
66
  end
61
67
  end
@@ -1,6 +1,6 @@
1
1
  module WhirledPeas
2
2
  module Frame
3
- class Loop
3
+ class EventLoop
4
4
  def initialize(template_factory, refresh_rate, logger=NullLogger.new)
5
5
  @template_factory = template_factory
6
6
  @queue = Queue.new
@@ -12,8 +12,12 @@ module WhirledPeas
12
12
  queue.push([name, duration, args])
13
13
  end
14
14
 
15
+ def running?
16
+ @running
17
+ end
18
+
15
19
  def start
16
- logger.info('EVENT LOOP') { "Starting" }
20
+ logger.info('EVENT LOOP') { 'Starting' }
17
21
  @running = true
18
22
  screen = UI::Screen.new
19
23
  sleep(0.01) while queue.empty? # Wait for the first event
@@ -33,17 +37,17 @@ module WhirledPeas
33
37
  end
34
38
  sleep(next_frame_at - Time.now)
35
39
  end
36
- logger.info('EVENT LOOP') { "Exiting normally" }
37
- rescue => e
38
- logger.warn('EVENT LOOP') { "Exiting with error" }
39
- logger.error('EVENT LOOP') { e.message }
40
- logger.error('EVENT LOOP') { e.backtrace.join("\n") }
40
+ logger.info('EVENT LOOP') { 'Exiting normally' }
41
+ rescue
42
+ logger.warn('EVENT LOOP') { 'Exiting with error' }
43
+ @running = false
44
+ raise
41
45
  ensure
42
46
  screen.finalize if screen
43
47
  end
44
48
 
45
49
  def stop
46
- logger.info('EVENT LOOP') { "Stopping..." }
50
+ logger.info('EVENT LOOP') { 'Stopping...' }
47
51
  @running = false
48
52
  end
49
53
 
@@ -51,6 +55,6 @@ module WhirledPeas
51
55
 
52
56
  attr_reader :template_factory, :queue, :refresh_rate, :logger
53
57
  end
54
- private_constant :Loop
58
+ private_constant :EventLoop
55
59
  end
56
60
  end
@@ -4,19 +4,26 @@ require 'json'
4
4
  module WhirledPeas
5
5
  module Frame
6
6
  class Producer
7
+ LOGGER_ID = 'PRODUCER'
8
+
7
9
  def self.start(logger: NullLogger.new, host:, port:, &block)
8
10
  server = TCPServer.new(host, port)
9
11
  client = server.accept
10
- logger.info('PRODUCER') { "Connected to #{host}:#{port}" }
12
+ logger.info(LOGGER_ID) { "Connected to #{host}:#{port}" }
11
13
  producer = new(client, logger)
12
14
  yield producer
13
- logger.info('PRODUCER') { "Exited normally" }
15
+ logger.info(LOGGER_ID) { 'Exited normally' }
14
16
  rescue => e
15
- logger.warn('PRODUCER') { "Exited with error" }
16
- logger.error('PRODUCER') { e.message }
17
- logger.error('PRODUCER') { e.backtrace.join("\n") }
17
+ producer.terminate
18
+ logger.warn(LOGGER_ID) { 'Exited with error' }
19
+ logger.error(LOGGER_ID) { e.message }
20
+ logger.error(LOGGER_ID) { e.backtrace.join("\n") }
21
+ raise
18
22
  ensure
19
- client.close if client
23
+ if client
24
+ logger.info(LOGGER_ID) { 'Closing connection'}
25
+ client.close
26
+ end
20
27
  end
21
28
 
22
29
  def initialize(client, logger=NullLogger.new)
@@ -27,7 +34,7 @@ module WhirledPeas
27
34
 
28
35
  def send(name, duration: nil, args: {})
29
36
  client.puts(JSON.generate('name' => name, 'duration' => duration, **args))
30
- logger.debug('PRODUCER') { "Sending frame: #{name}" }
37
+ logger.debug(LOGGER_ID) { "Sending frame: #{name}" }
31
38
  end
32
39
 
33
40
  def enqueue(name, duration: nil, args: {})
@@ -1,3 +1,3 @@
1
1
  module WhirledPeas
2
- VERSION = '0.1.0'
2
+ VERSION = '0.1.1'
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: whirled_peas
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tom Collier
@@ -73,7 +73,7 @@ files:
73
73
  - lib/whirled_peas.rb
74
74
  - lib/whirled_peas/frame.rb
75
75
  - lib/whirled_peas/frame/consumer.rb
76
- - lib/whirled_peas/frame/loop.rb
76
+ - lib/whirled_peas/frame/event_loop.rb
77
77
  - lib/whirled_peas/frame/producer.rb
78
78
  - lib/whirled_peas/null_logger.rb
79
79
  - lib/whirled_peas/ui.rb