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,41 @@
1
+
2
+ require "never_bounce/cli/feature/basic_initialize"
3
+ require "never_bounce/cli/feature/igetset"
4
+
5
+ require_relative "../envars"
6
+
7
+ module NeverBounce; module CLI; module Feature; module Envars
8
+ # Single envar item container.
9
+ class Item
10
+ CLI::Feature::BasicInitialize.load(self)
11
+ CLI::Feature::Igetset.load(self)
12
+
13
+ # @return [String]
14
+ attr_accessor :name
15
+
16
+ # @return [String]
17
+ attr_accessor :comment
18
+
19
+ attr_writer :default, :examples, :is_mandatory
20
+
21
+ # Default value. Default is <tt>nil</tt>.
22
+ # @return [mixed]
23
+ def default
24
+ igetset(:default) { nil }
25
+ end
26
+
27
+ # Value examples. Default is <tt>[]</tt>.
28
+ # @return [Array]
29
+ def examples
30
+ @examples ||= []
31
+ end
32
+
33
+ # True if this envar is mandatory. Default is <tt>false</tt>.
34
+ # @return [bool]
35
+ def is_mandatory
36
+ igetset(:is_mandatory) { false }
37
+ end
38
+
39
+ alias_method :mandatory?, :is_mandatory
40
+ end
41
+ end; end; end; end
@@ -0,0 +1,82 @@
1
+
2
+ module NeverBounce; module CLI; module Feature
3
+ # Declare and manage environment variables of the class.
4
+ module Envars
5
+ require_relative "envars/examples_mapper"
6
+ require_relative "envars/item"
7
+
8
+ # @param owner [Class]
9
+ # @return [nil]
10
+ def self.load(owner)
11
+ return if owner < InstanceMethods
12
+ owner.extend(ClassMethods)
13
+ owner.send(:include, InstanceMethods)
14
+ end
15
+
16
+ module ClassMethods
17
+ attr_writer :envars
18
+
19
+ def envars
20
+ @envars ||= if superclass.respond_to? :envars
21
+ superclass.envars.dup
22
+ else
23
+ []
24
+ end
25
+ end
26
+
27
+ #---------------------------------------
28
+ private
29
+
30
+ # Define an envar in a formal way.
31
+ # @param name [String]
32
+ # @param comment [String]
33
+ # @return [Item]
34
+ def define_envar(name, comment, is_mandatory: false, examples: [], default: nil)
35
+ Item.new({
36
+ name: name,
37
+
38
+ comment: comment,
39
+ default: default,
40
+ examples: examples,
41
+ is_mandatory: is_mandatory,
42
+ }).tap { |_| envars << _ }
43
+ end
44
+
45
+ # Declare an envar in a concise and magical way.
46
+ #
47
+ # envar "ID", "Job ID"
48
+ # => define_envar "ID", "Job ID"
49
+ # envar "API_KEY*", "API key", ["2ed45186c72f9319dc64338cdf16ab76b44cf3d1"]
50
+ # # => define_envar "API_KEY", "API key", is_mandatory: true, examples: [...]
51
+ # envar "RAW", "Print raw response body", ["y", default: "N"]
52
+ # # => define_envar "RAW", "Print raw response body", examples: ["y", "N"], default: "N"
53
+ def envar(name, comment, examples = [])
54
+ options = {}
55
+
56
+ real_name = if name[-1] == "*"
57
+ options[:is_mandatory] = true
58
+ name[0..-2]
59
+ else
60
+ name
61
+ end
62
+
63
+ if not examples.empty?
64
+ h = ExamplesMapper.new[examples]
65
+ options[:examples] = h[:values]
66
+ options[:default] = h[:default] if h.include?(:default)
67
+ end
68
+
69
+ define_envar(real_name, comment, options)
70
+ end
71
+ end
72
+
73
+ module InstanceMethods
74
+ end
75
+ end
76
+ end; end; end
77
+
78
+ #
79
+ # Implementation notes:
80
+ #
81
+ # * `envars` belongs to the class, but considering its superclasses.
82
+ # We don't use @@var to avoid unnecessary side effect.
@@ -0,0 +1,41 @@
1
+
2
+ module NeverBounce; module CLI; module Feature
3
+ # @see InstanceMethods#igetset
4
+ module Igetset
5
+ # @param owner [Class]
6
+ # @return [nil]
7
+ def self.load(owner)
8
+ return if owner < InstanceMethods
9
+ owner.send(:include, InstanceMethods)
10
+ end
11
+
12
+ module InstanceMethods
13
+ private
14
+
15
+ # Get/set an OTF instance variable of any type.
16
+ #
17
+ # Ruby's <tt>||=</tt> works nicely with object instances, but requires special bulky treatment for <tt>nil</tt> and <tt>false</tt>.
18
+ # For example, this will cause a hidden glitch since <tt>==</tt> can evaluate to <tt>false</tt>:
19
+ #
20
+ # @is_verbose ||= begin
21
+ # # This clause will be evaluated *every time* if its value is `false`.
22
+ # ENV["VERBOSE"] == "y"
23
+ # end
24
+ #
25
+ # There's a number of solutions to this problem, all of which involve calling <tt>instance_variable_*</tt> a few times per attribute accessor.
26
+ #
27
+ # <tt>igetset</tt> does this job for you. All you have to do is specify a block to compute the value.
28
+ #
29
+ # igetset(:is_verbose) { ENV["VERBOSE"] == "y" }
30
+ #
31
+ # See source for details.
32
+ def igetset(name, &compute)
33
+ if instance_variable_defined?(k = "@#{name}")
34
+ instance_variable_get(k)
35
+ else
36
+ instance_variable_set(k, compute.call)
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end; end; end
@@ -0,0 +1,25 @@
1
+
2
+ module NeverBounce; module CLI; module Feature
3
+ # Provide attribute validation method(s) to the owner class.
4
+ # @see InstanceMethods#initialize
5
+ module RequireAttr
6
+ # @param owner [Class]
7
+ # @return [nil]
8
+ def self.load(owner)
9
+ return if owner < InstanceMethods
10
+ owner.send(:include, InstanceMethods)
11
+ end
12
+
13
+ module InstanceMethods
14
+ private
15
+
16
+ # Require attribute to be set. Return attribute value.
17
+ # @return [mixed]
18
+ def require_attr(name)
19
+ send(name).tap do |_|
20
+ raise "Attribute must be set: #{name}" if _.nil?
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end; end; end
@@ -0,0 +1,85 @@
1
+
2
+ require "neverbounce"
3
+
4
+ require_relative "request_maker"
5
+
6
+ module NeverBounce; module CLI; module Script
7
+ class AccountInfo < RequestMaker
8
+ # An <tt>API::Request::AccountInfo</tt>.
9
+ # @!attribute request
10
+ # @return [Object]
11
+ def request
12
+ @request ||= API::Request::AccountInfo.new({
13
+ api_key: api_key,
14
+ })
15
+ end
16
+
17
+ #--------------------------------------- Manifest
18
+
19
+ # @!attribute manifest
20
+ # @return [Manifest]
21
+ def manifest
22
+ @manifest ||= Manifest.new(
23
+ name: "nb-account-info",
24
+ function: "Check account balance",
25
+ cmdline: "[options] [VAR1=value] [VAR2=value] ...",
26
+ )
27
+ end
28
+
29
+ #--------------------------------------- Main
30
+
31
+ # @return [Integer]
32
+ def slim_main
33
+ "Response".tap do |label|
34
+ headings = [
35
+ ["ExecTime", :execution_time, :right],
36
+ ]
37
+
38
+ table = Table.new(
39
+ headings: headings.map { |ar| ar[0] },
40
+ rows: [headings.map { |ar| get_table_value(response, ar) }],
41
+ ).align!(headings)
42
+
43
+ stdout.puts "\n#{label}:"
44
+ stdout.puts table
45
+ end
46
+
47
+ "Credits".tap do |label|
48
+ headings = [
49
+ ["FreeRmn", :free_credits_remaining, :right],
50
+ ["FreeUsed", :free_credits_used, :right],
51
+ (["MonthlyUsage", :monthly_api_usage, :right] if response.credits_info.monthly?),
52
+ (["PaidRmn", :paid_credits_remaining, :right] if response.credits_info.paid?),
53
+ (["PaidUsed", :paid_credits_used, :right] if response.credits_info.paid?),
54
+ ].compact
55
+
56
+ table = Table.new(
57
+ headings: headings.map { |ar| ar[0] },
58
+ rows: [headings.map { |ar| get_table_value(response.credits_info, ar) }],
59
+ ).align!(headings)
60
+
61
+ stdout.puts "\n#{label}:"
62
+ stdout.puts table
63
+ end
64
+
65
+ "JobCounts".tap do |label|
66
+ headings = [
67
+ ["Completed", :completed, :right],
68
+ ["Processing", :processing, :right],
69
+ ["Queued", :queued, :right],
70
+ ["UnderReview", :under_review, :right],
71
+ ]
72
+
73
+ table = Table.new(
74
+ headings: headings.map { |ar| ar[0] },
75
+ rows: [headings.map { |ar| get_table_value(response.job_counts, ar) }],
76
+ ).align!(headings)
77
+
78
+ stdout.puts "\n#{label}:"
79
+ stdout.puts table
80
+ end
81
+
82
+ 0
83
+ end
84
+ end
85
+ end; end; end
@@ -0,0 +1,101 @@
1
+
2
+ require "shellwords"
3
+
4
+ require "never_bounce/cli/feature/basic_initialize"
5
+ require "never_bounce/cli/feature/require_attr"
6
+
7
+ module NeverBounce; module CLI; module Script
8
+ # Barebones script base class.
9
+ # @abstract
10
+ # @see CLI::Feature::BasicInitialize
11
+ # @see CLI::Feature::RequireAttr
12
+ class Base
13
+ CLI::Feature::BasicInitialize.load(self)
14
+ CLI::Feature::RequireAttr.load(self)
15
+
16
+ attr_writer :argv, :env, :stderr, :stdout
17
+
18
+ # Command-line arguments. Default is <tt>ARGV</tt>.
19
+ # @return [Array]
20
+ def argv
21
+ @argv ||= ARGV
22
+ end
23
+
24
+ # A *copy* of the environment for value-reading purposes. Default is <tt>ENV.to_h</tt>.
25
+ # @!attribute env
26
+ # @return [Hash]
27
+ def env
28
+ # Ruby's `ENV` is a weird thing.
29
+ # It's a direct `Object` which acts like `Hash`.
30
+ # It can't be reliably dup'd cloned, at the same time writes to it are invocation-global.
31
+ # This implicit read/write nature of `ENV` is a major hassle in tests, since it creates unnecessary side effects we have to tackle with specifically.
32
+ # Solution for now:
33
+ #
34
+ # 1. Since 99% of the time our script isn't interested in *writing* to ENV, this method deals with the READ case as the most widely used one.
35
+ # 2. If we ever need to write to ENV in order to *create* the environment for a child process or something, we'll find a way to do it with grace.
36
+ #
37
+ # Everything above is a comment to `.to_h`, mysteriously present on the next line.
38
+ @env ||= ENV.to_h
39
+ end
40
+
41
+ # Script's error stream. Default is <tt>STDERR</tt>.
42
+ # @return [IO]
43
+ def stderr
44
+ @stderr ||= STDERR
45
+ end
46
+
47
+ # Script's output stream. Default is <tt>STDOUT</tt>.
48
+ # @return [IO]
49
+ def stdout
50
+ @stdout ||= STDOUT
51
+ end
52
+
53
+ # <tt>true</tt> if the script should be verbose.
54
+ # @return [true]
55
+ def verbose?
56
+ true
57
+ end
58
+
59
+ #--------------------------------------- Service
60
+
61
+ # @see #env_truthy?
62
+ def env_falsey?(k)
63
+ !env_truthy?(k)
64
+ end
65
+
66
+ # Return <tt>true</tt> if environment variable <tt>k</tt> is truthy.
67
+ #
68
+ # env_truthy? "WITH_HTTP" # => `true` or `false`
69
+ # env_truthy? :WITH_HTTP # same as above
70
+ def env_truthy?(k)
71
+ self.class.env_value_truthy?(env[k.to_s])
72
+ end
73
+
74
+ # Return <tt>true</tt> if environment variable value is truthy.
75
+ #
76
+ # # These are truthy.
77
+ # DEBUG=1
78
+ # DEBUG=true
79
+ # DEBUG=y
80
+ # DEBUG=yes
81
+ def self.env_value_truthy?(s)
82
+ ["1", "true", "y", "yes"].include? s.to_s.downcase
83
+ end
84
+
85
+ # Run system command, print it if verbose.
86
+ # @return [mixed] Result of <tt>Kernel.system</tt>.
87
+ def system(cmd, *args)
88
+ puts "### #{cmd} #{args.map(&:shellescape).join(' ')}" if verbose?
89
+ Kernel.system(cmd, *args)
90
+ end
91
+
92
+ #---------------------------------------
93
+
94
+ # Main routine.
95
+ # @abstract
96
+ # @return [Integer]
97
+ def main
98
+ raise NotImplementedError, "Redefine `main` in your class: #{self.class}"
99
+ end
100
+ end
101
+ end; end; end
@@ -0,0 +1,16 @@
1
+
2
+ module NeverBounce; module CLI; module Script
3
+ # Base script error class.
4
+ class Error < StandardError; end
5
+
6
+ # Something went wrong in the protocol land.
7
+ class ProtocolError < Error; end
8
+
9
+ # User didn't provide sufficient options, other stuff of that kind.
10
+ class UsageError < Error; end
11
+ end; end; end
12
+
13
+ #
14
+ # Implementation notes:
15
+ #
16
+ # * The file is named after the base class to follow the convention.
@@ -0,0 +1,28 @@
1
+
2
+ module NeverBounce; module CLI; module Script; module Feature
3
+ # Common traits for scripts which require job ID.
4
+ # @see InstanceMethods
5
+ module RequiresJobId
6
+ # @param owner [Class]
7
+ # @return [nil]
8
+ def self.load(owner)
9
+ return if owner < InstanceMethods
10
+ owner.send(:include, InstanceMethods)
11
+
12
+ owner.class_eval do
13
+ attr_writer :job_id
14
+
15
+ envar "ID", "Job ID", ["276816"]
16
+ end
17
+ end
18
+
19
+ module InstanceMethods
20
+ # Job ID. Default is <tt>env["ID"]</tt>.
21
+ # @!attribute job_id
22
+ # @return [String]
23
+ def job_id
24
+ @job_id ||= env[k = "ID"] or raise UsageError, "Job ID not given, use `#{k}=`"
25
+ end
26
+ end
27
+ end
28
+ end; end; end; end
@@ -0,0 +1,45 @@
1
+
2
+ module NeverBounce; module CLI; module Script; module Feature
3
+ # Common traits for scripts which support pagination.
4
+ # @see InstanceMethods
5
+ module UsesPagination
6
+ # @param owner [Class]
7
+ # @return [nil]
8
+ def self.load(owner)
9
+ return if owner < InstanceMethods
10
+ owner.send(:include, InstanceMethods)
11
+
12
+ owner.class_eval do
13
+ attr_writer :page, :per_page
14
+
15
+ envar "PAGE", "Fetch page number N", [{default: 1}, 5]
16
+ envar "PER_PAGE", "Paginate results N items per page", [10, default: 1000]
17
+ end
18
+ end
19
+
20
+ module InstanceMethods
21
+ # Page number. Default is <tt>env["PAGE"]</tt>.
22
+ # @!attribute page
23
+ # @return [Integer]
24
+ def page
25
+ # OPTIMIZE: Consider default-less behaviour some day.
26
+ @page ||= if (v = env["PAGE"])
27
+ Integer(v)
28
+ else
29
+ 1
30
+ end
31
+ end
32
+
33
+ # Items per page. Default is <tt>env["PER_PAGE"]</tt>.
34
+ # @!attribute per_page
35
+ # @return [Integer]
36
+ def per_page
37
+ @per_page ||= if (v = env["PER_PAGE"])
38
+ Integer(v)
39
+ else
40
+ 1000
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end; end; end; end