bales 0.0.4 → 0.1.3

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
- SHA1:
3
- metadata.gz: 3697a0dc2411e2fed3dc7bacbd03a6d33c4ce785
4
- data.tar.gz: d5aed1d5f97a63e0505ad77de3199be86b90a7dd
2
+ SHA256:
3
+ metadata.gz: b6ada6a857baba9d741859d2ec38d4992e6b9e4b409dd3bb7d0aacce7a6b129b
4
+ data.tar.gz: 1cbc31b944a5bd783aa563d36477bc15bcabaa5a44d7d6a7f03a586d7d02fd3e
5
5
  SHA512:
6
- metadata.gz: f15a4c35d75271d03d3e22b503c8eae3e8c295e1f912e724249ec7195e375df32282a1899e059901e9f7396d911a7c6675e6497c6c8885e1b8265f5b22eb686c
7
- data.tar.gz: 9a979f78d9de61e06e719bd12951c356a8d935c38fef170f9c8ed05815a6b13e09bef24aa7b55cb7f091f1235c464d0e81762f9ea489b51713eceec83f72a531
6
+ metadata.gz: 2c4b688015b19d0b11a110729ec6b955e6c11e2ca3cf01f2bec922a9aa03203798bd34fe1efb84f599c89e9cc235939d19e812057348b508b6abf1ff230d7b93
7
+ data.tar.gz: 1c52425d3d50315b2fcf93d78a603bc47df4a2afa083a4646c92edfad7289fb0e7f11e4e6b421035719cae1a2b81267035581404ebfda511241e2145b8b65a84
data/README.md CHANGED
@@ -26,9 +26,8 @@ module SimpleApp
26
26
  short_form: '-2',
27
27
  type: String
28
28
 
29
- action do |args, opts|
30
- opts[:recipient] ||= "world"
31
- puts "Hello, #{opts[:recipient]}!"
29
+ action do |recipient: "world"|
30
+ puts "Hello, #{recipient}!"
32
31
  end
33
32
 
34
33
  # Subcommand
@@ -39,13 +38,13 @@ module SimpleApp
39
38
  short_form: '-w',
40
39
  long_form: '--with'
41
40
 
42
- action do |victims, opts|
43
- suffix = opts[:weapon] ? " with a #{opts[:weapon]}" : ""
41
+ action do |*victims, weapon: nil|
42
+ suffix = weapon ? " with a #{weapon}" : ""
44
43
 
45
44
  if victims.none?
46
45
  puts "You have been smacked#{suffix}."
47
46
  else
48
- puts "#{victim} has been smacked#{suffix}."
47
+ victims.each {|v| puts "#{v} has been smacked#{suffix}."}
49
48
  end
50
49
  end
51
50
  end
@@ -53,6 +52,13 @@ module SimpleApp
53
52
  # Specify subcommand's parent class
54
53
  command "help", parent: Bales::Command::Help
55
54
 
55
+ # Subsubcommands!
56
+ command "smack with" do
57
+ action do |weapon, *victims|
58
+ SimpleApp::Command::Smack.run(*victims, weapon: weapon)
59
+ end
60
+ end
61
+
56
62
  # This is what makes the app actually run!
57
63
  parse_and_run
58
64
  end
@@ -61,7 +67,8 @@ end
61
67
  SimpleApp::Application.parse_and_run
62
68
  ```
63
69
 
64
- And like this (assuming the above script lives in `/usr/local/bin/simple-app`)!
70
+ And like this (assuming the above script lives in
71
+ `/usr/local/bin/simple-app`)!
65
72
 
66
73
  ```
67
74
  $ simple-app
@@ -79,59 +86,102 @@ Bruce has been smacked.
79
86
  Bruce has been smacked.
80
87
  $ simple-app smack Bruce --with fish
81
88
  Bruce has been smacked with a fish.
