cased-ruby 0.3.3 → 0.4.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +20 -15
  3. data/README.md +135 -54
  4. data/bin/cli +14 -0
  5. data/cased-ruby.gemspec +2 -0
  6. data/lib/cased.rb +1 -0
  7. data/lib/cased/cli.rb +13 -0
  8. data/lib/cased/cli/asciinema/file.rb +108 -0
  9. data/lib/cased/cli/asciinema/writer.rb +69 -0
  10. data/lib/cased/cli/authentication.rb +31 -0
  11. data/lib/cased/cli/identity.rb +43 -0
  12. data/lib/cased/cli/interactive_session.rb +121 -0
  13. data/lib/cased/cli/log.rb +27 -0
  14. data/lib/cased/cli/recorder.rb +58 -0
  15. data/lib/cased/cli/session.rb +292 -0
  16. data/lib/cased/clients.rb +7 -3
  17. data/lib/cased/config.rb +58 -0
  18. data/lib/cased/http/client.rb +13 -8
  19. data/lib/cased/http/error.rb +5 -2
  20. data/lib/cased/query.rb +6 -3
  21. data/lib/cased/version.rb +1 -1
  22. data/vendor/cache/activesupport-6.1.3.gem +0 -0
  23. data/vendor/cache/concurrent-ruby-1.1.8.gem +0 -0
  24. data/vendor/cache/faraday-1.3.0.gem +0 -0
  25. data/vendor/cache/faraday-net_http-1.0.1.gem +0 -0
  26. data/vendor/cache/i18n-1.8.9.gem +0 -0
  27. data/vendor/cache/json-2.5.1.gem +0 -0
  28. data/vendor/cache/jwt-2.2.2.gem +0 -0
  29. data/vendor/cache/ruby2_keywords-0.0.4.gem +0 -0
  30. data/vendor/cache/subprocess-1.5.4.gem +0 -0
  31. data/vendor/cache/tzinfo-2.0.4.gem +0 -0
  32. data/vendor/cache/zeitwerk-2.4.2.gem +0 -0
  33. metadata +51 -11
  34. data/vendor/cache/activesupport-6.0.3.4.gem +0 -0
  35. data/vendor/cache/concurrent-ruby-1.1.7.gem +0 -0
  36. data/vendor/cache/faraday-1.1.0.gem +0 -0
  37. data/vendor/cache/i18n-1.8.5.gem +0 -0
  38. data/vendor/cache/json-2.3.1.gem +0 -0
  39. data/vendor/cache/ruby2_keywords-0.0.2.gem +0 -0
  40. data/vendor/cache/thread_safe-0.3.6.gem +0 -0
  41. data/vendor/cache/tzinfo-1.2.7.gem +0 -0
  42. data/vendor/cache/zeitwerk-2.4.0.gem +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 45c422e1920ea510d16c5c8a5b91016ac2a4eedb9a2b880ff29018dc18580a13
4
- data.tar.gz: 83e14abb44637e8d61a3fff592d8b434cbf8373cd8a4c8afede513cf89852f8a
3
+ metadata.gz: 136251a6fad727d85e14bf64bfdb5ecf33fdba19735646c3599398c7a0e44abb
4
+ data.tar.gz: 94f191ec590bc6bd68ef98b137abbaa052c8438573bb84d9cb9b63ffec4ae8e4
5
5
  SHA512:
6
- metadata.gz: 5beffb4be2a539b74e6145c4d4b7b96ae6aea94c2bb5cdf9a79dfb90bfaeac615c80b537db0249702d807acb7f69727ff7b81cfc62896d03ad6e2c44124c1360
7
- data.tar.gz: 8ae4ac844f566981de31a5d51ecfe28d4e665f9cebd5c6f5327a596b052e43e92ab0d7db9bb07029211f0786db5baf00035db8022a376b5ea3ba30ac512dbbda
6
+ metadata.gz: 613409847cfcda2948b2b3559a754ac6822fca908cec0d1a089475be19801290bf8aee866e592d00bef882b1e3b7027ead484346bd39a404c171b8205a5631fa
7
+ data.tar.gz: 3a3d8435ecce240a7cff4a83061c57412d08dd1d16e91c9995caf1f755a4706f40744e7ce11c015a4557d66ea1948b6c54caf05f893205a2f8296aedb9780e7b
data/Gemfile.lock CHANGED
@@ -1,43 +1,48 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- cased-ruby (0.3.3)
4
+ cased-ruby (0.4.4)
5
5
  activesupport (~> 6)
