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 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,13 @@
1
+ module Belafonte
2
+ module Help
3
+ module CommandExtensions
4
+ def display_title
5
+ info(:title).to_s
6
+ end
7
+
8
+ def display_summary
9
+ info(:summary).to_s
10
+ end
11
+ end
12
+ end
13
+ 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,11 @@
1
+ require 'belafonte/help/generator'
2
+
3
+ module Belafonte
4
+ # Generates the help for a given app
5
+ module Help
6
+
7
+ def self.content_for(app)
8
+ Generator.new(app).content
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,10 @@
1
+ module Belafonte
2
+ module Helpers
3
+ # deprecated tokenizer
4
+ module Tokens
5
+ def command_tokens
6
+ commands.keys
7
+ end
8
+ end
9
+ end
10
+ 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,7 @@
1
+ require 'belafonte/flag'
2
+
3
+ module Belafonte
4
+ # Boolean flags
5
+ class Switch < Belafonte::Flag
6
+ end
7
+ end
@@ -0,0 +1,3 @@
1
+ module Belafonte
2
+ VERSION = "0.1.2"
3
+ 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
@@ -0,0 +1,6 @@
1
+ require "belafonte/version"
2
+ require 'belafonte/app'
3
+
4
+ # An awesome system for making CLI apps
5
+ module Belafonte
6
+ end
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: