clive 0.6.2 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,4 +1,4 @@
1
- class Clive
1
+ module Clive
2
2
 
3
3
  # A switch that takes one or more arguments.
4
4
  # eg. wget --tries=10
@@ -10,68 +10,58 @@ class Clive
10
10
  # Creates a new Flag instance. A flag is a switch that can take one or more
11
11
  # arguments.
12
12
  #
13
- # +short+ *or* +long+ can be omitted but not both.
14
- # +args+ can also be omitted (is "ARG" by default)
13
+ # @param names [Array[Symbol]]
14
+ # An array of names the flag can be invoked by. Can contain a long name
15
+ # and/or a short name.
15
16
  #
16
- # @overload flag(short, long, args, desc, &block)
17
- # @param [Symbol] short single character for short flag, eg. +:t+ => +-t 10+
18
- # @param [Symbol] long longer switch to be used, eg. +:tries+ => +--tries=10+
19
- # @param [String, Array] args
20
- # either a string showing the arguments to be given, eg.
21
- #
22
- # "FROM" # single arg, or
23
- # "FROM TO" # two args, or
24
- # "[VIA]" # optional arg surrounded by square brackets
17
+ # @param desc [String]
18
+ # A description of the flag.
25
19
  #
26
- # or an array of acceptable inputs, eg.
20
+ # @param args [String, Array, Range]
21
+ # Either, a string showing the arguments to be given, eg.
22
+ #
23
+ # "FROM" # single argument required, or
24
+ # "[FROM]" # single optional argument, or
25
+ # "FROM TO" # multiple arguments required, or
26
+ # "FROM [VIA] TO" # multiple arguments with optional argument
27
27
  #
28
- # ["large", "medium", "small"] # will only accept these args
28
+ # OR an array of acceptable inputs, eg.
29
29
  #
30
- # @param [String] desc the description for the flag
30
+ # %w(large small medium) # will only accept these arguments
31
+ #
32
+ # OR a range, showing the acceptable inputs, eg.
33
+ # 1..10 #=> means 1, 2, 3, ..., 8, 9, 10
31
34
  #
32
- # @yield [String] A block to be run if switch is triggered
35
+ # @yield [String]
36
+ # A block to be run if switch is triggered, will always be passed a string
33
37
  #
34
- def initialize(*args, &block)
35
- @names = []
36
- @args = []
38
+ def initialize(names, desc, args, &block)
39
+ @names = Clive::Array.new(names.map(&:to_s))
40
+ @args = Clive::Array.new
37
41
 
38
42
  # Need to be able to make each arg_name optional or not
39
43
  # and allow for type in future
40
44
  args.each do |i|
41
- if i.is_a? String
42
- if i =~ /\A(([A-Z0-9\[\]]+)\s?)+\Z/
43
- i.split(' ').each do |arg|
44
- type = String
45
- if arg[0] == "["
46
- @args << {
47
- :name => arg[1..arg.length-2],
48
- :optional => true,
49
- :type => type
50
- }
51
- else
52
- @args << {
53
- :name => arg,
54
- :optional => false,
55
- :type => type
56
- }
57
- end
45
+ case i
46
+ when String
47
+ i.split(' ').each do |arg|
48
+ optional = false
49
+ if arg[0] == "["
50
+ optional = true
51
+ arg = arg[1..-2]
58
52
  end
59
- else
60
- @desc = i
53
+ @args << {:name => arg, :optional => optional}
61
54
  end
62
55
  else
63
- if i.class == Symbol
64
- @names << i.to_s
65
- else
66
- @args = i
67
- end
56
+ @args = i
68
57
  end
69
58
  end
70
-
71
- if @args == []
72
- @args = [{:name => "ARG", :optional => false, :type => String}]
73
- end
74
59
 
60
+ if @args.empty?
61
+ @args = [{:name => "ARG", :optional => false}]
62
+ end
63
+
64
+ @desc = desc
75
65
  @block = block
76
66
  end
77
67
 
@@ -81,10 +71,10 @@ class Clive
81
71
  # @raise [InvalidArgument] only if +args+ is an array of acceptable inputs
82
72
  # and a match is not found.
83
73
  def run(args)
84
- if @args[0].is_a? Hash
74
+ if @args.is_a?(Array) && @args[0].is_a?(Hash)
85
75
  args = Clive::Array.new(@args.collect {|i| !i[:optional]}).optimise_fill(args)
86
76
  else # list
87
- unless @args.include? args[0]
77
+ unless @args.to_a.map(&:to_s).include? args[0]
88
78
  raise InvalidArgument.new(args)
89
79
  end
90
80
  end
@@ -94,33 +84,56 @@ class Clive
94
84
  # @param [Boolean] optional whether to include optional arguments
95
85
  # @return [Integer] number of arguments this takes
96
86
  def arg_num(optional)
97
- if @args[0].is_a? Hash
87
+ if @args.is_a?(Array) && @args[0].is_a?(Hash)
98
88
  @args.find_all {|i| i[:optional] == optional }.size
99
89
  else