6
6
  dotpath (= 0.1.0)
7
7
  faraday (~> 1.0)
8
8
  faraday_middleware (~> 1.0)
9
9
  json (~> 2)
10
+ jwt (~> 2)
10
11
  net-http-persistent (~> 3.0)
12
+ subprocess (~> 1.5.0)
11
13
 
12
14
  GEM
13
15
  remote: https://rubygems.org/
14
16
  specs:
15
- activesupport (6.0.3.4)
17
+ activesupport (6.1.3)
16
18
  concurrent-ruby (~> 1.0, >= 1.0.2)
17
- i18n (>= 0.7, < 2)
18
- minitest (~> 5.1)
19
- tzinfo (~> 1.1)
20
- zeitwerk (~> 2.2, >= 2.2.2)
19
+ i18n (>= 1.6, < 2)
20
+ minitest (>= 5.1)
21
+ tzinfo (~> 2.0)
22
+ zeitwerk (~> 2.3)
21
23
  addressable (2.7.0)
22
24
  public_suffix (>= 2.0.2, < 5.0)
23
25
  ast (2.4.0)
24
26
  byebug (11.0.1)
25
- concurrent-ruby (1.1.7)
27
+ concurrent-ruby (1.1.8)
26
28
  connection_pool (2.2.2)
27
29
  crack (0.4.3)
28
30
  safe_yaml (~> 1.0.0)
29
31
  docile (1.3.2)
30
32
  dotpath (0.1.0)
31
- faraday (1.1.0)
33
+ faraday (1.3.0)
34
+ faraday-net_http (~> 1.0)
32
35
  multipart-post (>= 1.2, < 3)
33
36
  ruby2_keywords
37
+ faraday-net_http (1.0.1)
34
38
  faraday_middleware (1.0.0)
35
39
  faraday (~> 1.0)
36
40
  hashdiff (1.0.1)
37
- i18n (1.8.5)
41
+ i18n (1.8.9)
38
42
  concurrent-ruby (~> 1.0)
39
43
  jaro_winkler (1.5.4)
40
- json (2.3.1)
44
+ json (2.5.1)
45
+ jwt (2.2.2)
41
46
  minitest (5.13.0)
42
47
  mocha (1.11.2)
43
48
  multipart-post (2.1.1)
@@ -63,7 +68,7 @@ GEM
63
68
  rubocop-performance (1.5.2)
64
69
  rubocop (>= 0.71.0)
65
70
  ruby-progressbar (1.10.1)
66
- ruby2_keywords (0.0.2)
71
+ ruby2_keywords (0.0.4)
67
72
  safe_yaml (1.0.5)
68
73
  sidekiq (6.0.7)
69
74
  connection_pool (>= 2.2.2)
@@ -74,16 +79,16 @@ GEM
74
79
  docile (~> 1.1)
75
80
  simplecov-html (~> 0.11)
76
81
  simplecov-html (0.12.2)
77
- thread_safe (0.3.6)
78
- tzinfo (1.2.7)
79
- thread_safe (~> 0.1)
82
+ subprocess (1.5.4)
83
+ tzinfo (2.0.4)
84
+ concurrent-ruby (~> 1.0)
80
85
  unicode-display_width (1.6.1)
81
86
  webmock (3.8.3)
82
87
  addressable (>= 2.3.6)
83
88
  crack (>= 0.3.2)
84
89
  hashdiff (>= 0.4.0, < 2.0.0)
85
90
  yard (0.9.24)
86
- zeitwerk (2.4.0)
91
+ zeitwerk (2.4.2)
87
92
 
88
93
  PLATFORMS
89
94
  ruby
