wtch 0.0.1
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.
- data/bin/wtch +11 -0
- data/lib/wtch.rb +34 -0
- data/lib/wtch/cli.rb +32 -0
- data/lib/wtch/ui.rb +60 -0
- data/lib/wtch/vendor/thor.rb +319 -0
- data/lib/wtch/vendor/thor/actions.rb +297 -0
- data/lib/wtch/vendor/thor/actions/create_file.rb +105 -0
- data/lib/wtch/vendor/thor/actions/directory.rb +93 -0
- data/lib/wtch/vendor/thor/actions/empty_directory.rb +134 -0
- data/lib/wtch/vendor/thor/actions/file_manipulation.rb +229 -0
- data/lib/wtch/vendor/thor/actions/inject_into_file.rb +104 -0
- data/lib/wtch/vendor/thor/base.rb +556 -0
- data/lib/wtch/vendor/thor/core_ext/file_binary_read.rb +9 -0
- data/lib/wtch/vendor/thor/core_ext/hash_with_indifferent_access.rb +75 -0
- data/lib/wtch/vendor/thor/core_ext/ordered_hash.rb +100 -0
- data/lib/wtch/vendor/thor/error.rb +30 -0
- data/lib/wtch/vendor/thor/invocation.rb +168 -0
- data/lib/wtch/vendor/thor/parser.rb +4 -0
- data/lib/wtch/vendor/thor/parser/argument.rb +67 -0
- data/lib/wtch/vendor/thor/parser/arguments.rb +161 -0
- data/lib/wtch/vendor/thor/parser/option.rb +120 -0
- data/lib/wtch/vendor/thor/parser/options.rb +166 -0
- data/lib/wtch/vendor/thor/shell.rb +88 -0
- data/lib/wtch/vendor/thor/shell/basic.rb +275 -0
- data/lib/wtch/vendor/thor/shell/color.rb +108 -0
- data/lib/wtch/vendor/thor/shell/html.rb +121 -0
- data/lib/wtch/vendor/thor/task.rb +114 -0
- data/lib/wtch/vendor/thor/util.rb +229 -0
- data/lib/wtch/vendor/thor/version.rb +3 -0
- data/lib/wtch/version.rb +3 -0
- metadata +93 -0
@@ -0,0 +1,161 @@
|
|
1
|
+
class Thor
|
2
|
+
class Arguments #:nodoc:
|
3
|
+
NUMERIC = /(\d*\.\d+|\d+)/
|
4
|
+
|
5
|
+
# Receives an array of args and returns two arrays, one with arguments
|
6
|
+
# and one with switches.
|
7
|
+
#
|
8
|
+
def self.split(args)
|
9
|
+
arguments = []
|
10
|
+
|
11
|
+
args.each do |item|
|
12
|
+
break if item =~ /^-/
|
13
|
+
arguments << item
|
14
|
+
end
|
15
|
+
|
16
|
+
return arguments, args[Range.new(arguments.size, -1)]
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.parse(*args)
|
20
|
+
to_parse = args.pop
|
21
|
+
new(*args).parse(to_parse)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Takes an array of Thor::Argument objects.
|
25
|
+
#
|
26
|
+
def initialize(arguments=[])
|
27
|
+
@assigns, @non_assigned_required = {}, []
|
28
|
+
@switches = arguments
|
29
|
+
|
30
|
+
arguments.each do |argument|
|
31
|
+
if argument.default
|
32
|
+
@assigns[argument.human_name] = argument.default
|
33
|
+
elsif argument.required?
|
34
|
+
@non_assigned_required << argument
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def parse(args)
|
40
|
+
@pile = args.dup
|
41
|
+
|
42
|
+
@switches.each do |argument|
|
43
|
+
break unless peek
|
44
|
+
@non_assigned_required.delete(argument)
|
45
|
+
@assigns[argument.human_name] = send(:"parse_#{argument.type}", argument.human_name)
|
46
|
+
end
|
47
|
+
|
48
|
+
check_requirement!
|
49
|
+
@assigns
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def no_or_skip?(arg)
|
55
|
+
arg =~ /^--(no|skip)-([-\w]+)$/
|
56
|
+
$2
|
57
|
+
end
|
58
|
+
|
59
|
+
def last?
|
60
|
+
@pile.empty?
|
61
|
+
end
|
62
|
+
|
63
|
+
def peek
|
64
|
+
@pile.first
|
65
|
+
end
|
66
|
+
|
67
|
+
def shift
|
68
|
+
@pile.shift
|
69
|
+
end
|
70
|
+
|
71
|
+
def unshift(arg)
|
72
|
+
unless arg.kind_of?(Array)
|
73
|
+
@pile.unshift(arg)
|
74
|
+
else
|
75
|
+
@pile = arg + @pile
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def current_is_value?
|
80
|
+
peek && peek.to_s !~ /^-/
|
81
|
+
end
|
82
|
+
|
83
|
+
# Runs through the argument array getting strings that contains ":" and
|
84
|
+
# mark it as a hash:
|
85
|
+
#
|
86
|
+
# [ "name:string", "age:integer" ]
|
87
|
+
#
|
88
|
+
# Becomes:
|
89
|
+
#
|
90
|
+
# { "name" => "string", "age" => "integer" }
|
91
|
+
#
|
92
|
+
def parse_hash(name)
|
93
|
+
return shift if peek.is_a?(Hash)
|
94
|
+
hash = {}
|
95
|
+
|
96
|
+
while current_is_value? && peek.include?(?:)
|
97
|
+
key, value = shift.split(':')
|
98
|
+
hash[key] = value
|
99
|
+
end
|
100
|
+
hash
|
101
|
+
end
|
102
|
+
|
103
|
+
# Runs through the argument array getting all strings until no string is
|
104
|
+
# found or a switch is found.
|
105
|
+
#
|
106
|
+
# ["a", "b", "c"]
|
107
|
+
#
|
108
|
+
# And returns it as an array:
|
109
|
+
#
|
110
|
+
# ["a", "b", "c"]
|
111
|
+
#
|
112
|
+
def parse_array(name)
|
113
|
+
return shift if peek.is_a?(Array)
|
114
|
+
array = []
|
115
|
+
|
116
|
+
while current_is_value?
|
117
|
+
array << shift
|
118
|
+
end
|
119
|
+
array
|
120
|
+
end
|
121
|
+
|
122
|
+
# Check if the peek is numeric format and return a Float or Integer.
|
123
|
+
# Otherwise raises an error.
|
124
|
+
#
|
125
|
+
def parse_numeric(name)
|
126
|
+
return shift if peek.is_a?(Numeric)
|
127
|
+
|
128
|
+
unless peek =~ NUMERIC && $& == peek
|
129
|
+
raise MalformattedArgumentError, "Expected numeric value for '#{name}'; got #{peek.inspect}"
|
130
|
+
end
|
131
|
+
|
132
|
+
$&.index('.') ? shift.to_f : shift.to_i
|
133
|
+
end
|
134
|
+
|
135
|
+
# Parse string:
|
136
|
+
# for --string-arg, just return the current value in the pile
|
137
|
+
# for --no-string-arg, nil
|
138
|
+
#
|
139
|
+
def parse_string(name)
|
140
|
+
if no_or_skip?(name)
|
141
|
+
nil
|
142
|
+
else
|
143
|
+
shift
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# Raises an error if @non_assigned_required array is not empty.
|
148
|
+
#
|
149
|
+
def check_requirement!
|
150
|
+
unless @non_assigned_required.empty?
|
151
|
+
names = @non_assigned_required.map do |o|
|
152
|
+
o.respond_to?(:switch_name) ? o.switch_name : o.human_name
|
153
|
+
end.join("', '")
|
154
|
+
|
155
|
+
class_name = self.class.name.split('::').last.downcase
|
156
|
+
raise RequiredArgumentMissingError, "No value provided for required #{class_name} '#{names}'"
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
end
|
161
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
class Thor
|
2
|
+
class Option < Argument #:nodoc:
|
3
|
+
attr_reader :aliases, :group, :lazy_default
|
4
|
+
|
5
|
+
VALID_TYPES = [:boolean, :numeric, :hash, :array, :string]
|
6
|
+
|
7
|
+
def initialize(name, description=nil, required=nil, type=nil, default=nil, banner=nil, lazy_default=nil, group=nil, aliases=nil)
|
8
|
+
super(name, description, required, type, default, banner)
|
9
|
+
@lazy_default = lazy_default
|
10
|
+
@group = group.to_s.capitalize if group
|
11
|
+
@aliases = [*aliases].compact
|
12
|
+
end
|
13
|
+
|
14
|
+
# This parse quick options given as method_options. It makes several
|
15
|
+
# assumptions, but you can be more specific using the option method.
|
16
|
+
#
|
17
|
+
# parse :foo => "bar"
|
18
|
+
# #=> Option foo with default value bar
|
19
|
+
#
|
20
|
+
# parse [:foo, :baz] => "bar"
|
21
|
+
# #=> Option foo with default value bar and alias :baz
|
22
|
+
#
|
23
|
+
# parse :foo => :required
|
24
|
+
# #=> Required option foo without default value
|
25
|
+
#
|
26
|
+
# parse :foo => 2
|
27
|
+
# #=> Option foo with default value 2 and type numeric
|
28
|
+
#
|
29
|
+
# parse :foo => :numeric
|
30
|
+
# #=> Option foo without default value and type numeric
|
31
|
+
#
|
32
|
+
# parse :foo => true
|
33
|
+
# #=> Option foo with default value true and type boolean
|
34
|
+
#
|
35
|
+
# The valid types are :boolean, :numeric, :hash, :array and :string. If none
|
36
|
+
# is given a default type is assumed. This default type accepts arguments as
|
37
|
+
# string (--foo=value) or booleans (just --foo).
|
38
|
+
#
|
39
|
+
# By default all options are optional, unless :required is given.
|
40
|
+
#
|
41
|
+
def self.parse(key, value)
|
42
|
+
if key.is_a?(Array)
|
43
|
+
name, *aliases = key
|
44
|
+
else
|
45
|
+
name, aliases = key, []
|
46
|
+
end
|
47
|
+
|
48
|
+
name = name.to_s
|
49
|
+
default = value
|
50
|
+
|
51
|
+
type = case value
|
52
|
+
when Symbol
|
53
|
+
default = nil
|
54
|
+
if VALID_TYPES.include?(value)
|
55
|
+
value
|
56
|
+
elsif required = (value == :required)
|
57
|
+
:string
|
58
|
+
end
|
59
|
+
when TrueClass, FalseClass
|
60
|
+
:boolean
|
61
|
+
when Numeric
|
62
|
+
:numeric
|
63
|
+
when Hash, Array, String
|
64
|
+
value.class.name.downcase.to_sym
|
65
|
+
end
|
66
|
+
|
67
|
+
self.new(name.to_s, nil, required, type, default, nil, nil, nil, aliases)
|
68
|
+
end
|
69
|
+
|
70
|
+
def switch_name
|
71
|
+
@switch_name ||= dasherized? ? name : dasherize(name)
|
72
|
+
end
|
73
|
+
|
74
|
+
def human_name
|
75
|
+
@human_name ||= dasherized? ? undasherize(name) : name
|
76
|
+
end
|
77
|
+
|
78
|
+
def usage(padding=0)
|
79
|
+
sample = if banner && !banner.to_s.empty?
|
80
|
+
"#{switch_name}=#{banner}"
|
81
|
+
else
|
82
|
+
switch_name
|
83
|
+
end
|
84
|
+
|
85
|
+
sample = "[#{sample}]" unless required?
|
86
|
+
|
87
|
+
if aliases.empty?
|
88
|
+
(" " * padding) << sample
|
89
|
+
else
|
90
|
+
"#{aliases.join(', ')}, #{sample}"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
VALID_TYPES.each do |type|
|
95
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
96
|
+
def #{type}?
|
97
|
+
self.type == #{type.inspect}
|
98
|
+
end
|
99
|
+
RUBY
|
100
|
+
end
|
101
|
+
|
102
|
+
protected
|
103
|
+
|
104
|
+
def validate!
|
105
|
+
raise ArgumentError, "An option cannot be boolean and required." if boolean? && required?
|
106
|
+
end
|
107
|
+
|
108
|
+
def dasherized?
|
109
|
+
name.index('-') == 0
|
110
|
+
end
|
111
|
+
|
112
|
+
def undasherize(str)
|
113
|
+
str.sub(/^-{1,2}/, '')
|
114
|
+
end
|
115
|
+
|
116
|
+
def dasherize(str)
|
117
|
+
(str.length > 1 ? "--" : "-") + str.gsub('_', '-')
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
class Thor
|
2
|
+
# This is a modified version of Daniel Berger's Getopt::Long class, licensed
|
3
|
+
# under Ruby's license.
|
4
|
+
#
|
5
|
+
class Options < Arguments #:nodoc:
|
6
|
+
LONG_RE = /^(--\w+(?:-\w+)*)$/
|
7
|
+
SHORT_RE = /^(-[a-z])$/i
|
8
|
+
EQ_RE = /^(--\w+(?:-\w+)*|-[a-z])=(.*)$/i
|
9
|
+
SHORT_SQ_RE = /^-([a-z]{2,})$/i # Allow either -x -v or -xv style for single char args
|
10
|
+
SHORT_NUM = /^(-[a-z])#{NUMERIC}$/i
|
11
|
+
|
12
|
+
# Receives a hash and makes it switches.
|
13
|
+
def self.to_switches(options)
|
14
|
+
options.map do |key, value|
|
15
|
+
case value
|
16
|
+
when true
|
17
|
+
"--#{key}"
|
18
|
+
when Array
|
19
|
+
"--#{key} #{value.map{ |v| v.inspect }.join(' ')}"
|
20
|
+
when Hash
|
21
|
+
"--#{key} #{value.map{ |k,v| "#{k}:#{v}" }.join(' ')}"
|
22
|
+
when nil, false
|
23
|
+
""
|
24
|
+
else
|
25
|
+
"--#{key} #{value.inspect}"
|
26
|
+
end
|
27
|
+
end.join(" ")
|
28
|
+
end
|
29
|
+
|
30
|
+
# Takes a hash of Thor::Option and a hash with defaults.
|
31
|
+
def initialize(hash_options={}, defaults={})
|
32
|
+
options = hash_options.values
|
33
|
+
super(options)
|
34
|
+
|
35
|
+
# Add defaults
|
36
|
+
defaults.each do |key, value|
|
37
|
+
@assigns[key.to_s] = value
|
38
|
+
@non_assigned_required.delete(hash_options[key])
|
39
|
+
end
|
40
|
+
|
41
|
+
@shorts, @switches, @unknown = {}, {}, []
|
42
|
+
|
43
|
+
options.each do |option|
|
44
|
+
@switches[option.switch_name] = option
|
45
|
+
|
46
|
+
option.aliases.each do |short|
|
47
|
+
@shorts[short.to_s] ||= option.switch_name
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def parse(args)
|
53
|
+
@pile = args.dup
|
54
|
+
|
55
|
+
while peek
|
56
|
+
if current_is_switch?
|
57
|
+
case shift
|
58
|
+
when SHORT_SQ_RE
|
59
|
+
unshift($1.split('').map { |f| "-#{f}" })
|
60
|
+
next
|
61
|
+
when EQ_RE, SHORT_NUM
|
62
|
+
unshift($2)
|
63
|
+
switch = $1
|
64
|
+
when LONG_RE, SHORT_RE
|
65
|
+
switch = $1
|
66
|
+
end
|
67
|
+
|
68
|
+
switch = normalize_switch(switch)
|
69
|
+
option = switch_option(switch)
|
70
|
+
@assigns[option.human_name] = parse_peek(switch, option)
|
71
|
+
elsif current_is_switch_formatted?
|
72
|
+
@unknown << shift
|
73
|
+
else
|
74
|
+
shift
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
check_requirement!
|
79
|
+
|
80
|
+
assigns = Thor::CoreExt::HashWithIndifferentAccess.new(@assigns)
|
81
|
+
assigns.freeze
|
82
|
+
assigns
|
83
|
+
end
|
84
|
+
|
85
|
+
def check_unknown!
|
86
|
+
raise UnknownArgumentError, "Unknown switches '#{@unknown.join(', ')}'" unless @unknown.empty?
|
87
|
+
end
|
88
|
+
|
89
|
+
protected
|
90
|
+
|
91
|
+
# Returns true if the current value in peek is a registered switch.
|
92
|
+
#
|
93
|
+
def current_is_switch?
|
94
|
+
case peek
|
95
|
+
when LONG_RE, SHORT_RE, EQ_RE, SHORT_NUM
|
96
|
+
switch?($1)
|
97
|
+
when SHORT_SQ_RE
|
98
|
+
$1.split('').any? { |f| switch?("-#{f}") }
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def switch_formatted?(arg)
|
103
|
+
case arg
|
104
|
+
when LONG_RE, SHORT_RE, EQ_RE, SHORT_NUM, SHORT_SQ_RE
|
105
|
+
true
|
106
|
+
else
|
107
|
+
false
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def current_is_switch_formatted?
|
112
|
+
switch_formatted? peek
|
113
|
+
end
|
114
|
+
|
115
|
+
def switch?(arg)
|
116
|
+
switch_option(arg) || @shorts.key?(arg)
|
117
|
+
end
|
118
|
+
|
119
|
+
def switch_option(arg)
|
120
|
+
if match = no_or_skip?(arg)
|
121
|
+
@switches[arg] || @switches["--#{match}"]
|
122
|
+
else
|
123
|
+
@switches[arg]
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# Check if the given argument is actually a shortcut.
|
128
|
+
#
|
129
|
+
def normalize_switch(arg)
|
130
|
+
@shorts.key?(arg) ? @shorts[arg] : arg
|
131
|
+
end
|
132
|
+
|
133
|
+
# Parse boolean values which can be given as --foo=true, --foo or --no-foo.
|
134
|
+
#
|
135
|
+
def parse_boolean(switch)
|
136
|
+
if current_is_value?
|
137
|
+
["true", "TRUE", "t", "T", true].include?(shift)
|
138
|
+
else
|
139
|
+
@switches.key?(switch) || !no_or_skip?(switch)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# Parse the value at the peek analyzing if it requires an input or not.
|
144
|
+
#
|
145
|
+
def parse_peek(switch, option)
|
146
|
+
if current_is_switch_formatted? || last?
|
147
|
+
if option.boolean?
|
148
|
+
# No problem for boolean types
|
149
|
+
elsif no_or_skip?(switch)
|
150
|
+
return nil # User set value to nil
|
151
|
+
elsif option.string? && !option.required?
|
152
|
+
# Return the default if there is one, else the human name
|
153
|
+
return option.lazy_default || option.default || option.human_name
|
154
|
+
elsif option.lazy_default
|
155
|
+
return option.lazy_default
|
156
|
+
else
|
157
|
+
raise MalformattedArgumentError, "No value provided for option '#{switch}'"
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
@non_assigned_required.delete(option)
|
162
|
+
send(:"parse_#{option.type}", switch)
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
166
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'rbconfig'
|
2
|
+
|
3
|
+
class Thor
|
4
|
+
module Base
|
5
|
+
# Returns the shell used in all Thor classes. If you are in a Unix platform
|
6
|
+
# it will use a colored log, otherwise it will use a basic one without color.
|
7
|
+
#
|
8
|
+
def self.shell
|
9
|
+
@shell ||= if ENV['THOR_SHELL'] && ENV['THOR_SHELL'].size > 0
|
10
|
+
Thor::Shell.const_get(ENV['THOR_SHELL'])
|
11
|
+
elsif Config::CONFIG['host_os'] =~ /mswin|mingw/
|
12
|
+
Thor::Shell::Basic
|
13
|
+
else
|
14
|
+
Thor::Shell::Color
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Sets the shell used in all Thor classes.
|
19
|
+
#
|
20
|
+
def self.shell=(klass)
|
21
|
+
@shell = klass
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
module Shell
|
26
|
+
SHELL_DELEGATED_METHODS = [:ask, :yes?, :no?, :say, :say_status, :print_table]
|
27
|
+
|
28
|
+
autoload :Basic, 'thor/shell/basic'
|
29
|
+
autoload :Color, 'thor/shell/color'
|
30
|
+
autoload :HTML, 'thor/shell/html'
|
31
|
+
|
32
|
+
# Add shell to initialize config values.
|
33
|
+
#
|
34
|
+
# ==== Configuration
|
35
|
+
# shell<Object>:: An instance of the shell to be used.
|
36
|
+
#
|
37
|
+
# ==== Examples
|
38
|
+
#
|
39
|
+
# class MyScript < Thor
|
40
|
+
# argument :first, :type => :numeric
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# MyScript.new [1.0], { :foo => :bar }, :shell => Thor::Shell::Basic.new
|
44
|
+
#
|
45
|
+
def initialize(args=[], options={}, config={})
|
46
|
+
super
|
47
|
+
self.shell = config[:shell]
|
48
|
+
self.shell.base ||= self if self.shell.respond_to?(:base)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Holds the shell for the given Thor instance. If no shell is given,
|
52
|
+
# it gets a default shell from Thor::Base.shell.
|
53
|
+
def shell
|
54
|
+
@shell ||= Thor::Base.shell.new
|
55
|
+
end
|
56
|
+
|
57
|
+
# Sets the shell for this thor class.
|
58
|
+
def shell=(shell)
|
59
|
+
@shell = shell
|
60
|
+
end
|
61
|
+
|
62
|
+
# Common methods that are delegated to the shell.
|
63
|
+
SHELL_DELEGATED_METHODS.each do |method|
|
64
|
+
module_eval <<-METHOD, __FILE__, __LINE__
|
65
|
+
def #{method}(*args)
|
66
|
+
shell.#{method}(*args)
|
67
|
+
end
|
68
|
+
METHOD
|
69
|
+
end
|
70
|
+
|
71
|
+
# Yields the given block with padding.
|
72
|
+
def with_padding
|
73
|
+
shell.padding += 1
|
74
|
+
yield
|
75
|
+
ensure
|
76
|
+
shell.padding -= 1
|
77
|
+
end
|
78
|
+
|
79
|
+
protected
|
80
|
+
|
81
|
+
# Allow shell to be shared between invocations.
|
82
|
+
#
|
83
|
+
def _shared_configuration #:nodoc:
|
84
|
+
super.merge!(:shell => self.shell)
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
end
|