89
+ $ simple-app smack with fish Bruce
90
+ Bruce has been smacked with a fish.
82
91
  ```
83
92
 
84
93
  ## So how does it work?
85
94
 
86
95
  * Come up with a name for your app, like `MyApp`
87
- * Create an `Application` class under that namespace which inherits from `Bales::Application`
96
+
97
+ * Create an `Application` class under that namespace which inherits
98
+ from `Bales::Application`
99
+
88
100
  * Use the DSL (or define classes manually, if that's your thing)
89
101
 
90
- Basically, a Bales app is just a bunch of classes with some fairy dust that turns them into runnable commands. Bales will check the namespace that your subclass of `Bales::Application` lives in for a `Command` namespace, then search there for available commands.
102
+ Basically, a Bales app is just a bunch of classes with some fairy dust
103
+ that turns them into runnable commands. Bales will check the
104
+ namespace that your subclass of `Bales::Application` lives in for a
105
+ `Command` namespace, then search there for available commands.
91
106
 
92
107
  The application has a few available DSL-ish functions for you to play with.
93
108
 
94
- * `version`: sets your app's version number. If you use semantic versioning, you can query this with the `major_version`, `minor_version`, and `patch_level` class methods.
95
- * `command "foo" { ... }`: defines a subcommand called "foo", which turns into a class called `MyApp::Command::Foo` (if you picked the name `MyApp` above). If you provide a block, said block will be evaluated in the class' context (see below for things you can do in said context).
109
+ * `version`: sets your app's version number. If you use semantic
110
+ versioning, you can query this with the `major_version`,
111
+ `minor_version`, and `patch_level` class methods.
96
112
 
97
- Meanwhile, commands *also* have some DSL-ish functions to play around with.
113
+ * `command "foo" { ... }`: defines a subcommand called "foo", which
114
+ turns into a class called `MyApp::Command::Foo` (if you picked the
115
+ name `MyApp` above). If you provide a block, said block will be
116
+ evaluated in the class' context (see below for things you can do in
117
+ said context).
98
118
 
99
- * `option`: defines a command-line option, like `--verbose` or `-f` or something. It takes the name of the option (which becomes a key in your command's options hash) and some named parameters:
100
- * `:type`: a valid Ruby class, like `String`. For a boolean, you should provide either `TrueClass` or `FalseClass`, which - when set - will set the option in question to `true` or `false` (respectively).
101
- * `:short_form`: a short flag, like `'-v'`. You must specify this if you want a short flag.
102
- * `:long_form`: a long flag, like `'--verbose'`. This will be created from the option's name if you don't override it here.
103
- * `:description`: a quick description of the option, like `"Whether or not to be verbose"`.
104
- * `action`: defines what the command should do when it's called. This is provided in the form of a block. Said block should accept two arguments (an array of arguments and a hash of options), though you don't *have* to name them with pipes and stuff if you know that your command won't take any arguments or options.
105
- * `description`: sets a long description of what your command does. Should be a string.
106
- * `summary`: sets a short description of what your command does. Should be a string. Should also be shorter than `:description`, though this isn't strictly necessary.
119
+ Meanwhile, commands *also* have some DSL-ish functions to play around with.
107
120
 
108
- Some of the command functions (`option`, `action`, `description`, `summary`) can also be used from within the application class; doing so will define and configure a "root command", which is what is run if you run your app without any arguments.
121
+ * `option`: defines a command-line option, like `--verbose` or `-f` or
122
+ something. It takes the name of the option (which becomes a key in
123
+ your command's options hash) and some named parameters:
124
+
125
+ * `:type`: a valid Ruby class, like `String`. For a boolean, you
126
+ should provide either `TrueClass` or `FalseClass`, which - when
127
+ set - will set the option in question to `true` or `false`
128
+ (respectively).
129
+
130
+ * `:short_form`: a short flag, like `'-v'`. You must specify this
131
+ if you want a short flag.
132
+
133
+ * `:long_form`: a long flag, like `'--verbose'`. This will be
134
+ created from the option's name if you don't override it here.
135
+
136
+ * `:description`: a quick description of the option, like `"Whether
137
+ or not to be verbose"`.
138
+
139
+ * `action`: defines what the command should do when it's called. This
140
+ is provided in the form of a block. Said block should accept two
141
+ arguments (an array of arguments and a hash of options), though you
142
+ don't *have* to name them with pipes and stuff if you know that your
143
+ command won't take any arguments or options.
144
+
145
+ * `description`: sets a long description of what your command does.
146
+ Should be a string.
147
+
148
+ * `summary`: sets a short description of what your command does.
149
+ Should be a string. Should also be shorter than `:description`,
150
+ though this isn't strictly necessary.
151
+
152
+ Some of the command functions (`option`, `action`, `description`,
153
+ `summary`) can also be used from within the application class; doing
154
+ so will define and configure a "root command", which is what is run if
155
+ you run your app without any arguments.
109
156
 
110
157
  ## What can this thing already do?
111
158
 
112
159
  * Create a working command-line app
113
- * Automatically produce subcommands (recursively, in fact) based on the namespaces of the corresponding `Bales::Command` subclasses
160
+
161
+ * Automatically produce subcommands (recursively, in fact) based on
162
+ the namespaces of the corresponding `Bales::Command` subclasses
163
+
114
164
  * Provide a DSL defining commands and options
115
165
 
116
166
  ## What might this thing someday do in the future?
117
167
 
118
168
  * Provide some helpers to wrap things like HighLine, curses, etc.
119
- * Provide some additional flexibility in how options are specified without requiring users to completely reimplement a command's option parsing functions
169
+
170
+ * Provide some additional flexibility in how options are specified
171
+ without requiring users to completely reimplement a command's option
172
+ parsing functions
120
173
 
121
174
  ## What kind of a silly name is "Bales", anyway?
122
175
 