data/README.md CHANGED
@@ -7,16 +7,19 @@ A Cased client for Ruby applications in your organization to control and monitor
7
7
  - [Installation](#installation)
8
8
  - [Configuration](#configuration)
9
9
  - [Usage](#usage)
10
- - [Publishing events to Cased](#publishing-events-to-cased)
11
- - [Retrieving events from a Cased Policy](#retrieving-events-from-a-cased-policy)
12
- - [Retrieving events from a Cased Policy containing variables](#retrieving-events-from-a-cased-policy-containing-variables)
13
- - [Retrieving events from multiple Cased Policies](#retrieving-events-from-multiple-cased-policies)
14
- - [Exporting events](#exporting-events)
15
- - [Masking & filtering sensitive information](#masking-and-filtering-sensitive-information)
16
- - [Disable publishing events](#disable-publishing-events)
17
- - [Context](#context)
18
- - [Testing](#testing)
10
+ - [Cased CLI](#cased-cli)
11
+ - [Starting an approval workflow](#starting-an-approval-workflow)
12
+ - [Audit trails](#audit-trails)
13
+ - [Publishing events to Cased](#publishing-events-to-cased)
14
+ - [Retrieving events from a Cased audit trail](#retrieving-events-from-a-cased-audit-trail)
15
+ - [Retrieving events from multiple Cased audit trails](#retrieving-events-from-multiple-cased-audit-trails)
16
+ - [Exporting events](#exporting-events)
17
+ - [Masking & filtering sensitive information](#masking--filtering-sensitive-information)
18
+ - [Disable publishing events](#disable-publishing-events)
19
+ - [Context](#context)
20
+ - [Testing](#testing)
19
21
  - [Customizing cased-ruby](#customizing-cased-ruby)
22
+ - [Contributing](#contributing)
20
23
 
21
24
  ## Installation
22
25
 
@@ -56,9 +59,21 @@ Cased.configure do |config|
56
59
  # CASED_PUBLISH_URL=https://publish.cased.com
57
60
  config.publish_url = 'https://publish.cased.com'
58
61
 
62
+ # CASED_URL=https://app.cased.com
63
+ config.url = 'https://app.cased.com'
64
+
59
65
  # CASED_API_URL=https://api.cased.com
60
66
  config.api_url = 'https://api.cased.com'
61
67
 
68
+ # GUARD_APPLICATION_KEY=guard_application_1ntKX0P4vUbKoc0lMWGiSbrBHcH
69
+ config.guard_application_key = 'guard_application_1ntKX0P4vUbKoc0lMWGiSbrBHcH'
70
+
71
+ # GUARD_USER_TOKEN=user_1oFqlROLNRGVLOXJSsHkJiVmylr
72
+ config.guard_user_token = 'user_1oFqlROLNRGVLOXJSsHkJiVmylr'
73
+
74
+ # DENY_IF_UNREACHABLE=1
75
+ config.guard_deny_if_unreachable = true
76
+
62
77
  # CASED_RAISE_ON_ERRORS=1
63
78
  config.raise_on_errors = false
64
79
 
@@ -75,7 +90,100 @@ end
75
90
 
76
91
  ## Usage
77
92
 
78
- ### Publishing events to Cased
93
+ ### Cased CLI
94
+
95
+ Keep any command line tool available as your team grows — monitor usage, require peer approvals for sensitive operations, and receive intelligent alerts to suspicious activity.
96
+
97
+ #### Starting an approval workflow
98
+
99
+ To start an approval workflow you must first obtain your application key and the
100
+ user token for who is requesting access.
101
+
102
+ ```ruby
103
+ Cased.configure do |config|
104
+ config.guard_application_key = 'guard_application_1pG43HF3aRHjNTTm10zzu0tngBO'
105
+ end
106
+
107
+ authentication = Cased::CLI::Authentication.new(token: 'user_1pG43D1AzTjLR8XWJHj8B3aNZ4Y')
108
+ session = Cased::CLI::Session.new(
109
+ authentication: authentication,
110
+ reason: 'I need export our GitHub issues.',
111
+ metadata: {
112
+ organization: 'GitHub',
113
+ },
114
+ )
115
+
116
+ if session.create && session.approved?
117
+ github.issues.each do |issue|
118
+ puts issue.title
119
+ end
120
+ else
121
+ puts 'Unauthorized to export GitHub issues.'
122
+ end
123
+ ```
124
+
125
+ If you do not have the user token you can always request it interactively.
126
+ [Cased::CLI::Identity#identify](https://github.com/cased/cased-ruby/blob/3b0c8ebd37ba7deb83236be7dba4d52c74d7e4e5/lib/cased/cli/identity.rb#L10-L21)
127
+ is a blocking operation prompting the user to visit Cased to identify
128
+ themselves, returning their user token upon identifying themselves which can be
129
+ used to start your session.
130
+
131
+ ```ruby
132
+ Cased.configure do |config|
133
+ config.guard_application_key = 'guard_application_1pG43HF3aRHjNTTm10zzu0tngBO'
134
+ end
135
+
136
+ authentication = Cased::CLI::Authentication.new
137
+ identity = Cased::CLI::Identity.new
138
+ token, ip_address = identity.identify
139
+ authentication.token = token
140
+
141
+ session = Cased::CLI::Session.new(
142
+ authentication: authentication,
143
+ reason: 'I need export our GitHub issues.',
144
+ metadata: {
145
+ organization: 'GitHub',
146
+ },
147
+ )
148
+
149
+ if session.create && session.approved?
150
+ github.issues.each do |issue|
151
+ puts issue.title
152
+ end
153
+ else
154
+ puts 'Unauthorized to export GitHub issues.'
155
+ end
156
+ ```
157
+
158
+ #### Starting an interactive approval workflow
159
+
160
+ If you do not want to manually create sessions and handle each state manually,
161
+ you can use the interactive approval workflow using
162
+ [Cased::CLI::InteractiveSession](https://github.com/cased/cased-ruby/blob/3b0c8ebd37ba7deb83236be7dba4d52c74d7e4e5/lib/cased/cli/interactive_session.rb).
163
+
164
+ ```ruby
165
+ Cased.configure do |config|
166
+ config.guard_application_key = 'guard_application_1pG43HF3aRHjNTTm10zzu0tngBO'
167
+ end
168
+
169
+ session = Cased::CLI::InteractiveSession.start
170
+
171
+ if session.approved?
172
+ github.issues.each do |issue|
173
+ puts issue.title
174
+ end
175
+ else
176
+ puts 'Unauthorized to export GitHub issues.'
177
+ end
178
+ ```
179
+
180
+ You no longer need to handle obtaining the user token or asking for a reason up
181
+ front, `Cased::CLI::InteractiveSession` will prompt the user for any reason
182
+ being required as necessary.
183
+
184
+ ### Audit trails
185
+
186
+ #### Publishing events to Cased
79
187
 
80
188
  There are two ways to publish your first Cased event.
81
189
 
@@ -100,7 +208,7 @@ Cased.publish(
100
208
 
101
209
  **Cased::Model**
102
210
 
103
- cased-ruby provides a class mixin that gives you a framework to publish events.
211
+ `cased-ruby` provides a class mixin that gives you a framework to publish events.
104
212
 
105
213
  ```ruby
106
214
  require 'cased-ruby'
@@ -170,9 +278,9 @@ Both examples above are equivelent in that they publish the following `credit_ca
170
278
  }
171
279
  ```
172
280
 
173
- ### Retrieving events from a Cased Policy
281
+ #### Retrieving events from a Cased audit trail
174
282
 
175
- If you plan on retrieving events from your audit trails you must use an Cased Policy token.
283
+ If you plan on retrieving audit events from your Cased audit trail you must use a Cased API key.
176
284
 
177
285
  ```ruby
178
286
  require 'cased-ruby'
@@ -193,31 +301,9 @@ query.success? # => true
193
301
  query.error? # => false
194
302
  ```
195
303
 
196
- ### Retrieving events from a Cased Policy containing variables
197
-
198
- Cased policies allow you to filter events by providing variables to your Cased Policy events query. One example of a Cased Policy is to have a single Cased Policy that you can use to query events for any user in your database without having to create a Cased Policy for each user.
199
-
200
- ```ruby
201
- require 'cased-ruby'
202
-
203
- Cased.configure do |config|
204
- config.policy_key = 'policy_live_1dQpY5JliYgHSkEntAbMVzuOROh'
205
- end
206
-
207
- variables = {
208
- user_id: 'user_1dSHQSNtAH90KA8zGTooMnmMdiD',
209
- }
210
- query = Cased.policy(variables: variables).events.limit(25).page(1)
211
- results = query.results
212
- results.each do |event|
213
- puts event['action'] # => credit_card.charge
214
- puts event['timestamp'] # => 2020-06-23T02:02:39.932759Z
215
- end
216
- ```
217
-
218
- ### Retrieving events from multiple Cased Policies
304
+ #### Retrieving events from multiple Cased audit trails
219
305
 
220
- To retrieve events from one or more Cased Policies you can configure multiple Cased Policy API keys and retrieve events for each one.
306
+ To retrieve audit events from one or more Cased audit trails you can configure multiple Cased Policy API keys and retrieve events for each one.
221
307
 
222
308
  ```ruby
223
309
  require 'cased-ruby'
@@ -229,14 +315,14 @@ Cased.configure do |config|
229
315
  }
230
316
  end
231
317
 
232
- query = Cased.policy[:users].events.limit(25).page(1)
318
+ query = Cased.policies[:users].events.limit(25).page(1)
233
319
  results = query.results
234
320
  results.each do |event|
235
321
  puts event['action'] # => user.login
236
322
  puts event['timestamp'] # => 2020-06-23T02:02:39.932759Z
237
323
  end
238
324
 
239
- query = Cased.policy[:organizations].events.limit(25).page(1)
325
+ query = Cased.policies[:organizations].events.limit(25).page(1)
240
326
  results = query.results
241
327
  results.each do |event|
242
328
  puts event['action'] # => organization.create
@@ -244,9 +330,9 @@ results.each do |event|
244
330
  end
245
331
  ```
246
332
 
247
- ### Exporting events
333
+ #### Exporting events
248
334
 
249
- Exporting events from a Cased Policy allows you to provide users with exports of their own data or to respond to data requests.
335
+ Exporting events from Cased allows you to provide users with exports of their own data or to respond to data requests.
250
336
 
251
337
  ```ruby
252
338
  require 'cased-ruby'
@@ -262,7 +348,7 @@ export = Cased.policy.exports.create(
262
348
  export.download_url # => https://api.cased.com/exports/export_1dSHQSNtAH90KA8zGTooMnmMdiD/download?token=eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoidXNlcl8xZFFwWThiQmdFd2RwbWRwVnJydER6TVg0ZkgiLCJ
263
349
  ```
264
350
 
265
- ### Masking & filtering sensitive information
351
+ #### Masking & filtering sensitive information
266
352
 
267
353
  If you are handling sensitive information on behalf of your users you should consider masking or filtering any sensitive information.
268
354
 
@@ -279,23 +365,18 @@ Cased.publish(
279
365
  )
280
366
  ```
281
367
 
282
- ### Console Usage
368
+ #### Console Usage
283
369
 
284
- Most Cased events will be created by users from actions on the website from
285
- custom defined events or lifecycle callbacks. The exception is any console
286
- session where models may generate Cased events as you start to modify records.
370
+ Most Cased events will be created by users from actions on the website from custom defined events or lifecycle callbacks. The exception is any console session where models may generate Cased events as you start to modify records.
287
371
 
288
- By default any console session will include the hostname of where the console
289
- session takes place. Since every event must have an actor, you must set the
290
- actor at the beginning of your console session. If you don't know the user,
291
- it's recommended you create a system/robot user.
372
+ By default any console session will include the hostname of where the console session takes place. Since every event must have an actor, you must set the actor at the beginning of your console session. If you don't know the user, it's recommended you create a system/robot user.
292
373
 
293
374
  ```ruby
294
375
  # OTHER CONSOLE INITIALIZATION HERE
295
376
  Cased.context.push(actor: @actor)
296
377
  ```
297
378
 
298
- ### Disable publishing events
379
+ #### Disable publishing events
299
380
 
300
381
  Although rare, there may be times where you wish to disable publishing events to Cased. To do so wrap your transaction inside of a `Cased.disable` block:
301
382
 
@@ -311,7 +392,7 @@ Or you can configure the entire process to disable publishing events.
311
392
  CASED_DISABLE_PUBLISHING=1 bundle exec ruby crawl.rb
312
393
  ```
313
394
 
314
- ### Context
395
+ #### Context
315
396
 
316
397
  One of the most easiest ways to publish detailed events to Cased is to push contextual information on to the Cased context.
317
398
 
@@ -366,7 +447,7 @@ To clear/reset the context:
366
447
  Cased.context.clear
367
448
  ```
368
449
 
369
- ### Testing
450
+ #### Testing
370
451
 
371
452
  cased-ruby provides a test helper class that you can use to test events are being published to Cased.
372
453
 
@@ -417,7 +498,7 @@ class CreditCardTest < Test::Unit::TestCase
417
498
  end
418
499
  ```
419
500
 
420
- ## Customizing cased-ruby
501
+ ### Customizing cased-ruby
421
502
 
422
503
  Out of the box cased-ruby takes care of serializing objects for you to the best of its ability, but you can customize cased-ruby should you like to fit your products needs.
423
504
 
data/bin/cli ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'cased-ruby'
6
+
7
+ Cased.configure do |config|
8
+ config.guard_application_key = 'guard_application_1oFqltbMqSEtJQKRCAYQNrQoXsS'
9
+ config.guard_deny_if_unreachable = true
10
+ end
11
+
12
+ Cased::CLI::InteractiveSession.start
13
+
14
+ puts 'Something destructive'
data/cased-ruby.gemspec CHANGED
@@ -33,7 +33,9 @@ Gem::Specification.new do |spec|
33
33
  spec.add_dependency 'faraday', '~> 1.0'
34
34
  spec.add_dependency 'faraday_middleware', '~> 1.0'
35
35
  spec.add_dependency 'json', '~> 2'
36
+ spec.add_dependency 'jwt', '~> 2'
36
37
  spec.add_dependency 'net-http-persistent', '~> 3.0'
38
+ spec.add_dependency 'subprocess', '~> 1.5.0'
37
39
  spec.add_development_dependency 'bundler', '2.1.4'
38
40
  spec.add_development_dependency 'byebug', '11.0.1'
39
41
  spec.add_development_dependency 'minitest', '5.13.0'
data/lib/cased.rb CHANGED
@@ -15,6 +15,7 @@ require 'cased/sensitive'
15
15
  require 'cased/publishers'
16
16
  require 'cased/test_helper'
17
17
  require 'cased/instrumentation/log_subscriber'
18
+ require 'cased/cli'
18
19
 
19
20
  # Integrations
20
21
  begin
data/lib/cased/cli.rb ADDED
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cased
4
+ module CLI
5
+ end
6
+ end
7
+
8
+ require 'cased/cli/asciinema/writer'
9
+ require 'cased/cli/asciinema/file'
10
+ require 'cased/cli/recorder'
11
+ require 'cased/cli/log'
12
+ require 'cased/cli/session'
13
+ require 'cased/cli/interactive_session'
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module Cased
6
+ module CLI
7
+ # Spec: https://github.com/asciinema/asciinema/blob/develop/doc/asciicast-v2.md
8
+ module Asciinema
9
+ class File
10
+ OUT = 'o'
11
+ IN = 'i'
12
+
13
+ def self.from_writer(writer)
14
+ new(writer.header, writer.stream)
15
+ end
16
+
17
+ def self.from_cast(cast)
18
+ stream = cast.split("\n").collect do |data|
19
+ JSON.parse(data)
20
+ end
21
+ header = stream.shift
22
+
23
+ new(header, stream)
24
+ end
25
+
26
+ # Required
27
+ attr_reader :header
28
+ attr_reader :version
29
+ attr_reader :width
30
+ attr_reader :height
31
+ attr_reader :stream
32
+
33
+ # Optional
34
+ attr_reader :timestamp
35
+ attr_reader :duration
36
+ attr_reader :idle_time_limit
37
+ attr_reader :command
38
+ attr_reader :title
39
+ attr_reader :env
40
+ attr_reader :theme
41
+
42
+ def initialize(header, stream)
43
+ @header = header
44
+ @version = header.fetch('version')
45
+ @width = header.fetch('width')
46
+ @height = header.fetch('height')
47
+ self.timestamp = header['timestamp']
48
+ self.duration = header['duration']
49
+ self.idle_time_limit = header['idle_time_limit']
50
+ @command = header['command']
51
+ @title = header['title']
52
+ @env = header.fetch('env', {})
53
+ @theme = header['theme']
54
+ @stream = stream
55
+ end
56
+
57
+ def timestamp=(new_timestamp)
58
+ @timestamp = case new_timestamp
59
+ when Integer
60
+ Time.at(new_timestamp)
61
+ when Time, NilClass
62
+ new_timestamp
63
+ else
64
+ raise ArgumentError, "unexpected timestamp format #{new_timestamp.class}, expected Integer, Time, or nil"
65
+ end
66
+ end
67
+
68
+ def duration=(new_duration)
69
+ @duration = case new_duration
70
+ when Float, NilClass
71
+ new_duration
72
+ else
73
+ raise ArgumentError, "unexpected duration format #{new_duration.class}, expected Float or nil"
74
+ end
75
+ end
76
+
77
+ def idle_time_limit=(new_idle_time_limit)
78
+ @idle_time_limit = case new_idle_time_limit
79
+ when Numeric, NilClass
80
+ new_idle_time_limit
81
+ else
82
+ raise ArgumentError, "unexpected idle_time_limit format #{new_idle_time_limit.class}, expected Integer, Float, or nil"
83
+ end
84
+ end
85
+
86
+ def to_cast
87
+ builder = []
88
+ builder << JSON.dump(header)
89
+ stream.each do |duration, type, data|
90
+ builder << JSON.dump([duration, type, data])
91
+ end
92
+ builder.join("\n")
93
+ end
94
+
95
+ def to_s
96
+ str = []
97
+ stream.each do |_timestamp, type, data|
98
+ next unless type == OUT
99
+
100
+ str << data
101
+ end
102
+
103
+ str.join
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end