configurable 0.1.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,35 +1,63 @@
1
1
  require 'config_parser/utils'
2
2
 
3
- class ConfigParser
3
+ class ConfigParser
4
+
5
+ # Represents an option registered with ConfigParser.
4
6
  class Option
5
-
7
+
8
+ # A format string used by to_s
9
+ LINE_FORMAT = "%-36s %-43s"
10
+
11
+ # The short switch mapping to self
6
12
  attr_reader :short
13
+
14
+ # The long switch mapping to self
7
15
  attr_reader :long
16
+
17
+ # The argument name printed by to_s. If arg_name
18
+ # is nil, no value will be parsed for self.
8
19
  attr_reader :arg_name
9
- attr_reader :desc
20
+
21
+ # The description printed by to_s
22
+ attr_reader :desc
23
+
24
+ # The block called when one of the switches mapping
25
+ # to self is parse; block will receive the parsed
26
+ # argument if arg_name is specified.
10
27
  attr_reader :block
11
28
 
12
- def initialize(options={}, &block)
13
- @short = Utils.shortify(options[:short])
14
- @long = Utils.longify(options[:long])
15
- @arg_name = options[:arg_name]
16
- @desc = options[:desc]
29
+ # Initializes a new Option using attribute values for :long, :short,
30
+ # :arg_name, and :desc. The long and short values are transformed
31
+ # using Utils.longify and Utils.shortify, meaning both bare strings
32
+ # (ex 'opt', 'o') and full switches ('--opt', '-o') are valid.
33
+ def initialize(attributes={}, &block)
34
+ @short = Utils.shortify(attributes[:short])
35
+ @long = Utils.longify(attributes[:long])
36
+ @arg_name = attributes[:arg_name]
37
+ @desc = attributes[:desc]
17
38
  @block = block
18
39
  end
19
40
 
20
- # Returns an array of non-nil switches mapping to this option
21
- # (ie [long, short]). May be overridden in subclasses.
41
+ # Returns an array of non-nil switches mapping to self (ie [long, short]).
22
42
  def switches
23
43
  [long, short].compact
24
44
  end
25
-
26
- # Selects the value or the shifts a value off of argv and sets
27
- # that value in config.
45
+
46
+ # Parse determines how an option is actually parsed from an argv. Parse
47
+ # recieves the switch mapping to self for cases in which it affects the
48
+ # outcome (see Switch). By default parse has two modes of action:
49
+ #
50
+ # ==== Argument-style option (arg_name is specified)
51
+ #
52
+ # If arg_name is set, then parse passes value to the block. If no value
53
+ # is specified, the next argument in argv is used instead. An error
54
+ # is raised if no value can be found.
55
+ #
56
+ # ==== Flag-style option (no arg_name is specified)
57
+ #
58
+ # In this case, parse simply calls the block. If a non-nil value is
59
+ # specified, parse raises an error.
28
60
  #
29
- # Parse is a hook for fancier ways of determining an option
30
- # value and/or setting the value in config. Parse recieves
31
- # the switch (ie long or short) mapping to self for subclasses
32
- # that need it (ex the Switch class).
33
61
  def parse(switch, value, argv)
34
62
  if arg_name
35
63
  unless value
@@ -42,11 +70,32 @@ class ConfigParser
42
70
  block ? block.call : nil
43
71
  end
44
72
  end
45
-
73
+
74
+ # Formats self as a help string for use on the command line.
46
75
  def to_s
47
- short_str = short ? short + ',' : ' '
48
- desc_str = desc.kind_of?(Lazydoc::Comment) ? desc.trailer : desc
49
- "%-37s%-43s" % [" #{short_str} #{long} #{arg_name}", desc_str]
76
+ lines = Lazydoc::Utils.wrap(desc.to_s, 43)
77
+
78
+ header = " #{short_str} #{long_str} #{arg_name}"
79
+ header = header.length > 36 ? header.ljust(80) : (LINE_FORMAT % [header, lines.shift])
80
+
81
+ if lines.empty?
82
+ header
83
+ else
84
+ lines.collect! {|line| LINE_FORMAT % [nil, line] }
85
+ "#{header}\n#{lines.join("\n")}"
86
+ end
87
+ end
88
+
89
+ private
90
+
91
+ # helper returning short formatted for to_s
92
+ def short_str # :nodoc:
93
+ short ? short + ',' : ' '
94
+ end
95
+
96
+ # helper returning long formatted for to_s
97
+ def long_str # :nodoc:
98
+ long
50
99
  end
51
100
  end
52
101
  end
@@ -1,29 +1,44 @@
1
- class ConfigParser
1
+ class ConfigParser
2
+
3
+ # Switch represents a special type of Option where both a positive
4
+ # (--switch) and negative (--no-switch) version of long should
5
+ # map to self. A short may be specified for Switch; it will always
6
+ # be treated like the positive switch.
2
7
  class Switch < Option