123
- It's shamelessly stolen^H^H^H^H^H^Hborrowed from Jason R. Clark's "Testing the Multiverse" talk at Ruby on Ales 2015 (which, if you haven't watched, you [totally should](http://confreaks.tv/videos/roa2015-testing-the-multiverse)). Sorry, Jason. Hope you don't mind.
176
+ It's shamelessly stolen^H^H^H^H^H^Hborrowed from Jason R. Clark's
177
+ "Testing the Multiverse" talk at Ruby on Ales 2015 (which, if you
178
+ haven't watched, you [totally
179
+ should](http://confreaks.tv/videos/roa2015-testing-the-multiverse)).
180
+ Sorry, Jason. Hope you don't mind.
124
181
 
125
- Ironically enough, despite ripping off the name from a talk about Ruby testing, Bales currently lacks any formal test suite. Hm...
182
+ Ironically enough, despite ripping off the name from a talk about Ruby
183
+ testing, Bales currently lacks any formal test suite. Hm...
126
184
 
127
185
  ## What's the license?
128
186
 
129
- MIT License
130
-
131
- Copyright (c) 2015 Ryan S. Northrup
132
-
133
- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
134
-
135
- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
136
-
137
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
187
+ MIT License; see COPYING for details.
@@ -45,7 +45,7 @@ module Bales
45
45
  end
46
46
 
47
47
  ##
48
- # Set or retrieve the application's banner.
48
+ # Set or retrieve the application's banner
49
49
  def self.banner(text=nil)
50
50
  root_command.banner(text) unless text.nil?
51
51
  root_command.banner
@@ -58,6 +58,12 @@ module Bales
58
58
  root_command.description
59
59
  end
60
60
 
61
+ ##
62
+ # Alias for +description+
63
+ def self.desc(text=nil)
64
+ self.description(text)
65
+ end
66
+
61
67
  ##
62
68
  # Set or retrieve the application's summary
63
69
  def self.summary(text=nil)
@@ -124,21 +130,15 @@ module Bales
124
130
  command ||= default_command
125
131
  opts, args = command.parse_opts result
126
132
  return command, args, opts
127
- end
128
-
129
- ##
130
- # Parses ARGV (or some other array if you specify one) for a
131
- # command to run and its arguments/options, then runs the command.
132
- def self.parse_and_run(argv=ARGV)
133
- command, args, opts = parse argv
134
- run command, *args, **opts
135
133
  rescue OptionParser::MissingArgument
136
134
  flag = $!.message.gsub("missing argument: ", '')
137
135
  puts "#{$0}: error: option needs an argument (#{flag})"
136
+ puts "Usage: #{command.usage}"
138
137
  exit!
139
138
  rescue OptionParser::InvalidOption
140
139
  flag = $!.message.gsub("invalid option: ", '')
141
140
  puts "#{$0}: error: unknown option (#{flag})"
141
+ puts "Usage: #{command.usage}"
142
142
  exit!
143
143
  rescue ArgumentError
144
144
  raise unless $!.message.match(/wrong number of arguments/)
@@ -148,9 +148,23 @@ module Bales
148
148
  .gsub(")", '')
149
149
  .split(" for ")
150
150
  puts "#{$0}: error: expected #{expected} args but got #{received}"
151
+ puts "Usage: #{command.usage}"
151
152
  exit!
152
153
  end
153
154
 
155
+ ##
156
+ # Parses ARGV (or some other array if you specify one) for a
157
+ # command to run and its arguments/options, then runs the command.
158
+ def self.parse_and_run(argv=ARGV)
159
+ command, args, opts = parse argv
160
+ # OptionParser includes nil values for missing options, so we
161
+ # need to make sure those don't clobber the command's defaults.
162
+ command.method(:run).parameters.select {|p| p[0] == :key}.map do |p|
163
+ opts.delete p[1] if opts[p[1]].nil?
164
+ end
165
+ run command, *args, **opts
166
+ end
167
+
154
168
  private
155
169
 
156
170
  def self.parse_command_name(argv)
@@ -165,10 +179,14 @@ module Bales
165
179
  .map { |p| p.capitalize }
166
180
  .join
167
181
  name = "#{const}::#{part}"
168
- if const.const_defined? name
169
- const = eval(name)
170
- depth += 1
171
- else
182
+ begin
183
+ if const.const_defined? name
184
+ const = eval(name)
185
+ depth += 1
186
+ else
187
+ break
188
+ end
189
+ rescue NameError
172
190
  break
173
191
  end
174
192
  end
@@ -60,6 +60,12 @@ module Bales
60
60
  const_set "DESCRIPTION", "(no description)"
61
61
  end
62
62
 
63
+ ##
64
+ # Alias for +description+
65
+ def self.desc(text=nil)
66
+ self.description(text)
67
+ end
68
+
63
69
  ##
64
70
  # Get the command's summary, or set it if a string is passed to
65
71
  # it.
@@ -87,6 +93,60 @@ module Bales
87
93
  end
88
94
  end
89
95
 
96
+ ##
97
+ # Translates the command's class name to a more complete name
98
+ # passed on the command line, including the subcommand, all
99
+ # commands leading to it, and the root command.
100
+ def self.full_name
101
+ parts = self
102
+ .name
103
+ .split('::')
104
+ .map { |p| p.gsub(/(.)([A-Z])/, '\1-\2').downcase }
105
+ parts = parts[2..-1].join(' ')
106
+ "#{$0} #{parts}"
107
+ end
108
+
109
+ ##
110
+ # Print the command's usage statement.
111
+ def self.usage
112
+ usage = []
113
+
114
+ # Name
115
+ usage << full_name
116
+
117
+ # Arguments
118
+ method(:run).parameters.each do |type, name|
119
+ # We don't handle keys and keyrests, since they're going to be
120
+ # taken care of once we start dealing with options.
121
+ case type
122
+ when :req then usage << "<#{name}>"
123
+ when :opt then usage << "[<#{name}>]"
124
+ when :rest then usage << "[<#{name}> ...]"
125
+ end
126
+ end
127
+
128
+ # Options
129
+ options.each_pair do |opt, args|
130
+ case
131
+ when (args[:type] <= TrueClass or args[:type] <= FalseClass)
132
+ if args.key?(:short_form)
133
+ usage << "[(#{args[:long_form]}|#{args[:short_form]})]"
134
+ else
135
+ usage << "[#{args[:long_form]}]"
136
+ end
137
+ else
138
+ if args.key?(:short_form)
139
+ usage << "[(#{args[:long_form]}|#{args[:short_form]}) " \
140
+ "<#{args[:arg]}>]"
141
+ else
142
+ usage << "[#{args[:long_form]} <#{args[:arg]}>]"
143
+ end
144
+ end
145
+ end
146
+
147
+ usage.join(' ')
148
+ end
149
+
90
150
  ##
91
151
  # Creates a new subcommand of the current command. Identical in
92
152
  # usage to +Bales::Application.command+, the only significant
@@ -203,6 +263,8 @@ module Bales
203
263
  opts[:default] = false if opts[:type] <= TrueClass
204
264
  opts[:default] = true if opts[:type] <= FalseClass
205
265
 
266
+ opts[:description] = opts[:desc] if opts.key?(:desc)
267
+
206
268
  result = options
207
269
  result[name] = opts
208
270
  options = result
@@ -14,6 +14,7 @@ class Bales::Command::Help < Bales::Command
14
14
  end
15
15
 
16
16
  print_summary(target)
17
+ print_usage(target)
17
18
  print_options(target)
18
19
  print_commands(target)
19
20
  end
@@ -40,8 +41,13 @@ class Bales::Command::Help < Bales::Command
40
41
  end
41
42
 
42
43
  ns.constants
43
- .select { |c| ns.const_defined? "#{ns}::#{c}" }
44
- .select { |c| eval("#{ns}::#{c}").class == Class }
44
+ .select { |c|
45
+ begin
46
+ ns.const_defined? "#{ns}::#{c}"
47
+ rescue NameError
48
+ false
49
+ end
50
+ }.select { |c| eval("#{ns}::#{c}").class == Class }
45
51
  .select { |c| eval("#{ns}::#{c}") <= Bales::Command }
46
52
  .map { |c| eval "#{ns}::#{c}" }
47
53
  end
@@ -109,6 +115,10 @@ class Bales::Command::Help < Bales::Command
109
115
  print "Description:\n#{command.description}\n\n"
110
116
  end
111
117
 
118
+ def self.print_usage(command)
119
+ print "Usage: #{command.usage}\n\n"
120
+ end
121
+
112
122
  def self.print_commands(namespace)
113
123
  cmds = commands(namespace)
114
124
 
@@ -1,3 +1,3 @@
1
1
  module Bales
2
- VERSION="0.0.4"
2
+ VERSION="0.1.3"
3
3
  end
metadata CHANGED
@@ -1,38 +1,33 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bales
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan S. Northrup
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-08-17 00:00:00.000000000 Z
11
+ date: 2020-10-26 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: A framework for building command-line applications
14
14
  email:
15
- - rnorthrup@newleaders.com
15
+ - northrup@yellowapple.us
16
16
  executables: []
17
17
  extensions: []
18
18
  extra_rdoc_files: []
19
19
  files:
20
20
  - README.md
21
21
  - lib/bales.rb
22
- - lib/bales.rb~
23
22
  - lib/bales/application.rb
24
- - lib/bales/application.rb~
25
23
  - lib/bales/command.rb
26
- - lib/bales/command.rb~
27
24
  - lib/bales/command/help.rb
28
- - lib/bales/command/help.rb~
29
25
  - lib/bales/version.rb
30
- - lib/bales/version.rb~
31
26
  homepage: http://github.com/YellowApple/bales
32
27
  licenses:
33
28
  - MIT
34
29
  metadata: {}
35
- post_install_message:
30
+ post_install_message:
36
31
  rdoc_options: []
37
32
  require_paths:
38
33
  - lib
@@ -47,9 +42,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
47
42
  - !ruby/object:Gem::Version
48
43
  version: '0'
49
44
  requirements: []
50
- rubyforge_project:
51
- rubygems_version: 2.4.6
52
- signing_key:
45
+ rubygems_version: 3.1.2
46
+ signing_key:
53
47
  specification_version: 4
54
48
  summary: Ruby on Bales
55
49
  test_files: []
@@ -1,236 +0,0 @@
1
- # :main: README.md
2
-
3
- ##
4
- # Ruby on Bales (or just "Bales" for short) is to command-line apps what
5
- # Ruby on Rails (or just "Rails" for short) is to websites/webapps.
6
- #
7
- # The name (and concept) was shamelessly stolen from Jason R. Clark's
8
- # "Testing the Multiverse" talk at Ruby on Ales 2015. Here's to hoping that
9
- # we, as a Ruby programming community, can get a headstart on a command-line
10
- # app framework *before* the Puma-Unicorn Wars ravage the Earth.
11
- module Bales
12
- ##
13
- # Base class for Bales apps. Your command-line program should create a
14
- # subclass of this, then call said subclass' #parse_and_run instance
15
- # method, like so:
16
- #
17
- # ```ruby
18
- # class MyApp::Application < Bales::Application
19
- # # insert customizations here
20
- # end
21
- #
22
- # MyApp::Application.parse_and_run
23
- # ```
24
- class Application
25
- ##
26
- # Runs the specified command (should be a valid class; preferably, should
27
- # be a subclass of Bales::Command). Takes a list of positional args
28
- # followed by named options.
29
- def self.run(command=Bales::Command::Help, *args, **opts)
30
- command.run *args, **opts
31
- end
32
-
33
- ##
34
- # Parses ARGV (or some other array if you specify one), returning the
35
- # class of the identified command, a hash containing the passed-in
36
- # options, and a list of any remaining arguments
37
- def self.parse(argv=ARGV)
38
- command, result = parse_command_name argv.dup
39
- opts, args = command.parse_opts result
40
- return command, args, opts
41
- end
42
-
43
- ##
44
- # Parses ARGV (or some other array if you specify one) for a command to
45
- # run and its arguments/options, then runs the command.
46
- def self.parse_and_run(argv=ARGV)
47
- command, args, opts = parse argv
48
- run command, *args, **opts
49
- end
50
-
51
- private
52
-
53
- def parse_command_name(argv)
54
- command_name_parts = []
55
- argv.each do |arg|
56
- last if arg.match(/^-/)
57
- test = args_to_constant [*command_name_parts, arg]
58
- if eval("defined? #{test}") == "constant"
59
- command_name_parts.push argv.shift
60
- else
61
- last
62
- end
63
- end
64
- command = args_to_constant [*command_name_parts, arg]
65
- command, argv
66
- end
67
-
68
- def args_to_constant(argv)
69
- result = argv.dup
70
- result.map! do |arg|
71
- arg.capitalize
72
- arg.gsub('-','_').split('_').map { |e| e.capitalize}.join
73
- end
74
- result.join('::')
75
- end
76
- end
77
-
78
- ##
79
- # Base class for all Bales commands. Subclass this class to create your
80
- # own command, like so:
81
- #
82
- # ```ruby
83
- # class MyApp::Command::Hello < Bales::Command
84
- # def self.run(*args, **opts)
85
- # puts "Hello, world!"
86
- # end
87
- # end # produces a `my-app hello` command that prints "Hello, world!"
88
- # ```
89
- #
90
- # Note that the above will accept any number of arguments (including none
91
- # at all!). If you want to change this behavior, change `self.run`'s
92
- # signature, like so:
93
- #
94
- # ```ruby
95
- # class MyApp::Command::Smack < Bales::Command
96
- # def self.run(target, **opts)
97
- # puts "#{target} has been smacked with a large trout"
98
- # end
99
- # end
100
- # ```
101
- #
102
- # Subcommands are automatically derived from namespacing, like so:
103
- #
104
- # ```ruby
105
- # class MyApp::Command::Foo::Bar < Bales::Command
106
- # def self.run(*args, **opts)
107
- # # ...
108
- # end
109
- # end # produces `my-app foo bar`
110
- # ```
111
- #
112
- # Camel-cased command classes can be accessed using either hyphenation or
113
- # underscores, like so:
114
- #
115
- # ```ruby
116
- # class MyApp::Command::FooBarBaz < Bales::Command
117
- # # ...
118
- # end
119
- # # valid result: "my-app foo-bar-baz"
120
- # # also valid: "my-app foo_bar_baz"
121
- # ```
122
- class Command
123
- @options = {}
124
-
125
- ##
126
- # Runs the command with the provided list of arguments and named options.
127
- # Your command should override this method (which by default does
128
- # nothing), since this is the method that `Bales::Application.run` calls
129
- # in order to actually run your command.
130
- #
131
- # For example:
132
- #
133
- # ```ruby
134
- # class MyApp::Command::Hello < Bales::Command
135
- # def self.run(*args, **opts)
136
- # puts "Hello, world!"
137
- # end
138
- # end
139
- # ```
140
- def self.run(*args, **opts)
141
- end
142
-
143
- ##
144
- # Defines a named option that the command will accept, along with some
145
- # named arguments:
146
- #
147
- # `:short_form` (optional)
148
- # : A shorthand flag to use for the option (like `-v`). This should be a
149
- # string, like `"-v"`.
150
- #
151
- # `:long_form` (optional)
152
- # : A longhand flag to use for the option (like `--verbose`). This is
153
- # derived from the name of the option if not specified. This should be
154
- # a string, like `"--verbose"`
155
- #
156
- # `:type` (optional)
157
- # : The type that this option represents. Defaults to `TrueClass` if
158
- # `:arg` is not specified; else, defaults to `String`. This must be a
159
- # valid class name.
160
- #
161
- # A special note on boolean options: if you want your boolean to
162
- # default to `true`, set `:type` to `TrueClass`. Likewise, if you want
163
- # it to default to `false`, set `:type` to `FalseClass`.
164
- #
165
- # `:arg` (required unless `:type` is `TrueClass` or `FalseClass`)
166
- # : The name of the argument this option accepts. This must not be
167
- # defined if `:type` is either `TrueClass` or `FalseClass`; for all
168
- # other types, this must be specified. As mentioned above, though, if
169
- # `:type` is unspecified, the existence of `:arg` then determines
170
- # whether the option's `:type` should default to `TrueClass` or
171
- # `String`. This should be a symbol, like `:level`.
172
- #
173
- # Aside from the hash of option-options, `option` takes a single `name`
174
- # argument, which should be a symbol representing the name of the option
175
- # to be set, like `:verbose`.
176
- def self.option(name, **opts)
177
- name = name.to_sym
178
- opts[:long_form] ||= "--#{name.to_s}".gsub("_","-")
179
-
180
- unless opts[:type].class == Class
181
- raise ArgumentError, ":type option should be a valid class"
182
- end
183
-
184
- if opts[:type] == TrueClass or opts[:type] == FalseClass
185
- raise ArgumentError, ":arg in boolean opt" unless opts[:arg].nil?
186
- else
187
- raise ArgumentError, "missing :arg" if opts[:arg].nil?
188
- end
189
-
190
- @options[name] = opts
191
- end
192
-
193
- ##
194
- # Takes an ARGV-like array and returns a hash of options and what's left
195
- # of the original array. This is rarely needed for normal use, but is
196
- # an integral part of how a Bales::Application parses the ARGV it
197
- # receives.
198
- #
199
- # Normally, this should be perfectly fine to leave alone, but if you
200
- # prefer to define your own parsing method (e.g. if you want to specify
201
- # an alternative format for command-line options, or you are otherwise
202
- # dissatisfied with the default approach of wrapping OptionParser), this
203
- # is the method you'd want to override.
204
- def self.parse_opts(argv)
205
- optparser = OptionParser.new
206
- result = {}
207
- @options.each do |name, opts|
208
- result[name] = opts[:default]
209
- parser_args = []
210
- parser_args.push opts[:short_form]
211
- parser_args.push opts[:long_form]
212
- unless opts[:type] == TrueClass or opts[:type] == FalseClass
213
- parser_args.push opts[:type]
214
- end
215
- parser_args.push opts[:description]
216
-
217
- if opts[:type] == FalseClass
218
- optparser.on(*parser_args) do |value|
219
- result[name] = !value
220
- end
221
- else
222
- optparser.on(*parser_args) do |value|
223
- result[name] = value
224
- end
225
- end
226
- end
227
- end
228
- end
229
-
230
- class Command::Help < Command
231
- def self.run(*args, **opts)
232
- puts "This will someday output some help text"
233
-
234
- end
235
- end
236
- end
@@ -1,71 +0,0 @@
1
- ##
2
- # Base class for Bales apps. Your command-line program should create a
3
- # subclass of this, then call said subclass' #parse_and_run instance
4
- # method, like so:
5
- #
6
- # ```ruby
7
- # class MyApp::Application < Bales::Application
8
- # # insert customizations here
9
- # end
10
- #
11
- # MyApp::Application.parse_and_run
12
- # ```
13
- class Bales::Application
14
- @default_command = Bales::Command::Help
15
- def self.default(command=Bales::Command::Help)
16
- @default_command = command
17
- end
18
-
19
- ##
20
- # Runs the specified command (should be a valid class; preferably, should
21
- # be a subclass of Bales::Command). Takes a list of positional args
22
- # followed by named options.
23
- def self.run(command, *args, **opts)
24
- command.run *args, **opts
25
- end
26
-
27
- ##
28
- # Parses ARGV (or some other array if you specify one), returning the
29
- # class of the identified command, a hash containing the passed-in
30
- # options, and a list of any remaining arguments
31
- def self.parse(argv=ARGV)
32
- command, result = parse_command_name argv.dup
33
- command ||= @default_command
34
- opts, args = command.parse_opts result
35
- return command, args, opts
36
- end
37
-
38
- ##
39
- # Parses ARGV (or some other array if you specify one) for a command to
40
- # run and its arguments/options, then runs the command.
41
- def self.parse_and_run(argv=ARGV)
42
- command, args, opts = parse argv
43
- run command, *args, **opts
44
- end
45
-
46
- private
47
-
48
- def self.parse_command_name(argv)
49
- command_name_parts = []
50
- argv.each do |arg|
51
- last if arg.match(/^-/)
52
- test = args_to_constant [*command_name_parts, arg]
53
- if eval("defined? #{test}") == "constant"
54
- command_name_parts.push argv.shift
55
- else
56
- last
57
- end
58
- end
59
- command = args_to_constant [*command_name_parts]
60
- return command, argv
61
- end
62
-
63
- def self.args_to_constant(argv)
64
- result = argv.dup
65
- result.map! do |arg|
66
- arg.capitalize
67
- arg.gsub('-','_').split('_').map { |e| e.capitalize}.join
68
- end
69
- eval result.join('::')
70
- end
71
- end
@@ -1,189 +0,0 @@
1
- ##
2
- # Base class for all Bales commands. Subclass this class to create your
3
- # own command, like so:
4
- #
5
- # ```ruby
6
- # class MyApp::Command::Hello < Bales::Command
7
- # def self.run(*args, **opts)
8
- # puts "Hello, world!"
9
- # end
10
- # end # produces a `my-app hello` command that prints "Hello, world!"
11
- # ```
12
- #
13
- # Note that the above will accept any number of arguments (including none
14
- # at all!). If you want to change this behavior, change `self.run`'s
15
- # signature, like so:
16
- #
17
- # ```ruby
18
- # class MyApp::Command::Smack < Bales::Command
19
- # def self.run(target, **opts)
20
- # puts "#{target} has been smacked with a large trout"
21
- # end
22
- # end
23
- # ```
24
- #
25
- # Subcommands are automatically derived from namespacing, like so:
26
- #
27
- # ```ruby
28
- # class MyApp::Command::Foo::Bar < Bales::Command
29
- # def self.run(*args, **opts)
30
- # # ...
31
- # end
32
- # end # produces `my-app foo bar`
33
- # ```
34
- #
35
- # Camel-cased command classes can be accessed using either hyphenation or
36
- # underscores, like so:
37
- #
38
- # ```ruby
39
- # class MyApp::Command::FooBarBaz < Bales::Command
40
- # # ...
41
- # end
42
- # # valid result: "my-app foo-bar-baz"
43
- # # also valid: "my-app foo_bar_baz"
44
- # ```
45
- class Bales::Command
46
- @options = {}
47
- def self.options
48
- @options
49
- end
50
- def self.options=(new)
51
- @options = new
52
- end
53
-
54
- ##
55
- # Assigns an action to this command. Said action is represented as a
56
- # block, which should accept an array of arguments and a hash of options.
57
- # For example:
58
- #
59
- # ```ruby
60
- # class MyApp::Hello < Bales::Command
61
- # action do |args, opts|
62
- # puts "Hello, world!"
63
- # end
64
- # end
65
- # ```
66
- def self.action(&code)
67
- @action = code
68
- end
69
-
70
- def self.run(*args, **opts)
71
- @action.call(args, opts)
72
- end
73
-
74
- ##
75
- # Defines a named option that the command will accept, along with some
76
- # named arguments:
77
- #
78
- # `:short_form` (optional)
79
- # : A shorthand flag to use for the option (like `-v`). This should be a
80
- # string, like `"-v"`.
81
- #
82
- # `:long_form` (optional)
83
- # : A longhand flag to use for the option (like `--verbose`). This is
84
- # derived from the name of the option if not specified. This should be
85
- # a string, like `"--verbose"`
86
- #
87
- # `:type` (optional)
88
- # : The type that this option represents. Defaults to `TrueClass`.
89
- # Should be a valid class name, like `String` or `Integer`
90
- #
91
- # A special note on boolean options: if you want your boolean to
92
- # default to `true`, set `:type` to `TrueClass`. Likewise, if you want
93
- # it to default to `false`, set `:type` to `FalseClass`.
94
- #
95
- # `:arg` (optional)
96
- # : The name of the argument this option accepts. This should be a
97
- # symbol (like :level) or `false` (if the option is a boolean flag).
98
- # Defaults to the name of the option or (if the option's `:type` is
99
- # `TrueClass` or `FalseClass`) `false`.
100
- #
101
- # If this is an array, and `:type` is set to `Enumerable` or some
102
- # subclass thereof, this will instead be interpreted as a list of
103
- # sample arguments during option parsing. It's recommended you set
104
- # this accordingly if `:type` is `Enumerable` or any of its subclasses.
105
- #
106
- # `:required` (optional)
107
- # : Whether or not the option is required. This should be a boolean
108
- # (`true` or `false`). Default is `false`.
109
- #
110
- # Aside from the hash of option-options, `option` takes a single `name`
111
- # argument, which should be a symbol representing the name of the option
112
- # to be set, like `:verbose`.
113
- def self.option(name, **opts)
114
- name = name.to_sym
115
- opts[:long_form] ||= "--#{name.to_s}".gsub("_","-")
116
-
117
- unless opts[:type].is_a? Class
118
- raise ArgumentError, ":type option should be a valid class"
119
- end
120
-
121
- unless opts[:type].is_a?(TrueClass) or opts[:type].is_a?(FalseClass)
122
- opts[:arg] ||= name
123
- end
124
-
125
- # if opts[:type] == TrueClass or opts[:type] == FalseClass
126
- # raise ArgumentError, ":arg in boolean opt" unless opts[:arg].nil?
127
- # else
128
- # raise ArgumentError, "missing :arg" if opts[:arg].nil?
129
- # end
130
-
131
- result = {}
132
- result[name] = opts
133
- self.options = result
134
- end
135
-
136
- ##
137
- # Takes an ARGV-like array and returns a hash of options and what's left
138
- # of the original array. This is rarely needed for normal use, but is
139
- # an integral part of how a Bales::Application parses the ARGV it
140
- # receives.
141
- #
142
- # Normally, this should be perfectly fine to leave alone, but if you
143
- # prefer to define your own parsing method (e.g. if you want to specify
144
- # an alternative format for command-line options, or you are otherwise
145
- # dissatisfied with the default approach of wrapping OptionParser), this
146
- # is the method you'd want to override.
147
- def self.parse_opts(argv)
148
- optparser = OptionParser.new
149
- result = {}
150
- @options.each do |name, opts|
151
- result[name] = opts[:default]
152
- parser_args = []
153
- parser_args.push opts[:short_form]
154
- if opts[:type].is_a?(TrueClass) or opts[:type].is_a?(FalseClass)
155
- parser_args.push opts[:long_form]
156
- else
157
- argstring = opts[:arg].to_s.upcase
158
- if opts[:required]
159
- parser_args.push "#{opts[:long_form]} #{argstring}"
160
- else
161
- parser_args.push "#{opts[:long_form]} [#{argstring}]"
162
- parser_args.push opts[:type]
163
- end
164
- parser_args.push opts[:description]
165
-
166
- if opts[:type].is_a? FalseClass
167
- optparser.on(*parser_args) do |value|
168
- result[name] = !value
169
- end
170
- else
171
- optparser.on(*parser_args) do |value|
172
- result[name] = value
173
- end
174
- end
175
- end
176
-
177
- opt_parser.parse! argv
178
- return result, argv
179
- end
180
- end
181
- end
182
-
183
- ##
184
- # Default help command. You'll probably use your own...
185
- class Bales::Command::Help < Bales::Command
186
- action do |args, opts|
187
- puts "This will someday output some help text"
188
- end
189
- end
@@ -1,37 +0,0 @@
1
- ##
2
- # Prints help text for a given namespace
3
- class Bales::Command::Help < Bales::Command
4
- action do |args, opts|
5
- puts "This will someday output some help text"
6
- end
7
-
8
- private
9
-
10
- def commands(ns)
11
- unless eval("defined? #{ns}") == "constant"
12
- raise ArgumentError, "expected a constant, but got a #{ns.class}"
13
- end
14
-
15
- ns.constants
16
- .select { |c| eval("#{ns}::#{c}") <= Bales::Command }
17
- .map { |c| eval "#{ns}::#{c}" }
18
- end
19
-
20
- def format_option(name, opts, width=72)
21
- long = "#{opts[:long_form]}"
22
- if opts[:type] <= TrueClass or opts[:type] <= FalseClass
23
- if opts[:required]
24
- long << " #{opts[:arg]}"
25
- else
26
- long << " [#{opts[:arg]}]"
27
- end
28
- end
29
-
30
- output = "#{name} (#{opts[:type]}): "
31
- output << "#{opts[:short_form]} / " if opts[:short_form]
32
- output << long
33
- output << "\n"
34
- output << opts[:description]
35
- output
36
- end
37
- end
@@ -1,3 +0,0 @@
1
- module Bales
2
- VERSION="0.0.0"
3
- end