exel 1.2.1 → 1.5.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +5 -5
  2. data/.codeclimate.yml +4 -4
  3. data/.gitignore +1 -2
  4. data/.rubocop.yml +23 -14
  5. data/.rubocop_airbnb.yml +2 -0
  6. data/.rubocop_todo.yml +1 -13
  7. data/.travis.yml +26 -0
  8. data/Gemfile +2 -2
  9. data/Gemfile.lock +118 -0
  10. data/Guardfile +1 -0
  11. data/README.md +96 -31
  12. data/Rakefile +2 -0
  13. data/exel.gemspec +7 -7
  14. data/lib/exel.rb +7 -1
  15. data/lib/exel/ast_node.rb +6 -10
  16. data/lib/exel/context.rb +4 -1
  17. data/lib/exel/deferred_context_value.rb +3 -1
  18. data/lib/exel/error/job_termination.rb +12 -0
  19. data/lib/exel/events.rb +6 -0
  20. data/lib/exel/instruction.rb +5 -2
  21. data/lib/exel/instruction_node.rb +2 -0
  22. data/lib/exel/job.rb +8 -4
  23. data/lib/exel/listen_instruction.rb +2 -0
  24. data/lib/exel/logging.rb +24 -1
  25. data/lib/exel/logging/logger_wrapper.rb +31 -0
  26. data/lib/exel/logging_helper.rb +36 -0
  27. data/lib/exel/middleware/chain.rb +67 -0
  28. data/lib/exel/middleware/logging.rb +30 -0
  29. data/lib/exel/null_instruction.rb +2 -0
  30. data/lib/exel/processor_helper.rb +9 -1
  31. data/lib/exel/processors/async_processor.rb +2 -8
  32. data/lib/exel/processors/run_processor.rb +2 -6
  33. data/lib/exel/processors/split_processor.rb +15 -10
  34. data/lib/exel/providers/local_file_provider.rb +9 -6
  35. data/lib/exel/providers/threaded_async_provider.rb +2 -0
  36. data/lib/exel/remote_value.rb +11 -0
  37. data/lib/exel/sequence_node.rb +2 -0
  38. data/lib/exel/value.rb +2 -0
  39. data/lib/exel/version.rb +3 -1
  40. data/spec/exel/ast_node_spec.rb +48 -27
  41. data/spec/exel/context_spec.rb +77 -77
  42. data/spec/exel/deferred_context_value_spec.rb +42 -42
  43. data/spec/exel/events_spec.rb +68 -59
  44. data/spec/exel/instruction_node_spec.rb +17 -16
  45. data/spec/exel/instruction_spec.rb +49 -42
  46. data/spec/exel/job_spec.rb +99 -84
  47. data/spec/exel/listen_instruction_spec.rb +11 -10
  48. data/spec/exel/logging/logger_wrapper_spec.rb +93 -0
  49. data/spec/exel/logging_helper_spec.rb +24 -0
  50. data/spec/exel/logging_spec.rb +69 -24
  51. data/spec/exel/middleware/chain_spec.rb +65 -0
  52. data/spec/exel/middleware/logging_spec.rb +31 -0
  53. data/spec/exel/middleware_spec.rb +68 -0
  54. data/spec/exel/null_instruction_spec.rb +4 -4
  55. data/spec/exel/processors/async_processor_spec.rb +17 -18
  56. data/spec/exel/processors/run_processor_spec.rb +10 -11
  57. data/spec/exel/processors/split_processor_spec.rb +99 -74
  58. data/spec/exel/providers/local_file_provider_spec.rb +26 -28
  59. data/spec/exel/providers/threaded_async_provider_spec.rb +37 -38
  60. data/spec/exel/sequence_node_spec.rb +12 -11
  61. data/spec/exel/value_spec.rb +33 -33
  62. data/spec/exel_spec.rb +9 -7
  63. data/spec/integration/integration_spec.rb +3 -1
  64. data/spec/spec_helper.rb +4 -2
  65. data/spec/support/integration_test_classes.rb +4 -3
  66. metadata +37 -48
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: ddd9afec5ea8298052cc0ee3aee03dedb9c1e085
4
- data.tar.gz: 51c89e0ccad9b2be2dbc8961c52d0c80fad5fe24
2
+ SHA256:
3
+ metadata.gz: 546280a360b5092ca3cca1b0e0ae670560c60fb87e0c143b04405f9a5a3de9f9
4
+ data.tar.gz: 1b4b4f78ae5797c82f12abdd61057f915d1f06f53e775b4b1f502b06e1bea463
5
5
  SHA512:
