neverbounce-cli 1.0.0

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.
Files changed (83) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +8 -0
  3. data/.gitignore +21 -0
  4. data/.rspec +4 -0
  5. data/.rubocop.yml +1161 -0
  6. data/.travis.yml +27 -0
  7. data/.yardopts +2 -0
  8. data/Gemfile +19 -0
  9. data/Gemfile.lock +60 -0
  10. data/LICENSE +9 -0
  11. data/README.md +138 -0
  12. data/exe/nb-account-info +5 -0
  13. data/exe/nb-jobs-create +5 -0
  14. data/exe/nb-jobs-delete +5 -0
  15. data/exe/nb-jobs-download +5 -0
  16. data/exe/nb-jobs-parse +5 -0
  17. data/exe/nb-jobs-results +5 -0
  18. data/exe/nb-jobs-search +5 -0
  19. data/exe/nb-jobs-start +5 -0
  20. data/exe/nb-jobs-status +5 -0
  21. data/exe/nb-single-check +5 -0
  22. data/lib/never_bounce/cli/feature/basic_initialize.rb +20 -0
  23. data/lib/never_bounce/cli/feature/eigencache.rb +47 -0
  24. data/lib/never_bounce/cli/feature/envars/examples_mapper.rb +38 -0
  25. data/lib/never_bounce/cli/feature/envars/item.rb +41 -0
  26. data/lib/never_bounce/cli/feature/envars.rb +82 -0
  27. data/lib/never_bounce/cli/feature/igetset.rb +41 -0
  28. data/lib/never_bounce/cli/feature/require_attr.rb +25 -0
  29. data/lib/never_bounce/cli/script/account_info.rb +85 -0
  30. data/lib/never_bounce/cli/script/base.rb +101 -0
  31. data/lib/never_bounce/cli/script/error.rb +16 -0
  32. data/lib/never_bounce/cli/script/feature/requires_job_id.rb +28 -0
  33. data/lib/never_bounce/cli/script/feature/uses_pagination.rb +45 -0
  34. data/lib/never_bounce/cli/script/jobs_create/supplied_input_parser.rb +48 -0
  35. data/lib/never_bounce/cli/script/jobs_create.rb +157 -0
  36. data/lib/never_bounce/cli/script/jobs_delete.rb +55 -0
  37. data/lib/never_bounce/cli/script/jobs_download.rb +44 -0
  38. data/lib/never_bounce/cli/script/jobs_parse.rb +73 -0
  39. data/lib/never_bounce/cli/script/jobs_results.rb +105 -0
  40. data/lib/never_bounce/cli/script/jobs_search.rb +135 -0
  41. data/lib/never_bounce/cli/script/jobs_start.rb +73 -0
  42. data/lib/never_bounce/cli/script/jobs_status.rb +96 -0
  43. data/lib/never_bounce/cli/script/manifest.rb +22 -0
  44. data/lib/never_bounce/cli/script/meaningful.rb +233 -0
  45. data/lib/never_bounce/cli/script/request_maker.rb +169 -0
  46. data/lib/never_bounce/cli/script/single_check.rb +146 -0
  47. data/lib/never_bounce/cli/script/table.rb +43 -0
  48. data/lib/never_bounce/cli/user_config/file_content.rb +66 -0
  49. data/lib/never_bounce/cli/user_config.rb +51 -0
  50. data/lib/never_bounce/cli/version.rb +4 -0
  51. data/lib/neverbounce-cli.rb +3 -0
  52. data/neverbounce-cli.gemspec +24 -0
  53. data/spec/lib/never_bounce/cli/feature/basic_initialize_spec.rb +26 -0
  54. data/spec/lib/never_bounce/cli/feature/eigencache_spec.rb +28 -0
  55. data/spec/lib/never_bounce/cli/feature/envars/examples_mapper_spec.rb +26 -0
  56. data/spec/lib/never_bounce/cli/feature/envars/item_spec.rb +9 -0
  57. data/spec/lib/never_bounce/cli/feature/envars_spec.rb +55 -0
  58. data/spec/lib/never_bounce/cli/feature/igetset_spec.rb +45 -0
  59. data/spec/lib/never_bounce/cli/feature/require_attr_spec.rb +25 -0
  60. data/spec/lib/never_bounce/cli/script/account_info_spec.rb +65 -0
  61. data/spec/lib/never_bounce/cli/script/base_spec.rb +56 -0
  62. data/spec/lib/never_bounce/cli/script/feature/requires_job_id_spec.rb +17 -0
  63. data/spec/lib/never_bounce/cli/script/feature/uses_pagination_spec.rb +21 -0
  64. data/spec/lib/never_bounce/cli/script/jobs_create/supplied_input_parser_spec.rb +35 -0
  65. data/spec/lib/never_bounce/cli/script/jobs_create_spec.rb +118 -0
  66. data/spec/lib/never_bounce/cli/script/jobs_delete_spec.rb +33 -0
  67. data/spec/lib/never_bounce/cli/script/jobs_download_spec.rb +37 -0
  68. data/spec/lib/never_bounce/cli/script/jobs_parse_spec.rb +45 -0
  69. data/spec/lib/never_bounce/cli/script/jobs_results_spec.rb +40 -0
  70. data/spec/lib/never_bounce/cli/script/jobs_search_spec.rb +59 -0
  71. data/spec/lib/never_bounce/cli/script/jobs_start_spec.rb +45 -0
  72. data/spec/lib/never_bounce/cli/script/jobs_status_spec.rb +37 -0
  73. data/spec/lib/never_bounce/cli/script/manifest_spec.rb +6 -0
  74. data/spec/lib/never_bounce/cli/script/meaningful_spec.rb +93 -0
  75. data/spec/lib/never_bounce/cli/script/request_maker_spec.rb +67 -0
  76. data/spec/lib/never_bounce/cli/script/single_check_spec.rb +94 -0
  77. data/spec/lib/never_bounce/cli/script/spec_helper.rb +59 -0
  78. data/spec/lib/never_bounce/cli/script/table_spec.rb +6 -0
  79. data/spec/lib/never_bounce/cli/user_config/file_content_spec.rb +43 -0
  80. data/spec/lib/never_bounce/cli/user_config_spec.rb +12 -0
  81. data/spec/lib/never_bounce/cli_spec.rb +6 -0
  82. data/spec/spec_helper.rb +51 -0
  83. metadata +193 -0
