ruby-configurable 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.autotest ADDED
@@ -0,0 +1,23 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'autotest/restart'
4
+
5
+ # Autotest.add_hook :initialize do |at|
6
+ # at.extra_files << "../some/external/dependency.rb"
7
+ #
8
+ # at.libs << ":../some/external"
9
+ #
10
+ # at.add_exception 'vendor'
11
+ #
12
+ # at.add_mapping(/dependency.rb/) do |f, _|
13
+ # at.files_matching(/test_.*rb$/)
14
+ # end
15
+ #
16
+ # %w(TestA TestB).each do |klass|
17
+ # at.extra_class_map[klass] = "test/test_misc.rb"
18
+ # end
19
+ # end
20
+
21
+ # Autotest.add_hook :run_command do |at|
22
+ # system "rake build"
23
+ # end
data/COPYING.txt ADDED
@@ -0,0 +1,339 @@
1
+ GNU GENERAL PUBLIC LICENSE
2
+ Version 2, June 1991
3
+
4
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
5
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
6
+ Everyone is permitted to copy and distribute verbatim copies
7
+ of this license document, but changing it is not allowed.
8
+
9
+ Preamble
10
+
11
+ The licenses for most software are designed to take away your
12
+ freedom to share and change it. By contrast, the GNU General Public
13
+ License is intended to guarantee your freedom to share and change free
14
+ software--to make sure the software is free for all its users. This
15
+ General Public License applies to most of the Free Software
16
+ Foundation's software and to any other program whose authors commit to
17
+ using it. (Some other Free Software Foundation software is covered by
18
+ the GNU Lesser General Public License instead.) You can apply it to
19
+ your programs, too.
20
+
21
+ When we speak of free software, we are referring to freedom, not
22
+ price. Our General Public Licenses are designed to make sure that you
23
+ have the freedom to distribute copies of free software (and charge for
24
+ this service if you wish), that you receive source code or can get it
25
+ if you want it, that you can change the software or use pieces of it
26
+ in new free programs; and that you know you can do these things.
27
+
28
+ To protect your rights, we need to make restrictions that forbid
29
+ anyone to deny you these rights or to ask you to surrender the rights.
30
+ These restrictions translate to certain responsibilities for you if you
31
+ distribute copies of the software, or if you modify it.
32
+
33
+ For example, if you distribute copies of such a program, whether
34
+ gratis or for a fee, you must give the recipients all the rights that
35
+ you have. You must make sure that they, too, receive or can get the
36
+ source code. And you must show them these terms so they know their
37
+ rights.
38
+
39
+ We protect your rights with two steps: (1) copyright the software, and
40
+ (2) offer you this license which gives you legal permission to copy,
41
+ distribute and/or modify the software.
42
+
43
+ Also, for each author's protection and ours, we want to make certain
44
+ that everyone understands that there is no warranty for this free
45
+ software. If the software is modified by someone else and passed on, we
46
+ want its recipients to know that what they have is not the original, so
47
+ that any problems introduced by others will not reflect on the original
48
+ authors' reputations.
49
+
50
+ Finally, any free program is threatened constantly by software
51
+ patents. We wish to avoid the danger that redistributors of a free
52
+ program will individually obtain patent licenses, in effect making the
53
+ program proprietary. To prevent this, we have made it clear that any
54
+ patent must be licensed for everyone's free use or not licensed at all.
55
+
56
+ The precise terms and conditions for copying, distribution and
57
+ modification follow.
58
+
59
+ GNU GENERAL PUBLIC LICENSE
60
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61
+
62
+ 0. This License applies to any program or other work which contains
63
+ a notice placed by the copyright holder saying it may be distributed
64
+ under the terms of this General Public License. The "Program", below,
65
+ refers to any such program or work, and a "work based on the Program"
66
+ means either the Program or any derivative work under copyright law:
67
+ that is to say, a work containing the Program or a portion of it,
68
+ either verbatim or with modifications and/or translated into another
69
+ language. (Hereinafter, translation is included without limitation in
70
+ the term "modification".) Each licensee is addressed as "you".
71
+
72
+ Activities other than copying, distribution and modification are not
73
+ covered by this License; they are outside its scope. The act of
74
+ running the Program is not restricted, and the output from the Program
75
+ is covered only if its contents constitute a work based on the
76
+ Program (independent of having been made by running the Program).
77
+ Whether that is true depends on what the Program does.
78
+
79
+ 1. You may copy and distribute verbatim copies of the Program's
80
+ source code as you receive it, in any medium, provided that you
81
+ conspicuously and appropriately publish on each copy an appropriate
82
+ copyright notice and disclaimer of warranty; keep intact all the
83
+ notices that refer to this License and to the absence of any warranty;
84
+ and give any other recipients of the Program a copy of this License
85
+ along with the Program.
86
+
87
+ You may charge a fee for the physical act of transferring a copy, and
88
+ you may at your option offer warranty protection in exchange for a fee.
89
+
90
+ 2. You may modify your copy or copies of the Program or any portion
91
+ of it, thus forming a work based on the Program, and copy and
92
+ distribute such modifications or work under the terms of Section 1
93
+ above, provided that you also meet all of these conditions:
94
+
95
+ a) You must cause the modified files to carry prominent notices
96
+ stating that you changed the files and the date of any change.
97
+
98
+ b) You must cause any work that you distribute or publish, that in
99
+ whole or in part contains or is derived from the Program or any
100
+ part thereof, to be licensed as a whole at no charge to all third
101
+ parties under the terms of this License.
102
+
103
+ c) If the modified program normally reads commands interactively
104
+ when run, you must cause it, when started running for such
105
+ interactive use in the most ordinary way, to print or display an
106
+ announcement including an appropriate copyright notice and a
107
+ notice that there is no warranty (or else, saying that you provide
108
+ a warranty) and that users may redistribute the program under
109
+ these conditions, and telling the user how to view a copy of this
110
+ License. (Exception: if the Program itself is interactive but
111
+ does not normally print such an announcement, your work based on
112
+ the Program is not required to print an announcement.)
113
+
114
+ These requirements apply to the modified work as a whole. If
115
+ identifiable sections of that work are not derived from the Program,
116
+ and can be reasonably considered independent and separate works in
117
+ themselves, then this License, and its terms, do not apply to those
118
+ sections when you distribute them as separate works. But when you
119
+ distribute the same sections as part of a whole which is a work based
120
+ on the Program, the distribution of the whole must be on the terms of
121
+ this License, whose permissions for other licensees extend to the
122
+ entire whole, and thus to each and every part regardless of who wrote it.
123
+
124
+ Thus, it is not the intent of this section to claim rights or contest
125
+ your rights to work written entirely by you; rather, the intent is to
126
+ exercise the right to control the distribution of derivative or
127
+ collective works based on the Program.
128
+
129
+ In addition, mere aggregation of another work not based on the Program
130
+ with the Program (or with a work based on the Program) on a volume of
131
+ a storage or distribution medium does not bring the other work under
132
+ the scope of this License.
133
+
134
+ 3. You may copy and distribute the Program (or a work based on it,
135
+ under Section 2) in object code or executable form under the terms of
136
+ Sections 1 and 2 above provided that you also do one of the following:
137
+
138
+ a) Accompany it with the complete corresponding machine-readable
139
+ source code, which must be distributed under the terms of Sections
140
+ 1 and 2 above on a medium customarily used for software interchange; or,
141
+
142
+ b) Accompany it with a written offer, valid for at least three
143
+ years, to give any third party, for a charge no more than your
144
+ cost of physically performing source distribution, a complete
145
+ machine-readable copy of the corresponding source code, to be
146
+ distributed under the terms of Sections 1 and 2 above on a medium
147
+ customarily used for software interchange; or,
148
+
149
+ c) Accompany it with the information you received as to the offer
150
+ to distribute corresponding source code. (This alternative is
151
+ allowed only for noncommercial distribution and only if you
152
+ received the program in object code or executable form with such
153
+ an offer, in accord with Subsection b above.)
154
+
155
+ The source code for a work means the preferred form of the work for
156
+ making modifications to it. For an executable work, complete source
157
+ code means all the source code for all modules it contains, plus any
158
+ associated interface definition files, plus the scripts used to
159
+ control compilation and installation of the executable. However, as a
160
+ special exception, the source code distributed need not include
161
+ anything that is normally distributed (in either source or binary
162
+ form) with the major components (compiler, kernel, and so on) of the
163
+ operating system on which the executable runs, unless that component
164
+ itself accompanies the executable.
165
+
166
+ If distribution of executable or object code is made by offering
167
+ access to copy from a designated place, then offering equivalent
168
+ access to copy the source code from the same place counts as
169
+ distribution of the source code, even though third parties are not
170
+ compelled to copy the source along with the object code.
171
+
172
+ 4. You may not copy, modify, sublicense, or distribute the Program
173
+ except as expressly provided under this License. Any attempt
174
+ otherwise to copy, modify, sublicense or distribute the Program is
175
+ void, and will automatically terminate your rights under this License.
176
+ However, parties who have received copies, or rights, from you under
177
+ this License will not have their licenses terminated so long as such
178
+ parties remain in full compliance.
179
+
180
+ 5. You are not required to accept this License, since you have not
181
+ signed it. However, nothing else grants you permission to modify or
182
+ distribute the Program or its derivative works. These actions are
183
+ prohibited by law if you do not accept this License. Therefore, by
184
+ modifying or distributing the Program (or any work based on the
185
+ Program), you indicate your acceptance of this License to do so, and
186
+ all its terms and conditions for copying, distributing or modifying
187
+ the Program or works based on it.
188
+
189
+ 6. Each time you redistribute the Program (or any work based on the
190
+ Program), the recipient automatically receives a license from the
191
+ original licensor to copy, distribute or modify the Program subject to
192
+ these terms and conditions. You may not impose any further
193
+ restrictions on the recipients' exercise of the rights granted herein.
194
+ You are not responsible for enforcing compliance by third parties to
195
+ this License.
196
+
197
+ 7. If, as a consequence of a court judgment or allegation of patent
198
+ infringement or for any other reason (not limited to patent issues),
199
+ conditions are imposed on you (whether by court order, agreement or
200
+ otherwise) that contradict the conditions of this License, they do not
201
+ excuse you from the conditions of this License. If you cannot
202
+ distribute so as to satisfy simultaneously your obligations under this
203
+ License and any other pertinent obligations, then as a consequence you
204
+ may not distribute the Program at all. For example, if a patent
205
+ license would not permit royalty-free redistribution of the Program by
206
+ all those who receive copies directly or indirectly through you, then
207
+ the only way you could satisfy both it and this License would be to
208
+ refrain entirely from distribution of the Program.
209
+
210
+ If any portion of this section is held invalid or unenforceable under
211
+ any particular circumstance, the balance of the section is intended to
212
+ apply and the section as a whole is intended to apply in other
213
+ circumstances.
214
+
215
+ It is not the purpose of this section to induce you to infringe any
216
+ patents or other property right claims or to contest validity of any
217
+ such claims; this section has the sole purpose of protecting the
218
+ integrity of the free software distribution system, which is
219
+ implemented by public license practices. Many people have made
220
+ generous contributions to the wide range of software distributed
221
+ through that system in reliance on consistent application of that
222
+ system; it is up to the author/donor to decide if he or she is willing
223
+ to distribute software through any other system and a licensee cannot
224
+ impose that choice.
225
+
226
+ This section is intended to make thoroughly clear what is believed to
227
+ be a consequence of the rest of this License.
228
+
229
+ 8. If the distribution and/or use of the Program is restricted in
230
+ certain countries either by patents or by copyrighted interfaces, the
231
+ original copyright holder who places the Program under this License
232
+ may add an explicit geographical distribution limitation excluding
233
+ those countries, so that distribution is permitted only in or among
234
+ countries not thus excluded. In such case, this License incorporates
235
+ the limitation as if written in the body of this License.
236
+
237
+ 9. The Free Software Foundation may publish revised and/or new versions
238
+ of the General Public License from time to time. Such new versions will
239
+ be similar in spirit to the present version, but may differ in detail to
240
+ address new problems or concerns.
241
+
242
+ Each version is given a distinguishing version number. If the Program
243
+ specifies a version number of this License which applies to it and "any
244
+ later version", you have the option of following the terms and conditions
245
+ either of that version or of any later version published by the Free
246
+ Software Foundation. If the Program does not specify a version number of
247
+ this License, you may choose any version ever published by the Free Software
248
+ Foundation.
249
+
250
+ 10. If you wish to incorporate parts of the Program into other free
251
+ programs whose distribution conditions are different, write to the author
252
+ to ask for permission. For software which is copyrighted by the Free
253
+ Software Foundation, write to the Free Software Foundation; we sometimes
254
+ make exceptions for this. Our decision will be guided by the two goals
255
+ of preserving the free status of all derivatives of our free software and
256
+ of promoting the sharing and reuse of software generally.
257
+
258
+ NO WARRANTY
259
+
260
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261
+ FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
262
+ OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263
+ PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264
+ OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265
+ MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
266
+ TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
267
+ PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268
+ REPAIR OR CORRECTION.
269
+
270
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271
+ WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272
+ REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273
+ INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274
+ OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275
+ TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276
+ YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277
+ PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278
+ POSSIBILITY OF SUCH DAMAGES.
279
+
280
+ END OF TERMS AND CONDITIONS
281
+
282
+ How to Apply These Terms to Your New Programs
283
+
284
+ If you develop a new program, and you want it to be of the greatest
285
+ possible use to the public, the best way to achieve this is to make it
286
+ free software which everyone can redistribute and change under these terms.
287
+
288
+ To do so, attach the following notices to the program. It is safest
289
+ to attach them to the start of each source file to most effectively
290
+ convey the exclusion of warranty; and each file should have at least
291
+ the "copyright" line and a pointer to where the full notice is found.
292
+
293
+ <one line to give the program's name and a brief idea of what it does.>
294
+ Copyright (C) <year> <name of author>
295
+
296
+ This program is free software; you can redistribute it and/or modify
297
+ it under the terms of the GNU General Public License as published by
298
+ the Free Software Foundation; either version 2 of the License, or
299
+ (at your option) any later version.
300
+
301
+ This program is distributed in the hope that it will be useful,
302
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
303
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
304
+ GNU General Public License for more details.
305
+
306
+ You should have received a copy of the GNU General Public License along
307
+ with this program; if not, write to the Free Software Foundation, Inc.,
308
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
309
+
310
+ Also add information on how to contact you by electronic and paper mail.
311
+
312
+ If the program is interactive, make it output a short notice like this
313
+ when it starts in an interactive mode:
314
+
315
+ Gnomovision version 69, Copyright (C) year name of author
316
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
317
+ This is free software, and you are welcome to redistribute it
318
+ under certain conditions; type `show c' for details.
319
+
320
+ The hypothetical commands `show w' and `show c' should show the appropriate
321
+ parts of the General Public License. Of course, the commands you use may
322
+ be called something other than `show w' and `show c'; they could even be
323
+ mouse-clicks or menu items--whatever suits your program.
324
+
325
+ You should also get your employer (if you work as a programmer) or your
326
+ school, if any, to sign a "copyright disclaimer" for the program, if
327
+ necessary. Here is a sample; alter the names:
328
+
329
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
330
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
331
+
332
+ <signature of Ty Coon>, 1 April 1989
333
+ Ty Coon, President of Vice
334
+
335
+ This General Public License does not permit incorporating your program into
336
+ proprietary programs. If your program is a subroutine library, you may
337
+ consider it more useful to permit linking proprietary applications with the
338
+ library. If this is what you want to do, use the GNU Lesser General
339
+ Public License instead of this License.
data/History.txt ADDED
@@ -0,0 +1,6 @@
1
+ === 1.0.0 / 2011-05-03
2
+
3
+ * 1 major enhancement
4
+
5
+ * Birthday!
6
+
data/Manifest.txt ADDED
@@ -0,0 +1,12 @@
1
+ .autotest
2
+ COPYING.txt
3
+ History.txt
4
+ Manifest.txt
5
+ README.txt
6
+ Rakefile
7
+ lib/configurable.rb
8
+ lib/configurable/config_struct.rb
9
+ lib/configurable/core_ext/extract_options.rb
10
+ lib/configurable/core_ext/inflections.rb
11
+ test/test_config_struct.rb
12
+ test/test_configurable.rb
data/README.txt ADDED
@@ -0,0 +1,110 @@
1
+ = configurable
2
+
3
+ * http://bitbucket.org/krbullock/configurable
4
+
5
+ == DESCRIPTION:
6
+
7
+ Lets you make your Ruby class configurable with a simple mixin.
8
+
9
+ == FEATURES/PROBLEMS:
10
+
11
+ * Generic mixin that you can use in any Ruby class (or application).
12
+
13
+ * Separates handling configuration of a class from its implementation.
14
+
15
+ * Supports nested settings.
16
+
17
+ * Rejects configuration keys outside those you specify.
18
+
19
+ * Supports setting defaults on a class which can then be modified for
20
+ each instance of the class.
21
+
22
+ * Settings are stored in Structs (well, actually a subclass of Struct),
23
+ so they can be accessed via normal methods calls, or by Symbol or
24
+ String keys.
25
+
26
+ * Settings (even nested settings) can be serialized to a hash with
27
+ string keys, which means you can also trivially serialize to YAML (or
28
+ JSON).
29
+
30
+ * Settings (even nested settings) can be loaded from a hash, which means
31
+ you can also trivially deserialize from YAML (and remember, YAML is a
32
+ superset of JSON).
33
+
34
+ * Plays nicely with the Singleton module.
35
+
36
+ * Can safely store other kinds of structs as opaque config values. They
37
+ won't get automatically deep-copied.
38
+
39
+ * Doesn't yet support independent configs for classes and their subclasses.
40
+
41
+ == SYNOPSIS:
42
+
43
+ require 'configurable'
44
+ class Doodad
45
+ include Configurable
46
+
47
+ # Declare the allowed options and their default values
48
+ configurable_options :foo => 'default',
49
+ :bar => {:quux => 42, :wibble => nil},
50
+ :baz => nil
51
+ end
52
+
53
+ Doodad::Config # => a ConfigStruct::Struct
54
+ Doodad::Config::Bar # => another ConfigStruct::Struct
55
+
56
+ Doodad.default_config # => #<struct Doodad::Config
57
+ # bar=#<struct Doodad::Config::Bar
58
+ # quux=42, wibble=nil>,
59
+ # baz=nil,
60
+ # foo="default">
61
+
62
+ Doodad.config # => a (deep) copy of default_config,
63
+ # ready for use
64
+
65
+ Doodad.config.replace( # replaces config with passed values
66
+ :foo => 'mine',
67
+ :bar => {:quux => 9})
68
+
69
+ Doodad.config.replace( # loads config from a YAML file
70
+ YAML.load('config.yml'))
71
+
72
+ See the documentation on Configurable for an overview and links to more
73
+ specifics.
74
+
75
+ == REQUIREMENTS:
76
+
77
+ * Ruby
78
+
79
+ == INSTALL:
80
+
81
+ $ gem install configurable
82
+
83
+ == DEVELOPERS:
84
+
85
+ After checking out the source, run:
86
+
87
+ $ rake newb
88
+
89
+ This task will install any missing dependencies, run the tests/specs,
90
+ and generate the RDoc.
91
+
92
+ == LICENSE:
93
+
94
+ (GNU General Public License v2.0)
95
+
96
+ Copyright (C) 2010 Kevin R. Bullock
97
+
98
+ This program is free software; you can redistribute it and/or modify it
99
+ under the terms of the GNU General Public License as published by the
100
+ Free Software Foundation; either version 2 of the License, or (at your
101
+ option) any later version.
102
+
103
+ This program is distributed in the hope that it will be useful, but
104
+ WITHOUT ANY WARRANTY; without even the implied warranty of
105
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
106
+ Public License for more details.
107
+
108
+ You should have received a copy of the GNU General Public License along
109
+ with this program; if not, write to the Free Software Foundation, Inc.,
110
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'rubygems'
4
+ require 'hoe'
5
+
6
+ Hoe.spec 'ruby-configurable' do
7
+ developer('Kevin R. Bullock', 'kbullock@ringworld.org')
8
+
9
+ self.rubyforge_name = 'handtools'
10
+ end
11
+
12
+ # vim: syntax=ruby
@@ -0,0 +1,163 @@
1
+ require 'configurable/core_ext/extract_options'
2
+
3
+ # See documentation on ConfigStruct::Struct.
4
+ module ConfigStruct
5
+ module HashInitializer #:nodoc:
6
+ def initialize(*args)
7
+ hash_args = args.extract_options!
8
+ super(*args)
9
+ for k,v in hash_args
10
+ self[k] = v
11
+ end
12
+ end
13
+ end
14
+
15
+ # A beefed-up version of Ruby's built-in Struct (and in fact a subclass of
16
+ # it). An instance of a ConfigStruct::Struct is created just as you would
17
+ # create a normal struct:
18
+ #
19
+ # A = ConfigStruct::Struct.new(:a, :b)
20
+ #
21
+ # You can then create an instance of this struct using hash parameters or
22
+ # ordered parameters:
23
+ #
24
+ # a = A.new(:a => 1, :b => 2) # => #<struct A a=1, b=2>
25
+ #
26
+ # You can pass values in order simultaneously with hash parameters. Values
27
+ # passed as hash parameters override those passed as normal parameters:
28
+ #
29
+ # a = A.new(1, 2, :a => 3) # => #<struct A a=3, b=2>
30
+ #
31
+ # ConfigStruct::Structs also get deeply copied, that is, any member of a
32
+ # ConfigStruct::Struct instance that is also an instance of a
33
+ # ConfigStruct::Struct will get copied recursively:
34
+ #
35
+ # a = A.new(1, 2) # => #<struct A a=1, b=2>
36
+ # b = A.new(3, a) # => #<struct A a=3, b=#<struct A a=1, b=2>>
37
+ # c = b.dup
38
+ # b.object_id == c.object_id # => false
39
+ # b.b.object_id == c.b.object_id # => false
40
+ #
41
+ class Struct < ::Struct
42
+ def self.new(*args) #:nodoc:
43
+ klass = super
44
+ klass.send(:include, HashInitializer)
45
+ end
46
+
47
+ # Recursively copies members of the original struct that are also
48
+ # instances of ConfigStruct::Struct.
49
+ def initialize_copy(orig)
50
+ super
51
+ for member in members
52
+ self[member] = self[member].dup if self[member].is_a? Struct
53
+ end
54
+ end
55
+
56
+ # Replaces the current values with the given values. Values can be given
57
+ # in order:
58
+ #
59
+ # A = ConfigStruct::Struct.new(:a, :b)
60
+ # a = A.new(1, 2)
61
+ # a.replace(3, 4) # => #<struct A a=3, b=4>
62
+ #
63
+ # ...or as hash parameters:
64
+ #
65
+ # a.replace(:a => 3, :b => 4) # => #<struct A a=3, b=4>
66
+ #
67
+ # As when creating new instances, values given as hash parameters
68
+ # override those given as positional parameters:
69
+ #
70
+ # a.replace(3, 4, :a => 5) # => #<struct A a=5, b=4>
71
+ #
72
+ # Any members not given values will be set to nil.
73
+ #
74
+ def replace(*args)
75
+ hash_args = args.extract_options!
76
+ for member, v in members.zip(args)
77
+ replace_member!(member, v)
78
+ end
79
+ for k, v in hash_args
80
+ replace_member!(k, v)
81
+ end
82
+ end
83
+
84
+ # Updates the current values from the given values. Values can be
85
+ # given in order:
86
+ #
87
+ # A = ConfigStruct::Struct.new(:a, :b)
88
+ # a = A.new(1, 2) # => #<struct A a=1, b=2>
89
+ # a.update(3) # => #<struct A a=3, b=2>
90
+ #
91
+ # ...or as hash parameters:
92
+ #
93
+ # a.update(:a => 3) # => #<struct A a=3, b=2>
94
+ #
95
+ # As when creating new instances, values given as hash parameters
96
+ # override those given as positional parameters:
97
+ #
98
+ # a.replace(3, :a => 5) # => #<struct A a=5, b=2>
99
+ #
100
+ def update(*args)
101
+ hash_args = args.extract_options!
102
+ args.each_with_index do |v, i|
103
+ update_member!(i, v)
104
+ end
105
+ for k, v in hash_args
106
+ update_member!(k, v)
107
+ end
108
+ end
109
+ alias merge! update
110
+
111
+ # Converts the struct to a hash with string keys. This allows you,
112
+ # for example, to trivially serialize the struct as YAML.
113
+ #
114
+ # A = ConfigStruct::Struct.new(:a, :b)
115
+ # A.new(1, 2).to_hash # => {"a"=>1, "b"=>2}
116
+ #
117
+ def to_hash
118
+ members.inject({}) do |hsh, k|
119
+ hsh.tap do |h|
120
+ h[k] = if self[k].is_a? Struct
121
+ self[k].to_hash
122
+ else
123
+ self[k]
124
+ end
125
+ end
126
+ end
127
+ end
128
+
129
+ # Puts config values into an array suitable for expanding into a
130
+ # parameter list. This is for convenience in recursive traversals of
131
+ # a struct instance.
132
+ def to_args
133
+ self.to_hash.to_args
134
+ end
135
+
136
+
137
+ private
138
+
139
+ # member may also be an index
140
+ def replace_member!(member, value) #:nodoc:
141
+ if self[member].is_a? Struct
142
+ raise TypeError,
143
+ 'cannot replace a ConfigStruct::Struct with a scalar' unless
144
+ value.respond_to? :to_args
145
+ self[member].replace(*value.to_args)
146
+ else
147
+ self[member] = value
148
+ end
149
+ end
150
+
151
+ # member may also be an index
152
+ def update_member!(member, value) #:nodoc:
153
+ if self[member].is_a? Struct
154
+ raise TypeError,
155
+ 'cannot replace a ConfigStruct::Struct with a scalar' unless
156
+ value.respond_to? :to_args
157
+ self[member].update(*value.to_args)
158
+ else
159
+ self[member] = value
160
+ end
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,49 @@
1
+ # Define Array#extract_options! and associated methods if they're not
2
+ # already defined. Code taken from Rails' ActiveSupport.
3
+ class Array
4
+ unless [].respond_to? :extract_options!
5
+ # Extracts options from a set of arguments. Removes and returns the last
6
+ # element in the array if it's a hash, otherwise returns a blank hash.
7
+ #
8
+ # def options(*args)
9
+ # args.extract_options!
10
+ # end
11
+ #
12
+ # options(1, 2) # => {}
13
+ # options(1, 2, :a => :b) # => {:a=>:b}
14
+ def extract_options!
15
+ if last.is_a?(Hash) && last.extractable_options?
16
+ pop
17
+ else
18
+ {}
19
+ end
20
+ end
21
+ end
22
+
23
+ # Returns the array in a form suitable for expanding into a parameter
24
+ # list, that is, just the array itself.
25
+ def to_args
26
+ self
27
+ end
28
+ end
29
+
30
+ class Hash
31
+ unless {}.respond_to? :extractable_options?
32
+ # By default, only instances of Hash itself are extractable.
33
+ # Subclasses of Hash may implement this method and return
34
+ # true to declare themselves as extractable. If a Hash
35
+ # is extractable, Array#extract_options! pops it from
36
+ # the Array when it is the last element of the Array.
37
+ def extractable_options?
38
+ instance_of?(Hash)
39
+ end
40
+ end
41
+
42
+ # Puts the hash into an array suitable for expanding into a parameter
43
+ # list, such that it will be interpreted as keyword parameters if the
44
+ # method you're calling expects that.
45
+ def to_args
46
+ [self]
47
+ end
48
+ end
49
+
@@ -0,0 +1,10 @@
1
+ # Define some simplified inflection methods on String if they're not already
2
+ # defined. Code adapted from Rails' ActiveSupport.
3
+ class String
4
+ unless String.respond_to?(:camelize)
5
+ # Turns a_string_like_this into AStringLikeThis.
6
+ def camelize
7
+ self.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,141 @@
1
+ require 'configurable/config_struct'
2
+ require 'configurable/core_ext/extract_options'
3
+ require 'configurable/core_ext/inflections'
4
+
5
+ # Include this module in a class to make it configurable, and declare
6
+ # allowed settings with the Macros.configurable_options method:
7
+ #
8
+ # require 'configurable'
9
+ # class Doodad
10
+ # include Configurable
11
+ # configurable_options :one, :two, :three, # keep options in order
12
+ # :one => 1, :two => 'zwei' # set defaults
13
+ # end
14
+ #
15
+ # This creates a ConfigStruct::Struct called Doodad::Config, along with
16
+ # a frozen instance of it containing the default options accessible with
17
+ # the Doodad.default_config method (see ConfigAccessors.default_config).
18
+ #
19
+ # You can then use Doodad.config (see ConfigAccessors.config) to store
20
+ # the configuration for your class. See ConfigStruct::Struct for the
21
+ # available methods. The config object is lazily created when
22
+ # Doodad.config is called.
23
+ #
24
+ # Each instance of Doodad can have its own configuration object as well:
25
+ #
26
+ # aDoodad = Doodad.new
27
+ # aDoodad.config # => a copy of Doodad.config
28
+ # aDoodad.config.one = 'eins'
29
+ # aDoodad.config.one # => 'eins'
30
+ # Doodad.config.one # => 1
31
+ #
32
+ module Configurable
33
+ VERSION = '1.0.0' #:nodoc:
34
+
35
+ def self.included(klass) #:nodoc:
36
+ klass.extend Macros
37
+ klass.extend ConfigAccessors
38
+ end
39
+
40
+ # Returns the class' default configuration object.
41
+ def default_config
42
+ self.class.default_config
43
+ end
44
+
45
+ # Returns the object's current configuration object. This object is
46
+ # lazily created the first time the method is called as a copy of the
47
+ # class' current config.
48
+ def config
49
+ @_config ||= self.class.config.dup
50
+ end
51
+
52
+
53
+ # Setup macros for classes that include Configurable.
54
+ module Macros
55
+
56
+ # Declares the allowed configuration settings and (optionally) their
57
+ # default values. Field names should be passed as symbols. Example:
58
+ #
59
+ # configurable_options :one, :two
60
+ #
61
+ # This creates a new ConfigStruct::Struct (which is a subclass of
62
+ # Ruby's built-in Struct) called Config within the class it's called
63
+ # on. (Thus if you call it within a class called Doodad, the
64
+ # configuration struct will be created as Doodad::Config.)
65
+ #
66
+ # You may also declare options with default settings, which will
67
+ # then be available thru the default_config method on your class
68
+ # (e.g. Doodad.default_config; see documentation on
69
+ # ConfigAccessors.default_config). To declare defaults, pass them as
70
+ # hash parameters:
71
+ #
72
+ # configurable_options :one => 1, :two => 2
73
+ #
74
+ # In Ruby 1.8, this will not preserve the order of the options. To
75
+ # define the options in a specific order _and_ declare defaults for
76
+ # them, declare the field names first and then the defaults:
77
+ #
78
+ # configurable_options :one, :two, :one => 1
79
+ #
80
+ # Fields not listed in order but that are passed as hash params will
81
+ # be added after all the ordered parameters, in unspecified order:
82
+ #
83
+ # configurable_options :one, :two, :one => 1, :five => '3 sir'
84
+ # Config.members # => ["one", "two", "five"]
85
+ #
86
+ def configurable_options(*args)
87
+ @_config_struct, @_default_config =
88
+ create_struct(self, 'Config', *args)
89
+ end
90
+
91
+
92
+ private
93
+
94
+ def create_struct(base, name, *args) #:nodoc:
95
+ defaults = args.extract_options!
96
+ members = (args + defaults.keys).uniq
97
+ struct = ConfigStruct::Struct.new(*members)
98
+ default_config = struct.new
99
+ base.const_set(name, struct)
100
+ for k, v in defaults
101
+ if v.respond_to?(:to_args)
102
+ substruct, subdefault =
103
+ create_struct(struct, k.to_s.camelize, *(v.to_args))
104
+ default_config[k] = subdefault
105
+ else
106
+ default_config[k] = v
107
+ end
108
+ end
109
+ [struct, default_config.freeze]
110
+ end
111
+
112
+ def config_struct #:nodoc:
113
+ @_config_struct ||= nil
114
+ end
115
+ end
116
+
117
+ # Methods to access the class' current configuration and default
118
+ # configuration.
119
+ module ConfigAccessors
120
+ # Returns the class' configuration object. This object is an
121
+ # instance of the class' Config struct (which is a
122
+ # ConfigStruct::Struct created by the Macros.configurable_options
123
+ # method). It is lazily created the first time the method is called
124
+ # as a copy of the default configuration object.
125
+ def config
126
+ raise NameError, 'use configurable_options to define settings' unless
127
+ config_struct
128
+ @_config ||= @_default_config.dup # dup unfreezes
129
+ end
130
+
131
+ # Returns the class' default configuration object. This object is a
132
+ # frozen instance of the class' Config struct (which is a
133
+ # ConfigStruct::Struct created by the Macros.configurable_options
134
+ # method).
135
+ def default_config
136
+ raise NameError, 'use configurable_options to define settings' unless
137
+ config_struct
138
+ @_default_config
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,125 @@
1
+ require 'minitest/unit'
2
+ require 'minitest/autorun'
3
+
4
+ require 'configurable/config_struct'
5
+
6
+ describe ConfigStruct::Struct do
7
+ before do
8
+ @struct = ConfigStruct::Struct.new(:a, :b)
9
+ end
10
+
11
+ it 'should be initialized from keyword args' do
12
+ inst = @struct.new(:a => 42)
13
+ assert_equal 42, inst.a
14
+ end
15
+
16
+ it 'should reject unknown keywords' do
17
+ assert_raises(NameError) { @struct.new(:z => 'invalid argument') }
18
+ end
19
+
20
+ it 'should override positional args with keyword args' do
21
+ inst = @struct.new(1, :a => 42)
22
+ assert_equal 42, inst.a
23
+ end
24
+
25
+ it 'should convert to a hash' do
26
+ inst = @struct.new(42)
27
+ assert_equal 42, inst.to_hash['a']
28
+ end
29
+
30
+ describe 'replace' do
31
+ before do
32
+ @inst = @struct.new(5, 3)
33
+ end
34
+
35
+ it 'should replace values from positional args' do
36
+ @inst.replace(42)
37
+ assert_equal 42, @inst.a
38
+ end
39
+
40
+ it 'should replace values from keyword args' do
41
+ @inst.replace(:a => 42)
42
+ assert_equal 42, @inst.a
43
+ end
44
+
45
+ it 'should override positional args with keyword args' do
46
+ @inst.replace(42, :a => 3)
47
+ assert_equal 3, @inst.a
48
+ end
49
+
50
+ it 'should set unspecified values to nil' do
51
+ @inst.replace(:b => 42)
52
+ assert_nil @inst.a
53
+ end
54
+ end
55
+
56
+ describe 'update' do
57
+ before do
58
+ @inst = @struct.new(5)
59
+ end
60
+
61
+ it 'should replace values from positional args' do
62
+ @inst.replace(42)
63
+ assert_equal 42, @inst.a
64
+ end
65
+
66
+ it 'should replace values from keyword args' do
67
+ @inst.replace(:a => 42)
68
+ assert_equal 42, @inst.a
69
+ end
70
+
71
+ it 'should override positional args with keyword args' do
72
+ @inst.replace(42, :a => 3)
73
+ assert_equal 3, @inst.a
74
+ end
75
+
76
+ it 'should merge values' do
77
+ @inst.update(:b => 42)
78
+ assert_equal 5, @inst.a
79
+ assert_equal 42, @inst.b
80
+ end
81
+ end
82
+
83
+ describe 'with a nested ConfigStruct::Struct' do
84
+ before do
85
+ @nested = ConfigStruct::Struct.new(:b)
86
+ @inst = @struct.new(:a => @nested.new(:b => 1))
87
+ @inst.freeze
88
+ @inst.a.freeze
89
+ assert_equal 1, @inst.a.b
90
+ end
91
+
92
+ it 'should deep-copy' do
93
+ @copy = @inst.dup
94
+ refute_nil @copy.a
95
+ @copy.a.b = 2
96
+ assert_equal 1, @inst.a.b
97
+ end
98
+
99
+ it 'should convert to a hash recursively' do
100
+ assert_kind_of Hash, @inst.to_hash['a']
101
+ assert_equal 1, @inst.to_hash['a']['b']
102
+ end
103
+ end
104
+
105
+ describe 'with a nested ::Struct' do
106
+ before do
107
+ @nested = ::Struct.new(:b)
108
+ @inst = @struct.new(:a => @nested.new(1))
109
+ @inst.freeze
110
+ assert_equal 1, @inst.a.b
111
+ end
112
+
113
+ it 'should shallow-copy' do
114
+ @copy = @inst.dup
115
+ refute_nil @copy.a
116
+ @copy.a.b = 2
117
+ assert_equal 2, @inst.a.b
118
+ end
119
+
120
+ it 'should not convert to a hash recursively' do
121
+ assert_kind_of @nested, @inst.to_hash['a']
122
+ assert_equal 1, @inst.to_hash['a'].b
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,172 @@
1
+ require 'minitest/unit'
2
+ require 'minitest/autorun'
3
+ require 'set'
4
+
5
+ require 'configurable'
6
+
7
+ describe Configurable do
8
+ before do
9
+ @test_class = Class.new
10
+ @test_class.instance_eval do
11
+ include Configurable
12
+ end
13
+ end
14
+
15
+ describe 'before configurable_options is called' do
16
+ describe 'config' do
17
+ it 'should raise NameError' do
18
+ assert_raises(NameError) { @test_class.config }
19
+ end
20
+ end
21
+
22
+ describe 'default_config' do
23
+ it 'should raise NameError' do
24
+ assert_raises(NameError) { @test_class.default_config }
25
+ end
26
+ end
27
+ end
28
+
29
+ describe 'configurable_options' do
30
+ before do
31
+ @test_class.instance_eval do
32
+ configurable_options :one => 1, :two => nil
33
+ end
34
+ end
35
+
36
+ it 'should create the config struct' do
37
+ begin
38
+ # config struct should be created as <base class>::Config
39
+ @struct = @test_class.const_get('Config')
40
+ rescue NameError
41
+ flunk "#{@test_class}::Config not found"
42
+ end
43
+ refute_equal ::Config, @struct
44
+ assert @struct.ancestors.include? ConfigStruct::Struct
45
+ end
46
+
47
+ it 'should store defaults' do
48
+ assert_respond_to @test_class.default_config, :[]
49
+ assert_equal 1, @test_class.default_config[:one]
50
+ assert_nil @test_class.default_config[:two]
51
+ end
52
+ end
53
+
54
+ describe 'configurable_options with positional args' do
55
+ before do
56
+ @test_class.instance_eval do
57
+ configurable_options :one, :two,
58
+ :one => 1, :five => 3
59
+ end
60
+ @struct = @test_class.instance_eval { @_config_struct }
61
+ end
62
+
63
+ it 'should order the keys the config struct' do
64
+ assert_equal ['one', 'two'], @struct.members[0,2]
65
+ end
66
+
67
+ it 'should create members from hash keys at the end' do
68
+ assert_equal 'five', @struct.members.last
69
+ end
70
+ end
71
+
72
+ describe 'configurable_options with nested args' do
73
+ before do
74
+ @test_class.instance_eval do
75
+ configurable_options :a => [:b], :c => {:d => 42}
76
+ end
77
+ @struct = @test_class.const_get('Config')
78
+ end
79
+
80
+ it 'should create the config struct' do
81
+ refute_equal ::Config, @struct
82
+ assert_equal %w(a c).to_set, @struct.members.to_set
83
+ end
84
+
85
+ it 'should create a nested A struct' do
86
+ begin
87
+ a_struct = @struct.const_get('A')
88
+ rescue NameError
89
+ flunk "#{@test_class}::Config::A not found"
90
+ end
91
+ assert a_struct.ancestors.include? ConfigStruct::Struct
92
+ end
93
+
94
+ it 'should create a nested C struct' do
95
+ begin
96
+ c_struct = @struct.const_get('C')
97
+ rescue NameError
98
+ flunk "#{@test_class}::Config::C not found"
99
+ end
100
+ assert c_struct.ancestors.include? ConfigStruct::Struct
101
+ end
102
+
103
+ it 'should store defaults' do
104
+ defaults = @test_class.default_config
105
+ assert_respond_to defaults, :a
106
+ assert_respond_to defaults.a, :b
107
+ assert_equal nil, defaults.a.b
108
+
109
+ assert_respond_to defaults, :c
110
+ assert_respond_to defaults.c, :d
111
+ assert_equal 42, defaults.c.d
112
+ end
113
+ end
114
+
115
+ describe 'after configurable_options is called' do
116
+ before do
117
+ @test_class.instance_eval do
118
+ configurable_options :one => 1, :two => nil
119
+ end
120
+ end
121
+
122
+ describe 'an instance' do
123
+ before do
124
+ @test_inst = @test_class.new
125
+ end
126
+
127
+ it 'should have its own config' do
128
+ assert @test_inst.config
129
+ @test_inst.config.one = 42
130
+ refute_equal 42, @test_class.config
131
+ end
132
+
133
+ it "should defer to the class's default config" do
134
+ assert @test_inst.default_config
135
+ assert_equal @test_class.default_config, @test_inst.default_config
136
+ assert_equal @test_class.default_config.object_id,
137
+ @test_inst.default_config.object_id
138
+ end
139
+ end
140
+
141
+ describe 'default_config' do
142
+ before do
143
+ @defaults = @test_class.default_config
144
+ end
145
+
146
+ it 'should return the default config' do
147
+ assert_equal 1, @defaults.one
148
+ assert_equal nil, @defaults.two
149
+ end
150
+
151
+ it 'should be frozen' do
152
+ assert @defaults.frozen?
153
+ assert_raises(TypeError) { @defaults.one = 42 }
154
+ end
155
+ end
156
+
157
+ describe 'config' do
158
+ before do
159
+ @config = @test_class.config
160
+ end
161
+
162
+ it 'should not be frozen' do
163
+ refute @config.frozen?
164
+ begin
165
+ @config.one = 42
166
+ rescue TypeError
167
+ flunk "couldn't set config.one"
168
+ end
169
+ end
170
+ end
171
+ end
172
+ end
metadata ADDED
@@ -0,0 +1,113 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruby-configurable
3
+ version: !ruby/object:Gem::Version
4
+ hash: 23
5
+ prerelease:
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 0
10
+ version: 1.0.0
11
+ platform: ruby
12
+ authors:
13
+ - Kevin R. Bullock
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-05-05 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: rubyforge
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 7
29
+ segments:
30
+ - 2
31
+ - 0
32
+ - 4
33
+ version: 2.0.4
34
+ type: :development
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: hoe
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ hash: 21
45
+ segments:
46
+ - 2
47
+ - 6
48
+ - 1
49
+ version: 2.6.1
50
+ type: :development
51
+ version_requirements: *id002
52
+ description: Lets you make your Ruby class configurable with a simple mixin.
53
+ email:
54
+ - kbullock@ringworld.org
55
+ executables: []
56
+
57
+ extensions: []
58
+
59
+ extra_rdoc_files:
60
+ - COPYING.txt
61
+ - History.txt
62
+ - Manifest.txt
63
+ - README.txt
64
+ files:
65
+ - .autotest
66
+ - COPYING.txt
67
+ - History.txt
68
+ - Manifest.txt
69
+ - README.txt
70
+ - Rakefile
71
+ - lib/configurable.rb
72
+ - lib/configurable/config_struct.rb
73
+ - lib/configurable/core_ext/extract_options.rb
74
+ - lib/configurable/core_ext/inflections.rb
75
+ - test/test_config_struct.rb
76
+ - test/test_configurable.rb
77
+ homepage: http://bitbucket.org/krbullock/configurable
78
+ licenses: []
79
+
80
+ post_install_message:
81
+ rdoc_options:
82
+ - --main
83
+ - README.txt
84
+ require_paths:
85
+ - lib
86
+ required_ruby_version: !ruby/object:Gem::Requirement
87
+ none: false
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ hash: 3
92
+ segments:
93
+ - 0
94
+ version: "0"
95
+ required_rubygems_version: !ruby/object:Gem::Requirement
96
+ none: false
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ hash: 3
101
+ segments:
102
+ - 0
103
+ version: "0"
104
+ requirements: []
105
+
106
+ rubyforge_project: handtools
107
+ rubygems_version: 1.7.2
108
+ signing_key:
109
+ specification_version: 3
110
+ summary: Lets you make your Ruby class configurable with a simple mixin.
111
+ test_files:
112
+ - test/test_config_struct.rb
113
+ - test/test_configurable.rb