whirled_peas 0.1.0 → 0.1.1

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 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