6
- metadata.gz: 81c0b0b05b1d5fbd01de78c75e342e7a4beb557d9c4bc70ff5aed5835e50c108e6f3326b6fc095e15375fc38a0872decaa6df63d2f627cbcaa26a6852a12c144
7
- data.tar.gz: 3628008c5f8479ef6dab87a5229bc024117b49a3ba668a16e451f1dbdf0b216b323bd8ac80a4154eda090ac0db1c322e805ef6861a07f21c881224577e068979
6
+ metadata.gz: 0117f598e372c74606421159b405fdd4aa4fa1be3d2d658741ed4163b8c223cc3f75ce68ab0e67adbd495cd53a72b47b8411cf74135b067f2c430f00a3ff70da
7
+ data.tar.gz: 7a40ed7071070e4799681b750595c78b07326d72859f3824b9e411d94a782147fc94101b0c9466795270b41126a0ad0bbdb3a137185db7ae0ef825a90f3f5f5d
@@ -4,13 +4,13 @@ engines:
4
4
  enabled: true
5
5
  config:
6
6
  languages:
7
- - ruby
7
+ - ruby
8
8
  fixme:
9
9
  enabled: true
10
10
  rubocop:
11
- enabled: true
11
+ enabled: false
12
12
  ratings:
13
13
  paths:
14
- - "**.rb"
14
+ - "**.rb"
15
15
  exclude_paths:
16
- - spec/**/*
16
+ - spec/**/*
data/.gitignore CHANGED
@@ -3,7 +3,6 @@
3
3
  .bundle
4
4
  .config
5
5
  .yardoc
6
- Gemfile.lock
7
6
  InstalledFiles
8
7
  _yardoc
9
8
  coverage
@@ -22,4 +21,4 @@ tmp
22
21
  mkmf.log
23
22
  .idea
24
23
  *.iml
25
- .ruby-*
24
+ .ruby-*
@@ -1,22 +1,31 @@
1
- require:
2
- - rubocop-rspec
3
- - rubocop/rspec/focused
4
-
5
- inherit_from: .rubocop_todo.yml
1
+ inherit_from:
2
+ - .rubocop_airbnb.yml
3
+ - .rubocop_todo.yml
6
4
 
7
5
  AllCops:
6
+ TargetRubyVersion: 2.7
8
7
  DisplayCopNames: true
9
8
  DisplayStyleGuide: true
10
9
 
11
- Metrics/LineLength:
12
- Max: 120
10
+ Airbnb/OptArgParameters:
11
+ Exclude:
12
+ - "lib/exel/processor_helper.rb"
13
+ - "lib/exel/logging/logger_wrapper.rb"
14
+ - "lib/exel/processors/*.rb"
15
+ - "lib/exel/error/job_termination.rb"
13
16
 
14
- Style/SpaceInsideHashLiteralBraces:
15
- EnforcedStyle: no_space
17
+ Layout/DotPosition:
18
+ EnforcedStyle: leading
16
19
 
17
- RSpec/DescribedClass:
18
- Enabled: false
20
+ Layout/IndentFirstArrayElement:
21
+ IndentationWidth: 4
19
22
 
