bogo-cli 0.2.18 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b9fab210ebb0f9a9f7d1a1877fa7abacf6cfb89398c23ee7602e086628523a85
4
- data.tar.gz: 3d5913b9aa21d962c3868276031719a19582216195a71abdf6e3ba262c58d7f0
3
+ metadata.gz: 97c776598127786a35b12a9d95a6d8453c3606aca1551b5acbb374a99606061d
4
+ data.tar.gz: e97a5d3ad04f9e7573f11146c46e3b24e4536dfc1c758cbf56802723275adfd4
5
5
  SHA512:
6
- metadata.gz: b4e72a675b1ba15da4cc87236f3ead957da814c7246692d00a69ef55d26bfdb857d58cbafee830e8cf03ed11681bce016be238155bef9f0367b46f6c28f0bd63
7
- data.tar.gz: 2c77c1be0fdbb2f6f4b5df7eb58c2ef423c50366a8bf7b2869ec4e5c2ac996468aa20de6c7020f73c2592068678323492ce0f8f1074d1e10b0d7a91287689cb2
6
+ metadata.gz: b651506c2871283b831264a32a8cadd8414a1dc1743b1a57fd54ba44cf7756c899752307237d4efc77f2d3d0dc86291b62a183ee43f48bd57427f472644b6e1d
7
+ data.tar.gz: 6a99913415692998685c3e60e803a2ac5df8dfd299f8b9e94b6dfe89c105d2beacf67339f4b1ea99a342e13d1ac603d9c66cda07d9483f295b62092443547d7f
data/CHANGELOG.md CHANGED
@@ -1,3 +1,6 @@
1
+ # v0.3.0
2
+ * Remove dependency on slop library (#14)
3
+
1
4
  # v0.2.18
2
5
  * Include original error message when available
3
6
 
data/CONTRIBUTING.md CHANGED
@@ -1,22 +1,18 @@
1
1
  # Contributing
2
2
 
3
- ## Branches
3
+ ## Fixes
4
4
 
5
- ### `master` branch
5
+ Have a fix to some bug you want to submit? Well you're
6
+ awesome. Please just include a description of the bug
7
+ (or link to originating issue) and test coverage on the
8
+ modifications.
6
9
 
7
- The master branch is the current stable released version.
10
+ ## New Features
8
11
 
9
- ### `develop` branch
10
-
11
- The develop branch is the current edge of development.
12
-
13
- ## Pull requests
14
-
15
- * https://github.com/spox/bogo-cli/pulls
16
-
17
- Please base all pull requests of the `develop` branch. Merges to
18
- `master` only occur through the `develop` branch. Pull requests
19
- based on `master` will likely be cherry picked.
12
+ Have a new feature you want to add? Well you're awesome
13
+ too! It may be a good idea to submit an issue first to
14
+ describe the desired feature and get any feedback. Please
15
+ be sure to include tests.
20
16
 
21
17
  ## Issues
22
18
 
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright 2014 Chris Roberts
1
+ Copyright 2022 Chris Roberts
2
2
 
3
3
  Licensed under the Apache License, Version 2.0 (the "License");
4
4
  you may not use this file except in compliance with the License.
@@ -10,4 +10,4 @@
10
10
  distributed under the License is distributed on an "AS IS" BASIS,
11
11
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
12
  See the License for the specific language governing permissions and
13
- limitations under the License.
13
+ limitations under the License.
data/bogo-cli.gemspec CHANGED
@@ -1,5 +1,5 @@
1
1
  $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__)) + "/lib/"
2
- require "bogo-cli/version"
2
+ require "bogo-cli"
3
3
  Gem::Specification.new do |s|
4
4
  s.name = "bogo-cli"
5
5
  s.version = Bogo::Cli::VERSION.version
@@ -13,8 +13,7 @@ Gem::Specification.new do |s|
13
13
  s.add_runtime_dependency "bogo", ">= 0.2.14", "< 0.6"
14
14
  s.add_runtime_dependency "bogo-config", ">= 0.1.15", "< 0.5"
15
15
  s.add_runtime_dependency "bogo-ui"
