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,169 @@
1
+
2
+ require "never_bounce/cli/script/table"
3
+ require "never_bounce/cli/user_config"
4
+
5
+ require_relative "meaningful"
6
+
7
+ module NeverBounce; module CLI; module Script
8
+ # An API request maker script base class.
9
+ class RequestMaker < Meaningful
10
+ attr_writer :api_key, :request, :request_curl, :response, :server_raw, :session, :user_config
11
+
12
+ envar "API_KEY*", "API key", ["2ed45186c72f9319dc64338cdf16ab76b44cf3d1"]
13
+ envar "API_URL", "Custom API URL", ["https://staging-api.isp.com/v5"]
14
+ envar "CURL", "Print cURL request and exit", ["y", default: "N"]
15
+ envar "RAW", "Print raw response body", ["y", default: "N"]
16
+
17
+ # Shared envar defaults.
18
+ SHARED_ENVARS = {
19
+ "AUTO_START" => ["Start processing the job immediately after parsing", ["y", "n"]],
20
+ "RUN_SAMPLE" => ["Run this job as a sample", ["y", "n"]],
21
+ }
22
+
23
+ # @!attribute api_key
24
+ # @return [String]
25
+ def api_key
26
+ @api_key ||= env["API_KEY"] || user_config.api_key or raise UsageError, "API key not given, use `API_KEY=`"
27
+ end
28
+
29
+ # @!attribute api_url
30
+ # @return [String]
31
+ def api_url
32
+ @api_url ||= igetset(:api_url) do
33
+ env["API_URL"] || user_config.api_url
34
+ end
35
+ end
36
+
37
+ # An instance of <tt>API::Request::Base</tt> successor.
38
+ # @abstract
39
+ # @!attribute request
40
+ # @return [Object]
41
+ def request
42
+ @request or raise NotImplementedError, "Redefine `request` in your class: #{self.class}"
43
+ end
44
+
45
+ # Request's cURL representation. Default is <tt>request.to_curl</tt>
46
+ # @!attribute request_curl
47
+ # @return [Array]
48
+ def request_curl
49
+ require_attr(:request).to_curl
50
+ end
51
+
52
+ # An <tt>API::Response::Base</tt> successor instance.
53
+ # @!attribute response
54
+ # @return [Object]
55
+ def response
56
+ @response ||= require_attr(:session).response
57
+ end
58
+
59
+ # Raw server response text. Default is <tt>session.server_raw</tt>.
60
+ # This attribute is used by <tt>RAW=y</tt> mode only.
61
+ # @!attribute server_raw
62
+ # @return [String]
63
+ def server_raw
64
+ @server_raw ||= require_attr(:session).server_raw
65
+ end
66
+
67
+ # An instance of <tt>API::Session</tt> built around {#request}.
68
+ # @!attribute session
69
+ # @return [Object]
70
+ def session
71
+ @session ||= API::Session.new(request: require_attr(:request))
72
+ end
73
+
74
+ # @!attribute user_config
75
+ # @return [UserConfig]
76
+ def user_config
77
+ @user_config ||= UserConfig.new
78
+ end
79
+
80
+ #--------------------------------------- Misc
81
+
82
+ # Extract response's value according to a heading spec.
83
+ #
84
+ # get_table_value(reasponse, ["ID", :id, :right])
85
+ def get_table_value(r, hspec)
86
+ if (m = hspec[1]).is_a? Proc
87
+ m.(r)
88
+ else
89
+ r.public_send(m)
90
+ end
91
+ end
92
+ private :get_table_value
93
+
94
+ # "Inspect or nil" -- format a scalar for table-friendly output.
95
+ #
96
+ # inil(5) # => "5"
97
+ # inil(nil) # => "-"
98
+ #
99
+ # @return [String]
100
+ def inil(v)
101
+ v.nil?? "-" : v.inspect
102
+ end
103
+ private :inil
104
+
105
+ # Print request as a ready-to-run cURL command. Return 0.
106
+ def print_curl_request
107
+ stdout.puts "curl #{request_curl.map(&:shellescape).join(' ')}"
108
+ 0
109
+ end
110
+
111
+ # Print error response as a standard table. Return 1.
112
+ #
113
+ # return print_error_response if response_error?
114
+ def print_error_response
115
+ "ErrorResponse".tap do |label|
116
+ headings = [
117
+ ["Status", :status, :center],
118
+ ["Message", :message],
119
+ ["ExecTime", :execution_time, :right],
120
+ ]
121
+
122
+ table = Table.new(
123
+ headings: headings.map { |ar| ar[0] },
124
+ rows: [headings.map { |ar| get_table_value(response, ar) }],
125
+ ).align!(headings)
126
+
127
+ stdout.puts "\n#{label}:"
128
+ stdout.puts table
129
+ end
130
+
131
+ 1
132
+ end
133
+
134
+ # Print raw response. Return 0.
135
+ #
136
+ # return print_raw_response if env_truthy? "RAW"
137
+ def print_server_raw
138
+ stdout.puts server_raw
139
+ 0
140
+ end
141
+
142
+ #--------------------------------------- Main
143
+
144
+ def slim_main1
145
+ # Perform common boilerplate actions.
146
+ return print_curl_request if env_truthy? "CURL"
147
+
148
+ # Any of these, unless during tests, touch `response`, which triggers the actual request.
149
+ return print_server_raw if env_truthy? "RAW"
150
+ return print_error_response if response.error?
151
+
152
+ call_slim_main(0)
153
+ end
154
+ end
155
+ end; end; end
156
+
157
+ #
158
+ # Implementation notes:
159
+ #
160
+ # * See `def request` for a cool testing hack: `@request || raise`.
161
+ # It makes it possible to test abstract's class functionality without having to create successors
162
+ # just to stub an attribute.
163
+ # * Column output order in all scripts is logical+alphabetical.
164
+ # Most meaningful attributes are output first, the rest are loosely grouped based on decreasing
165
+ # importance.
166
+ # * We deliberately don't use `OptionParser` in final scripts, using "meta-documented" env variables
167
+ # instead. See `envar` and stuff.
168
+ # * `manifest` is an instance method to simplify its usage in output code. `self.class.manifest` is
169
+ # clunky and thus less smart.
@@ -0,0 +1,146 @@
1
+
2
+ require "neverbounce"
3
+
4
+ require_relative "request_maker"
5
+
6
+ module NeverBounce; module CLI; module Script
7
+ class SingleCheck < RequestMaker
8
+ attr_writer :address_info, :email, :credits_info, :timeout
9
+
10
+ envar "ADDRESS_INFO", "Request additional address info", ["y", "n"]
11
+ envar "EMAIL*", "E-mail to check", ["alice@isp.com", "bob.smith+1@domain.com"]
12
+ envar "CREDITS_INFO", "Request additional credits info", ["y", "n"]
13
+ envar "TIMEOUT", "Timeout in seconds to verify the address", ["5"]
14
+
15
+ # @return [true]
16
+ # @return [false]
17
+ # @return [nil]
18
+ def address_info
19
+ igetset(:address_info) do
20
+ if env.has_key?(k = "ADDRESS_INFO")
21
+ env_truthy?(k)
22
+ end
23
+ end
24
+ end
25
+
26
+ # @return [true]
27
+ # @return [false]
28
+ # @return [nil]
29
+ def credits_info
30
+ igetset(:credits_info) do
31
+ if env.has_key?(k = "CREDITS_INFO")
32
+ env_truthy?(k)
33
+ end
34
+ end
35
+ end
36
+
37
+ def email
38
+ @email ||= env[k = "EMAIL"] or raise UsageError, "E-mail address not given, use `#{k}=`"
39
+ end
40
+
41
+ # An <tt>API::Request::SingleCheck</tt>.
42
+ # @!attribute request
43
+ # @return [Object]
44
+ def request
45
+ @request ||= API::Request::SingleCheck.new({
46
+ address_info: address_info,
47
+ api_key: api_key,
48
+ credits_info: credits_info,
49
+ email: email,
50
+ timeout: timeout,
51
+ })
52
+ end
53
+
54
+ def timeout
55
+ igetset(:timeout) do
56
+ if (v = env["TIMEOUT"])
57
+ begin
58
+ Integer(v)
59
+ rescue ArgumentError => e
60
+ raise UsageError, e.message
61
+ end
62
+ end
63
+ end
64
+ end
65
+
66
+ #--------------------------------------- Manifest
67
+
68
+ # @!attribute manifest
69
+ # @return [Manifest]
70
+ def manifest
71
+ @manifest ||= Manifest.new(
72
+ name: "nb-single-check",
73
+ function: "Check a single e-mail",
74
+ cmdline: "[options] [VAR1=value] [VAR2=value] ...",
75
+ )
76
+ end
77
+
78
+ #--------------------------------------- Main
79
+
80
+ # @return [Integer]
81
+ def slim_main
82
+ "Response".tap do |label|
83
+ headings = [
84
+ ["Result", :result, :center],
85
+ [
86
+ "Flags",
87
+ ->(r) { r.flags.sort.join("\n") },
88
+ ],
89
+ ["SuggCorr", :suggested_correction],
90
+
91
+ ["ExecTime", :execution_time, :right],
92
+ ]
93
+
94
+ table = Table.new(
95
+ headings: headings.map { |ar| ar[0] },
96
+ rows: [headings.map { |ar| get_table_value(response, ar) }],
97
+ ).align!(headings)
98
+
99
+ stdout.puts "\n#{label}:"
100
+ stdout.puts table
101
+ end
102
+
103
+ response.address_info? and "AddressInfo".tap do |label|
104
+ headings = [
105
+ ["Addr", :addr],
106
+ ["Alias", :alias],
107
+ ["Domain", :domain],
108
+ ["FQDN", :fqdn],
109
+ ["Host", :host],
110
+ ["NormEmail", :normalized_email],
111
+ ["OrigEmail", :original_email],
112
+ ["Subdomain", :subdomain],
113
+ ["TLD", :tld],
114
+ ]
115
+
116
+ table = Table.new(
117
+ headings: headings.map { |ar| ar[0] },
118
+ rows: [headings.map { |ar| get_table_value(response.address_info, ar) }],
119
+ ).align!(headings)
120
+
121
+ stdout.puts "\n#{label}:"
122
+ stdout.puts table
123
+ end # response.address_info?
124
+
125
+ response.credits_info? and "CreditsInfo".tap do |label|
126
+ headings = [
127
+ ["FreeRmn", :free_credits_remaining, :right],
128
+ ["FreeUsed", :free_credits_used, :right],
129
+ (["MonthlyUsage", :monthly_api_usage, :right] if response.credits_info.monthly?),
130
+ (["PaidRmn", :paid_credits_remaining, :right] if response.credits_info.paid?),
131
+ (["PaidUsed", :paid_credits_used, :right] if response.credits_info.paid?),
132
+ ].compact
133
+
134
+ table = Table.new(
135
+ headings: headings.map { |ar| ar[0] },
136
+ rows: [headings.map { |ar| get_table_value(response.credits_info, ar) }],
137
+ ).align!(headings)
138
+
139
+ stdout.puts "\n#{label}:"
140
+ stdout.puts table
141
+ end # response.credits_info?
142
+
143
+ 0
144
+ end
145
+ end
146
+ end; end; end
@@ -0,0 +1,43 @@
1
+
2
+ require "terminal-table"
3
+
4
+ module NeverBounce; module CLI; module Script
5
+ # Our custom table class.
6
+ class Table < Terminal::Table
7
+ # Align table rows according to headings spec.
8
+ #
9
+ # headings = [
10
+ # ["Status", :status],
11
+ # ["Completed", :completed, :right],
12
+ # ["Processing", :processing, :right],
13
+ # ]
14
+ #
15
+ # table = Table.new(headings: ..., rows: ...).align!(headings)
16
+ # puts table
17
+ #
18
+ # NOTE: Invoke <b>after</b> adding row data.
19
+ #
20
+ # @return [self]
21
+ def align!(headings)
22
+ headings.each_with_index do |ar, i|
23
+ if (v = ar[2])
24
+ align_column(i, v)
25
+ end
26
+ end
27
+
28
+ self
29
+ end
30
+
31
+ # Center-align headings by default.
32
+ # @return [void]
33
+ def headings=(ar)
34
+ super(ar.map do |item|
35
+ if item.is_a? String
36
+ {value: item, alignment: :center}
37
+ else
38
+ item
39
+ end
40
+ end)
41
+ end
42
+ end
43
+ end; end; end
@@ -0,0 +1,66 @@
1
+
2
+ require "yaml"
3
+
4
+ require "never_bounce/cli/feature/basic_initialize"
5
+
6
+ require_relative "../user_config"
7
+
8
+ module NeverBounce; module CLI; class UserConfig
9
+ # User's configuration file content.
10
+ # @see #filename
11
+ # @see UserConfig
12
+ # @see CLI::Feature::BasicInitialize
13
+ class FileContent
14
+ CLI::Feature::BasicInitialize.load(self)
15
+
16
+ attr_writer :body, :body_hash, :env, :filename
17
+
18
+ # YAML configuration, source text.
19
+ # @!attribute body
20
+ # @return [String]
21
+ def body
22
+ @body ||= begin
23
+ File.read(filename)
24
+ rescue Errno::ENOENT # Missing file is okay, let other exceptions manifest.
25
+ ""
26
+ end
27
+ end
28
+
29
+ # YAML configuration, parsed.
30
+ # @!attribute body_hash
31
+ # @return [Hash]
32
+ def body_hash
33
+ @body_hash ||= body.to_s.empty?? {} : YAML.load(body)
34
+ end
35
+
36
+ # A copy of environment for read purposes. Default is <tt>ENV.to_h</tt>.
37
+ # @return [Hash]
38
+ def env
39
+ @env ||= ENV.to_h
40
+ end
41
+
42
+ # Configuration filename. Default is <tt>$HOME/.neverbounce.yml</tt>.
43
+ # @return [String]
44
+ def filename
45
+ @filename ||= File.join(env["HOME"], ".neverbounce.yml")
46
+ end
47
+
48
+ #---------------------------------------
49
+
50
+ # Fetch a value.
51
+ #
52
+ # config_file["api_key"]
53
+ # config_file[:api_key] # Same as above.
54
+ def [](k)
55
+ body_hash[k.to_s]
56
+ end
57
+
58
+ # <tt>true</tt> if key is set.
59
+ #
60
+ # has_key?("api_key")
61
+ # has_key?(:api_key) # Identical to the previous one.
62
+ def has_key?(k)
63
+ body_hash.has_key? k.to_s
64
+ end
65
+ end
66
+ end; end; end
@@ -0,0 +1,51 @@
1
+
2
+ require "never_bounce/cli/feature/eigencache"
3
+
4
+ module NeverBounce; module CLI
5
+ # User's configuration values.
6
+ # @see UserConfig::FileContent
7
+ class UserConfig
8
+ require_relative "user_config/file_content"
9
+
10
+ CLI::Feature::BasicInitialize.load(self)
11
+ CLI::Feature::Eigencache.load(self)
12
+
13
+ attr_writer :api_key, :api_url
14
+
15
+ # API key.
16
+ # @return [String]
17
+ def api_key
18
+ @api_key ||= fc[:api_key]
19
+ end
20
+
21
+ # API URL.
22
+ # @return [String]
23
+ def api_url
24
+ @api_url ||= fc[:api_url]
25
+ end
26
+
27
+ #---------------------------------------
28
+
29
+ # @!attribute fc
30
+ # @return [FileContent]
31
+ def fc
32
+ _cache[:fc] ||= FileContent.new
33
+ end
34
+
35
+ def fc=(obj)
36
+ _cache[:fc] = obj
37
+ end
38
+
39
+ # "Touch" all attributes by loading them.
40
+ # @return [self]
41
+ def touch
42
+ api_key; api_url
43
+ self
44
+ end
45
+ end
46
+ end; end
47
+
48
+ #
49
+ # Implementation notes:
50
+ #
51
+ # * `api_url` has got no default here, it isn't a config responsibility.