100
90
  1
101
91
  end
102
92
  end
103
93
 
104
- # @return [String] summary for help
105
- def summary(width=30, prepend=5)
106
- n = names_to_strings.join(', ')
107
- a = nil
108
- if @args[0].is_a? Hash
109
- a = @args.map {|i| i[:name]}.join(' ')
110
- if @optional
111
- n << " [#{a}]"
112
- else
113
- n << " #{a}"
94
+ def args_to_strings
95
+ if @args.is_a? Range
96
+ [""]
97
+ elsif @args[0].is_a? Hash
98
+ r = []
99
+ @args.each do |arg|
100
+ if arg[:optional]
101
+ r << "[" + arg[:name] + "]"
102
+ else
103
+ r << arg[:name]
104
+ end
114
105
  end
106
+ r
115
107
  else
116
- n << " {" << @args.join(', ') << "}"
108
+ [""]
117
109
  end
118
-
119
- spaces = width-n.length
120
- spaces = 1 if spaces < 1
121
- s = spaces(spaces)
122
- p = spaces(prepend)
123
- "#{p}#{n}#{s}#{@desc}"
110
+ end
111
+
112
+ def arg_size
113
+ if @args.is_a?(Range) || @args.is_a?(Array)
114
+ 1
115
+ else
116
+ @args.size
117
+ end
118
+ end
119
+
120
+ def options_to_strings
121
+ if @args.is_a? Range
122
+ [@args.to_s]
123
+ elsif @args[0].is_a? Hash
124
+ ['']
125
+ else
126
+ @args
127
+ end
128
+ end
129
+
130
+ def to_h
131
+ {
132
+ 'names' => Clive::Array.new(names_to_strings),
133
+ 'desc' => @desc,
134
+ 'args' => Clive::Array.new(args_to_strings),
135
+ 'options' => Clive::Array.new(options_to_strings)
136
+ }
124
137
  end
125
138
 
126
139
  end
@@ -0,0 +1,180 @@
1
+ require 'strscan'
2
+
3
+ module Clive
4
+
5
+ # The formatter controls formatting of help. It can be configured
6
+ # using Clive::Command#help_formatter.
7
+ class Formatter
8
+
9
+ class Obj
10
+ def initialize(args)
11
+ args.each do |k, v|
12
+ self.class.send(:define_method, k) { v }
13
+ end
14
+ end
15
+
16
+ # Evaluate the code given within the Obj created.
17
+ def evaluate(code)
18
+ eval(code)
19
+ end
20
+ end
21
+
22
+ # Sizes
23
+ attr_accessor :width, :prepend
24
+
25
+ def initialize(width, prepend, &block)
26
+ @width = width
27
+ @prepend = prepend
28
+ end
29
+
30
+
31
+ def format(header, footer, commands, options)
32
+ result = ""
33
+
34
+ switches = options.find_all {|i| i.class == Clive::Switch }.map(&:to_h)
35
+ bools = options.find_all {|i| i.class == Clive::Bool }.map(&:to_h).compact
36
+ flags = options.find_all {|i| i.class == Clive::Flag }.map(&:to_h)
37
+ commands = commands.map(&:to_h)
38
+
39
+ result << header << "\n" if header
40
+
41
+ unless commands.empty?
42
+ result << "\n Commands: \n"
43
+
44
+ commands.each do |hash|
45
+ hash['prepend'] = " " * @prepend
46
+ result << parse(@command, hash) << "\n"
47
+ end
48
+ end
49
+
50
+
51
+ unless options.empty?
52
+ result << "\n Options: \n"
53
+
54
+ switches.each do |hash|
55
+ hash['prepend'] = " " * @prepend
56
+ result << parse(@switch, hash) << "\n"
57
+ end
58
+
59
+ bools.each do |hash|
60
+ hash['prepend'] = " " * @prepend
61
+ result << parse(@bool, hash) << "\n"
62
+ end
63
+
64
+ flags.each do |hash|
65
+ hash['prepend'] = " " * @prepend
66
+ result << parse(@flag, hash) << "\n"
67
+ end
68
+ end
69
+
70
+ result << "\n" << footer << "\n" if footer
71
+
72
+ result
73
+ end
74
+
75
+
76
+ def switch(format)
77
+ @switch = format
78
+ end
79
+
80
+ def bool(format)
81
+ @bool = format
82
+ end
83
+
84
+ def flag(format)
85
+ @flag = format
86
+ end
87
+
88
+ def command(format)
89
+ @command = format
90
+ end
91
+
92
+ def help(format)
93
+ @help = format
94
+ end
95
+
96
+ def summary(format)
97
+ @summary = format
98
+ end
99
+
100
+
101
+ def parse(format, args)
102
+ front, back = format.split('{spaces}')
103
+
104
+ front_p = parse_format(front, args)
105
+ back_p = parse_format(back, args)
106
+
107
+ s = @width - front_p.length
108
+ s = 0 if s < 0 # can't have negative spaces!
109
+ spaces = " " * s
110
+
111
+ front_p << spaces << back_p
112
+ end
113
+
114
+ def parse_format(format, args)
115
+ if format
116
+ @scanner = StringScanner.new(format)
117
+ result = []
118
+
119
+ # Create object to eval in
120
+ obj = Obj.new(args)
121
+
122
+ until @scanner.eos?
123
+ a = scan_block || a = scan_text
124
+ result << a
125
+ end
126
+
127
+ r = ""
128
+ result.each do |(t, v)|
129
+ case t
130
+ when :block # contains ruby to eval
131
+ r << obj.evaluate(v)
132
+ when :text # add this with no adjustment
133
+ r << v
134
+ end
135
+ end
136
+ r
137
+ else
138
+ ""
139
+ end
140
+ end
141
+
142
+
143
+ # @group Scanning
144
+ def scan_block
145
+ return unless @scanner.scan /\{/
146
+
147
+ pos = @scanner.pos
148
+ if @scanner.scan_until /\}/
149
+ @scanner.pos -= @scanner.matched.size
150
+ [:block, @scanner.pre_match[pos..-1]]
151
+ end
152
+ end
153
+
154
+ def scan_text
155
+ text = nil
156
+
157
+ pos = @scanner.pos
158
+ if @scanner.scan_until /(?<=[^\\])\{/
159
+ @scanner.pos -= @scanner.matched.size
160
+ text = @scanner.pre_match[pos..-1]
161
+ end
162
+
163
+ if text.nil?
164
+ text = @scanner.rest
165
+ @scanner.clear
166
+ end
167
+
168
+ # Remove }s from text
169
+ if text[0] == "}"
170
+ text = text[1..-1]
171
+ end
172
+
173
+ text.gsub!(/\\(.)/) {|m| m[1] }
174
+
175
+ [:text, text]
176
+ end
177
+ # @endgroup
178
+
179
+ end
180
+ end
@@ -1,32 +1,44 @@
1
- class Clive
1
+ module Clive
2
2
 
3
- # @abstract Subclass and override {#initialize} and {#run} to create a new Option class.
3
+ # @abstract Subclass and override {#initialize} and {#run} to create a
4
+ # new Option class. {#to_h} can also be overriden to provide information
5
+ # when building the help.
6
+ #
7
+ # Option is the base class for switches, flags, commands, etc. It should be
8
+ # used as a template for the way options (or whatever) are initialized, and
9
+ # the other methods that may need implementing.
10
+ #
4
11
  class Option
5
12
  attr_accessor :names, :desc, :block
6
13
 
7
- def initialize(*args, &block)
8
- # assign name and description
9
- # @block = block
14
+ # Create a new Option instance.
15
+ #
16
+ # For subclasses the method call should take the form:
17
+ # +initialize(names, desc, [special args], &block)+.
18
+ #
19
+ # @param names [Array[Symbol]]
20
+ # An array of names the option can be invoked by.
21
+ #
22
+ # @param desc [String]
23
+ # A description of what the option does.
24
+ #
25
+ # @yield A block to run if the switch is triggered
26
+ #
27
+ def initialize(names, desc, &block)
28
+ @names = names.map(&:to_s)
29
+ @desc = desc
30
+ @block = block
10
31
  end
11
32
 
33
+ # Calls the block.
12
34
  def run
13
- # call the block!
14
- # @block.call
15
- end
16
-
17
- def summary(width=30, prepend=5)
18
- n = names_to_strings.join(', ')
19
- spaces = width-n.length
20
- spaces = 1 if spaces < 1
21
- s = spaces(spaces)
22
- p = spaces(prepend)
23
- "#{p}#{n}#{s}#{@desc}"
35
+ @block.call
24
36
  end
25
37
 
26
38
  # Convert the names to strings, if name is single character appends
27
39
  # +-+, else appends +--+.
28
40
  #
29
- # @param [Boolean] bool whether to add [no-] to long
41
+ # @param bool [Boolean] whether to add [no-] to long
30
42
  #
31
43
  # @example
32
44
  #
@@ -51,16 +63,10 @@ class Clive
51
63
  r
52
64
  end
53
65
 
54
- # Create a string of +n+ spaces
55
- def spaces(n)
56
- s = ''
57
- (0...n).each {s << ' '}
58
- s
59
- end
60
-
61
- # Tries to get the short name, if not choose lowest alphabetically
66
+ # Tries to get the short name, if not chooses lowest alphabetically.
62
67
  #
63
68
  # @return [String] name to sort by
69
+ #
64
70
  def sort_name
65
71
  r = @names.sort[0]
66
72
  @names.each do |i|
@@ -75,6 +81,16 @@ class Clive
75
81
  def <=>(other)
76
82
  self.sort_name <=> other.sort_name
77
83
  end
84
+
85
+ # @return [Hash{String=>Object}]
86
+ # Returns a hash which can be passed to the help formatter.
87
+ #
88
+ def to_h
89
+ {
90
+ "names" => names_to_strings,
91
+ "desc" => @desc
92
+ }
93
+ end
78
94
 
79
95
  end
80
96
  end