20
- Metrics/ModuleLength:
21
- Exclude:
22
- - 'spec/**/*'
23
+ Layout/MultilineMethodCallIndentation:
24
+ EnforcedStyle: indented
25
+ IndentationWidth: 4
26
+
27
+ Layout/SpaceInsideHashLiteralBraces:
28
+ EnforcedStyle: no_space
29
+
30
+ Metrics/LineLength:
31
+ Max: 120
@@ -0,0 +1,2 @@
1
+ require:
2
+ - rubocop-airbnb
@@ -1,23 +1,11 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2016-03-09 23:07:44 -0500 using RuboCop version 0.37.2.
3
+ # on 2016-10-14 22:09:13 -0400 using RuboCop version 0.39.0.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
7
7
  # versions of RuboCop, may require this file to be generated again.
8
8
 
9
- # Offense count: 5
10
- RSpec/AnyInstance:
11
- Exclude:
12
- - 'spec/exel/processors/split_processor_spec.rb'
13
- - 'spec/exel/value_spec.rb'
14
- - 'spec/integration/integration_spec.rb'
15
-
16
- # Offense count: 1
17
- RSpec/InstanceVariable:
18
- Exclude:
19
- - 'spec/exel/logging_spec.rb'
20
-
21
9
  # Offense count: 1
22
10
  Style/Documentation:
23
11
  Exclude:
@@ -0,0 +1,26 @@
1
+ env:
2
+ global:
3
+ - CC_TEST_REPORTER_ID=29a10c062e6416be84441296b1ec7b212f9c01a252e78b3e3df02cd9fb076abe
4
+
5
+ language: ruby
6
+
7
+ before_script:
8
+ - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
9
+ - chmod +x ./cc-test-reporter
10
+ - ./cc-test-reporter before-build
11
+
12
+ script: bundle exec rspec
13
+
14
+ after_script:
15
+ - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT
16
+
17
+ rvm:
18
+ - 2.6
19
+ - 2.7
20
+
21
+ notifications:
22
+ email:
23
+ recipients:
24
+ - dev@yroo.com
25
+ on_success: change
26
+ on_failure: always
data/Gemfile CHANGED
@@ -1,6 +1,6 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
5
  # Specify your gem's dependencies in exel.gemspec
4
6
  gemspec
5
-
6
- gem 'codeclimate-test-reporter', group: :test, require: nil
@@ -0,0 +1,118 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ exel (1.5.2)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ ast (2.4.0)
10
+ byebug (11.1.3)
11
+ coderay (1.1.2)
12
+ diff-lcs (1.3)
13
+ docile (1.3.2)
14
+ ffi (1.12.2)
15
+ formatador (0.2.5)
16
+ guard (2.16.2)
17
+ formatador (>= 0.2.4)
18
+ listen (>= 2.7, < 4.0)
19
+ lumberjack (>= 1.0.12, < 2.0)
20
+ nenv (~> 0.1)
21
+ notiffany (~> 0.0)
22
+ pry (>= 0.9.12)
23
+ shellany (~> 0.0)
24
+ thor (>= 0.18.1)
25
+ guard-compat (1.2.1)
26
+ guard-rspec (4.7.3)
27
+ guard (~> 2.1)
28
+ guard-compat (~> 1.1)
29
+ rspec (>= 2.99.0, < 4.0)
30
+ guard-rubocop (1.3.0)
31
+ guard (~> 2.0)
32
+ rubocop (~> 0.20)
33
+ jaro_winkler (1.5.4)
34
+ listen (3.2.1)
35
+ rb-fsevent (~> 0.10, >= 0.10.3)
36
+ rb-inotify (~> 0.9, >= 0.9.10)
37
+ lumberjack (1.2.4)
38
+ method_source (1.0.0)
39
+ nenv (0.3.0)
40
+ notiffany (0.1.3)
41
+ nenv (~> 0.1)
42
+ shellany (~> 0.0)
43
+ parallel (1.19.1)
44
+ parser (2.7.1.1)
45
+ ast (~> 2.4.0)
46
+ pry (0.13.1)
47
+ coderay (~> 1.1)
48
+ method_source (~> 1.0)
49
+ pry-byebug (3.9.0)
50
+ byebug (~> 11.0)
51
+ pry (~> 0.13.0)
52
+ rack (2.2.2)
53
+ rainbow (3.0.0)
54
+ rake (13.0.1)
55
+ rb-fsevent (0.10.3)
56
+ rb-inotify (0.10.1)
57
+ ffi (~> 1.0)
58
+ rspec (3.9.0)
59
+ rspec-core (~> 3.9.0)
60
+ rspec-expectations (~> 3.9.0)
61
+ rspec-mocks (~> 3.9.0)
62
+ rspec-core (3.9.1)
63
+ rspec-support (~> 3.9.1)
64
+ rspec-expectations (3.9.1)
65
+ diff-lcs (>= 1.2.0, < 2.0)
66
+ rspec-support (~> 3.9.0)
67
+ rspec-mocks (3.9.1)
68
+ diff-lcs (>= 1.2.0, < 2.0)
69
+ rspec-support (~> 3.9.0)
70
+ rspec-support (3.9.2)
71
+ rubocop (0.76.0)
72
+ jaro_winkler (~> 1.5.1)
73
+ parallel (~> 1.10)
74
+ parser (>= 2.6)
75
+ rainbow (>= 2.2.2, < 4.0)
76
+ ruby-progressbar (~> 1.7)
77
+ unicode-display_width (>= 1.4.0, < 1.7)
78
+ rubocop-airbnb (3.0.2)
79
+ rubocop (~> 0.76.0)
80
+ rubocop-performance (~> 1.5.0)
81
+ rubocop-rails (~> 2.3.2)
82
+ rubocop-rspec (~> 1.30.0)
83
+ rubocop-performance (1.5.2)
84
+ rubocop (>= 0.71.0)
85
+ rubocop-rails (2.3.2)
86
+ rack (>= 1.1)
87
+ rubocop (>= 0.72.0)
88
+ rubocop-rspec (1.30.1)
89
+ rubocop (>= 0.60.0)
90
+ ruby-progressbar (1.10.1)
91
+ shellany (0.0.1)
92
+ simplecov (0.18.5)
93
+ docile (~> 1.1)
94
+ simplecov-html (~> 0.11)
95
+ simplecov-html (0.12.2)
96
+ terminal-notifier (1.6.3)
97
+ terminal-notifier-guard (1.7.0)
98
+ thor (1.0.1)
99
+ unicode-display_width (1.6.1)
100
+
101
+ PLATFORMS
102
+ ruby
103
+
104
+ DEPENDENCIES
105
+ exel!
106
+ guard (~> 2)
107
+ guard-rspec (~> 4)
108
+ guard-rubocop (~> 1)
109
+ pry-byebug
110
+ rake (~> 13)
111
+ rspec (~> 3)
112
+ rubocop-airbnb (~> 3.0)
113
+ simplecov (~> 0.17)
114
+ terminal-notifier (~> 1.6.0)
115
+ terminal-notifier-guard (~> 1.7.0)
116
+
117
+ BUNDLED WITH
118
+ 2.1.4
data/Guardfile CHANGED
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  group :rspec_rubocop, halt_on_fail: true do
2
3
  guard :rspec, cmd: 'bundle exec rspec' do
3
4
  require 'guard/rspec/dsl'
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
  [![Gem Version](https://badge.fury.io/rb/exel.svg)](https://badge.fury.io/rb/exel)
3
3
  [![Code Climate](https://codeclimate.com/github/47colborne/exel/badges/gpa.svg)](https://codeclimate.com/github/47colborne/exel)
4
4
  [![Test Coverage](https://codeclimate.com/github/47colborne/exel/badges/coverage.svg)](https://codeclimate.com/github/47colborne/exel/coverage)
5
- [![Build Status](https://snap-ci.com/47colborne/exel/branch/master/build_image)](https://snap-ci.com/47colborne/exel/branch/master)
5
+ [![Build Status](https://travis-ci.org/47colborne/exel.svg?branch=master)](https://travis-ci.org/47colborne/exel)
6
6
  [![Documentation](http://img.shields.io/badge/docs-rdoc.info-blue.svg)](http://www.rubydoc.info/github/47colborne/exel/master)
7
7
 
8
8
  EXEL is the Elastic eXEcution Language, a simple Ruby DSL for creating processing jobs that can be run on a single machine, or scaled up to run on dozens of machines with no changes to the job itself. To run a job on more than one machine, simply install EXEL async and remote provider gems to integrate with your preferred platforms. The currently implemented providers so far are:
@@ -35,15 +35,17 @@ Or install it yourself as:
35
35
 
36
36
  A processor can be any class that provides the following interface:
37
37
 
38
- class MyProcessor
39
- def initialize(context)
40
- # typically context is assigned to @context here
41
- end
42
-
43
- def process(block)
44
- # do your work here
45
- end
46
- end
38
+ ```ruby
39
+ class MyProcessor
40
+ def initialize(context)
41
+ # typically context is assigned to @context here
42
+ end
43
+
44
+ def process(block)
45
+ # do your work here
46
+ end
47
+ end
48
+ ```
47
49
 
48
50
  Processors are initialized immediately before ```#process``` is called, allowing them to set up any state that they need from the context. The ```#process``` method is where your processing logic will be implemented. Processors should be focused on performing one particular aspect of the processing that you want to accomplish, allowing your job to be composed of a sequence of small processing steps. If a block was given in the call to ```process``` in the job DSL, it will be passed as the argument to ```#process``` and can be run with: ```block.run(@context)```
49
51
 
@@ -59,35 +61,98 @@ If you use EXEL with an async provider, such as [exel-sidekiq](https://github.co
59
61
 
60
62
  ### Supported Instructions
61
63
 
62
- * ```process``` Execute the given processor class (specified by the ```:with``` option), given the current context and any additional arguments provided
63
- * ```split``` Split the input data into 1000 line chunks and run the given block for each chunk. Assumes that the input data is a CSV formatted file referenced by ```context[:resource]```. When each block is run, ```context[:resource]``` will reference to the chunk file.
64
- * ```async``` Asynchronously run the given block. Uses the configured async provider to execute the block.
64
+ * ```process``` Executes the given processor class (specified by the ```:with``` option), given the current context and any additional arguments provided
65
+ * ```split``` Splits the input data into 1000 line chunks and run the given block for each chunk. Assumes that the input data is a CSV formatted file referenced by ```context[:resource]```. When each block is run, ```context[:resource]``` will reference to the chunk file.
66
+ * ```async``` Asynchronously runs the given block. Uses the configured async provider to execute the block.
65
67
  * ```run``` Runs the job specified by the ```:job``` option. The job will run using the current context.
68
+ * ```listen``` Registers an event listener. See the [Events](#events) section below for more detail.
66
69
 
67
70
  ### Example job
68
71
 
69
- EXEL::Job.define :example_job do
70
- # Download a large CSV data file
71
- process with: FTPDownloader, host: ftp.example.com, path: context[:file_path]
72
-
73
- # split it into smaller 1000 line files
74
- split do
75
- # for each file asynchronously run the following sequence of processors
76
- async do
77
- process with: RecordLoader # convert each row of data into your domain model
78
- process with: SomeProcessor # apply some additional processing to each record
79
- process with: RecordSaver # write this batch of records to your database
80
- process with: ExternalServiceProcessor # interact with some service, ex: updating a search index
81
- end
82
- end
72
+ ```ruby
73
+ EXEL::Job.define :example_job do
74
+ # Download a large CSV data file
75
+ process with: FTPDownloader, host: ftp.example.com, path: context[:file_path]
76
+
77
+ # split it into smaller 1000 line files
78
+ split do
79
+ # for each file asynchronously run the following sequence of processors
80
+ async do
81
+ process with: RecordLoader # convert each row of data into your domain model
82
+ process with: SomeProcessor # apply some additional processing to each record
83
+ process with: RecordSaver # write this batch of records to your database
84
+ process with: ExternalServiceProcessor # interact with some service, ex: updating a search index
83
85
  end
86
+ end
87
+ end
88
+ ```
84
89
 
85
90
  Elsewhere in your application, you could run this job as follows:
86
91
 
87
- def run_example_job(file_path)
88
- context = EXEL::Context.new(file_path: file_path, user: 'username')
89
- EXEL::Job.run(:example_job, context)
90
- end
92
+ ```ruby
93
+ def run_example_job(file_path)
94
+ # context can also be passed as a Hash
95
+ context = EXEL::Context.new(file_path: file_path, user: 'username')
96
+ EXEL::Job.run(:example_job, context)
97
+ end
98
+ ```
99
+
100
+ ### Events
101
+
102
+ Event listeners can be registered using the ```listen``` instruction:
103
+
104
+ ```ruby
105
+ listen for: :my_event, with: MyEventListener
106
+ ```
107
+
108
+ The event listener must implement a method with the same name as the event which accepts two arguments: the context and any data passed when the event was triggered:
109
+
110
+ ```ruby
111
+ class MyEventListener
112
+ def self.my_event(context, data)
113
+ # handle event
114
+ end
115
+ end
116
+ ```
117
+
118
+ To trigger an event, include the ```EXEL::Events``` module and call #trigger with the event name and data:
119
+
120
+ ```ruby
121
+ include EXEL::Events
122
+
123
+ def process(_block)
124
+ # trigger event and optionally pass data to the event listener
125
+ trigger :my_event, foo: 'bar'
126
+ end
127
+ ```
128
+
129
+ ### Middleware
130
+
131
+ Middleware is code configured to run around each processor execution. It is modelled after [Rack](https://github.com/rack/rack) and [Sidekiq](https://github.com/mperham/sidekiq). Custom middleware can be added as follows:
132
+
133
+ ```ruby
134
+ EXEL.configure do |config|
135
+ config.middleware.add(MyMiddleware)
136
+ config.middleware.add(AnotherMiddleware, 'constructor arg')
137
+ end
138
+ ```
139
+
140
+ Middleware can be any class that implements a ```call``` method that includes a call to ```yield```:
141
+
142
+ ```ruby
143
+ class MyMiddleware
144
+ def call(processor_class, context, args)
145
+ puts 'before process'
146
+
147
+ # must yield so other middleware and processor will run
148
+ yield
149
+
150
+ puts 'after process'
151
+ end
152
+ end
153
+ ```
154
+
155
+ The ```call``` method will be passed the class of the processor that will be executed, the current context, and any args that were passed to the processor in the job definition.
91
156
 
92
157
  ## Contributing
93
158
 
data/Rakefile CHANGED
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'bundler/gem_tasks'
@@ -1,4 +1,6 @@
1
1
  # coding: utf-8
2
+ # frozen_string_literal: true
3
+
2
4
  lib = File.expand_path('../lib', __FILE__)
3
5
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
6
  require 'exel/version'
@@ -18,16 +20,14 @@ Gem::Specification.new do |spec|
18
20
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
21
  spec.require_paths = ['lib']
20
22
 
21
- spec.add_development_dependency 'bundler', '~> 1.6'
22
- spec.add_development_dependency 'rake', '~> 10'
23
+ spec.add_development_dependency 'rake', '~> 13'
23
24
  spec.add_development_dependency 'rspec', '~> 3'
25
+ spec.add_development_dependency 'simplecov', '~> 0.17'
24
26
  spec.add_development_dependency 'guard', '~> 2'
25
27
  spec.add_development_dependency 'guard-rspec', '~> 4'
26
28
  spec.add_development_dependency 'guard-rubocop', '~> 1'
27
- spec.add_development_dependency 'terminal-notifier', '~> 1'
28
- spec.add_development_dependency 'terminal-notifier-guard', '~> 1'
29
- spec.add_development_dependency 'rubocop', '~> 0.37.0'
30
- spec.add_development_dependency 'rubocop-rspec', '~> 1'
31
- spec.add_development_dependency 'rubocop-rspec-focused', '~> 0'
29
+ spec.add_development_dependency 'terminal-notifier', '~> 1.6.0'
30
+ spec.add_development_dependency 'terminal-notifier-guard', '~> 1.7.0'
31
+ spec.add_development_dependency 'rubocop-airbnb', '~> 3.0'
32
32
  spec.add_development_dependency 'pry-byebug'
33
33
  end