belafonte 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
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: