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.
@@ -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