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.
- checksums.yaml +7 -0
- data/.codeclimate.yml +8 -0
- data/.gitignore +21 -0
- data/.rspec +4 -0
- data/.rubocop.yml +1161 -0
- data/.travis.yml +27 -0
- data/.yardopts +2 -0
- data/Gemfile +19 -0
- data/Gemfile.lock +60 -0
- data/LICENSE +9 -0
- data/README.md +138 -0
- data/exe/nb-account-info +5 -0
- data/exe/nb-jobs-create +5 -0
- data/exe/nb-jobs-delete +5 -0
- data/exe/nb-jobs-download +5 -0
- data/exe/nb-jobs-parse +5 -0
- data/exe/nb-jobs-results +5 -0
- data/exe/nb-jobs-search +5 -0
- data/exe/nb-jobs-start +5 -0
- data/exe/nb-jobs-status +5 -0
- data/exe/nb-single-check +5 -0
- data/lib/never_bounce/cli/feature/basic_initialize.rb +20 -0
- data/lib/never_bounce/cli/feature/eigencache.rb +47 -0
- data/lib/never_bounce/cli/feature/envars/examples_mapper.rb +38 -0
- data/lib/never_bounce/cli/feature/envars/item.rb +41 -0
- data/lib/never_bounce/cli/feature/envars.rb +82 -0
- data/lib/never_bounce/cli/feature/igetset.rb +41 -0
- data/lib/never_bounce/cli/feature/require_attr.rb +25 -0
- data/lib/never_bounce/cli/script/account_info.rb +85 -0
- data/lib/never_bounce/cli/script/base.rb +101 -0
- data/lib/never_bounce/cli/script/error.rb +16 -0
- data/lib/never_bounce/cli/script/feature/requires_job_id.rb +28 -0
- data/lib/never_bounce/cli/script/feature/uses_pagination.rb +45 -0
- data/lib/never_bounce/cli/script/jobs_create/supplied_input_parser.rb +48 -0
- data/lib/never_bounce/cli/script/jobs_create.rb +157 -0
- data/lib/never_bounce/cli/script/jobs_delete.rb +55 -0
- data/lib/never_bounce/cli/script/jobs_download.rb +44 -0
- data/lib/never_bounce/cli/script/jobs_parse.rb +73 -0
- data/lib/never_bounce/cli/script/jobs_results.rb +105 -0
- data/lib/never_bounce/cli/script/jobs_search.rb +135 -0
- data/lib/never_bounce/cli/script/jobs_start.rb +73 -0
- data/lib/never_bounce/cli/script/jobs_status.rb +96 -0
- data/lib/never_bounce/cli/script/manifest.rb +22 -0
- data/lib/never_bounce/cli/script/meaningful.rb +233 -0
- data/lib/never_bounce/cli/script/request_maker.rb +169 -0
- data/lib/never_bounce/cli/script/single_check.rb +146 -0
- data/lib/never_bounce/cli/script/table.rb +43 -0
- data/lib/never_bounce/cli/user_config/file_content.rb +66 -0
- data/lib/never_bounce/cli/user_config.rb +51 -0
- data/lib/never_bounce/cli/version.rb +4 -0
- data/lib/neverbounce-cli.rb +3 -0
- data/neverbounce-cli.gemspec +24 -0
- data/spec/lib/never_bounce/cli/feature/basic_initialize_spec.rb +26 -0
- data/spec/lib/never_bounce/cli/feature/eigencache_spec.rb +28 -0
- data/spec/lib/never_bounce/cli/feature/envars/examples_mapper_spec.rb +26 -0
- data/spec/lib/never_bounce/cli/feature/envars/item_spec.rb +9 -0
- data/spec/lib/never_bounce/cli/feature/envars_spec.rb +55 -0
- data/spec/lib/never_bounce/cli/feature/igetset_spec.rb +45 -0
- data/spec/lib/never_bounce/cli/feature/require_attr_spec.rb +25 -0
- data/spec/lib/never_bounce/cli/script/account_info_spec.rb +65 -0
- data/spec/lib/never_bounce/cli/script/base_spec.rb +56 -0
- data/spec/lib/never_bounce/cli/script/feature/requires_job_id_spec.rb +17 -0
- data/spec/lib/never_bounce/cli/script/feature/uses_pagination_spec.rb +21 -0
- data/spec/lib/never_bounce/cli/script/jobs_create/supplied_input_parser_spec.rb +35 -0
- data/spec/lib/never_bounce/cli/script/jobs_create_spec.rb +118 -0
- data/spec/lib/never_bounce/cli/script/jobs_delete_spec.rb +33 -0
- data/spec/lib/never_bounce/cli/script/jobs_download_spec.rb +37 -0
- data/spec/lib/never_bounce/cli/script/jobs_parse_spec.rb +45 -0
- data/spec/lib/never_bounce/cli/script/jobs_results_spec.rb +40 -0
- data/spec/lib/never_bounce/cli/script/jobs_search_spec.rb +59 -0
- data/spec/lib/never_bounce/cli/script/jobs_start_spec.rb +45 -0
- data/spec/lib/never_bounce/cli/script/jobs_status_spec.rb +37 -0
- data/spec/lib/never_bounce/cli/script/manifest_spec.rb +6 -0
- data/spec/lib/never_bounce/cli/script/meaningful_spec.rb +93 -0
- data/spec/lib/never_bounce/cli/script/request_maker_spec.rb +67 -0
- data/spec/lib/never_bounce/cli/script/single_check_spec.rb +94 -0
- data/spec/lib/never_bounce/cli/script/spec_helper.rb +59 -0
- data/spec/lib/never_bounce/cli/script/table_spec.rb +6 -0
- data/spec/lib/never_bounce/cli/user_config/file_content_spec.rb +43 -0
- data/spec/lib/never_bounce/cli/user_config_spec.rb +12 -0
- data/spec/lib/never_bounce/cli_spec.rb +6 -0
- data/spec/spec_helper.rb +51 -0
- 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
|