vli 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +1 -0
- data/lib/vli.rb +10 -0
- data/lib/vli/action.rb +9 -0
- data/lib/vli/action/builder.rb +128 -0
- data/lib/vli/action/env.rb +5 -0
- data/lib/vli/action/env/set.rb +21 -0
- data/lib/vli/action/environment.rb +10 -0
- data/lib/vli/action/runner.rb +56 -0
- data/lib/vli/action/warden.rb +86 -0
- data/lib/vli/command.rb +5 -0
- data/lib/vli/command/base.rb +64 -0
- data/lib/vli/error.rb +7 -0
- data/lib/vli/registry.rb +56 -0
- data/lib/vli/ui.rb +161 -0
- data/lib/vli/util.rb +8 -0
- data/lib/vli/util/busy.rb +59 -0
- data/lib/vli/util/hash_with_indifferent_access.rb +63 -0
- data/lib/vli/util/platform.rb +68 -0
- data/lib/vli/util/safe_puts.rb +31 -0
- data/lib/vli/version.rb +3 -0
- data/vli.gemspec +19 -0
- metadata +72 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Tyler Flint
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# Vli
|
2
|
+
|
3
|
+
Vagrant Like Interface is a library of components extracted from the vagrant project to aid in building command line interfaces. If you've ever used or extended the vagrant project you can appreciate the brilliance in the architecture. Special thanks to the vagrant project: https://github.com/mitchellh/vagrant
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'vli'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install vli
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
1. Fork it
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/lib/vli.rb
ADDED
data/lib/vli/action.rb
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
module Vli
|
2
|
+
module Action
|
3
|
+
# Action builder which provides a nice DSL for building up
|
4
|
+
# a middleware sequence for Vli actions. This code is based
|
5
|
+
# heavily off of `Rack::Builder` and `ActionDispatch::MiddlewareStack`
|
6
|
+
# in Rack and Rails, respectively.
|
7
|
+
#
|
8
|
+
# Usage
|
9
|
+
#
|
10
|
+
# Building an action sequence is very easy:
|
11
|
+
#
|
12
|
+
# app = Vli::Action::Builder.new do
|
13
|
+
# use MiddlewareA
|
14
|
+
# use MiddlewareB
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# Vli::Action.run(app)
|
18
|
+
#
|
19
|
+
class Builder
|
20
|
+
# Initializes the builder. An optional block can be passed which
|
21
|
+
# will be evaluated in the context of the instance.
|
22
|
+
def initialize(&block)
|
23
|
+
instance_eval(&block) if block_given?
|
24
|
+
end
|
25
|
+
|
26
|
+
# Returns a mergeable version of the builder. If `use` is called with
|
27
|
+
# the return value of this method, then the stack will merge, instead
|
28
|
+
# of being treated as a separate single middleware.
|
29
|
+
def flatten
|
30
|
+
lambda do |env|
|
31
|
+
self.call(env)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Adds a middleware class to the middleware stack. Any additional
|
36
|
+
# args and a block, if given, are saved and passed to the initializer
|
37
|
+
# of the middleware.
|
38
|
+
#
|
39
|
+
# @param [Class] middleware The middleware class
|
40
|
+
def use(middleware, *args, &block)
|
41
|
+
# Prepend with a environment setter if args are given
|
42
|
+
if !args.empty? && args.first.is_a?(Hash) && middleware != Env::Set
|
43
|
+
self.use(Env::Set, args.shift, &block)
|
44
|
+
end
|
45
|
+
|
46
|
+
if middleware.kind_of?(Builder)
|
47
|
+
# Merge in the other builder's stack into our own
|
48
|
+
self.stack.concat(middleware.stack)
|
49
|
+
else
|
50
|
+
self.stack << [middleware, args, block]
|
51
|
+
end
|
52
|
+
|
53
|
+
self
|
54
|
+
end
|
55
|
+
|
56
|
+
# Inserts a middleware at the given index or directly before the
|
57
|
+
# given middleware object.
|
58
|
+
def insert(index, middleware, *args, &block)
|
59
|
+
index = self.index(index) unless index.is_a?(Integer)
|
60
|
+
stack.insert(index, [middleware, args, block])
|
61
|
+
end
|
62
|
+
|
63
|
+
alias_method :insert_before, :insert
|
64
|
+
|
65
|
+
# Inserts a middleware after the given index or middleware object.
|
66
|
+
def insert_after(index, middleware, *args, &block)
|
67
|
+
index = self.index(index) unless index.is_a?(Integer)
|
68
|
+
raise "no such middleware to insert after: #{index.inspect}" unless index
|
69
|
+
insert(index + 1, middleware, *args, &block)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Replaces the given middlware object or index with the new
|
73
|
+
# middleware.
|
74
|
+
def replace(index, middleware, *args, &block)
|
75
|
+
if index.is_a?(Integer)
|
76
|
+
delete(index)
|
77
|
+
insert(index, middleware, *args, &block)
|
78
|
+
else
|
79
|
+
insert_before(index, middleware, *args, &block)
|
80
|
+
delete(index)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Deletes the given middleware object or index
|
85
|
+
def delete(index)
|
86
|
+
index = self.index(index) unless index.is_a?(Integer)
|
87
|
+
stack.delete_at(index)
|
88
|
+
end
|
89
|
+
|
90
|
+
# Runs the builder stack with the given environment.
|
91
|
+
def call(env)
|
92
|
+
to_app(env).call(env)
|
93
|
+
end
|
94
|
+
|
95
|
+
protected
|
96
|
+
|
97
|
+
# Returns the numeric index for the given middleware object.
|
98
|
+
#
|
99
|
+
# @param [Object] object The item to find the index for
|
100
|
+
# @return [Integer]
|
101
|
+
def index(object)
|
102
|
+
stack.each_with_index do |item, i|
|
103
|
+
return i if item[0] == object
|
104
|
+
end
|
105
|
+
|
106
|
+
nil
|
107
|
+
end
|
108
|
+
|
109
|
+
# Returns the current stack of middlewares. You probably won't
|
110
|
+
# need to use this directly, and it's recommended that you don't.
|
111
|
+
#
|
112
|
+
# @return [Array]
|
113
|
+
def stack
|
114
|
+
@stack ||= []
|
115
|
+
end
|
116
|
+
|
117
|
+
# Converts the builder stack to a runnable action sequence.
|
118
|
+
#
|
119
|
+
# @param [Vli::Action::Environment] env The action environment
|
120
|
+
# @return [Object] A callable object
|
121
|
+
def to_app(env)
|
122
|
+
# Wrap the middleware stack with the Warden to provide a consistent
|
123
|
+
# and predictable behavior upon exceptions.
|
124
|
+
Warden.new(stack.dup, env)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Vli
|
2
|
+
module Action
|
3
|
+
module Env
|
4
|
+
# A middleware which just sets up the environment with some
|
5
|
+
# options which are passed to it.
|
6
|
+
class Set
|
7
|
+
def initialize(app, env, options=nil)
|
8
|
+
@app = app
|
9
|
+
@options = options || {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def call(env)
|
13
|
+
# Merge the options that were given to us
|
14
|
+
env.merge!(@options)
|
15
|
+
|
16
|
+
@app.call(env)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module Vli
|
2
|
+
module Action
|
3
|
+
# Represents an action environment which is what is passed
|
4
|
+
# to the `call` method of each action. This environment contains
|
5
|
+
# some helper methods for accessing the environment as well
|
6
|
+
# as being a hash, to store any additional options.
|
7
|
+
class Environment < Util::HashWithIndifferentAccess
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# TODO:
|
2
|
+
# * env.lock
|
3
|
+
|
4
|
+
module Vli
|
5
|
+
module Action
|
6
|
+
class Runner
|
7
|
+
@@reported_interrupt = false
|
8
|
+
|
9
|
+
def initialize(registry, globals=nil, &block)
|
10
|
+
@registry = registry
|
11
|
+
@globals = globals || {}
|
12
|
+
@lazy_globals = block
|
13
|
+
end
|
14
|
+
|
15
|
+
def run(callable_id, options=nil)
|
16
|
+
callable = callable_id
|
17
|
+
callable = Builder.new.use(callable_id) if callable_id.kind_of?(Class)
|
18
|
+
callable = @registry.get(callable_id) if callable_id.kind_of?(Symbol)
|
19
|
+
raise ArgumentError, "Argument to run must be a callable object or registered action." if !callable || !callable.respond_to?(:call)
|
20
|
+
|
21
|
+
# Create the initial environment with the options given
|
22
|
+
environment = Environment.new
|
23
|
+
environment.merge!(@globals)
|
24
|
+
environment.merge!(@lazy_globals.call) if @lazy_globals
|
25
|
+
environment.merge!(options || {})
|
26
|
+
|
27
|
+
# Run the action chain in a busy block, marking the environment as
|
28
|
+
# interrupted if a SIGINT occurs, and exiting cleanly once the
|
29
|
+
# chain has been run.
|
30
|
+
ui = environment[:ui] if environment.has_key?(:ui)
|
31
|
+
int_callback = lambda do
|
32
|
+
if environment[:interrupted]
|
33
|
+
ui.error exit_immediately_message if ui
|
34
|
+
abort
|
35
|
+
end
|
36
|
+
|
37
|
+
ui.warn waiting_cleanup_message if ui && !@@reported_interrupt
|
38
|
+
environment[:interrupted] = true
|
39
|
+
@@reported_interrupt = true
|
40
|
+
end
|
41
|
+
|
42
|
+
# We place a process lock around every action that is called
|
43
|
+
Util::Busy.busy(int_callback) { callable.call(environment) }
|
44
|
+
end
|
45
|
+
|
46
|
+
def exit_immediately_message
|
47
|
+
"Exiting immediately, without cleanup!"
|
48
|
+
end
|
49
|
+
|
50
|
+
def waiting_cleanup_message
|
51
|
+
"Waiting for cleanup before exiting..."
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module Vli
|
2
|
+
module Action
|
3
|
+
# The action warden is a middleware which injects itself between
|
4
|
+
# every other middleware, watching for exceptions which are raised
|
5
|
+
# and performing proper cleanup on every action by calling the `recover`
|
6
|
+
# method. The warden therefore allows middlewares to not worry about
|
7
|
+
# exceptional events, and by providing a simple callback, can clean up
|
8
|
+
# in any erroneous case.
|
9
|
+
#
|
10
|
+
# Warden will "just work" behind the scenes, and is not of particular
|
11
|
+
# interest except to those who are curious about the internal workings
|
12
|
+
# of Vli.
|
13
|
+
class Warden
|
14
|
+
attr_accessor :actions, :stack
|
15
|
+
|
16
|
+
def initialize(actions, env)
|
17
|
+
@stack = []
|
18
|
+
@actions = actions.map { |m| finalize_action(m, env) }
|
19
|
+
end
|
20
|
+
|
21
|
+
def call(env)
|
22
|
+
return if @actions.empty?
|
23
|
+
|
24
|
+
begin
|
25
|
+
# Call the next middleware in the sequence, appending to the stack
|
26
|
+
# of "recoverable" middlewares in case something goes wrong!
|
27
|
+
raise Error::VliInterrupt if env[:interrupted]
|
28
|
+
action = @actions.shift
|
29
|
+
@stack.unshift(action).first.call(env)
|
30
|
+
raise Error::VliInterrupt if env[:interrupted]
|
31
|
+
rescue SystemExit
|
32
|
+
# This means that an "exit" or "abort" was called. In these cases,
|
33
|
+
# we just exit immediately.
|
34
|
+
raise
|
35
|
+
rescue Exception => e
|
36
|
+
env["Vli.error"] = e
|
37
|
+
|
38
|
+
# Something went horribly wrong. Start the rescue chain then
|
39
|
+
# reraise the exception to properly kick us out of limbo here.
|
40
|
+
begin_rescue(env)
|
41
|
+
raise
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Begins the recovery sequence for all middlewares which have run.
|
46
|
+
# It does this by calling `recover` (if it exists) on each middleware
|
47
|
+
# which has already run, in reverse order.
|
48
|
+
def begin_rescue(env)
|
49
|
+
@stack.each do |act|
|
50
|
+
if act.respond_to?(:recover)
|
51
|
+
act.recover(env)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Clear stack so that warden down the middleware chain doesn't
|
56
|
+
# rescue again.
|
57
|
+
@stack.clear
|
58
|
+
end
|
59
|
+
|
60
|
+
# A somewhat confusing function which simply initializes each
|
61
|
+
# middleware properly to call the next middleware in the sequence.
|
62
|
+
def finalize_action(action, env)
|
63
|
+
klass, args, block = action
|
64
|
+
|
65
|
+
# Default the arguments to an empty array. Otherwise in Ruby 1.8
|
66
|
+
# a `nil` args will actually pass `nil` into the class.
|
67
|
+
args ||= []
|
68
|
+
|
69
|
+
if klass.is_a?(Class)
|
70
|
+
# A action klass which is to be instantiated with the
|
71
|
+
# app, env, and any arguments given
|
72
|
+
klass.new(self, env, *args, &block)
|
73
|
+
elsif klass.respond_to?(:call)
|
74
|
+
# Make it a lambda which calls the item then forwards
|
75
|
+
# up the chain
|
76
|
+
lambda do |e|
|
77
|
+
klass.call(e)
|
78
|
+
self.call(e)
|
79
|
+
end
|
80
|
+
else
|
81
|
+
raise "Invalid action: #{action.inspect}"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
data/lib/vli/command.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
# Credit:
|
2
|
+
# special thanks to Vagrant project: https://github.com/mitchellh/vagrant
|
3
|
+
# where the source of this module was originally extracted.
|
4
|
+
|
5
|
+
module Vli
|
6
|
+
module Command
|
7
|
+
class Base
|
8
|
+
|
9
|
+
def initialize(argv, env)
|
10
|
+
@argv = argv
|
11
|
+
@env = env
|
12
|
+
end
|
13
|
+
|
14
|
+
def execute; end
|
15
|
+
|
16
|
+
# This method will split the argv given into three parts: the
|
17
|
+
# flags to this command, the subcommand, and the flags to the
|
18
|
+
# subcommand. For example:
|
19
|
+
#
|
20
|
+
# -v status -h -v
|
21
|
+
#
|
22
|
+
# The above would yield 3 parts:
|
23
|
+
#
|
24
|
+
# ["-v"]
|
25
|
+
# "status"
|
26
|
+
# ["-h", "-v"]
|
27
|
+
#
|
28
|
+
# These parts are useful because the first is a list of arguments
|
29
|
+
# given to the current command, the second is a subcommand, and the
|
30
|
+
# third are the commands given to the subcommand.
|
31
|
+
#
|
32
|
+
# @return [Array] The three parts.
|
33
|
+
def split_main_and_subcommand(argv)
|
34
|
+
# Initialize return variables
|
35
|
+
main_args = nil
|
36
|
+
sub_command = nil
|
37
|
+
sub_args = []
|
38
|
+
|
39
|
+
# We split the arguments into two: One set containing any
|
40
|
+
# flags before a word, and then the rest. The rest are what
|
41
|
+
# get actually sent on to the subcommand.
|
42
|
+
argv.each_index do |i|
|
43
|
+
if !argv[i].start_with?("-")
|
44
|
+
# We found the beginning of the sub command. Split the
|
45
|
+
# args up.
|
46
|
+
main_args = argv[0, i]
|
47
|
+
sub_command = argv[i]
|
48
|
+
sub_args = argv[i + 1, argv.length - i + 1]
|
49
|
+
|
50
|
+
# Break so we don't find the next non flag and shift our
|
51
|
+
# main args.
|
52
|
+
break
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Handle the case that argv was empty or didn't contain any subcommand
|
57
|
+
main_args = argv.dup if main_args.nil?
|
58
|
+
|
59
|
+
return [main_args, sub_command, sub_args]
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
data/lib/vli/error.rb
ADDED
data/lib/vli/registry.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
# Credit:
|
2
|
+
# special thanks to Vagrant project: https://github.com/mitchellh/vagrant
|
3
|
+
# where the source of this module was originally extracted.
|
4
|
+
|
5
|
+
module Vli
|
6
|
+
# Register components in a single location that can be queried.
|
7
|
+
#
|
8
|
+
# This allows certain components (such as guest systems, configuration
|
9
|
+
# pieces, etc.) to be registered and queried.
|
10
|
+
class Registry
|
11
|
+
def initialize
|
12
|
+
@actions = {}
|
13
|
+
@results_cache = {}
|
14
|
+
end
|
15
|
+
|
16
|
+
# Register a callable by key.
|
17
|
+
#
|
18
|
+
# The callable should be given in a block which will be lazily evaluated
|
19
|
+
# when the action is needed.
|
20
|
+
#
|
21
|
+
# If an action by the given name already exists then it will be
|
22
|
+
# overwritten.
|
23
|
+
def register(key, value=nil, &block)
|
24
|
+
block = lambda { value } if value
|
25
|
+
@actions[key] = block
|
26
|
+
end
|
27
|
+
|
28
|
+
# Get an action by the given key.
|
29
|
+
#
|
30
|
+
# This will evaluate the block given to `register` and return the resulting
|
31
|
+
# action stack.
|
32
|
+
def get(key)
|
33
|
+
return nil if !@actions.has_key?(key)
|
34
|
+
return @results_cache[key] if @results_cache.has_key?(key)
|
35
|
+
@results_cache[key] = @actions[key].call
|
36
|
+
end
|
37
|
+
alias :[] :get
|
38
|
+
|
39
|
+
# Iterate over the keyspace.
|
40
|
+
def each(&block)
|
41
|
+
@actions.each do |key, _|
|
42
|
+
yield key, get(key)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Converts this registry to a hash
|
47
|
+
def to_hash
|
48
|
+
result = {}
|
49
|
+
self.each do |key, value|
|
50
|
+
result[key] = value
|
51
|
+
end
|
52
|
+
|
53
|
+
result
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/lib/vli/ui.rb
ADDED
@@ -0,0 +1,161 @@
|
|
1
|
+
module Vli
|
2
|
+
module UI
|
3
|
+
# Vli UIs handle communication with the outside world (typically
|
4
|
+
# through a shell). They must respond to the following methods:
|
5
|
+
#
|
6
|
+
# * `info`
|
7
|
+
# * `warn`
|
8
|
+
# * `error`
|
9
|
+
# * `success`
|
10
|
+
class Interface
|
11
|
+
attr_accessor :resource
|
12
|
+
|
13
|
+
def initialize(resource)
|
14
|
+
@resource = resource
|
15
|
+
end
|
16
|
+
|
17
|
+
[:ask, :warn, :error, :info, :success].each do |method|
|
18
|
+
define_method(method) do |message, *opts|
|
19
|
+
define_method(method) { |*args| }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
[:clear_line, :report_progress].each do |method|
|
24
|
+
define_method(method) { |*args| }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# This is a UI implementation that does nothing.
|
29
|
+
class Silent < Interface
|
30
|
+
def ask(*args)
|
31
|
+
super
|
32
|
+
|
33
|
+
# Silent can't do this, obviously.
|
34
|
+
raise Errors::UIExpectsTTY
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# This is a UI implementation that outputs the text as is. It
|
39
|
+
# doesn't add any color.
|
40
|
+
class Basic < Interface
|
41
|
+
include Util::SafePuts
|
42
|
+
|
43
|
+
# Use some light meta-programming to create the various methods to
|
44
|
+
# output text to the UI. These all delegate the real functionality
|
45
|
+
# to `say`.
|
46
|
+
[:info, :warn, :error, :success].each do |method|
|
47
|
+
class_eval <<-CODE
|
48
|
+
def #{method}(message, *args)
|
49
|
+
super(message)
|
50
|
+
say(#{method.inspect}, message, *args)
|
51
|
+
end
|
52
|
+
CODE
|
53
|
+
end
|
54
|
+
|
55
|
+
def ask(message, opts=nil)
|
56
|
+
super(message)
|
57
|
+
|
58
|
+
# We can't ask questions when the output isn't a TTY.
|
59
|
+
raise Errors::UIExpectsTTY if !$stdin.tty?
|
60
|
+
|
61
|
+
# Setup the options so that the new line is suppressed
|
62
|
+
opts ||= {}
|
63
|
+
opts[:new_line] = false if !opts.has_key?(:new_line)
|
64
|
+
opts[:prefix] = false if !opts.has_key?(:prefix)
|
65
|
+
|
66
|
+
# Output the data
|
67
|
+
say(:info, message, opts)
|
68
|
+
|
69
|
+
# Get the results and chomp off the newline
|
70
|
+
$stdin.gets.chomp
|
71
|
+
end
|
72
|
+
|
73
|
+
# This is used to output progress reports to the UI.
|
74
|
+
# Send this method progress/total and it will output it
|
75
|
+
# to the UI. Send `clear_line` to clear the line to show
|
76
|
+
# a continuous progress meter.
|
77
|
+
def report_progress(progress, total, show_parts=true)
|
78
|
+
if total && total > 0
|
79
|
+
percent = (progress.to_f / total.to_f) * 100
|
80
|
+
line = "Progress: #{percent.to_i}%"
|
81
|
+
line << " (#{progress} / #{total})" if show_parts
|
82
|
+
else
|
83
|
+
line = "Progress: #{progress}"
|
84
|
+
end
|
85
|
+
|
86
|
+
info(line, :new_line => false)
|
87
|
+
end
|
88
|
+
|
89
|
+
def clear_line
|
90
|
+
reset = "\r"
|
91
|
+
reset += "\e[0K" unless Util::Platform.windows?
|
92
|
+
reset
|
93
|
+
|
94
|
+
info(reset, :new_line => false)
|
95
|
+
end
|
96
|
+
|
97
|
+
# This method handles actually outputting a message of a given type
|
98
|
+
# to the console.
|
99
|
+
def say(type, message, opts=nil)
|
100
|
+
defaults = { :new_line => true, :prefix => true }
|
101
|
+
opts = defaults.merge(opts || {})
|
102
|
+
|
103
|
+
# Determine whether we're expecting to output our
|
104
|
+
# own new line or not.
|
105
|
+
printer = opts[:new_line] ? :puts : :print
|
106
|
+
|
107
|
+
# Determine the proper IO channel to send this message
|
108
|
+
# to based on the type of the message
|
109
|
+
channel = type == :error || opts[:channel] == :error ? $stderr : $stdout
|
110
|
+
|
111
|
+
# Output!
|
112
|
+
safe_puts(format_message(type, message, opts),
|
113
|
+
:io => channel, :printer => printer)
|
114
|
+
end
|
115
|
+
|
116
|
+
# This is called by `say` to format the message for output.
|
117
|
+
def format_message(type, message, opts=nil)
|
118
|
+
opts ||= {}
|
119
|
+
message = "[#{@resource}] #{message}" if opts[:prefix]
|
120
|
+
message
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# This is a UI implementation that outputs color for various types
|
125
|
+
# of messages. This should only be used with a TTY that supports color,
|
126
|
+
# but is up to the user of the class to verify this is the case.
|
127
|
+
class Colored < Basic
|
128
|
+
# Terminal colors
|
129
|
+
COLORS = {
|
130
|
+
:clear => "\e[0m",
|
131
|
+
:red => "\e[31m",
|
132
|
+
:green => "\e[32m",
|
133
|
+
:yellow => "\e[33m"
|
134
|
+
}
|
135
|
+
|
136
|
+
# Mapping between type of message and the color to output
|
137
|
+
COLOR_MAP = {
|
138
|
+
:warn => COLORS[:yellow],
|
139
|
+
:error => COLORS[:red],
|
140
|
+
:success => COLORS[:green]
|
141
|
+
}
|
142
|
+
|
143
|
+
# This is called by `say` to format the message for output.
|
144
|
+
def format_message(type, message, opts=nil)
|
145
|
+
# Get the format of the message before adding color.
|
146
|
+
message = super
|
147
|
+
|
148
|
+
# Colorize the message if there is a color for this type of message,
|
149
|
+
# either specified by the options or via the default color map.
|
150
|
+
if opts.has_key?(:color)
|
151
|
+
color = COLORS[opts[:color]]
|
152
|
+
message = "#{color}#{message}#{COLORS[:clear]}"
|
153
|
+
else
|
154
|
+
message = "#{COLOR_MAP[type]}#{message}#{COLORS[:clear]}" if COLOR_MAP[type]
|
155
|
+
end
|
156
|
+
|
157
|
+
message
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
data/lib/vli/util.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
module Vli
|
2
|
+
module Util
|
3
|
+
# Utility class which allows blocks of code to be marked as "busy"
|
4
|
+
# with a specified interrupt handler. During busy areas of code, it
|
5
|
+
# is often undesirable for SIGINTs to immediately kill the application.
|
6
|
+
# This class is a helper to cleanly register callbacks to handle this
|
7
|
+
# situation.
|
8
|
+
class Busy
|
9
|
+
@@registered = []
|
10
|
+
@@mutex = Mutex.new
|
11
|
+
|
12
|
+
class << self
|
13
|
+
# Mark a given block of code as a "busy" block of code, which will
|
14
|
+
# register a SIGINT handler for the duration of the block. When a
|
15
|
+
# SIGINT occurs, the `sig_callback` proc will be called. It is up
|
16
|
+
# to the callback to behave properly and exit the application.
|
17
|
+
def busy(sig_callback)
|
18
|
+
register(sig_callback)
|
19
|
+
yield
|
20
|
+
ensure
|
21
|
+
unregister(sig_callback)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Registers a SIGINT handler. This typically is called from {busy}.
|
25
|
+
# Callbacks are only registered once, so calling this multiple times
|
26
|
+
# with the same callback has no consequence.
|
27
|
+
def register(sig_callback)
|
28
|
+
@@mutex.synchronize do
|
29
|
+
registered << sig_callback
|
30
|
+
registered.uniq!
|
31
|
+
|
32
|
+
# Register the handler if this is our first callback.
|
33
|
+
Signal.trap("INT") { fire_callbacks } if registered.length == 1
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Unregisters a SIGINT handler.
|
38
|
+
def unregister(sig_callback)
|
39
|
+
@@mutex.synchronize do
|
40
|
+
registered.delete(sig_callback)
|
41
|
+
|
42
|
+
# Remove the signal trap if no more registered callbacks exist
|
43
|
+
Signal.trap("INT", "DEFAULT") if registered.empty?
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Fires all the registered callbacks.
|
48
|
+
def fire_callbacks
|
49
|
+
registered.reverse.each { |r| r.call }
|
50
|
+
end
|
51
|
+
|
52
|
+
# Helper method to get access to the class variable. This is mostly
|
53
|
+
# exposed for tests. This shouldn't be mucked with directly, since it's
|
54
|
+
# structure may change at any time.
|
55
|
+
def registered; @@registered; end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Vli
|
2
|
+
module Util
|
3
|
+
# A hash with indifferent access. Mostly taken from Thor/Rails (thanks).
|
4
|
+
# Normally I'm not a fan of using an indifferent access hash since Symbols
|
5
|
+
# are basically memory leaks in Ruby, but since Vli is typically a quick
|
6
|
+
# one-off binary run and it doesn't use too many hash keys where this is
|
7
|
+
# used, the effect should be minimal.
|
8
|
+
#
|
9
|
+
# hash[:foo] #=> 'bar'
|
10
|
+
# hash['foo'] #=> 'bar'
|
11
|
+
#
|
12
|
+
class HashWithIndifferentAccess < ::Hash
|
13
|
+
def initialize(hash={}, &block)
|
14
|
+
super(&block)
|
15
|
+
|
16
|
+
hash.each do |key, value|
|
17
|
+
self[convert_key(key)] = value
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def [](key)
|
22
|
+
super(convert_key(key))
|
23
|
+
end
|
24
|
+
|
25
|
+
def []=(key, value)
|
26
|
+
super(convert_key(key), value)
|
27
|
+
end
|
28
|
+
|
29
|
+
def delete(key)
|
30
|
+
super(convert_key(key))
|
31
|
+
end
|
32
|
+
|
33
|
+
def values_at(*indices)
|
34
|
+
indices.collect { |key| self[convert_key(key)] }
|
35
|
+
end
|
36
|
+
|
37
|
+
def merge(other)
|
38
|
+
dup.merge!(other)
|
39
|
+
end
|
40
|
+
|
41
|
+
def merge!(other)
|
42
|
+
other.each do |key, value|
|
43
|
+
self[convert_key(key)] = value
|
44
|
+
end
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
48
|
+
def key?(key)
|
49
|
+
super(convert_key(key))
|
50
|
+
end
|
51
|
+
|
52
|
+
alias_method :include?, :key?
|
53
|
+
alias_method :has_key?, :key?
|
54
|
+
alias_method :member?, :key?
|
55
|
+
|
56
|
+
protected
|
57
|
+
|
58
|
+
def convert_key(key)
|
59
|
+
key.is_a?(Symbol) ? key.to_s : key
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'rbconfig'
|
2
|
+
require 'tempfile'
|
3
|
+
|
4
|
+
module Vli
|
5
|
+
module Util
|
6
|
+
# This class just contains some platform checking code.
|
7
|
+
class Platform
|
8
|
+
class << self
|
9
|
+
def tiger?
|
10
|
+
platform.include?("darwin8")
|
11
|
+
end
|
12
|
+
|
13
|
+
def leopard?
|
14
|
+
platform.include?("darwin9")
|
15
|
+
end
|
16
|
+
|
17
|
+
[:darwin, :bsd, :freebsd, :linux, :solaris].each do |type|
|
18
|
+
define_method("#{type}?") do
|
19
|
+
platform.include?(type.to_s)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def windows?
|
24
|
+
%W[mingw mswin].each do |text|
|
25
|
+
return true if platform.include?(text)
|
26
|
+
end
|
27
|
+
|
28
|
+
false
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns boolean noting whether this is a 64-bit CPU. This
|
32
|
+
# is not 100% accurate and there could easily be false negatives.
|
33
|
+
#
|
34
|
+
# @return [Boolean]
|
35
|
+
def bit64?
|
36
|
+
["x86_64", "amd64"].include?(RbConfig::CONFIG["host_cpu"])
|
37
|
+
end
|
38
|
+
|
39
|
+
# Returns boolean noting whether this is a 32-bit CPU. This
|
40
|
+
# can easily throw false positives since it relies on {#bit64?}.
|
41
|
+
#
|
42
|
+
# @return [Boolean]
|
43
|
+
def bit32?
|
44
|
+
!bit64?
|
45
|
+
end
|
46
|
+
|
47
|
+
# Returns a boolean noting whether the terminal supports color.
|
48
|
+
# output.
|
49
|
+
def terminal_supports_colors?
|
50
|
+
if windows?
|
51
|
+
return ENV.has_key?("ANSICON")
|
52
|
+
end
|
53
|
+
|
54
|
+
true
|
55
|
+
end
|
56
|
+
|
57
|
+
def tar_file_options
|
58
|
+
# create, write only, fail if the file exists, binary if windows
|
59
|
+
File::WRONLY | File::EXCL | File::CREAT | (windows? ? File::BINARY : 0)
|
60
|
+
end
|
61
|
+
|
62
|
+
def platform
|
63
|
+
RbConfig::CONFIG["host_os"].downcase
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Vli
|
2
|
+
module Util
|
3
|
+
# This module provides a `safe_puts` method which outputs to
|
4
|
+
# the given IO object, and rescues any broken pipe errors and
|
5
|
+
# ignores them. This is useful in cases where you're outputting
|
6
|
+
# to stdout, for example, and the stdout is closed, but you want to
|
7
|
+
# keep running.
|
8
|
+
module SafePuts
|
9
|
+
# Uses `puts` on the given IO object and safely ignores any
|
10
|
+
# Errno::EPIPE.
|
11
|
+
#
|
12
|
+
# @param [String] message Message to output.
|
13
|
+
# @param [Hash] opts Options hash.
|
14
|
+
def safe_puts(message=nil, opts=nil)
|
15
|
+
message ||= ""
|
16
|
+
opts = {
|
17
|
+
:io => $stdout,
|
18
|
+
:printer => :puts
|
19
|
+
}.merge(opts || {})
|
20
|
+
|
21
|
+
begin
|
22
|
+
opts[:io].send(opts[:printer], message)
|
23
|
+
rescue Errno::EPIPE
|
24
|
+
# This is what makes this a `safe` puts.
|
25
|
+
return
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
data/lib/vli/version.rb
ADDED
data/vli.gemspec
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'vli/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "vli"
|
8
|
+
gem.version = Vli::VERSION
|
9
|
+
gem.authors = ["Tyler Flint"]
|
10
|
+
gem.email = ["tylerflint@gmail.com"]
|
11
|
+
gem.description = %q{Vagrant Like Interface is a library of components extracted from the vagrant project to aid in building command line interfaces.}
|
12
|
+
gem.summary = %q{library of components extracted from the vagrant project}
|
13
|
+
gem.homepage = "http://github.com/tylerflint/vli"
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
end
|
metadata
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: vli
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Tyler Flint
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-12-05 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: Vagrant Like Interface is a library of components extracted from the
|
15
|
+
vagrant project to aid in building command line interfaces.
|
16
|
+
email:
|
17
|
+
- tylerflint@gmail.com
|
18
|
+
executables: []
|
19
|
+
extensions: []
|
20
|
+
extra_rdoc_files: []
|
21
|
+
files:
|
22
|
+
- .gitignore
|
23
|
+
- Gemfile
|
24
|
+
- LICENSE.txt
|
25
|
+
- README.md
|
26
|
+
- Rakefile
|
27
|
+
- lib/vli.rb
|
28
|
+
- lib/vli/action.rb
|
29
|
+
- lib/vli/action/builder.rb
|
30
|
+
- lib/vli/action/env.rb
|
31
|
+
- lib/vli/action/env/set.rb
|
32
|
+
- lib/vli/action/environment.rb
|
33
|
+
- lib/vli/action/runner.rb
|
34
|
+
- lib/vli/action/warden.rb
|
35
|
+
- lib/vli/command.rb
|
36
|
+
- lib/vli/command/base.rb
|
37
|
+
- lib/vli/error.rb
|
38
|
+
- lib/vli/registry.rb
|
39
|
+
- lib/vli/ui.rb
|
40
|
+
- lib/vli/util.rb
|
41
|
+
- lib/vli/util/busy.rb
|
42
|
+
- lib/vli/util/hash_with_indifferent_access.rb
|
43
|
+
- lib/vli/util/platform.rb
|
44
|
+
- lib/vli/util/safe_puts.rb
|
45
|
+
- lib/vli/version.rb
|
46
|
+
- vli.gemspec
|
47
|
+
homepage: http://github.com/tylerflint/vli
|
48
|
+
licenses: []
|
49
|
+
post_install_message:
|
50
|
+
rdoc_options: []
|
51
|
+
require_paths:
|
52
|
+
- lib
|
53
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
55
|
+
requirements:
|
56
|
+
- - ! '>='
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: '0'
|
59
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
|
+
none: false
|
61
|
+
requirements:
|
62
|
+
- - ! '>='
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: '0'
|
65
|
+
requirements: []
|
66
|
+
rubyforge_project:
|
67
|
+
rubygems_version: 1.8.24
|
68
|
+
signing_key:
|
69
|
+
specification_version: 3
|
70
|
+
summary: library of components extracted from the vagrant project
|
71
|
+
test_files: []
|
72
|
+
has_rdoc:
|