16
- s.add_runtime_dependency "slop", "~> 3"
17
- s.add_development_dependency "rake", "~> 10"
16
+ s.add_development_dependency "rake"
18
17
  s.add_development_dependency "minitest"
19
18
  s.files = Dir["lib/**/*"] + %w(bogo-cli.gemspec README.md CHANGELOG.md CONTRIBUTING.md LICENSE)
20
19
  end
@@ -1,6 +1,7 @@
1
+ require "bogo"
1
2
  require "bogo-ui"
2
3
  require "bogo-config"
3
- require "bogo-cli"
4
+ require "ostruct"
4
5
 
5
6
  module Bogo
6
7
  module Cli
@@ -30,10 +31,12 @@ module Bogo
30
31
  #
31
32
  # @return [self]
32
33
  def initialize(cli_opts, args)
33
- if (cli_opts.is_a?(Slop))
34
+ @options = Smash.new
35
+ @defaults = Smash.new
36
+ case cli_opts
37
+ when OpenStruct
34
38
  process_cli_options(cli_opts)
35
39
  else
36
- @defaults = Smash.new
37
40
  @options = cli_opts.to_hash.to_smash(:snake)
38
41
  [@options, *@options.values].compact.each do |hsh|
39
42
  next unless hsh.is_a?(Hash)
@@ -44,7 +47,7 @@ module Bogo
44
47
  load_config!
45
48
  ui_args = Smash.new(
46
49
  :app_name => options.fetch(:app_name,
47
- self.class.name.split("::").first),
50
+ self.class.name.split("::").first),
48
51
  ).merge(config)
49
52
  @ui = options.delete(:ui) || Ui.new(ui_args)
50
53
  Bogo::Cli::Command.ui(ui)
@@ -89,7 +92,7 @@ module Bogo
89
92
  ),
90
93
  Hash.new
91
94
  ).map { |k, v|
92
- unless (v.nil?)
95
+ unless v.nil?
93
96
  [k, v]
94
97
  end
95
98
  }.compact
@@ -101,16 +104,16 @@ module Bogo
101
104
  #
102
105
  # @return [Hash]
103
106
  def load_config!
104
- if (options[:config])
107
+ if options[:config]
105
108
  config_inst = Config.new(options[:config])
106
- elsif (self.class.const_defined?(:DEFAULT_CONFIGURATION_FILES))
109
+ elsif self.class.const_defined?(:DEFAULT_CONFIGURATION_FILES)
107
110
  path = self.class.const_get(:DEFAULT_CONFIGURATION_FILES).detect do |check|
108
111
  full_check = File.expand_path(check)
109
112
  File.exists?(full_check)
110
113
  end
111
114
  config_inst = Config.new(path) if path
112
115
  end
113
- if (config_inst)
116
+ if config_inst
114
117
  options.delete(:config)
