belafonte 0.1.2
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.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +158 -0
- data/belafonte.gemspec +27 -0
- data/lib/belafonte/app.rb +86 -0
- data/lib/belafonte/argument.rb +48 -0
- data/lib/belafonte/argument_processor.rb +40 -0
- data/lib/belafonte/dsl/class_methods.rb +75 -0
- data/lib/belafonte/dsl/instance_methods.rb +98 -0
- data/lib/belafonte/dsl.rb +14 -0
- data/lib/belafonte/errors.rb +25 -0
- data/lib/belafonte/flag.rb +64 -0
- data/lib/belafonte/help/app_extensions.rb +72 -0
- data/lib/belafonte/help/command_extensions.rb +13 -0
- data/lib/belafonte/help/flag_extensions.rb +17 -0
- data/lib/belafonte/help/generator.rb +60 -0
- data/lib/belafonte/help.rb +11 -0
- data/lib/belafonte/helpers/tokens.rb +10 -0
- data/lib/belafonte/option.rb +26 -0
- data/lib/belafonte/parser.rb +108 -0
- data/lib/belafonte/switch.rb +7 -0
- data/lib/belafonte/version.rb +3 -0
- data/lib/belafonte/wrapomatic.rb +58 -0
- data/lib/belafonte.rb +6 -0
- metadata +152 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a460418210e2ac70ea041d21767a9e07d66a2a59
|
4
|
+
data.tar.gz: 93cdbe5d558f4ad27929bcee53f9d3d4772a6a5a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9b4299b8461f277f49c286e15a0d161e84bf4028733b58e50359773b3ac0c1e2b3c9096d0763354de1ab400976f4d7385467085ac73be8725d464f0f27ef3855
|
7
|
+
data.tar.gz: b4b6256af663d2a3574ee1a7ece14e35af67832d0269078fe3c8cf1b61db7410baff18af87f9152d194480b6f81b532f20d412f4e63cbdabe399b9267bcaa157
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Dennis Walters
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,158 @@
|
|
1
|
+
# Belafonte #
|
2
|
+
|
3
|
+
This library is all about making command-line applications. It's a lot like GLI, methadone, Main, and the other options. This is just my take.
|
4
|
+
|
5
|
+
My goals in this:
|
6
|
+
|
7
|
+
* Make a system that suits my tastes
|
8
|
+
* Avoid magic
|
9
|
+
* Make apps easier to test (specifically with aruba in-process)
|
10
|
+
|
11
|
+
## Installation ##
|
12
|
+
|
13
|
+
Add this line to your application's Gemfile:
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
gem 'belafonte'
|
17
|
+
```
|
18
|
+
|
19
|
+
And then execute:
|
20
|
+
|
21
|
+
$ bundle
|
22
|
+
|
23
|
+
Or install it yourself as:
|
24
|
+
|
25
|
+
$ gem install belafonte
|
26
|
+
|
27
|
+
## Usage ##
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
require 'belafonte'
|
31
|
+
|
32
|
+
class MyApp < Belafonte::App
|
33
|
+
|
34
|
+
# Every Belafonte application must have a title.
|
35
|
+
title "myapp"
|
36
|
+
summary "A short description"
|
37
|
+
description <<-DESCRIPTION
|
38
|
+
This is a much longer description of the app, command, or what have you.
|
39
|
+
DESCRIPTION
|
40
|
+
|
41
|
+
# Switches are boolean flags that can be passed on the command line.
|
42
|
+
# At least one short or long flag is required, but you can pass multiple
|
43
|
+
# flags (without hyphens) for each if you like.
|
44
|
+
|
45
|
+
switch :switch_name, # name the switch
|
46
|
+
short: 's', # short flags for the switch (array for 2+)
|
47
|
+
long: 'switch', # long flag for the switch (array for 2+)
|
48
|
+
description: 'This is a switch' # describe the switch (default '')
|
49
|
+
|
50
|
+
# Options are flags that take an argument. The basic setup for this is the
|
51
|
+
# same as that for switches, but with the additional requirement of a display
|
52
|
+
# name for the option's argument.
|
53
|
+
|
54
|
+
option :option_name, # name the option
|
55
|
+
short: 'o', # short flags (array for 2+)
|
56
|
+
long: 'option', # long flags (array for 2+)
|
57
|
+
description: 'This is an option', # describe the option (default: '')
|
58
|
+
argument: 'option name' # display name (required)
|
59
|
+
|
60
|
+
# Args are actual arguments to the command. These require a name, and by
|
61
|
+
# default are expected 1 time. You can specify any number of explicit
|
62
|
+
# occurrences that are greater-than 0, and you can also make them unlimited,
|
63
|
+
# but you're not allowed to add any further args after adding an unlimited
|
64
|
+
# arg.
|
65
|
+
|
66
|
+
arg :argument_name,
|
67
|
+
times: 1
|
68
|
+
|
69
|
+
arg :unlimited_argument,
|
70
|
+
times: :unlimited
|
71
|
+
|
72
|
+
# The `handle` method that you define for your app is the hook that Belafonte
|
73
|
+
# uses to run your code. If you don't define this, your app won't actually
|
74
|
+
# do much of anything.
|
75
|
+
|
76
|
+
def handle
|
77
|
+
|
78
|
+
# Do something if the :switch_name switch is active
|
79
|
+
|
80
|
+
if switch_active(:switch_name)
|
81
|
+
stdout.puts "Switch is active!"
|
82
|
+
else
|
83
|
+
stdout.puts "Switch is not active :/"
|
84
|
+
end
|
85
|
+
|
86
|
+
# Do something based on the options that came in
|
87
|
+
stdout.puts "We got this option: #{option[:option]}"
|
88
|
+
|
89
|
+
# Do something with the first argument we defined
|
90
|
+
stdout.puts arg[:argument_name].first
|
91
|
+
|
92
|
+
# Do something with the unlimited argument we defined
|
93
|
+
arg[:unlimited_argument].each do |unlimited|
|
94
|
+
stdout.puts "unlimited == '#{unlimited}'"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Actually run the application. In addition to ARGV, the following .new args
|
100
|
+
# are defaulted:
|
101
|
+
#
|
102
|
+
# * stdin (default: STDIN)
|
103
|
+
# * stdout (default: STDOUT)
|
104
|
+
# * stderr (default: STDERR)
|
105
|
+
# * kernel (default: Kernel)
|
106
|
+
#
|
107
|
+
# Unfortunately, order matters, and you can't specify (say) kernel without
|
108
|
+
# first specifying the items that come before it.
|
109
|
+
#
|
110
|
+
# In a more perfect world, your executable file is basically just a require to
|
111
|
+
# pull in your app, then this line.
|
112
|
+
|
113
|
+
exit MyApp.new(ARGV).execute!
|
114
|
+
```
|
115
|
+
|
116
|
+
## Upcoming Features ##
|
117
|
+
|
118
|
+
So as to allow for "command suite" utilities, I'm planning to allow for the mounting of one application into another, a-la Rack, Sinatra, Grape, etc.
|
119
|
+
|
120
|
+
The API for this is unstable, and I need to figure out some business logic, but it should look something like this:
|
121
|
+
|
122
|
+
```ruby
|
123
|
+
require 'belafonte'
|
124
|
+
|
125
|
+
class InnerApp < Belafonte::App
|
126
|
+
title "inner"
|
127
|
+
|
128
|
+
def handle
|
129
|
+
stdout.puts "This is the inner app"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
class OuterApp < Belafonte::App
|
134
|
+
title "outer"
|
135
|
+
|
136
|
+
mount InnerApp
|
137
|
+
|
138
|
+
def handle
|
139
|
+
stdout.puts "This is the outer app"
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
exit OuterApp.new(ARGV).execute!
|
144
|
+
```
|
145
|
+
|
146
|
+
## Development ##
|
147
|
+
|
148
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/console` for an interactive prompt that will allow you to experiment.
|
149
|
+
|
150
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release` to create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
151
|
+
|
152
|
+
## Contributing ##
|
153
|
+
|
154
|
+
1. Fork it ( https://github.com/[my-github-username]/belafonte/fork )
|
155
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
156
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
157
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
158
|
+
5. Create a new Pull Request
|
data/belafonte.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'belafonte/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "belafonte"
|
8
|
+
spec.version = Belafonte::VERSION
|
9
|
+
spec.authors = ["Dennis Walters"]
|
10
|
+
spec.email = ["dwalters@engineyard.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Jump in the command line!}
|
13
|
+
spec.homepage = "https://github.com/ess/belafonte"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|examples|bin|.gitignore|.rspec|.ruby-gemset|.ruby-version|.travis.yml|Gemfile|Rakefile)/?}) }
|
17
|
+
spec.bindir = "exe"
|
18
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.9"
|
22
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
23
|
+
spec.add_development_dependency "rspec"
|
24
|
+
spec.add_development_dependency "simplecov"
|
25
|
+
spec.add_development_dependency "yard"
|
26
|
+
spec.add_development_dependency "toady"
|
27
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'belafonte/dsl'
|
3
|
+
require 'belafonte/errors'
|
4
|
+
require 'belafonte/help'
|
5
|
+
|
6
|
+
module Belafonte
|
7
|
+
# An application container
|
8
|
+
class App
|
9
|
+
include Belafonte::DSL
|
10
|
+
|
11
|
+
attr_reader :argv, :stdin, :stdout, :stderr, :kernel
|
12
|
+
|
13
|
+
def initialize(argv, stdin = STDIN, stdout = STDOUT, stderr = STDERR, kernel = Kernel, parent = nil)
|
14
|
+
@argv = argv
|
15
|
+
@stdin = stdin
|
16
|
+
@stdout = stdout
|
17
|
+
@stderr = stderr
|
18
|
+
@kernel = kernel
|
19
|
+
@parent = parent
|
20
|
+
end
|
21
|
+
|
22
|
+
def execute!
|
23
|
+
(@parser = Parser.new(
|
24
|
+
switches: configured_switches,
|
25
|
+
options: configured_options,
|
26
|
+
commands: configured_subcommands,
|
27
|
+
arguments: configured_args,
|
28
|
+
argv: @argv
|
29
|
+
)).parsed.tap do |parsed|
|
30
|
+
@switches = parsed[:switches]
|
31
|
+
@options = parsed[:options]
|
32
|
+
@arguments = parsed[:args]
|
33
|
+
activate_help! if parsed[:help]
|
34
|
+
end
|
35
|
+
|
36
|
+
@command = arg(:command).shift if arg(:command)
|
37
|
+
|
38
|
+
unless dispatch || show_help || run_handle
|
39
|
+
stderr.puts "No handler for the provided command line"
|
40
|
+
return 1
|
41
|
+
end
|
42
|
+
|
43
|
+
0
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
def parent
|
48
|
+
@parent
|
49
|
+
end
|
50
|
+
|
51
|
+
def subcommand_instance(command)
|
52
|
+
command_class = configured_subcommands.
|
53
|
+
find {|subcommand| subcommand.info(:title) == command}
|
54
|
+
|
55
|
+
return nil unless command_class
|
56
|
+
|
57
|
+
command_class.new(arg(:command), stdin, stdout, stderr, kernel, self)
|
58
|
+
end
|
59
|
+
|
60
|
+
def dispatch
|
61
|
+
return false if @command.nil?
|
62
|
+
handler = subcommand_instance(@command)
|
63
|
+
|
64
|
+
unless handler
|
65
|
+
activate_help!
|
66
|
+
return false
|
67
|
+
end
|
68
|
+
|
69
|
+
handler.activate_help! if help_active?
|
70
|
+
handler.execute!
|
71
|
+
true
|
72
|
+
end
|
73
|
+
|
74
|
+
def show_help
|
75
|
+
return false unless help_active?
|
76
|
+
stdout.print Belafonte::Help.content_for(self)
|
77
|
+
true
|
78
|
+
end
|
79
|
+
|
80
|
+
def run_handle
|
81
|
+
return false unless respond_to?(:handle)
|
82
|
+
handle
|
83
|
+
true
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'belafonte/errors'
|
2
|
+
|
3
|
+
module Belafonte
|
4
|
+
# Represents a command line argument
|
5
|
+
class Argument
|
6
|
+
attr_reader :name, :times
|
7
|
+
|
8
|
+
def initialize(options = {})
|
9
|
+
@name = options[:name]
|
10
|
+
@times = options[:times]
|
11
|
+
normalize
|
12
|
+
end
|
13
|
+
|
14
|
+
def process(argv)
|
15
|
+
case times
|
16
|
+
when -1
|
17
|
+
argv
|
18
|
+
else
|
19
|
+
if argv.length < times
|
20
|
+
raise Belafonte::Errors::TooFewArguments.new("Not enough arguments were given")
|
21
|
+
end
|
22
|
+
argv.first(times)
|
23
|
+
end.clone
|
24
|
+
end
|
25
|
+
|
26
|
+
def unlimited?
|
27
|
+
@unlimited ||= false
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
def normalize
|
32
|
+
raise Belafonte::Errors::NoName.new("Arguments must be named") unless name
|
33
|
+
|
34
|
+
case times
|
35
|
+
when nil
|
36
|
+
@times = 1
|
37
|
+
when :unlimited
|
38
|
+
@times = -1
|
39
|
+
@unlimited = true
|
40
|
+
else
|
41
|
+
@times = times.to_i
|
42
|
+
end
|
43
|
+
|
44
|
+
raise Belafonte::Errors::InvalidArgument.new("There must be at least one occurrence") unless times > 0 || unlimited?
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'belafonte/errors'
|
2
|
+
|
3
|
+
module Belafonte
|
4
|
+
# Processes command line arguments
|
5
|
+
class ArgumentProcessor
|
6
|
+
def initialize(options = {})
|
7
|
+
@argv = options[:argv] || []
|
8
|
+
@arguments = options[:arguments] || []
|
9
|
+
process!
|
10
|
+
end
|
11
|
+
|
12
|
+
def processed
|
13
|
+
@processed ||= {}
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
def process!
|
18
|
+
argv = @argv.clone
|
19
|
+
arguments.each do |arg|
|
20
|
+
values = arg.process(argv)
|
21
|
+
processed[arg.name] = values
|
22
|
+
argv.shift(values.length)
|
23
|
+
end
|
24
|
+
|
25
|
+
validate_processed_args
|
26
|
+
|
27
|
+
if argv.length > 0
|
28
|
+
raise Belafonte::Errors::TooManyArguments.new("More args provided than I can handle")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def arguments
|
33
|
+
@arguments
|
34
|
+
end
|
35
|
+
|
36
|
+
def validate_processed_args
|
37
|
+
raise Belafonte::Errors::TooFewArguments.new("You didn't provide enough arguments") if processed.values.any? {|arg| arg.empty?}
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'belafonte/switch'
|
3
|
+
require 'belafonte/option'
|
4
|
+
require 'belafonte/argument'
|
5
|
+
require 'belafonte/errors'
|
6
|
+
|
7
|
+
module Belafonte
|
8
|
+
module DSL
|
9
|
+
# Class methods for defining apps
|
10
|
+
module ClassMethods
|
11
|
+
def meta
|
12
|
+
@meta ||= {}
|
13
|
+
end
|
14
|
+
|
15
|
+
def info(item)
|
16
|
+
meta[item]
|
17
|
+
end
|
18
|
+
|
19
|
+
def title(title)
|
20
|
+
meta[:title] = title
|
21
|
+
end
|
22
|
+
|
23
|
+
def summary(summary)
|
24
|
+
meta[:summary] = summary
|
25
|
+
end
|
26
|
+
|
27
|
+
def description(description)
|
28
|
+
meta[:description] = description
|
29
|
+
end
|
30
|
+
|
31
|
+
def switches
|
32
|
+
meta[:switches] ||= []
|
33
|
+
end
|
34
|
+
|
35
|
+
def switch(name, switch_options = {})
|
36
|
+
switches.push(Belafonte::Switch.new(switch_options.merge({name: name})))
|
37
|
+
end
|
38
|
+
|
39
|
+
def options
|
40
|
+
meta[:options] ||= []
|
41
|
+
end
|
42
|
+
|
43
|
+
def option(name, option_options = {})
|
44
|
+
options.push(Belafonte::Option.new(option_options.merge({name: name})))
|
45
|
+
end
|
46
|
+
|
47
|
+
def args
|
48
|
+
meta[:args] ||= []
|
49
|
+
end
|
50
|
+
|
51
|
+
def arg(name, arg_options = {})
|
52
|
+
args.last.tap do |arg|
|
53
|
+
if arg && arg.unlimited?
|
54
|
+
raise Belafonte::Errors::InvalidArgument.new("You may not add other arguments after an unlimited argument")
|
55
|
+
else
|
56
|
+
args.push(Belafonte::Argument.new(arg_options.merge({name: name})))
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def subcommands
|
62
|
+
meta[:subcommands] ||= []
|
63
|
+
end
|
64
|
+
|
65
|
+
def mount(app)
|
66
|
+
unless args.any? {|arg| arg.name.to_sym == :command}
|
67
|
+
arg :command, times: :unlimited
|
68
|
+
end
|
69
|
+
|
70
|
+
raise Belafonte::Errors::CircularMount.new("An app cannot mount itself") if app == self
|
71
|
+
subcommands.push(app)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'belafonte/parser'
|
2
|
+
|
3
|
+
module Belafonte
|
4
|
+
module DSL
|
5
|
+
# Instance methods for apps
|
6
|
+
module InstanceMethods
|
7
|
+
def title
|
8
|
+
self.class.info(:title)
|
9
|
+
end
|
10
|
+
|
11
|
+
def summary
|
12
|
+
self.class.info(:summary)
|
13
|
+
end
|
14
|
+
|
15
|
+
def description
|
16
|
+
self.class.info(:description)
|
17
|
+
end
|
18
|
+
|
19
|
+
def configured_switches
|
20
|
+
self.class.switches
|
21
|
+
end
|
22
|
+
|
23
|
+
def configured_options
|
24
|
+
self.class.options
|
25
|
+
end
|
26
|
+
|
27
|
+
def configured_args
|
28
|
+
self.class.args
|
29
|
+
end
|
30
|
+
|
31
|
+
def configured_subcommands
|
32
|
+
self.class.subcommands
|
33
|
+
end
|
34
|
+
|
35
|
+
def switches
|
36
|
+
@switches ||= {}
|
37
|
+
end
|
38
|
+
|
39
|
+
def switch_active(switch)
|
40
|
+
switches[switch]
|
41
|
+
end
|
42
|
+
|
43
|
+
def options
|
44
|
+
@options ||= {}
|
45
|
+
end
|
46
|
+
|
47
|
+
def option(option)
|
48
|
+
options[option]
|
49
|
+
end
|
50
|
+
|
51
|
+
def args
|
52
|
+
@arguments ||= {}
|
53
|
+
end
|
54
|
+
|
55
|
+
def arg(arg)
|
56
|
+
args[arg]
|
57
|
+
end
|
58
|
+
|
59
|
+
def subcommands
|
60
|
+
@subcommands ||= []
|
61
|
+
end
|
62
|
+
|
63
|
+
def estate
|
64
|
+
@estate ||= {}
|
65
|
+
end
|
66
|
+
|
67
|
+
def bequeathed(name, value)
|
68
|
+
estate[name] ||= value
|
69
|
+
end
|
70
|
+
|
71
|
+
def activate_help!
|
72
|
+
@help = true
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def parser
|
78
|
+
@parser
|
79
|
+
end
|
80
|
+
|
81
|
+
#def short(option)
|
82
|
+
#"-#{option.to_s}"
|
83
|
+
#end
|
84
|
+
|
85
|
+
#def long(option)
|
86
|
+
#"-#{short(option)}"
|
87
|
+
#end
|
88
|
+
|
89
|
+
def help
|
90
|
+
@help ||= false
|
91
|
+
end
|
92
|
+
|
93
|
+
def help_active?
|
94
|
+
help
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'belafonte/dsl/instance_methods'
|
2
|
+
require 'belafonte/dsl/class_methods'
|
3
|
+
|
4
|
+
module Belafonte
|
5
|
+
# A DSL for making apps
|
6
|
+
module DSL
|
7
|
+
def self.included(klass)
|
8
|
+
klass.extend(Belafonte::DSL::ClassMethods)
|
9
|
+
end
|
10
|
+
|
11
|
+
include Belafonte::DSL::InstanceMethods
|
12
|
+
#include Belafonte::DSL::Mountin
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Belafonte
|
2
|
+
module Errors
|
3
|
+
|
4
|
+
# Raised when an app attempts to mount itself
|
5
|
+
CircularMount = Class.new(StandardError)
|
6
|
+
|
7
|
+
# Raised when creation of an argument breaks the arg rules
|
8
|
+
InvalidArgument = Class.new(StandardError)
|
9
|
+
|
10
|
+
# Raised when a name is required but not provided
|
11
|
+
NoName = Class.new(StandardError)
|
12
|
+
|
13
|
+
# ArgumentNotNamed poop
|
14
|
+
ArgumentNotNamed = Class.new(StandardError)
|
15
|
+
|
16
|
+
# TooFewArguments poop
|
17
|
+
TooFewArguments = Class.new(StandardError)
|
18
|
+
|
19
|
+
# TooManyArguments poop
|
20
|
+
TooManyArguments = Class.new(StandardError)
|
21
|
+
|
22
|
+
# UnknownCommand poop
|
23
|
+
UnknownCommand = Class.new(StandardError)
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'belafonte/errors'
|
2
|
+
|
3
|
+
module Belafonte
|
4
|
+
# Flag is the base class for switches and options
|
5
|
+
class Flag
|
6
|
+
# No flags were given
|
7
|
+
NoFlags = Class.new(StandardError)
|
8
|
+
# No name was given
|
9
|
+
NoName = Class.new(StandardError)
|
10
|
+
|
11
|
+
attr_reader :name, :short, :long, :description
|
12
|
+
|
13
|
+
def normalize_flag(flag)
|
14
|
+
flag.to_s.strip.gsub(/\s+/, '-')
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(options = {})
|
18
|
+
options[:name].tap do |name|
|
19
|
+
unless options[:name]
|
20
|
+
raise Belafonte::Errors::NoName.new("Flag name cannot be blank")
|
21
|
+
end
|
22
|
+
@name = name.to_sym
|
23
|
+
end
|
24
|
+
|
25
|
+
@short = flag_array(options[:short])
|
26
|
+
@long = flag_array(options[:long])
|
27
|
+
|
28
|
+
if short.empty? && long.empty?
|
29
|
+
raise NoFlags.new("You must define at least one flag")
|
30
|
+
end
|
31
|
+
|
32
|
+
@description = options[:description] || ''
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_opt_parse
|
36
|
+
[
|
37
|
+
short.map {|short_option| shortify(short_option)},
|
38
|
+
long.map {|long_option| longify(long_option)},
|
39
|
+
description
|
40
|
+
].flatten
|
41
|
+
end
|
42
|
+
|
43
|
+
def variations
|
44
|
+
short + long
|
45
|
+
end
|
46
|
+
alias_method :flags, :variations
|
47
|
+
|
48
|
+
private
|
49
|
+
def shortify(option)
|
50
|
+
shortened = "-#{option.to_s}"
|
51
|
+
end
|
52
|
+
|
53
|
+
def longify(option)
|
54
|
+
"--#{option.to_s}"
|
55
|
+
end
|
56
|
+
|
57
|
+
def flag_array(items)
|
58
|
+
[items].
|
59
|
+
flatten.
|
60
|
+
map {|flag| normalize_flag(flag)}.
|
61
|
+
reject {|flag| flag == ''}
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Belafonte
|
2
|
+
module Help
|
3
|
+
module AppExtensions
|
4
|
+
def root
|
5
|
+
if parent
|
6
|
+
parent.extend(AppExtensions)
|
7
|
+
parent.root
|
8
|
+
else
|
9
|
+
self
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def root?
|
14
|
+
root == self
|
15
|
+
end
|
16
|
+
|
17
|
+
def executable
|
18
|
+
File.basename($0)
|
19
|
+
end
|
20
|
+
|
21
|
+
def display_title
|
22
|
+
root? ? executable : title
|
23
|
+
end
|
24
|
+
|
25
|
+
def display_description
|
26
|
+
description.to_s
|
27
|
+
end
|
28
|
+
|
29
|
+
def full_path
|
30
|
+
return signature unless parent
|
31
|
+
parent.extend(AppExtensions)
|
32
|
+
"#{parent.full_path} #{signature}"
|
33
|
+
end
|
34
|
+
|
35
|
+
def signature
|
36
|
+
cmd = display_title
|
37
|
+
|
38
|
+
cmd += " [#{cmd} options]" if has_flags?
|
39
|
+
|
40
|
+
if has_args?
|
41
|
+
cmd += " #{configured_args.map(&:name).map(&:to_s).join(' ')}"
|
42
|
+
end
|
43
|
+
|
44
|
+
if has_subcommands? && @command.nil?
|
45
|
+
cmd += " command"
|
46
|
+
end
|
47
|
+
|
48
|
+
cmd
|
49
|
+
end
|
50
|
+
|
51
|
+
def has_flags?
|
52
|
+
configured_switches.any? || configured_options.any?
|
53
|
+
end
|
54
|
+
|
55
|
+
def has_args?
|
56
|
+
configured_args.reject {|arg| arg.name.to_sym == :command}.any?
|
57
|
+
end
|
58
|
+
|
59
|
+
def has_subcommands?
|
60
|
+
configured_subcommands.any?
|
61
|
+
end
|
62
|
+
|
63
|
+
def sorted_flags
|
64
|
+
(configured_switches + configured_options).sort {|a,b| a.name <=> b.name}
|
65
|
+
end
|
66
|
+
|
67
|
+
def sorted_commands
|
68
|
+
configured_subcommands.sort {|a,b| a.name <=> b.name}
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Belafonte
|
2
|
+
module Help
|
3
|
+
module FlagExtensions
|
4
|
+
def short_flags
|
5
|
+
short.map {|short_flag| shortify(short_flag)}
|
6
|
+
end
|
7
|
+
|
8
|
+
def long_flags
|
9
|
+
long.map {|long_flag| longify(long_flag)}
|
10
|
+
end
|
11
|
+
|
12
|
+
def signature
|
13
|
+
"#{(short_flags + long_flags).join(', ')} - #{description}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'belafonte/wrapomatic'
|
2
|
+
require 'belafonte/help/app_extensions'
|
3
|
+
require 'belafonte/help/flag_extensions'
|
4
|
+
require 'belafonte/help/command_extensions'
|
5
|
+
|
6
|
+
module Belafonte
|
7
|
+
module Help
|
8
|
+
class Generator
|
9
|
+
def initialize(app)
|
10
|
+
@app = app
|
11
|
+
app.extend(AppExtensions)
|
12
|
+
@content = name_section + synopsis + options + commands
|
13
|
+
end
|
14
|
+
|
15
|
+
def content
|
16
|
+
@content
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
def app
|
21
|
+
@app
|
22
|
+
end
|
23
|
+
|
24
|
+
def name_section
|
25
|
+
"NAME\n#{Wrapomatic.new("#{app.display_title} - #{app.summary}").wrapped}\n"
|
26
|
+
end
|
27
|
+
|
28
|
+
def synopsis
|
29
|
+
synopsis = "\nSYNOPSIS\n#{Wrapomatic.new(app.full_path).wrapped}"
|
30
|
+
if app.description
|
31
|
+
synopsis += "\n\n#{Wrapomatic.wrap(app.display_description)}"
|
32
|
+
end
|
33
|
+
synopsis + "\n"
|
34
|
+
end
|
35
|
+
|
36
|
+
def options
|
37
|
+
return '' unless app.has_flags?
|
38
|
+
options = "\nOPTIONS\n"
|
39
|
+
app.sorted_flags.each do |flag|
|
40
|
+
flag.extend(FlagExtensions)
|
41
|
+
options += "#{Wrapomatic.new(flag.signature).wrapped}"
|
42
|
+
end
|
43
|
+
|
44
|
+
options + "\n"
|
45
|
+
end
|
46
|
+
|
47
|
+
def commands
|
48
|
+
return '' unless app.has_subcommands?
|
49
|
+
commands = "\nCOMMANDS\n"
|
50
|
+
|
51
|
+
app.sorted_commands.each do |command|
|
52
|
+
command.extend(CommandExtensions)
|
53
|
+
commands += "#{Wrapomatic.new("#{command.display_title} - #{command.display_summary}").wrapped}\n"
|
54
|
+
end
|
55
|
+
|
56
|
+
commands + "\n"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'belafonte/flag'
|
2
|
+
|
3
|
+
module Belafonte
|
4
|
+
# Flags that take arguments
|
5
|
+
class Option < Belafonte::Flag
|
6
|
+
# No argument given
|
7
|
+
NoArgument = Class.new(StandardError)
|
8
|
+
|
9
|
+
def initialize(options = {})
|
10
|
+
super(options)
|
11
|
+
options[:argument].tap do |arg|
|
12
|
+
raise NoArgument.new("Option requires an argument name") unless arg
|
13
|
+
@argument = arg.to_s.strip.upcase.gsub(/\s+/, '_')
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
def shortify(option)
|
19
|
+
"#{super(option)} #{@argument}"
|
20
|
+
end
|
21
|
+
|
22
|
+
def longify(option)
|
23
|
+
"#{super(option)}=#{@argument}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'belafonte/argument_processor'
|
3
|
+
|
4
|
+
module Belafonte
|
5
|
+
# A service object that parses out argv
|
6
|
+
class Parser
|
7
|
+
def initialize(params = {})
|
8
|
+
@data = {
|
9
|
+
switches: params[:switches] || [],
|
10
|
+
options: params[:options] || [],
|
11
|
+
commands: params[:commands] || [],
|
12
|
+
arguments: params[:arguments] || [],
|
13
|
+
argv: params[:argv] || []
|
14
|
+
}
|
15
|
+
setup
|
16
|
+
parse
|
17
|
+
end
|
18
|
+
|
19
|
+
def parsed
|
20
|
+
@parsed ||= {
|
21
|
+
switches: {},
|
22
|
+
options: {},
|
23
|
+
args: {},
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
def help
|
28
|
+
parser.to_s
|
29
|
+
end
|
30
|
+
|
31
|
+
def parser
|
32
|
+
@parser ||= OptionParser.new
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def setup
|
38
|
+
parsify_switches
|
39
|
+
parsify_options
|
40
|
+
add_help
|
41
|
+
end
|
42
|
+
|
43
|
+
def parsify_switches
|
44
|
+
switches.each do |switch|
|
45
|
+
parser.on(*(switch.to_opt_parse)) do
|
46
|
+
switch_results[switch.name] = true
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def parsify_options
|
52
|
+
options.each do |option|
|
53
|
+
parser.on(*(option.to_opt_parse)) do |value|
|
54
|
+
option_results[option.name] = value
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def add_help
|
60
|
+
parser.on_tail('-h', '--help', 'Shows this message') do
|
61
|
+
parsed[:help] = true
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def parse
|
66
|
+
begin
|
67
|
+
parsed[:args] = ArgumentProcessor.new(
|
68
|
+
argv: parser.order(argv),
|
69
|
+
arguments: arguments
|
70
|
+
).processed
|
71
|
+
rescue Errors::TooFewArguments, Errors::TooManyArguments
|
72
|
+
parsed[:help] = true
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def switches
|
77
|
+
data[:switches]
|
78
|
+
end
|
79
|
+
|
80
|
+
def switch_results
|
81
|
+
parsed[:switches]
|
82
|
+
end
|
83
|
+
|
84
|
+
def option_results
|
85
|
+
parsed[:options]
|
86
|
+
end
|
87
|
+
|
88
|
+
def options
|
89
|
+
data[:options]
|
90
|
+
end
|
91
|
+
|
92
|
+
def commands
|
93
|
+
data[:commands]
|
94
|
+
end
|
95
|
+
|
96
|
+
def arguments
|
97
|
+
data[:arguments]
|
98
|
+
end
|
99
|
+
|
100
|
+
def argv
|
101
|
+
data[:argv]
|
102
|
+
end
|
103
|
+
|
104
|
+
def data
|
105
|
+
@data
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Belafonte
|
2
|
+
class Wrapomatic
|
3
|
+
attr_reader :text, :lines, :indents, :columns
|
4
|
+
|
5
|
+
def self.wrap(text, indents = 1, columns = 80)
|
6
|
+
new(text, indents, columns).wrapped
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize(text, indents = 1, columns = 80)
|
10
|
+
@text = text
|
11
|
+
@indents = indents
|
12
|
+
@columns = columns
|
13
|
+
@lines = []
|
14
|
+
indentomize
|
15
|
+
end
|
16
|
+
|
17
|
+
def refresh(text, indents = 1, columns = 80)
|
18
|
+
@text = text
|
19
|
+
@remainder = ''
|
20
|
+
@lines = []
|
21
|
+
indentomize
|
22
|
+
end
|
23
|
+
|
24
|
+
def wrapped
|
25
|
+
lines.join("\n")
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def indentomize
|
31
|
+
begin
|
32
|
+
@lines.push(next_line)
|
33
|
+
end until next_line.nil?
|
34
|
+
end
|
35
|
+
|
36
|
+
def next_line
|
37
|
+
return nil if text.length == 0
|
38
|
+
offset + text_up_to(space_before_location(columns - offset.length - 1))
|
39
|
+
end
|
40
|
+
|
41
|
+
def space_before_location(start)
|
42
|
+
return start if start > text.length
|
43
|
+
text.rindex(/(\s|-)/, start) || start
|
44
|
+
end
|
45
|
+
|
46
|
+
def text_up_to(count)
|
47
|
+
text.slice!(0 .. count)
|
48
|
+
end
|
49
|
+
|
50
|
+
def indentation
|
51
|
+
' '
|
52
|
+
end
|
53
|
+
|
54
|
+
def offset
|
55
|
+
indentation * indents
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/lib/belafonte.rb
ADDED
metadata
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: belafonte
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Dennis Walters
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-10-21 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.9'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.9'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: simplecov
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: yard
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: toady
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
description:
|
98
|
+
email:
|
99
|
+
- dwalters@engineyard.com
|
100
|
+
executables: []
|
101
|
+
extensions: []
|
102
|
+
extra_rdoc_files: []
|
103
|
+
files:
|
104
|
+
- LICENSE.txt
|
105
|
+
- README.md
|
106
|
+
- belafonte.gemspec
|
107
|
+
- lib/belafonte.rb
|
108
|
+
- lib/belafonte/app.rb
|
109
|
+
- lib/belafonte/argument.rb
|
110
|
+
- lib/belafonte/argument_processor.rb
|
111
|
+
- lib/belafonte/dsl.rb
|
112
|
+
- lib/belafonte/dsl/class_methods.rb
|
113
|
+
- lib/belafonte/dsl/instance_methods.rb
|
114
|
+
- lib/belafonte/errors.rb
|
115
|
+
- lib/belafonte/flag.rb
|
116
|
+
- lib/belafonte/help.rb
|
117
|
+
- lib/belafonte/help/app_extensions.rb
|
118
|
+
- lib/belafonte/help/command_extensions.rb
|
119
|
+
- lib/belafonte/help/flag_extensions.rb
|
120
|
+
- lib/belafonte/help/generator.rb
|
121
|
+
- lib/belafonte/helpers/tokens.rb
|
122
|
+
- lib/belafonte/option.rb
|
123
|
+
- lib/belafonte/parser.rb
|
124
|
+
- lib/belafonte/switch.rb
|
125
|
+
- lib/belafonte/version.rb
|
126
|
+
- lib/belafonte/wrapomatic.rb
|
127
|
+
homepage: https://github.com/ess/belafonte
|
128
|
+
licenses:
|
129
|
+
- MIT
|
130
|
+
metadata: {}
|
131
|
+
post_install_message:
|
132
|
+
rdoc_options: []
|
133
|
+
require_paths:
|
134
|
+
- lib
|
135
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
136
|
+
requirements:
|
137
|
+
- - ">="
|
138
|
+
- !ruby/object:Gem::Version
|
139
|
+
version: '0'
|
140
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
141
|
+
requirements:
|
142
|
+
- - ">="
|
143
|
+
- !ruby/object:Gem::Version
|
144
|
+
version: '0'
|
145
|
+
requirements: []
|
146
|
+
rubyforge_project:
|
147
|
+
rubygems_version: 2.4.7
|
148
|
+
signing_key:
|
149
|
+
specification_version: 4
|
150
|
+
summary: Jump in the command line!
|
151
|
+
test_files: []
|
152
|
+
has_rdoc:
|