neverbounce-cli 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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