clive 0.6.2 → 0.7.0

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