8
+
9
+ # The negative long switch, determined from long.
3
10
  attr_reader :negative_long
4
-
11
+
12
+ # Initializes a new Switch. Raises an error if an arg_name is
13
+ # specified for self (as switches are intended to be boolean
14
+ # in nature), or if no long option is specified.
5
15
  def initialize(options={})
6
16
  super
7
17
  raise ArgumentError, "arg_name specified for switch: #{arg_name}" if arg_name
8
18
  raise ArgumentError, "no long specified" unless long
9
- @negative_long = Utils.longify("no-#{long[2,long.length-2]}")
19
+ @negative_long = Utils.prefix_long(long, 'no-')
10
20
  end
11
-
21
+
22
+ # Returns an array of non-nil switches mapping to self (ie
23
+ # [long, negative_long, short]).
12
24
  def switches
13
25
  [long, negative_long, short].compact
14
26
  end
15
-
27
+
28
+ # Calls the block with false if the negative long is specified,
29
+ # or calls the block with true in all other cases. Raises an
30
+ # error if a value is specified.
16
31
  def parse(switch, value, argv)
17
32
  raise "value specified for switch" if value
18
33
  value = (switch == negative_long ? false : true)
19
34
  block ? block.call(value) : value
20
35
  end
21
36
 
22
- def to_s
23
- short_str = short ? short + ',' : ' '
24
- long_str = long ? "--[no-]#{long[2,long.length-2]}" : ''
25
- desc_str = desc.kind_of?(Lazydoc::Comment) ? desc.trailer : desc
26
- "%-37s%-43s" % [" #{short_str} #{long_str}", desc_str]
37
+ private
38
+
39
+ # helper returning long formatted for to_s
40
+ def long_str # :nodoc:
41
+ long ? Utils.prefix_long(long, '[no-]') : ''
27
42
  end
28
43
  end
29
44
  end
@@ -1,4 +1,6 @@
1
1
  class ConfigParser
2
+
3
+ # A medly of methods used throughout the ConfigParser classes.
2
4
  module Utils
3
5
  module_function
4
6
 
@@ -34,8 +36,8 @@ class ConfigParser
34
36
  # an error if the option does not match SHORT_OPTION. Nils
35
37
  # are returned directly.
36
38
  #
37
- # ConfigParser.shortify("-o") # => '-o'
38
- # ConfigParser.shortify(:o) # => '-o'
39
+ # shortify("-o") # => '-o'
40
+ # shortify(:o) # => '-o'
39
41
  #
40
42
  def shortify(str)
41
43
  return nil if str == nil
@@ -52,9 +54,9 @@ class ConfigParser
52
54
  # are converted to hyphens. Raises an error if the option does
53
55
  # not match LONG_OPTION. Nils are returned directly.
54
56
  #
55
- # ConfigParser.longify("--opt") # => '--opt'
56
- # ConfigParser.longify(:opt) # => '--opt'
57
- # ConfigParser.longify(:opt_ion) # => '--opt-ion'
57
+ # longify("--opt") # => '--opt'
58
+ # longify(:opt) # => '--opt'
59
+ # longify(:opt_ion) # => '--opt-ion'
58
60
  #
59
61
  def longify(str)
60
62
  return nil if str == nil
@@ -68,58 +70,70 @@ class ConfigParser
68
70
  str
69
71
  end
70
72
 
71
- # Options:
73
+ # Adds a prefix onto the last nested segment of a long option.
74
+ #
75
+ # prefix_long("--opt", 'no-') # => '--no-opt'
76
+ # prefix_long("--nested:opt", 'no-') # => '--nested:no-opt'
77
+ #
78
+ def prefix_long(switch, prefix, split_char=':')
79
+ switch = switch[2,switch.length-2] if switch =~ /^--/
80
+ switch = switch.split(split_char)
81
+ switch[-1] = "#{prefix}#{switch[-1]}"
82
+ "--#{switch.join(':')}"
83
+ end
84
+
85
+ # Attributes:
72
86
  #
73
87
  # :long the long key ("--key")
74
88
  # :arg_name the argument name ("KEY")
75
89
  #
76
- def setup_option(key, options={})
77
- options[:long] ||= "--#{key}"
78
- options[:long].to_s =~ /^(--)?(.*)$/
79
- options[:arg_name] ||= $2.upcase
90
+ def setup_option(key, attributes={})
91
+ attributes[:long] ||= "--#{key}"
92
+ attributes[:long].to_s =~ /^(--)?(.*)$/
93
+ attributes[:arg_name] ||= $2.upcase
80
94
 
81
95
  lambda {|value| config[key] = value }
82
96
  end
83
97
 
84
- # Options:
98
+ # Attributes:
85
99
  #
86
100
  # :long the long key ("--key")
87
101
  #
88
- def setup_flag(key, default=true, options={})
89
- options[:long] ||= "--#{key}"
102
+ def setup_flag(key, default=true, attributes={})
103
+ attributes[:long] ||= "--#{key}"
90
104
 
91
105
  lambda {config[key] = !default }
92
106
  end
93
107
 
94
- # Options:
108
+ # Attributes:
95
109
  #
96
110
  # :long the long key ("--[no-]key")
97
111
  #
98
- def setup_switch(key, default=true, options={})
99
- options[:long] ||= "--#{key}"
100
- options[:long].to_s =~ /^(--)?(\[no-\])?(.*)$/
101
- options[:long] = "--[no-]#{$3}" unless $2
112
+ def setup_switch(key, default=true, attributes={})
113
+ attributes[:long] ||= "--#{key}"
114
+ attributes[:long].to_s =~ /^(--)?(\[no-\])?(.*)$/
115
+ attributes[:long] = "--[no-]#{$3}" unless $2
102
116
 
103
- lambda {|value| config[key] = (value ? !default : default) }
117
+ lambda {|value| config[key] = value }
104
118
  end
105
119
 
106
- # Options:
120
+ # Attributes:
107
121
  #
108
122
  # :long the long key ("--key")
109
123
  # :arg_name the argument name ("KEY" or "A,B,C" for a comma split)
110
124
  # :split the split character
111
125
  #
112
- def setup_list(key, options={})
113
- options[:long] ||= "--#{key}"
126
+ def setup_list(key, attributes={})
127
+ attributes[:long] ||= "--#{key}"
114
128
 
115
- if split = options[:split]
116
- options[:arg_name] ||= %w{A B C}.join(split)
129
+ if split = attributes[:split]
130
+ attributes[:arg_name] ||= %w{A B C}.join(split)
117
131
  else
118
- options[:long].to_s =~ /^(--)?(.*)$/
119
- options[:arg_name] ||= $2.upcase
132
+ attributes[:long].to_s =~ /^(--)?(.*)$/
133
+ attributes[:arg_name] ||= $2.upcase
120
134
  end
121
135
 
122
- n = options[:n]
136
+ n = attributes[:n]
123
137
 
124
138
  lambda do |value|
125
139
  array = (config[key] ||= [])
data/lib/configurable.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require 'configurable/class_methods'
2
2
 
3
- # Configurable enables the specification of configurations within a class definition.
3
+ # Configurable enables the specification of configurations within a class
4
+ # definition.
4
5
  #
5
6
  # class ConfigClass
6
7
  # include Configurable
@@ -9,17 +10,15 @@ require 'configurable/class_methods'
9
10
  # config :two, 'two'
10
11
  # config :three, 'three'
11
12
  #
12
- # def initialize(overrides={})
13
- # initialize_config(overrides)
14
- # end
15
13
  # end
16
14
  #
17
15
  # c = ConfigClass.new
18
16
  # c.config.class # => Configurable::DelegateHash
19
17
  # c.config # => {:one => 'one', :two => 'two', :three => 'three'}
20
18
  #
21
- # The <tt>config</tt> object acts as a forwarding hash; declared configurations
22
- # map to accessors while undeclared configurations are stored internally:
19
+ # Instances have a <tt>config</tt> object that acts like a forwarding hash;
20
+ # configuration keys delegate to accessors while undeclared key-value pairs
21
+ # are stored internally:
23
22
  #
24
23
  # c.config[:one] = 'ONE'
25
24
  # c.one # => 'ONE'
@@ -32,7 +31,7 @@ require 'configurable/class_methods'
32
31
  #
33
32
  # The writer for a configuration can be defined by providing a block to config.
34
33
  # The Validation module provides a number of common validation/transform
35
- # blocks which can be accessed through the class method 'c':
34
+ # blocks accessible through the class method 'c':
36
35
  #
37
36
  # class SubClass < ConfigClass
38
37
  # config(:one, 'one') {|v| v.upcase }
@@ -52,12 +51,39 @@ require 'configurable/class_methods'
52
51
  # s.two = nil # !> ValidationError
53
52
  # s.two = 'str' # !> ValidationError
54
53
  #