115
118
  defaults_inst = Smash[
116
119
  config_class.new(
@@ -165,7 +168,7 @@ module Bogo
165
168
  begin
166
169
  result = yield
167
170
  ui.puts ui.color("complete!", :green, :bold)
168
- if (result)
171
+ if result
169
172
  ui.puts "---> Results:"
170
173
  case result
171
174
  when Hash
@@ -190,15 +193,16 @@ module Bogo
190
193
  # @param cli_opts [Slop]
191
194
  # @return [NilClass]
192
195
  def process_cli_options(cli_opts)
193
- unless (cli_opts.is_a?(Slop))
194
- raise TypeError.new "Expecting type `Slop` but received type `#{cli_opts.class}`"
196
+ unless cli_opts.is_a?(OpenStruct)
197
+ raise TypeError,
198
+ "Expecting `OpenStruct' but received `#{cli_opts.class}'"
195
199
  end
196
200
  @options = Smash.new
197
201
  @defaults = Smash.new
198
202
  cli_opts.each do |cli_opt|
199
- unless (cli_opt.value.nil?)
203
+ unless cli_opt.value.nil?
200
204
  opt_key = Bogo::Utility.snake(cli_opt.key)
201
- if (cli_opt.default?)
205
+ if cli_opt.default?
202
206
  @defaults[opt_key] = cli_opt.value
203
207
  else
204
208
  @options[opt_key] = cli_opt.value
@@ -216,11 +220,11 @@ module Bogo
216
220
  chk_idx = list.find_index do |item|
217
221
  item.start_with?("-")
218
222
  end
219
- if (chk_idx)
223
+ if chk_idx
220
224
  marker = list.find_index do |item|
221
225
  item == "--"
222
226
  end
223
- if (marker.nil? || chk_idx.to_i < marker)
227
+ if marker.nil? || chk_idx.to_i < marker
224
228
  raise ArgumentError.new "Unknown CLI option provided `#{list[chk_idx]}`"
225
229
  end
226
230
  end
@@ -0,0 +1,315 @@
1
+ require 'optparse'
2
+ require 'ostruct'
3
+
4
+ module Bogo
5
+ module Cli
6
+ # Parser for CLI arguments
7
+ class Parser
8
+ # @return [Symbol] represent unset value
9
+ UNSET = :__unset__
10
+
11
+ # Modified option parser to include
12
+ # subcommand information
13
+ class OptionParser < ::OptionParser
14
+ attr_accessor :subcommands
15
+
16
+ def help
17
+ v = super
18
+ return v if Array(subcommands).empty?
19
+ v += "\n" + "Available Commands:\n\n"
20
+ Array(subcommands).each do |sc|
21
+ v += summary_indent + sc.name.to_s + "\t" + sc.description.to_s + "\n"
22
+ end
23
+ v + "\n" + 'See `<command> --help` for more information on a specific command.'
24
+ end
25
+ end
26
+
27
+ class Command
28
+ # @return [Array<Command>] list of subcommands
29
+ attr_reader :commands
30
+ # @return [String] name of command
31
+ attr_reader :name
32
+ # @return [Array<Flag>] flags for command
33
+ attr_reader :flags
34
+ # @return [Proc] callable to be executed
35
+ attr_reader :callable
36
+ # @return [OpenStruct] flag option values
37
+ attr_reader :options
38
+ # @return [OptionParser] command parser
39
+ attr_reader :parser
40
+
41
+ # Create a new command
42
+ #
43
+ # @param name [String, Symbol] name of command
44
+ # @return [Command]
45
+ def initialize(name)
46
+ @name = name.to_sym
47
+ @commands = []
48
+ @flags = []
49
+ @callable = nil
50
+ @options = OpenStruct.new
51
+ end
52
+
53
+ # Add a new flag
54
+ #
55
+ # @param short [String, Symbol] short flag
56
+ # @param long [String, Symbol] long flag
57
+ # @param description [String] description of flag
58
+ # @param default [String] default flag value
59
+ def on(short, long, description=UNSET, opts={}, &block)
60
+ if short.to_s.size > 1
61
+ if description == UNSET
62
+ description = long
63
+ long = short
64
+ short = nil
65
+ elsif description.is_a?(Hash)
66
+ opts = description
67
+ description = long
68
+ long = short
69
+ short = nil
70
+ end
71
+ end
72
+ Flag.new(
73
+ short_name: short,
74
+ long_name: long,
75
+ description: description,
76
+ default: opts[:default],
77
+ callable: block,
78
+ ).tap do |f|
79
+ @flags << f
80
+ end
81
+ end
82
+
83
+ # Add a new command
84
+ #
85
+ # @param name [String, Symbol] name of command
86
+ def command(name, &block)
87
+ Command.new(name).load(&block).tap do |c|
88
+ @commands << c
89
+ end
90
+ end
91
+
92
+ # @return [String] description of command
93
+ def description(v=UNSET)
94
+ @description = v unless v == UNSET
95
+ @description
96
+ end
97
+
98
+ # Register callable for command
99
+ def run(&block)
100
+ @callable = block
101
+ end
102
+
103
+ # Load a command configuration block
104
+ def load(&block)
105
+ instance_exec(&block)
106
+ self
107
+ end
108
+
109
+ # @return [String] help output
110
+ def help
111
+ parser.help
112
+ end
113
+
114
+ # Parse the arguments
115
+ #
116
+ # @param arguments [Array<String>] CLI arguments
117
+ # @return [OpenStruct, Array<String>]
118
+ def parse(arguments)
119
+ raise "Must call #generate before #parse" if
120
+ parser.nil?
121
+ @options = OpenStruct.new.tap do |opts|
122
+ flags.each do |f|
123
+ next if f.default.nil?
124
+ opts[f.option_name] = f.default
125
+ end
126
+ end
127
+ parser.parse!(arguments, into: options)
128
+ [options, arguments]
129
+ end
130
+
131
+ # Generate command parsers
132
+ #
133
+ # @param parents [Array<String,Symbol>] ancestors of command
134
+ # @return [Hash<string,OptParser>]
135
+ def generate(parents=[], add_help: true)
136
+ Hash.new.tap { |cmds|
137
+ @parser = OptionParser.new
138
+ full_name = parents + [name]
139
+ parser.program_name = full_name.join(' ')
140
+ parser.banner = description unless
141
+ description == UNSET
142
+ if add_help
143
+ parser.on('-h', '--help', "Display help information") do
144
+ $stderr.puts parser.help
145
+ exit
146
+ end
147
+ end
148
+ flags.each { |f|
149
+ if !f.boolean? && f.long_name.end_with?('=')
150
+ short = "-#{f.short_name}" if f.short_name
151
+ long = "--#{f.long_name[0, f.long_name.size - 1]} VALUE"
152
+ else
153
+ short = "-#{f.short_name}" if f.short_name
154
+ long = "--#{f.long_name}"
155
+ end
156
+ parser.on(short, long, f.description, &f.callable)
157
+ }
158
+
159
+ commands.map { |c|
160
+ c.generate(full_name, add_help: add_help)
161
+ }.inject(cmds) { |memo, list|
162
+ memo.merge!(list)
163
+ }
164
+
165
+ parser.subcommands = commands
166
+
167
+ cmds[full_name.join(' ')] = self
168
+ }
169
+ end
170
+
171
+ def to_hash
172
+ options.to_h
173
+ end
174
+ end
175
+
176
+ class Flag
177
+ # @return [String] short flag
178
+ attr_reader :short_name
179
+ # @return [String] long flag
180
+ attr_reader :long_name
181
+ # @return [String] default value
182
+ attr_reader :default
183
+ # @return [String] flag description
184
+ attr_reader :description
185
+ # @return [Proc] block to execute on flag called
186
+ attr_reader :callable
187
+
188
+ # Create a new flag
189
+ #
190
+ # @param short_name [String, Symbol] Single character flag
191
+ # @param long_name [String, Symbol] Full flag name
192
+ # @param description [String] Description of flag
193
+ # @param default [Object] Default value for flag
194
+ # @return Flag
195
+ def initialize(long_name:,
196
+ short_name: nil,
197
+ description: nil,
198
+ default: nil,
199
+ callable: nil
200
+ )
201
+ if short_name
202
+ short_name = short_name.to_s
203
+ short_name = short_name[1, short_name.size] if
204
+ short_name.start_with?('-')
205
+ raise ArgumentError,
206
+ "Flag short name must be single character (flag: #{short_name})" if
207
+ short_name.size > 1
208
+ end
209
+ @short_name = short_name
210
+ @long_name = long_name.to_s.sub(/^-+/, '').gsub('_', '-')
211
+ @description = description
212
+ @default = default
213
+ @callable = callable
214
+ end
215
+
216
+ # @return [Symbol] option compatible name
217
+ def option_name
218
+ long_name.split('=').first.gsub('-', '_').to_sym
219
+ end
220
+
221
+ # @return [Boolean] flag is boolean value
222
+ def boolean?
223
+ !long_name.include?('=')
224
+ end
225
+ end
226
+
227
+ # @return [Boolean] generate help content
228
+ attr_accessor :generate_help
229
+
230
+ # Parse a command setup block, process arguments
231
+ # and execute command if match found
232
+ #
233
+ # @param help [Boolean] generate help content
234
+ # @return [Object] result
235
+ def self.parse(help: true, &block)
236
+ parser = self.new
237
+ parser.generate_help = !!help
238
+ parser.load(&block)
239
+ parser.execute
240
+ end
241
+
242
+ # Create a new parser
243
+ #
244
+ # @param name [String, Symbol] name of root command
245
+ # @return [Parser]
246
+ def initialize(name: nil)
247
+ name = self.class.root_name unless name
248
+ @root = Command.new(name)
249
+ end
250
+
251
+ # Add a new command
252
+ #
253
+ # @param name [String, Symbol] name of command
254
+ def command(name, &block)
255
+ @root.command(name, &block)
256
+ end
257
+
258
+ # Add a new flag
259
+ #
260
+ # @param short [String, Symbol] short flag
261
+ # @param long [String, Symbol] long flag
262
+ # @param description [String] description of flag
263
+ # @param default [String] default flag value
264
+ def on(short, long, description, **options, &block)
265
+ @root.on(short, long, description, options, &block)
266
+ end
267
+
268
+ # Register callable for command
269
+ def run(&block)
270
+ @root.run(&block)
271
+ end
272
+
273
+ # Load a command configuration block
274
+ def load(&block)
275
+ @root.load(&block)
276
+ end
277
+
278
+ # Generate all parsers
279
+ #
280
+ # @return [Hash<String,Hash<Parser,Command>>]
281
+ def generate
282
+ @root.generate(add_help: generate_help)
283
+ end
284
+
285
+ # Execute command based on CLI arguments
286
+ def execute
287
+ cmds = @root.generate
288
+ base_args = arguments
289
+ line = base_args.join(' ')
290
+ cmd_key = cmds.keys.find_all { |k|
291
+ line.start_with?(k)
292
+ }.sort_by(&:size).last
293
+ if cmd_key.nil?
294
+ return cmds[@root.name].parser
295
+ end
296
+ base_args = base_args.slice(cmd_key.split(' ').size, base_args.size)
297
+ a = cmds[cmd_key].parse(base_args)
298
+ return cmds[cmd_key] unless cmds[cmd_key].callable
299
+ cmds[cmd_key].callable.call(*a)
300
+ exit 0
301
+ end
302
+
303
+ private
304
+
305
+ # @return [Array<String>]
306
+ def arguments
307
+ [self.class.root_name] + ARGV
308
+ end
309
+
310
+ def self.root_name
311
+ File.basename($0)
312
+ end
313
+ end
314
+ end
315
+ end
@@ -1,7 +1,7 @@
1
1
  # Trigger shutdown on INT or TERM signals
2
2
  o_int = Signal.trap("INT") {
3
3
  o_int.call if o_int.respond_to?(:call)
4
- if (Bogo::Cli.exit_on_signal == false)
4
+ if Bogo::Cli.exit_on_signal == false
5
5
  Thread.main.raise SignalException.new("SIGINT")
6
6
  else
7
7
  exit 0
@@ -9,49 +9,30 @@ o_int = Signal.trap("INT") {
9
9
  }
10
10
 
11
11
  o_term = Signal.trap("TERM") {
12
- o_int.call if o_int.respond_to?(:call)
13
- if (Bogo::Cli.exit_on_signal == false)
12
+ o_term.call if o_term.respond_to?(:call)
13
+ if Bogo::Cli.exit_on_signal == false
14
14
  Thread.main.raise SignalException.new("SIGTERM")
15
15
  else
16
16
  exit 0
17
17
  end
18
18
  }
19
19
 
20
- require "bogo-cli"
21
-
22
- class Slop
23
- def bogo_cli_run(*args, &block)
24
- slop_run(*args, &block)
25
- old_runner = @runner
26
- @runner = proc { |*args| old_runner.call(*args); exit 0 }
27
- end
28
-
29
- alias_method :slop_run, :run
30
- alias_method :run, :bogo_cli_run
31
-
32
- class Option
33
- def default?
34
- @value.nil?
35
- end
36
- end
37
- end
38
-
39
20
  module Bogo
40
21
  module Cli
41
22
  class Setup
42
23
  class << self
43
24
 
44
- # Wrap slop setup for consistent usage
25
+ # Wrap parsing setup for consistent usage
45
26
  #
46
- # @yield Slop setup block
27
+ # @yield CLI setup block
47
28
  # @return [TrueClass]
48
29
  def define(&block)
49
30
  begin
50
- slop_result = Slop.parse(:help => true) do
31
+ result = Parser.parse(help: true) do
51
32
  instance_exec(&block)
52
33
  end
53
- puts slop_result.help
54
- exit -1
34
+ puts result.help
35
+ exit 255
55
36
  rescue StandardError, ScriptError => err
56
37
  err_msg = err.message
57
38
  if err.respond_to?(:original) && err.original
@@ -1,6 +1,6 @@
1
1
  module Bogo
2
2
  module Cli
3
3
  # Current library version
4
- VERSION = Gem::Version.new("0.2.18")
4
+ VERSION = Gem::Version.new("0.3.0")
5
5
  end
6
6
  end
data/lib/bogo-cli.rb CHANGED
@@ -1,17 +1,12 @@
1
- require 'bogo'
2
- require 'slop'
3
- require 'bogo-config'
4
-
5
1
  module Bogo
6
2
  module Cli
7
3
  autoload :Command, 'bogo-cli/command'
4
+ autoload :Parser, 'bogo-cli/parser'
8
5
  autoload :Setup, 'bogo-cli/setup'
6
+ autoload :VERSION, 'bogo-cli/version'
9
7
 
10
8
  class << self
11
9
  attr_accessor :exit_on_signal
12
10
  end
13
-
14
11
  end
15
12
  end
16
-
17
- require 'bogo-cli/version'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bogo-cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.18
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Roberts
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-12-08 00:00:00.000000000 Z
11
+ date: 2022-10-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bogo
@@ -64,34 +64,20 @@ dependencies:
64
64
  - - ">="
65
65
  - !ruby/object:Gem::Version
66
66
  version: '0'
67
- - !ruby/object:Gem::Dependency
68
- name: slop
69
- requirement: !ruby/object:Gem::Requirement
70
- requirements:
71
- - - "~>"
72
- - !ruby/object:Gem::Version
73
- version: '3'
74
- type: :runtime
75
- prerelease: false
76
- version_requirements: !ruby/object:Gem::Requirement
77
- requirements:
78
- - - "~>"
79
- - !ruby/object:Gem::Version
80
- version: '3'
81
67
  - !ruby/object:Gem::Dependency
82
68
  name: rake
83
69
  requirement: !ruby/object:Gem::Requirement
84
70
  requirements:
85
- - - "~>"
71
+ - - ">="
86
72
  - !ruby/object:Gem::Version
87
- version: '10'
73
+ version: '0'
88
74
  type: :development
89
75
  prerelease: false
90
76
  version_requirements: !ruby/object:Gem::Requirement
91
77
  requirements:
92
- - - "~>"
78
+ - - ">="
93
79
  - !ruby/object:Gem::Version
94
- version: '10'
80
+ version: '0'
95
81
  - !ruby/object:Gem::Dependency
96
82
  name: minitest
97
83
  requirement: !ruby/object:Gem::Requirement
@@ -119,6 +105,7 @@ files:
119
105
  - bogo-cli.gemspec
120
106
  - lib/bogo-cli.rb
121
107
  - lib/bogo-cli/command.rb
108
+ - lib/bogo-cli/parser.rb
122
109
  - lib/bogo-cli/setup.rb
123
110
  - lib/bogo-cli/version.rb
124
111
  homepage: https://github.com/spox/bogo-cli
@@ -140,8 +127,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
140
127
  - !ruby/object:Gem::Version
141
128
  version: '0'
142
129
  requirements: []
143
- rubyforge_project:
144
- rubygems_version: 2.7.6
130
+ rubygems_version: 3.4.0.dev
145
131
  signing_key:
146
132
  specification_version: 4
147
133
  summary: CLI Helper libraries