@@ -0,0 +1,135 @@
1
+
2
+ require "neverbounce"
3
+
4
+ require "never_bounce/cli/script/feature/uses_pagination"
5
+
6
+ require_relative "request_maker"
7
+
8
+ module NeverBounce; module CLI; module Script
9
+ class JobsSearch < RequestMaker
10
+ Script::Feature::UsesPagination.load(self)
11
+
12
+ attr_writer :job_id
13
+
14
+ envar "ID", "Filter by Job ID", ["276816"]
15
+
16
+ # Job ID. Default is <tt>env["ID"]</tt>.
17
+ # @!attribute job_id
18
+ # @return [String]
19
+ def job_id
20
+ igetset(:job_id) { env["ID"] }
21
+ end
22
+
23
+ # An <tt>API::Request::JobsSearch</tt>.
24
+ # @!attribute request
25
+ # @return [Object]
26
+ def request
27
+ @request ||= API::Request::JobsSearch.new({
28
+ api_key: api_key,
29
+ job_id: job_id,
30
+ page: page,
31
+ per_page: per_page,
32
+ })
33
+ end
34
+
35
+ #--------------------------------------- Manifest
36
+
37
+ # @!attribute manifest
38
+ # @return [Manifest]
39
+ def manifest
40
+ @manifest ||= Manifest.new(
41
+ name: "nb-jobs-search",
42
+ function: "List jobs",
43
+ cmdline: "[options] [VAR1=value] [VAR2=value] ...",
44
+ )
45
+ end
46
+
47
+ #--------------------------------------- Main
48
+
49
+ # @return [Integer]
50
+ def slim_main
51
+ "Response".tap do |label|
52
+ headings = [
53
+ ["nPages", :total_pages, :right],
54
+ ["nResults", :total_results, :right],
55
+
56
+ ["ExecTime", :execution_time, :right],
57
+ ]
58
+
59
+ table = Table.new(
60
+ headings: headings.map { |ar| ar[0] },
61
+ rows: [headings.map { |ar| get_table_value(response, ar) }],
62
+ ).align!(headings)
63
+
64
+ stdout.puts "\n#{label}:"
65
+ stdout.puts table
66
+ end
67
+
68
+ "Query".tap do |label|
69
+ headings = [
70
+ ["Page", :page, :right],
71
+ ["PerPage", :items_per_page, :right],
72
+ ]
73
+
74
+ table = Table.new(
75
+ headings: headings.map { |ar| ar[0] },
76
+ rows: [headings.map { |ar| get_table_value(response.query, ar) }],
77
+ ).align!(headings)
78
+
79
+ stdout.puts "\n#{label}:"
80
+ stdout.puts table
81
+ end
82
+
83
+ "Results".tap do |label|
84
+ headings = [
85
+ ["ID", :id, :right],
86
+ ["JobStatus", :job_status, :center],
87
+ ["Filename", :filename, :center],
88
+ ["BncEst", ->(r) { r.bounce_estimate.round(2) }, :right],
89
+ ["Complete%", :percent_complete, :right],
90
+
91
+ [
92
+ "At",
93
+ ->(r) { [
94
+ "Created:#{inil(r.created_at)}",
95
+ "Started:#{inil(r.started_at)}",
96
+ "Finished:#{inil(r.finished_at)}",
97
+ ].join("\n") },
98
+ :right,
99
+ ],
100
+
101
+ [
102
+ "Total",
103
+ ->(r) { [
104
+ "BadSynt:#{inil(r.total.bad_syntax)}",
105
+ "Billable:#{inil(r.total.billable)}",
106
+ "Catchall:#{inil(r.total.catchall)}",
107
+ "Disp:#{inil(r.total.disposable)}",
108
+ "Dup:#{inil(r.total.duplicates)}",
109
+ "Invalid:#{inil(r.total.invalid)}",
110
+ "Proc'd:#{inil(r.total.processed)}",
111
+ "Records:#{inil(r.total.records)}",
112
+ "Unknown:#{inil(r.total.unknown)}",
113
+ "Valid:#{inil(r.total.valid)}",
114
+ ].join("\n") },
115
+ :right,
116
+ ],
117
+ ]
118
+
119
+ table = Table.new(
120
+ headings: headings.map { |ar| ar[0] },
121
+ rows: response.results.map do |r|
122
+ headings.map do |ar|
123
+ get_table_value(r, ar)
124
+ end
125
+ end,
126
+ ).align!(headings)
127
+
128
+ stdout.puts "\n#{label}:"
129
+ stdout.puts table
130
+ end
131
+
132
+ 0
133
+ end
134
+ end
135
+ end; end; end
@@ -0,0 +1,73 @@
1
+
2
+ require "neverbounce"
3
+
4
+ require "never_bounce/cli/script/feature/requires_job_id"
5
+
6
+ require_relative "request_maker"
7
+
8
+ module NeverBounce; module CLI; module Script
9
+ class JobsStart < RequestMaker
10
+ Script::Feature::RequiresJobId.load(self)
11
+
12
+ attr_writer :run_sample
13
+
14
+ envar (k = "RUN_SAMPLE"), *SHARED_ENVARS[k]
15
+
16
+ # @return [true]
17
+ # @return [false]
18
+ # @return [nil]
19
+ def run_sample
20
+ igetset(:run_sample) do
21
+ if env.has_key?(k = "RUN_SAMPLE")
22
+ env_truthy?(k)
23
+ end
24
+ end
25
+ end
26
+
27
+ # An <tt>API::Request::JobsStart</tt>.
28
+ # @!attribute request
29
+ # @return [Object]
30
+ def request
31
+ @request ||= API::Request::JobsStart.new({
32
+ api_key: api_key,
33
+ job_id: job_id,
34
+ run_sample: run_sample,
35
+ })
36
+ end
37
+
38
+ #--------------------------------------- Manifest
39
+
40
+ # @!attribute manifest
41
+ # @return [Manifest]
42
+ def manifest
43
+ @manifest ||= Manifest.new(
44
+ name: "nb-jobs-start",
45
+ function: "Start a job created with `auto_start` disabled",
46
+ cmdline: "[options] [VAR1=value] [VAR2=value] ...",
47
+ )
48
+ end
49
+
50
+ #--------------------------------------- Main
51
+
52
+ # @return [Integer]
53
+ def slim_main
54
+ "Response".tap do |label|
55
+ headings = [
56
+ ["QueueId", :queue_id],
57
+
58
+ ["ExecTime", :execution_time, :right],
59
+ ]
60
+
61
+ table = Table.new(
62
+ headings: headings.map { |ar| ar[0] },
63
+ rows: [headings.map { |ar| get_table_value(response, ar) }],
64
+ ).align!(headings)
65
+
66
+ stdout.puts "\n#{label}:"
67
+ stdout.puts table
68
+ end
69
+
70
+ 0
71
+ end
72
+ end
73
+ end; end; end
@@ -0,0 +1,96 @@
1
+
2
+ require "neverbounce"
3
+
4
+ require "never_bounce/cli/script/feature/requires_job_id"
5
+
6
+ require_relative "request_maker"
7
+
8
+ module NeverBounce; module CLI; module Script
9
+ class JobsStatus < RequestMaker
10
+ Script::Feature::RequiresJobId.load(self)
11
+
12
+ # An <tt>API::Request::JobsStatus</tt>.
13
+ # @!attribute request
14
+ # @return [Object]
15
+ def request
16
+ @request ||= API::Request::JobsStatus.new({
17
+ api_key: api_key,
18
+ job_id: job_id,
19
+ })
20
+ end
21
+
22
+ #--------------------------------------- Manifest
23
+
24
+ # @!attribute manifest
25
+ # @return [Manifest]
26
+ def manifest
27
+ @manifest ||= Manifest.new(
28
+ name: "nb-jobs-status",
29
+ function: "Get job status",
30
+ cmdline: "[options] [VAR1=value] [VAR2=value] ...",
31
+ )
32
+ end
33
+
34
+ #--------------------------------------- Main
35
+
36
+ # @return [Integer]
37
+ def slim_main
38
+ "Response".tap do |label|
39
+ headings = [
40
+ ["ID", :id, :right],
41
+
42
+ ["JStatus", :job_status, :center],
43
+ ["BncEst", ->(r) { r.bounce_estimate.round(2) }, :right],
44
+ ["Complete%", :percent_complete, :right],
45
+
46
+ [
47
+ "At",
48
+ ->(r) { [
49
+ "Created:#{inil(r.created_at)}",
50
+ "Started:#{inil(r.started_at)}",
51
+ "Finished:#{inil(r.finished_at)}",
52
+ ].join("\n") },
53
+ :right,
54
+ ],
55
+
56
+ ["Filename", :filename],
57
+
58
+ ["ExecTime", :execution_time, :right],
59
+ ]
60
+
61
+ table = Table.new(
62
+ headings: headings.map { |ar| ar[0] },
63
+ rows: [headings.map { |ar| get_table_value(response, ar) }],
64
+ ).align!(headings)
65
+
66
+ stdout.puts "\n#{label}:"
67
+ stdout.puts table
68
+ end
69
+
70
+ "Total".tap do |label|
71
+ headings = [
72
+ ["BadSyntax", :bad_syntax, :right],
73
+ ["Billable", :billable, :right],
74
+ ["Catchall", :catchall, :right],
75
+ ["Disposable", :disposable, :right],
76
+ ["Duplicates", :duplicates, :right],
77
+ ["Invalid", :invalid, :right],
78
+ ["Processed", :processed, :right],
79
+ ["Records", :records, :right],
80
+ ["Unknown", :unknown, :right],
81
+ ["Valid", :valid, :right],
82
+ ]
83
+
84
+ table = Table.new(
85
+ headings: headings.map { |ar| ar[0] },
86
+ rows: [headings.map { |ar| get_table_value(response.total, ar) }],
87
+ ).align!(headings)
88
+
89
+ stdout.puts "\n#{label}:"
90
+ stdout.puts table
91
+ end
92
+
93
+ 0
94
+ end
95
+ end
96
+ end; end; end
@@ -0,0 +1,22 @@
1
+
2
+ require "never_bounce/cli/feature/basic_initialize"
3
+
4
+ module NeverBounce; module CLI; module Script
5
+ # A short descriptive piece of information about the script.
6
+ # @see CLI::Feature::BasicInitialize
7
+ class Manifest
8
+ CLI::Feature::BasicInitialize.load(self)
9
+
10
+ # Script command-line options.
11
+ # @return [String]
12
+ attr_accessor :cmdline
13
+
14
+ # Script function, one line.
15
+ # @return [String]
16
+ attr_accessor :function
17
+
18
+ # Script name, as typed in a shell.
19
+ # @return [String]
20
+ attr_accessor :name
21
+ end
22
+ end; end; end
@@ -0,0 +1,233 @@
1
+
2
+ require "optparse"
3
+
4
+ require "never_bounce/cli/feature/envars"
5
+ require "never_bounce/cli/script/error"
6
+ require "never_bounce/cli/script/manifest" # Provide successors with the class they'll need.
7
+
8
+ require_relative "base"
9
+
10
+ module NeverBounce; module CLI; module Script
11
+ # A meaningful base script class. Features:
12
+ #
13
+ # * Handle command-line options.
14
+ # * Handle envars.
15
+ # * Handle boilerplate actions like printing usage on `--help`.
16
+ #
17
+ # @abstract
18
+ # @see CLI::Feature::Envars
19
+ # @see CLI::Feature::Igetset
20
+ class Meaningful < Base
21
+ CLI::Feature::Envars.load(self)
22
+ CLI::Feature::Igetset.load(self)
23
+
24
+ attr_writer :banner_text, :envar_text, :help_text, :manifest, :options_text
25
+
26
+ # @return [String]
27
+ def banner_text
28
+ @banner_text ||= "#{manifest.name} - #{manifest.function}"
29
+ end
30
+
31
+ # @return [String]
32
+ def envar_text
33
+ @envar_text ||= begin
34
+ max_width = (envars = self.class.envars).map(&:name).map(&:size).max
35
+ envars.sort_by { |_| [_.mandatory?? 0 : 1, _.name] }.map do |r|
36
+ "%s %-#{max_width}s - %s%s" % [
37
+ (r.mandatory?? "*" : "-"),
38
+ r.name,
39
+ r.comment,
40
+ (s = self.class.format_envar_examples(r)) ? " (#{s})" : "",
41
+ ]
42
+ end.join("\n")
43
+ end
44
+ end
45
+
46
+ # Exception classes which are rescued fromc in {#main} and printed to user.
47
+ # @note Should be a few very high-level excaptions which you fully control.
48
+ # @return [Array]
49
+ def self.error_klasses
50
+ [Error]
51
+ end
52
+
53
+ # <tt>true</tt> if help has been requested via command-line options.
54
+ def help?
55
+ !!options[:help]
56
+ end
57
+
58
+ def help_text
59
+ @help_text ||= begin
60
+ [
61
+ banner_text,
62
+ "",
63
+ "USAGE: #{manifest.name} #{manifest.cmdline}",
64
+ "",
65
+ options_text,
66
+ "",
67
+ "Environment variables:",
68
+ envar_text,
69
+ ].join("\n")
70
+ end
71
+ end
72
+
73
+ # Parse command-line options with <tt>OptionParser</tt>.
74
+ #
75
+ # options # => {:help => true}
76
+ #
77
+ # @note This isn't an attribute and there's no writer for it. For simulation and testing
78
+ # we use {#argv=}.
79
+ # @return [Hash]
80
+ def options
81
+ @options ||= begin
82
+ h = {}
83
+
84
+ @option_parser = OptionParser.new do |opts|
85
+ opts.banner = "" # A hack to remove Ruby's "-e".
86
+
87
+ opts.on("-h", "--help", "Show help information") do
88
+ h[:help] = true
89
+ end
90
+ end
91
+
92
+ rmn_options = begin
93
+ @option_parser.parse!(argv)
94
+ rescue OptionParser::ParseError => e
95
+ (h[:errors] ||= []) << "Error: #{e.message}"
96
+ retry
97
+ end
98
+
99
+ # Parse and add "KEY=value" options to environment.
100
+ rmn_options.reject! do |s|
101
+ if s =~ /^(\w+)=(.*)$/m # NOTE: `/m` allows for multiline options.
102
+ env[$1] = $2
103
+ true
104
+ end
105
+ end
106
+
107
+ # Treat remaining options as errors.
108
+ # If this behaviour changes, we should provide access to `rmn_options` via method.
109
+ rmn_options.each do |s|
110
+ (h[:errors] ||= []) << "Error: unexpected option: #{s}"
111
+ end
112
+
113
+ h
114
+ end
115
+ end
116
+
117
+ # Our <tt>OptionParser</tt> object.
118
+ def option_parser
119
+ options if not @options
120
+ @option_parser
121
+ end
122
+
123
+ def options_text
124
+ @options_text ||= option_parser.help.strip
125
+ end
126
+
127
+ #---------------------------------------
128
+
129
+ # Invoke one of the slim_main methods available in self.
130
+ #
131
+ # call_slim_main(3) # Try <tt>slim_main3</tt>, then <tt>slim_main2</tt> down to <tt>slim_main</tt>.
132
+ #
133
+ # @return [Integer] Result of <tt>slim_main[N]</tt>.
134
+ def call_slim_main(from_level = 3)
135
+ from_level.downto(0) do |i|
136
+ if respond_to?(m = "slim_main#{i > 0 ? i : ''}")
137
+ return send(m)
138
+ end
139
+ end
140
+
141
+ # This is in theory possible, stay sane.
142
+ raise "No `slim_main` responded, check your class hierarchy"
143
+ end
144
+ private :call_slim_main
145
+
146
+ #--------------------------------------- `main` and its friends
147
+
148
+ # Format an envar examples string.
149
+ #
150
+ # format_examples_string(envar) # => ""a", *"B""
151
+ #
152
+ # @return [String] Insertion-ready string or <tt>nil</tt> if envar doesn't have examples.
153
+ def self.format_envar_examples(envar)
154
+ return nil if envar.examples.empty?
155
+
156
+ envar.examples.map do |v|
157
+ if envar.default and v == envar.default
158
+ "[" + v.inspect + "]"
159
+ else
160
+ v.inspect
161
+ end
162
+ end.join(", ")
163
+ end
164
+
165
+ # Handle help request and invalid options.
166
+ # @note See method source for important details.
167
+ # @return [Integer] Program exit code (0, 1) if the program should exit now.
168
+ # @return [nil] If all okay.
169
+ def handle_help_and_options
170
+ # NOTES:
171
+ #
172
+ # * Due to `OptionParser` specifics we don't use on-the-fly handling of options.
173
+ # We do it procedurally instead, to avoid running into a circular dependency.
174
+ # Thus, it's okay to call this method directly in specs as long as `argv` is concerned.
175
+ # * The method has internal protection to ensure it's called just ones to make speccing a bit easier.
176
+ igetset(:handle_help_and_options) do
177
+ if help?
178
+ # We ignore errors if there's a help request.
179
+ stdout.puts help_text
180
+ 0
181
+ elsif (ar = options[:errors])
182
+ stderr.puts ar
183
+ 1
184
+ end
185
+ end
186
+ end
187
+
188
+ # Program manifest object. Successors should return it.
189
+ # @abstract
190
+ # @return [Manicest]
191
+ def manifest
192
+ raise NotImplementedError, "Redefine `manifest` in your class: #{self.class}"
193
+ end
194
+
195
+ # Main routine which handles most boilerplate.
196
+ # @return [Integer]
197
+ # @see #slim_main
198
+ def main
199
+ # Handle top-level errors like `UsageError`.
200
+ begin
201
+ if (res = handle_help_and_options)
202
+ return res
203
+ end
204
+
205
+ # Invoke dump'n'exit, if `def dx` defined.
206
+ # NOTE: This code is production-compatible. It's active only if final script responds to `dx`, which is strictly debug-time.
207
+ if respond_to? :dx and env_truthy? "DX"
208
+ dx
209
+ return 1
210
+ end
211
+
212
+ # Do it.
213
+ result = call_slim_main
214
+
215
+ # Help us stay sane.
216
+ raise "Unknown `slim_main` result: #{result.inspect}" if not result.is_a? Integer
217
+
218
+ result
219
+ rescue *self.class.error_klasses => e
220
+ stderr.puts "#{e.class.to_s.split('::').last}: #{e.message}" # Like "UsageError: this and that".
221
+ 1
222
+ end
223
+ end
224
+
225
+ # Slim or "real" main routine of the successor class.
226
+ # Called by {#main} considering all boilerplate has been taken care of.
227
+ # @abstract
228
+ # @return [Integer]
229
+ def slim_main
230
+ raise NotImplementedError, "Redefine `slim_main` in your class: #{self.class}"
231
+ end
232
+ end
233
+ end; end; end