55
- # Configurations are inherited from the parent and may be overridden in
56
- # subclasses.
54
+ # Note that config blocks are defined in class-context and will have access
55
+ # to variables outside the block (as you would expect). For instance, these
56
+ # are analagous declarations:
57
+ #
58
+ # class ClassConfig
59
+ # config :key, 'value' do |input|
60
+ # input.upcase
61
+ # end
62
+ # end
63
+ #
64
+ # class AnalagousClass
65
+ # block = lambda {|input| input.upcase}
66
+ #
67
+ # define_method(:key=) do |input|
68
+ # @key = block.call(input)
69
+ # end
70
+ # end
71
+ #
72
+ # To have the block literally define the writer, use the config_attr method.
73
+ # Blocks provided to config_attr will have instance context and must set
74
+ # the instance variable themselves.
75
+ #
76
+ # class ConfigClass
77
+ # config_attr :key, 'value' do |input|
78
+ # @key = input.upcase
79
+ # end
80
+ # end
81
+ #
82
+ # Configurations are inherited and may be overridden in subclasses.
57
83
  #
58
- # === Options
84
+ # === Attributes
59
85
  #
60
- # Alternative reader and writer methods may be specified as options to config.
86
+ # Alternative reader and writer methods may be specified as config attributes.
61
87
  # When alternate methods are specified, Configurable assumes the methods are
62
88
  # declared elsewhere and will not define accessors.
63
89
  #
@@ -66,10 +92,6 @@ require 'configurable/class_methods'
66
92
  #
67
93
  # config_attr :sym, 'value', :reader => :get_sym, :writer => :set_sym
68
94
  #
69
- # def initialize
70
- # initialize_config
71
- # end
72
- #
73
95
  # def get_sym
74
96
  # @sym
75
97
  # end
@@ -89,8 +111,8 @@ require 'configurable/class_methods'
89
111
  # alt.set_sym('two')
90
112
  # alt.config[:sym] # => :two
91
113
  #
92
- # Idiosyncratically, true, false, and nil may also be provided as
93
- # reader/writer options.
114
+ # Idiosyncratically, true, false, and nil may also be provided as reader/writer
115
+ # options.
94
116
  #
95
117
  # true Same as using the defaults, accessors are defined.
96
118
  #
@@ -102,25 +124,39 @@ require 'configurable/class_methods'
102
124
  # that does not map to the instance, but will be
103
125
  # present in instance.config
104
126
  #
127
+ # ==== Non-reader/writer attributes
128
+ #
129
+ # Metadata for a config may be specified in attributes as well. Attributes like
130
+ # :desc, and :type are used by ConfigParser, for instance, to determine how to
131
+ # represent the configuration on the command line. Attributes are unstructured
132
+ # so they can accomodate metadata for multiple contexts (ex a web or desktop
133
+ # interface), as needed.
134
+ #
105
135
  module Configurable
106
-
136
+ autoload(:Utils, 'configurable/utils')
137
+
107
138
  # Extends including classes with Configurable::ClassMethods
108
139
  def self.included(mod) # :nodoc:
109
140
  mod.extend ClassMethods if mod.kind_of?(Class)
110
141
  end
111
142
 
112
- # A ConfigHash bound to self
143
+ # A DelegateHash bound to self
113
144
  attr_reader :config
145
+
146
+ # Initializes config, if necessary, and then calls super. If initialize
147
+ # is overridden without calling super, be sure to call initialize_config
148
+ # manually within the new initialize method.
149
+ def initialize(*args)
150
+ initialize_config unless instance_variable_defined?(:@config)
151
+ super
152
+ end
114
153
 
115
- # Reconfigures self with the given overrides. Only the specified configs
116
- # are modified. Keys are symbolized.
154
+ # Reconfigures self with the given overrides. Only the
155
+ # specified configs are modified.
117
156
  #
118
157
  # Returns self.
119
158
  def reconfigure(overrides={})
120
- overrides.each_pair do |key, value|
121
- config[key] = value
122
- end
123
-
159
+ config.merge!(overrides)
124
160
  self
125
161
  end
126
162
 
@@ -129,7 +165,7 @@ module Configurable
129
165
  # separate from the original object.
130
166
  def initialize_copy(orig)
131
167
  super
132
- initialize_config(orig.config)
168
+ initialize_config(orig.config.dup)
133
169
  end
134
170
 
135
171
  protected
@@ -137,19 +173,7 @@ module Configurable
137
173
  # Initializes config. Default config values
138
174
  # are overridden as specified by overrides.
139
175
  def initialize_config(overrides={})
140
- delegates = self.class.configurations
141
-
142
- # note the defaults could be stored first and overridden
143
- # by the overrides, but this is likely more efficient
144
- # on average since delegates duplicate default values.
145
- store = {}
146
- overrides.each_pair do |key, value|
147
- store[key] = value
148
- end
149
- delegates.each_pair do |key, delegate|
150
- store[key] = delegate.default unless store.has_key?(key)
151
- end
152
-
153
- @config = DelegateHash.new(delegates, store).bind(self)
176
+ @config = overrides.kind_of?(DelegateHash) ? overrides : DelegateHash.new(self.class.configurations, overrides)
177
+ @config.bind(self)
154
178
  end
155
179
  end