travis-cl 1.2.4

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 (114) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +134 -0
  3. data/Gemfile +11 -0
  4. data/Gemfile.lock +59 -0
  5. data/MIT_LICENSE.md +21 -0
  6. data/README.md +1283 -0
  7. data/cl.gemspec +30 -0
  8. data/examples/README.md +22 -0
  9. data/examples/_src/args/cast.erb.rb +100 -0
  10. data/examples/_src/args/opts.erb.rb +100 -0
  11. data/examples/_src/args/required.erb.rb +63 -0
  12. data/examples/_src/args/splat.erb.rb +55 -0
  13. data/examples/_src/gem.erb.rb +99 -0
  14. data/examples/_src/heroku.erb.rb +47 -0
  15. data/examples/_src/rakeish.erb.rb +54 -0
  16. data/examples/_src/readme/abstract.erb.rb +27 -0
  17. data/examples/_src/readme/alias.erb.rb +22 -0
  18. data/examples/_src/readme/arg.erb.rb +21 -0
  19. data/examples/_src/readme/arg_array.erb.rb +21 -0
  20. data/examples/_src/readme/arg_type.erb.rb +23 -0
  21. data/examples/_src/readme/args_splat.erb.rb +55 -0
  22. data/examples/_src/readme/array.erb.rb +21 -0
  23. data/examples/_src/readme/basic.erb.rb +72 -0
  24. data/examples/_src/readme/default.erb.rb +21 -0
  25. data/examples/_src/readme/deprecated.erb.rb +21 -0
  26. data/examples/_src/readme/deprecated_alias.erb.rb +21 -0
  27. data/examples/_src/readme/description.erb.rb +60 -0
  28. data/examples/_src/readme/downcase.erb.rb +21 -0
  29. data/examples/_src/readme/enum.erb.rb +35 -0
  30. data/examples/_src/readme/example.erb.rb +25 -0
  31. data/examples/_src/readme/format.erb.rb +35 -0
  32. data/examples/_src/readme/internal.erb.rb +28 -0
  33. data/examples/_src/readme/negate.erb.rb +37 -0
  34. data/examples/_src/readme/note.erb.rb +25 -0
  35. data/examples/_src/readme/opts.erb.rb +33 -0
  36. data/examples/_src/readme/opts_block.erb.rb +30 -0
  37. data/examples/_src/readme/range.erb.rb +35 -0
  38. data/examples/_src/readme/registry.erb.rb +18 -0
  39. data/examples/_src/readme/required.erb.rb +35 -0
  40. data/examples/_src/readme/requireds.erb.rb +46 -0
  41. data/examples/_src/readme/requires.erb.rb +35 -0
  42. data/examples/_src/readme/runner.erb.rb +29 -0
  43. data/examples/_src/readme/runner_custom.erb.rb +25 -0
  44. data/examples/_src/readme/secret.erb.rb +22 -0
  45. data/examples/_src/readme/see.erb.rb +25 -0
  46. data/examples/_src/readme/type.erb.rb +21 -0
  47. data/examples/args/cast +98 -0
  48. data/examples/args/opts +98 -0
  49. data/examples/args/required +62 -0
  50. data/examples/args/splat +58 -0
  51. data/examples/gem +97 -0
  52. data/examples/heroku +48 -0
  53. data/examples/rakeish +50 -0
  54. data/examples/readme/abstract +28 -0
  55. data/examples/readme/alias +21 -0
  56. data/examples/readme/arg +20 -0
  57. data/examples/readme/arg_array +20 -0
  58. data/examples/readme/arg_type +22 -0
  59. data/examples/readme/args_splat +58 -0
  60. data/examples/readme/array +20 -0
  61. data/examples/readme/basic +67 -0
  62. data/examples/readme/default +20 -0
  63. data/examples/readme/deprecated +20 -0
  64. data/examples/readme/deprecated_alias +20 -0
  65. data/examples/readme/description +56 -0
  66. data/examples/readme/downcase +20 -0
  67. data/examples/readme/enum +33 -0
  68. data/examples/readme/example +21 -0
  69. data/examples/readme/format +33 -0
  70. data/examples/readme/internal +24 -0
  71. data/examples/readme/negate +44 -0
  72. data/examples/readme/note +21 -0
  73. data/examples/readme/opts +31 -0
  74. data/examples/readme/opts_block +29 -0
  75. data/examples/readme/range +33 -0
  76. data/examples/readme/registry +15 -0
  77. data/examples/readme/required +33 -0
  78. data/examples/readme/requireds +46 -0
  79. data/examples/readme/requires +33 -0
  80. data/examples/readme/runner +30 -0
  81. data/examples/readme/runner_custom +22 -0
  82. data/examples/readme/secret +21 -0
  83. data/examples/readme/see +21 -0
  84. data/examples/readme/type +20 -0
  85. data/lib/cl/arg.rb +79 -0
  86. data/lib/cl/args.rb +92 -0
  87. data/lib/cl/cast.rb +55 -0
  88. data/lib/cl/cmd.rb +74 -0
  89. data/lib/cl/config/env.rb +52 -0
  90. data/lib/cl/config/files.rb +34 -0
  91. data/lib/cl/config.rb +30 -0
  92. data/lib/cl/ctx.rb +36 -0
  93. data/lib/cl/dsl.rb +182 -0
  94. data/lib/cl/errors.rb +119 -0
  95. data/lib/cl/help/cmd.rb +118 -0
  96. data/lib/cl/help/cmds.rb +26 -0
  97. data/lib/cl/help/format.rb +69 -0
  98. data/lib/cl/help/table.rb +58 -0
  99. data/lib/cl/help/usage.rb +26 -0
  100. data/lib/cl/help.rb +37 -0
  101. data/lib/cl/helper/suggest.rb +10 -0
  102. data/lib/cl/helper.rb +47 -0
  103. data/lib/cl/opt.rb +276 -0
  104. data/lib/cl/opts/validate.rb +117 -0
  105. data/lib/cl/opts.rb +114 -0
  106. data/lib/cl/parser/format.rb +63 -0
  107. data/lib/cl/parser.rb +70 -0
  108. data/lib/cl/runner/default.rb +86 -0
  109. data/lib/cl/runner/multi.rb +34 -0
  110. data/lib/cl/runner.rb +10 -0
  111. data/lib/cl/ui.rb +146 -0
  112. data/lib/cl/version.rb +3 -0
  113. data/lib/cl.rb +62 -0
  114. metadata +177 -0
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env ruby
2
+ $: << File.expand_path('lib')
3
+
4
+ class Add < Cl::Cmd
5
+ register :add
6
+
7
+ # read DNF, i.e. "token OR user AND pass
8
+ required :token, [:user, :pass]
9
+
10
+ opt '--token TOKEN'
11
+ opt '--user NAME'
12
+ opt '--pass PASS'
13
+
14
+ def run
15
+ p token: token, user: user, pass: pass
16
+ end
17
+ end
18
+
19
+ Cl.new('owners').run(%w(add --token token))
20
+
21
+ # Output:
22
+ #
23
+ # {:token=>"token", :user=>nil, :pass=>nil}
24
+
25
+ Cl.new('owners').run(%w(add --user user --pass pass))
26
+
27
+ # Output:
28
+ #
29
+ # {:token=>nil, :user=>"user", :pass=>"pass"}
30
+
31
+ Cl.new('owners').run(%w(add))
32
+
33
+ # Output:
34
+ #
35
+ # Missing options: token, or user and pass
36
+ #
37
+ # Usage: owners add [options]
38
+ #
39
+ # Options:
40
+ #
41
+ # Either token, or user and pass are required.
42
+ #
43
+ # --token TOKEN type: string
44
+ # --user NAME type: string
45
+ # --pass PASS type: string
46
+ # --help Get help on this command
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env ruby
2
+ $: << File.expand_path('lib')
3
+
4
+ class Add < Cl::Cmd
5
+ register :add
6
+
7
+ opt '--to GROUP'
8
+ opt '--other GROUP', requires: :to
9
+
10
+ def run
11
+ p to: to, other: other
12
+ end
13
+ end
14
+
15
+ Cl.new('owners').run(%w(add --to one --other two))
16
+
17
+ # Output:
18
+ #
19
+ # {:to=>"one", :other=>"two"}
20
+
21
+ Cl.new('owners').run(%w(add --other two))
22
+
23
+ # Output:
24
+ #
25
+ # Missing option: to (required by other)
26
+ #
27
+ # Usage: owners add [options]
28
+ #
29
+ # Options:
30
+ #
31
+ # --to GROUP type: string
32
+ # --other GROUP type: string, requires: to
33
+ # --help Get help on this command
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env ruby
2
+ $: << File.expand_path('lib')
3
+
4
+ module Git
5
+ class Pull < Cl::Cmd
6
+ register :'git:pull'
7
+
8
+ arg :branch
9
+
10
+ def run
11
+ p cmd: registry_key, args: args
12
+ end
13
+ end
14
+ end
15
+
16
+ # With this class registered (and assuming the executable that calls `Cl` is
17
+ # `bin/run`) the default runner would recognize and run it:
18
+ #
19
+ # $ bin/run git:pull master # instantiates Git::Pull, and passes ["master"] as args
20
+ # $ bin/run git pull master # does the same
21
+
22
+ Cl.new('run').run(%w(git:pull master))
23
+ # Output:
24
+ #
25
+ # {:cmd=>:"git:pull", :args=>["master"]}
26
+
27
+ Cl.new('run').run(%w(git pull master))
28
+ # Output:
29
+ #
30
+ # {:cmd=>:"git:pull", :args=>["master"]}
@@ -0,0 +1,22 @@
1
+ # anywhere in your library
2
+
3
+ require 'cl'
4
+
5
+ class Runner
6
+ Cl::Runner.register :custom, self
7
+
8
+ def initialize(ctx, args)
9
+ # ...
10
+ end
11
+
12
+ def run
13
+ const = identify_cmd_class_from_args
14
+ const.new(ctx, args).run
15
+ end
16
+ end
17
+
18
+ # in bin/run
19
+ Cl.new('run', runner: :custom).run(ARGV)
20
+
21
+
22
+
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env ruby
2
+ $: << File.expand_path('lib')
3
+
4
+ class Add < Cl::Cmd
5
+ register :add
6
+
7
+ opt '--pass PASS', secret: true
8
+
9
+ def run
10
+ p(
11
+ secret?: self.class.opts[:pass].secret?,
12
+ tainted?: pass
13
+ )
14
+ end
15
+ end
16
+
17
+ Cl.new('owners').run(%w(add --pass pass))
18
+
19
+ # Output:
20
+ #
21
+ # {:secret?=>true, :tainted?=>true}
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env ruby
2
+ $: << File.expand_path('lib')
3
+
4
+ require 'cl'
5
+
6
+ class Add < Cl::Cmd
7
+ register :add
8
+
9
+ opt '--to GROUP', see: 'https://docs.io/cli/owners/add'
10
+ end
11
+
12
+ Cl.new('owners').run(%w(add --help))
13
+
14
+ # Output:
15
+ #
16
+ # Usage: owners add [options]
17
+ #
18
+ # Options:
19
+ #
20
+ # --to GROUP type: string, see: https://docs.io/cli/owners/add
21
+ # --help Get help on this command
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env ruby
2
+ $: << File.expand_path('lib')
3
+
4
+ class Add < Cl::Cmd
5
+ register :add
6
+
7
+ opt '--active BOOL', type: :boolean
8
+ opt '--retries INT', type: :integer
9
+ opt '--sleep FLOAT', type: :float
10
+
11
+ def run
12
+ p active: active.class, retries: retries.class, sleep: sleep.class
13
+ end
14
+ end
15
+
16
+ Cl.new('owners').run(%w(add --active yes --retries 1 --sleep 0.1))
17
+
18
+ # Output:
19
+ #
20
+ # {:active=>TrueClass, :retries=>Integer, :sleep=>Float}
data/lib/cl/arg.rb ADDED
@@ -0,0 +1,79 @@
1
+ require 'cl/cast'
2
+
3
+ class Cl
4
+ class Arg < Struct.new(:name, :opts)
5
+ include Cast
6
+
7
+ def define(const)
8
+ mod = Module.new
9
+ mod.send(:attr_accessor, name)
10
+ mod.class_eval "def #{name}?; #{name}.is_a?(Array) ? !#{name}.empty? : !!#{name} end"
11
+ const.send(:include, mod)
12
+ end
13
+
14
+ def set(cmd, value)
15
+ value = cast(value)
16
+ unknown(value) if enum? && !known?(value)
17
+ cmd.send(:"#{name}=", value)
18
+ end
19
+
20
+ def type
21
+ opts[:type] || :string
22
+ end
23
+
24
+ def array?
25
+ type == :array
26
+ end
27
+
28
+ def description
29
+ opts[:description]
30
+ end
31
+
32
+ def enum
33
+ Array(opts[:enum])
34
+ end
35
+
36
+ def enum?
37
+ opts.key?(:enum)
38
+ end
39
+
40
+ def default
41
+ opts[:default]
42
+ end
43
+
44
+ def default?
45
+ opts.key?(:default)
46
+ end
47
+
48
+ def known?(value)
49
+ enum.include?(value)
50
+ end
51
+
52
+ def required?
53
+ !!opts[:required]
54
+ end
55
+
56
+ def separator
57
+ opts[:sep]
58
+ end
59
+
60
+ def splat?
61
+ !!opts[:splat] && array?
62
+ end
63
+
64
+ def unknown(value)
65
+ raise UnknownArgumentValue.new(value, enum.join(', '))
66
+ end
67
+
68
+ def to_s
69
+ str = name
70
+ case type
71
+ when :array then str = "#{str}.."
72
+ when :boolean, :bool then str = "#{str}:bool"
73
+ when :integer, :int then str = "#{str}:int"
74
+ when :float then str = "#{str}:float"
75
+ end
76
+ required? ? str : "[#{str}]"
77
+ end
78
+ end
79
+ end
data/lib/cl/args.rb ADDED
@@ -0,0 +1,92 @@
1
+ require 'cl/arg'
2
+
3
+ class Cl
4
+ class Args
5
+ include Enumerable
6
+
7
+ def define(const, name, *args)
8
+ opts = args.last.is_a?(Hash) ? args.pop.dup : {}
9
+ opts[:description] = args.shift if args.any?
10
+
11
+ arg = Arg.new(name, opts)
12
+ arg.define(const)
13
+ self.args << arg
14
+ end
15
+
16
+ def apply(cmd, values, opts)
17
+ values = splat(values) if splat?
18
+ values = default(values) if default?
19
+ validate(values)
20
+ return values if args.empty?
21
+ values = args.zip(values).map { |(arg, value)| arg.set(cmd, value) }.flatten(1) #.compact
22
+ compact_args(values)
23
+ end
24
+
25
+ def each(&block)
26
+ args.each(&block)
27
+ end
28
+
29
+ def index(*args, &block)
30
+ self.args.index(*args, &block)
31
+ end
32
+
33
+ attr_writer :args
34
+
35
+ def args
36
+ @args ||= []
37
+ end
38
+
39
+ def clear
40
+ args.clear
41
+ end
42
+
43
+ def dup
44
+ args = super
45
+ args.args = args.args.dup
46
+ args
47
+ end
48
+
49
+ private
50
+
51
+ def validate(args)
52
+ # raise ArgumentError.new(:unknown_arg, arg) if unknown?(arg)
53
+ raise ArgumentError.new(:missing_args, args.size, required) if args.size < required
54
+ raise ArgumentError.new(:too_many_args, args.join(' '), args.size, allowed) if args.size > allowed && !splat?
55
+ end
56
+
57
+ def allowed
58
+ args.size
59
+ end
60
+
61
+ def splat?
62
+ any?(&:splat?)
63
+ end
64
+
65
+ def default?
66
+ any?(&:default?)
67
+ end
68
+
69
+ def required
70
+ select(&:required?).size
71
+ end
72
+
73
+ def splat(values)
74
+ args.each.with_index.inject([]) do |group, (arg, ix)|
75
+ count = arg && arg.splat? ? [values.size - args.size + ix + 1] : []
76
+ count = 0 if count.first.to_i < 0
77
+ group << values.shift(*count)
78
+ end
79
+ end
80
+
81
+ def default(values)
82
+ args.each.with_index.inject([]) do |args, (arg, ix)|
83
+ args << (values[ix] || arg.default)
84
+ end
85
+ end
86
+
87
+ def compact_args(args)
88
+ args = compact_args(args[0..-2]) while args.last.nil? && args.size > 0
89
+ args
90
+ end
91
+ end
92
+ end
data/lib/cl/cast.rb ADDED
@@ -0,0 +1,55 @@
1
+ class Cl
2
+ module Cast
3
+ class Cast < Struct.new(:type, :value, :opts)
4
+ TRUE = /^(true|yes|on)$/
5
+ FALSE = /^(false|no|off)$/
6
+
7
+ def apply
8
+ return send(type) if respond_to?(type, true)
9
+ raise ArgumentError, "Unknown type: #{type}"
10
+ rescue ::ArgumentError => e
11
+ raise ArgumentError.new(:wrong_type, value.inspect, type)
12
+ end
13
+
14
+ private
15
+
16
+ def array
17
+ Array(value).compact.flatten.map { |value| split(value) }.flatten.compact
18
+ end
19
+
20
+ def string
21
+ value.to_s unless value.to_s.empty?
22
+ end
23
+ alias str string
24
+
25
+ def boolean
26
+ return true if value.to_s =~ TRUE
27
+ return false if value.to_s =~ FALSE
28
+ !!value
29
+ end
30
+ alias bool boolean
31
+ alias flag boolean
32
+
33
+ def int
34
+ Integer(value) if value
35
+ end
36
+ alias integer int
37
+
38
+ def float
39
+ Float(value) if value
40
+ end
41
+
42
+ def split(value)
43
+ separator ? value.to_s.split(separator) : value
44
+ end
45
+
46
+ def separator
47
+ opts[:separator]
48
+ end
49
+ end
50
+
51
+ def cast(value)
52
+ type ? Cast.new(type, value, separator: separator).apply : value
53
+ end
54
+ end
55
+ end
data/lib/cl/cmd.rb ADDED
@@ -0,0 +1,74 @@
1
+ require 'registry'
2
+ require 'cl'
3
+ require 'cl/args'
4
+ require 'cl/dsl'
5
+ require 'cl/opts'
6
+ require 'cl/parser'
7
+
8
+ class Cl
9
+ # Base class for all command classes that can be run.
10
+ #
11
+ # Inherit your command classes from this class, use the {Cl::Cmd::Dsl} to
12
+ # declare arguments, options, summary, description, examples etc., and
13
+ # implement the method #run.
14
+ #
15
+ # See {Cl::Cmd::Dsl} for details on the DSL methods.
16
+ class Cmd
17
+ include Registry
18
+ extend Dsl
19
+
20
+ class << self
21
+ include Merge, Suggest, Underscore
22
+
23
+ attr_accessor :auto_register
24
+
25
+ inherited = ->(const) do
26
+ if const.name && Cmd.auto_register
27
+ key = underscore(const.name.split('::').last)
28
+ key = [registry_key, key].compact.join(':') unless abstract?
29
+ const.register(key)
30
+ end
31
+ const.define_singleton_method(:inherited, &inherited)
32
+ end
33
+ define_method(:inherited, &inherited)
34
+
35
+ def cmds
36
+ registry.values.uniq
37
+ end
38
+
39
+ def parse(ctx, cmd, args)
40
+ parser = Parser.new(cmd, args)
41
+ args, opts = parser.args, parser.opts unless self == Help
42
+ opts = merge(ctx.config[registry_key], opts) if ctx.config[registry_key]
43
+ [args, opts || {}]
44
+ end
45
+
46
+ def suggestions(opt)
47
+ suggest(opts.map(&:name), opt.sub(/^--/, ''))
48
+ end
49
+ end
50
+
51
+ self.auto_register = true
52
+
53
+ abstract
54
+
55
+ opt '--help', 'Get help on this command'
56
+
57
+ attr_reader :ctx, :args
58
+
59
+ def initialize(ctx, args)
60
+ @ctx = ctx
61
+ args, opts = self.class.parse(ctx, self, args)
62
+ @opts = self.class.opts.apply(self, self.opts.merge(opts))
63
+ @args = self.class.args.apply(self, args, opts) unless help? && !is_a?(Help)
64
+ end
65
+
66
+ def opts
67
+ @opts ||= {}
68
+ end
69
+
70
+ def deprecations
71
+ @deprecations ||= {}
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,52 @@
1
+ require 'cl/helper'
2
+
3
+ class Cl
4
+ class Config
5
+ class Env < Struct.new(:name)
6
+ include Merge
7
+
8
+ TRUE = /^(true|yes|on)$/
9
+ FALSE = /^(false|no|off)$/
10
+
11
+ def load
12
+ vars = opts.map { |cmd, opts| vars(cmd, opts) }
13
+ merge(*vars.flatten.compact)
14
+ end
15
+
16
+ private
17
+
18
+ def vars(cmd, opts)
19
+ opts.map { |opt| var(cmd, opt, key(cmd, opt)) }
20
+ end
21
+
22
+ def opts
23
+ Cmd.registry.map { |key, cmd| [key, cmd.opts.map(&:name) - [:help]] }
24
+ end
25
+
26
+ def var(cmd, opt, key)
27
+ { cmd => { opt => cast(ENV[key]) } } if ENV[key]
28
+ end
29
+
30
+ def key(*keys)
31
+ [name.upcase, *keys].join('_').upcase.sub('-', '_')
32
+ end
33
+
34
+ def only(hash, *keys)
35
+ hash.select { |key, _| keys.include?(key) }.to_h
36
+ end
37
+
38
+ def cast(value)
39
+ case value
40
+ when TRUE
41
+ true
42
+ when FALSE
43
+ false
44
+ when ''
45
+ false
46
+ else
47
+ value
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,34 @@
1
+ require 'yaml'
2
+ require 'cl/helper'
3
+
4
+ class Cl
5
+ class Config
6
+ class Files < Struct.new(:name)
7
+ include Merge
8
+
9
+ PATHS = %w(
10
+ ~/.%s.yml
11
+ ./.%s.yml
12
+ )
13
+
14
+ def load
15
+ configs.any? ? symbolize(merge(*configs)) : {}
16
+ end
17
+
18
+ private
19
+
20
+ def configs
21
+ @configs ||= paths.map { |path| YAML.load_file(path) || {} }
22
+ end
23
+
24
+ def paths
25
+ paths = PATHS.map { |path| File.expand_path(path % name) }
26
+ paths.select { |path| File.exist?(path) }
27
+ end
28
+
29
+ def symbolize(hash)
30
+ hash.map { |key, value| [key.to_sym, value] }.to_h
31
+ end
32
+ end
33
+ end
34
+ end
data/lib/cl/config.rb ADDED
@@ -0,0 +1,30 @@
1
+ require 'cl/config/env'
2
+ require 'cl/config/files'
3
+ require 'cl/helper'
4
+
5
+ class Cl
6
+ class Config
7
+ include Merge
8
+
9
+ attr_reader :name, :opts
10
+
11
+ def initialize(name)
12
+ @name = name
13
+ @opts = load
14
+ end
15
+
16
+ def to_h
17
+ opts
18
+ end
19
+
20
+ private
21
+
22
+ def load
23
+ merge(*sources.map(&:load))
24
+ end
25
+
26
+ def sources
27
+ [Files.new(name), Env.new(name)]
28
+ end
29
+ end
30
+ end
data/lib/cl/ctx.rb ADDED
@@ -0,0 +1,36 @@
1
+ require 'forwardable'
2
+ require 'cl/config'
3
+ require 'cl/ui'
4
+
5
+ class Cl
6
+ class Ctx
7
+ extend Forwardable
8
+
9
+ def_delegators :ui, :puts, :stdout, :announce, :info, :notice, :warn,
10
+ :error, :success, :cmd
11
+
12
+ attr_accessor :config, :name, :opts
13
+
14
+ def initialize(name, opts = {})
15
+ @config = Config.new(name).to_h
16
+ @opts = opts
17
+ @name = name
18
+ end
19
+
20
+ def ui
21
+ @ui ||= opts[:ui] || Ui.new(self, opts)
22
+ end
23
+
24
+ def abort(error, *strs)
25
+ abort? ? ui.abort(error, *strs) : raise(error)
26
+ end
27
+
28
+ def abort?
29
+ !opts[:abort].is_a?(FalseClass)
30
+ end
31
+
32
+ def test?
33
+ ENV['ENV'] == 'test'
34
+ end
35
+ end
36
+ end