kdict 0.1.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.
Files changed (56) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +7 -0
  5. data/.yardopts +3 -0
  6. data/CODE_OF_CONDUCT.md +74 -0
  7. data/Gemfile +6 -0
  8. data/Gemfile.lock +43 -0
  9. data/LICENSE.txt +21 -0
  10. data/README.md +301 -0
  11. data/Rakefile +8 -0
  12. data/bin/console +14 -0
  13. data/bin/setup +8 -0
  14. data/docs/Between.html +302 -0
  15. data/docs/Bool.html +121 -0
  16. data/docs/Example1.md +195 -0
  17. data/docs/FalseClass.html +143 -0
  18. data/docs/KDict/Error.html +135 -0
  19. data/docs/KDict.html +128 -0
  20. data/docs/Kdict/Error.html +135 -0
  21. data/docs/Kdict.html +157 -0
  22. data/docs/Kdict_.html +143 -0
  23. data/docs/KwargDict.html +697 -0
  24. data/docs/KwargModel.html +638 -0
  25. data/docs/KwargTypes.html +988 -0
  26. data/docs/Numeric.html +148 -0
  27. data/docs/TrueClass.html +143 -0
  28. data/docs/_index.html +184 -0
  29. data/docs/class_list.html +51 -0
  30. data/docs/css/common.css +1 -0
  31. data/docs/css/full_list.css +58 -0
  32. data/docs/css/style.css +496 -0
  33. data/docs/file.Example1.html +240 -0
  34. data/docs/file.README.html +370 -0
  35. data/docs/file_list.html +61 -0
  36. data/docs/frames.html +17 -0
  37. data/docs/index.html +370 -0
  38. data/docs/js/app.js +303 -0
  39. data/docs/js/full_list.js +216 -0
  40. data/docs/js/jquery.js +4 -0
  41. data/docs/method_list.html +187 -0
  42. data/docs/top-level-namespace.html +112 -0
  43. data/examples/example1.rb +90 -0
  44. data/kdict.gemspec +39 -0
  45. data/lib/kdict/kwargdict/kwargtypes/bool.rb +9 -0
  46. data/lib/kdict/kwargdict/kwargtypes.rb +196 -0
  47. data/lib/kdict/kwargdict.rb +75 -0
  48. data/lib/kdict/kwargmodel.rb +40 -0
  49. data/lib/kdict/version.rb +5 -0
  50. data/lib/kdict.rb +19 -0
  51. data/ndoc/checksums +6 -0
  52. data/ndoc/complete +0 -0
  53. data/ndoc/object_types +0 -0
  54. data/ndoc/objects/root.dat +0 -0
  55. data/ndoc/proxy_types +0 -0
  56. metadata +150 -0
data/docs/Between.html ADDED
@@ -0,0 +1,302 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>
7
+ Module: Between
8
+
9
+ &mdash; Documentation by YARD 0.9.19
10
+
11
+ </title>
12
+
13
+ <link rel="stylesheet" href="css/style.css" type="text/css" charset="utf-8" />
14
+
15
+ <link rel="stylesheet" href="css/common.css" type="text/css" charset="utf-8" />
16
+
17
+ <script type="text/javascript" charset="utf-8">
18
+ pathId = "Between";
19
+ relpath = '';
20
+ </script>
21
+
22
+
23
+ <script type="text/javascript" charset="utf-8" src="js/jquery.js"></script>
24
+
25
+ <script type="text/javascript" charset="utf-8" src="js/app.js"></script>
26
+
27
+
28
+ </head>
29
+ <body>
30
+ <div class="nav_wrap">
31
+ <iframe id="nav" src="class_list.html?1"></iframe>
32
+ <div id="resizer"></div>
33
+ </div>
34
+
35
+ <div id="main" tabindex="-1">
36
+ <div id="header">
37
+ <div id="menu">
38
+
39
+ <a href="_index.html">Index (B)</a> &raquo;
40
+
41
+
42
+ <span class="title">Between</span>
43
+
44
+ </div>
45
+
46
+ <div id="search">
47
+
48
+ <a class="full_list_link" id="class_list_link"
49
+ href="class_list.html">
50
+
51
+ <svg width="24" height="24">
52
+ <rect x="0" y="4" width="24" height="4" rx="1" ry="1"></rect>
53
+ <rect x="0" y="12" width="24" height="4" rx="1" ry="1"></rect>
54
+ <rect x="0" y="20" width="24" height="4" rx="1" ry="1"></rect>
55
+ </svg>
56
+ </a>
57
+
58
+ </div>
59
+ <div class="clear"></div>
60
+ </div>
61
+
62
+ <div id="content"><h1>Module: Between
63
+
64
+
65
+
66
+ </h1>
67
+ <div class="box_info">
68
+
69
+
70
+
71
+
72
+
73
+
74
+
75
+
76
+
77
+ <dl>
78
+ <dt>Included in:</dt>
79
+ <dd><span class='object_link'><a href="Numeric.html" title="Numeric (class)">Numeric</a></span></dd>
80
+ </dl>
81
+
82
+
83
+
84
+ <dl>
85
+ <dt>Defined in:</dt>
86
+ <dd>lib/kdict/kwargdict/kwargtypes/conveniences.rb</dd>
87
+ </dl>
88
+
89
+ </div>
90
+
91
+ <h2>Overview</h2><div class="docstring">
92
+ <div class="discussion">
93
+
94
+ <div class="note notetag">
95
+ <strong>Note:</strong>
96
+ <div class='inline'>
97
+ <p>This isn&#39;t directly used in any of Kdict, but IS used in the examples given in the README and other documentation.</p>
98
+ </div>
99
+ </div>
100
+
101
+
102
+ <p>A Simple module that adds two similar methods for checking whether self is between two other numbers.</p>
103
+
104
+
105
+ </div>
106
+ </div>
107
+ <div class="tags">
108
+
109
+
110
+ </div>
111
+
112
+
113
+
114
+
115
+
116
+
117
+
118
+ <h2>
119
+ Instance Method Summary
120
+ <small><a href="#" class="summary_toggle">collapse</a></small>
121
+ </h2>
122
+
123
+ <ul class="summary">
124
+
125
+ <li class="public ">
126
+ <span class="summary_signature">
127
+
128
+ <a href="#between%3F-instance_method" title="#between? (instance method)">#<strong>between?</strong>(a, b) &#x21d2; Boolean </a>
129
+
130
+
131
+
132
+ </span>
133
+
134
+
135
+
136
+
137
+
138
+
139
+
140
+
141
+
142
+ <span class="summary_desc"><div class='inline'>
143
+ <p>Non-inclusive bounds.</p>
144
+ </div></span>
145
+
146
+ </li>
147
+
148
+
149
+ <li class="public ">
150
+ <span class="summary_signature">
151
+
152
+ <a href="#ibetween%3F-instance_method" title="#ibetween? (instance method)">#<strong>ibetween?</strong>(a, b) &#x21d2; Boolean </a>
153
+
154
+
155
+
156
+ </span>
157
+
158
+
159
+
160
+
161
+
162
+
163
+
164
+
165
+
166
+ <span class="summary_desc"><div class='inline'>
167
+ <p>Inclusive bounds.</p>
168
+ </div></span>
169
+
170
+ </li>
171
+
172
+
173
+ </ul>
174
+
175
+
176
+
177
+
178
+ <div id="instance_method_details" class="method_details_list">
179
+ <h2>Instance Method Details</h2>
180
+
181
+
182
+ <div class="method_details first">
183
+ <h3 class="signature first" id="between?-instance_method">
184
+
185
+ #<strong>between?</strong>(a, b) &#x21d2; <tt>Boolean</tt>
186
+
187
+
188
+
189
+
190
+
191
+ </h3><div class="docstring">
192
+ <div class="discussion">
193
+
194
+ <p>Non-inclusive bounds. Checks if ( <tt>a &lt; self &lt; b</tt> ).</p>
195
+
196
+
197
+ </div>
198
+ </div>
199
+ <div class="tags">
200
+
201
+ <p class="tag_title">Returns:</p>
202
+ <ul class="return">
203
+
204
+ <li>
205
+
206
+
207
+ <span class='type'>(<tt>Boolean</tt>)</span>
208
+
209
+
210
+
211
+ </li>
212
+
213
+ </ul>
214
+
215
+ </div><table class="source_code">
216
+ <tr>
217
+ <td>
218
+ <pre class="lines">
219
+
220
+
221
+ 19
222
+ 20
223
+ 21</pre>
224
+ </td>
225
+ <td>
226
+ <pre class="code"><span class="info file"># File 'lib/kdict/kwargdict/kwargtypes/conveniences.rb', line 19</span>
227
+
228
+ <span class='kw'>def</span> <span class='id identifier rubyid_between?'>between?</span><span class='lparen'>(</span><span class='id identifier rubyid_a'>a</span><span class='comma'>,</span> <span class='id identifier rubyid_b'>b</span><span class='rparen'>)</span>
229
+ <span class='kw'>self</span> <span class='op'>&gt;</span> <span class='id identifier rubyid_a'>a</span> <span class='op'>&amp;&amp;</span> <span class='kw'>self</span> <span class='op'>&lt;</span> <span class='id identifier rubyid_b'>b</span>
230
+ <span class='kw'>end</span></pre>
231
+ </td>
232
+ </tr>
233
+ </table>
234
+ </div>
235
+
236
+ <div class="method_details ">
237
+ <h3 class="signature " id="ibetween?-instance_method">
238
+
239
+ #<strong>ibetween?</strong>(a, b) &#x21d2; <tt>Boolean</tt>
240
+
241
+
242
+
243
+
244
+
245
+ </h3><div class="docstring">
246
+ <div class="discussion">
247
+
248
+ <p>Inclusive bounds. Checks if ( <tt>a &lt;= self &lt;= b</tt> ).</p>
249
+
250
+
251
+ </div>
252
+ </div>
253
+ <div class="tags">
254
+
255
+ <p class="tag_title">Returns:</p>
256
+ <ul class="return">
257
+
258
+ <li>
259
+
260
+
261
+ <span class='type'>(<tt>Boolean</tt>)</span>
262
+
263
+
264
+
265
+ </li>
266
+
267
+ </ul>
268
+
269
+ </div><table class="source_code">
270
+ <tr>
271
+ <td>
272
+ <pre class="lines">
273
+
274
+
275
+ 23
276
+ 24
277
+ 25</pre>
278
+ </td>
279
+ <td>
280
+ <pre class="code"><span class="info file"># File 'lib/kdict/kwargdict/kwargtypes/conveniences.rb', line 23</span>
281
+
282
+ <span class='kw'>def</span> <span class='id identifier rubyid_ibetween?'>ibetween?</span><span class='lparen'>(</span><span class='id identifier rubyid_a'>a</span><span class='comma'>,</span> <span class='id identifier rubyid_b'>b</span><span class='rparen'>)</span>
283
+ <span class='kw'>self</span> <span class='op'>&gt;=</span> <span class='id identifier rubyid_a'>a</span> <span class='op'>&amp;&amp;</span> <span class='kw'>self</span> <span class='op'>&lt;=</span> <span class='id identifier rubyid_b'>b</span>
284
+ <span class='kw'>end</span></pre>
285
+ </td>
286
+ </tr>
287
+ </table>
288
+ </div>
289
+
290
+ </div>
291
+
292
+ </div>
293
+
294
+ <div id="footer">
295
+ Generated on Thu Apr 11 02:15:17 2019 by
296
+ <a href="http://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
297
+ 0.9.19 (ruby-2.6.2).
298
+ </div>
299
+
300
+ </div>
301
+ </body>
302
+ </html>
data/docs/Bool.html ADDED
@@ -0,0 +1,121 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>
7
+ Module: Bool
8
+
9
+ &mdash; Documentation by YARD 0.9.19
10
+
11
+ </title>
12
+
13
+ <link rel="stylesheet" href="css/style.css" type="text/css" charset="utf-8" />
14
+
15
+ <link rel="stylesheet" href="css/common.css" type="text/css" charset="utf-8" />
16
+
17
+ <script type="text/javascript" charset="utf-8">
18
+ pathId = "Bool";
19
+ relpath = '';
20
+ </script>
21
+
22
+
23
+ <script type="text/javascript" charset="utf-8" src="js/jquery.js"></script>
24
+
25
+ <script type="text/javascript" charset="utf-8" src="js/app.js"></script>
26
+
27
+
28
+ </head>
29
+ <body>
30
+ <div class="nav_wrap">
31
+ <iframe id="nav" src="class_list.html?1"></iframe>
32
+ <div id="resizer"></div>
33
+ </div>
34
+
35
+ <div id="main" tabindex="-1">
36
+ <div id="header">
37
+ <div id="menu">
38
+
39
+ <a href="_index.html">Index (B)</a> &raquo;
40
+
41
+
42
+ <span class="title">Bool</span>
43
+
44
+ </div>
45
+
46
+ <div id="search">
47
+
48
+ <a class="full_list_link" id="class_list_link"
49
+ href="class_list.html">
50
+
51
+ <svg width="24" height="24">
52
+ <rect x="0" y="4" width="24" height="4" rx="1" ry="1"></rect>
53
+ <rect x="0" y="12" width="24" height="4" rx="1" ry="1"></rect>
54
+ <rect x="0" y="20" width="24" height="4" rx="1" ry="1"></rect>
55
+ </svg>
56
+ </a>
57
+
58
+ </div>
59
+ <div class="clear"></div>
60
+ </div>
61
+
62
+ <div id="content"><h1>Module: Bool
63
+
64
+
65
+
66
+ </h1>
67
+ <div class="box_info">
68
+
69
+
70
+
71
+
72
+
73
+
74
+
75
+
76
+
77
+ <dl>
78
+ <dt>Included in:</dt>
79
+ <dd><span class='object_link'><a href="FalseClass.html" title="FalseClass (class)">FalseClass</a></span>, <span class='object_link'><a href="TrueClass.html" title="TrueClass (class)">TrueClass</a></span></dd>
80
+ </dl>
81
+
82
+
83
+
84
+ <dl>
85
+ <dt>Defined in:</dt>
86
+ <dd>lib/kdict/kwargdict/kwargtypes/bool.rb</dd>
87
+ </dl>
88
+
89
+ </div>
90
+
91
+ <h2>Overview</h2><div class="docstring">
92
+ <div class="discussion">
93
+
94
+ <p>Empty module that&#39;s assigned to both <span class='object_link'><a href="TrueClass.html" title="TrueClass (class)">TrueClass</a></span> and <span class='object_link'><a href="FalseClass.html" title="FalseClass (class)">FalseClass</a></span>.</p>
95
+
96
+
97
+ </div>
98
+ </div>
99
+ <div class="tags">
100
+
101
+
102
+ </div>
103
+
104
+
105
+
106
+
107
+
108
+
109
+
110
+
111
+ </div>
112
+
113
+ <div id="footer">
114
+ Generated on Thu Apr 11 21:45:30 2019 by
115
+ <a href="http://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
116
+ 0.9.19 (ruby-2.6.2).
117
+ </div>
118
+
119
+ </div>
120
+ </body>
121
+ </html>
data/docs/Example1.md ADDED
@@ -0,0 +1,195 @@
1
+ # A Hilariously Contrieved Though Functional Example
2
+ I'll just state outright that this demonstration is merely to show the usefulness
3
+ of KDict in a larger example.
4
+
5
+ Before the code, let's just pretend you're trying to create a database
6
+ of rare cars from another planet... in another galaxy, and information available
7
+ to you is sparse. You just want to fill in what you know. We're going to create
8
+ a set of dictionaries and a MyCar class that let's us fill in information.
9
+
10
+ Along the way there'll be two helper modules, HashPrint. It's irrelevant to the task
11
+ at hand but will help your output look like mine, if that at all matters to you.
12
+
13
+ For simplicity sake, refer back to the {file:README.md} file if
14
+ you're lost on what's going on. It might be useful when looking at how the
15
+ dictionaries are built to refer back to the specific *typedef* (AKA {KwargTypes} )
16
+ on a line-by-line basis to get a feel for what each does.
17
+
18
+ ## Building a CarDict Module
19
+ The module will contain a few related KwargDicts just to make it
20
+ easier to split and/or manage the way we use them. The final dict combines the
21
+ former two just to demonstrate that there's a *typedef* for that purpose.
22
+
23
+ ```ruby
24
+ module CarDict
25
+ ENGINE = KwargDict.new
26
+ ENGINE.add(
27
+ [:hp, :typeof, Integer, Proc.new { |n| 95 <= n && n <= 800 } ],
28
+ [:liters, :typeof, Float, Proc.new { |n| 1 <= n && n <= 10 }],
29
+ [:max_torque, :typeof, Numeric, Proc.new { |n| n >= 25 }],
30
+ [:turbo, :typeof, Bool])
31
+
32
+ CAR_OPTIONS = KwargDict.new
33
+ CAR_OPTIONS.add([:sunroof, :typeof, Bool],
34
+ [:sport_package, :anyof, ['K1', 'K2', 'K3']],
35
+ [:heated_seats, :typeof, Bool],
36
+ [:audio_package, :anyof, ['Loud', 'Louder', 'Loudest']])
37
+
38
+ CAR_DICT = KwargDict.new
39
+ CAR_DICT.add(
40
+ [:doors, :typeof, Integer, Proc.new{ |n| n >= 2 && n.even? }],
41
+ [:wheels, :typeof, Integer, Proc.new{ |n| n >= 4 && n.even? }],
42
+ [:weight, :typeof, Numeric, Proc.new { |n| n >= 500 }],
43
+ [:year, :typeof, Integer, Proc.new { |n| n >= 1900 } ],
44
+ [:model, :anyof, ['SpaceCowboy', 'Peanut', 'Icarus']],
45
+ [:coupe, :typeof, Bool],
46
+ [:comments, :arrayof, String],
47
+ [:custom_color, :adv_formof, [[:typeof, String],[:formof, [Float]*3, Proc.new { |n| 0 <= n && n <= 1 } ]]],
48
+ [:engine, :kwargsof, ENGINE],
49
+ [:options, :kwargsof, CAR_OPTIONS])
50
+ end
51
+ ```
52
+ From {KwargDict#add} you can see the expected order for each definition is,
53
+ [**kword**, **typedef**, **struct**, **optional_prc**]
54
+
55
+
56
+ ## Adding the HashPrint Module
57
+ Just a convenient tool for printing hashes that might contain other hashes, as
58
+ will be the case here. For every nested hash, it'll indent by two spaces and
59
+ continute printing line by line. You'll see the fast and dirty approach is just
60
+ to store all the valid input in a single hash for this example.
61
+
62
+ ```ruby
63
+ module HashPrint
64
+ def hash_print(hash, ident=0)
65
+ hash.each do |k, v|
66
+ if v.is_a?(Hash)
67
+ puts " "*ident + "#{k}:"
68
+ ident += 2
69
+ hash_print(v, ident)
70
+ ident -= 2
71
+ else
72
+ puts " "*ident + "#{k}: #{v}"
73
+ end
74
+ end
75
+ end
76
+ end
77
+ ```
78
+ ## MyCar Class
79
+ I *think* this class is pretty straight forward. You tell me. We've included
80
+ both the HashPrint and CarDict modules, and so by extension the dictionaries and
81
+ {#hash_print}.
82
+
83
+ ```ruby
84
+ class MyCar
85
+
86
+ include CarDict
87
+ include HashPrint
88
+
89
+ def initialize(*kwords_vals)
90
+ @dat = {}
91
+ set(*kwords_vals)
92
+ end
93
+
94
+ def [](dat_ele)
95
+ @dat[dat_ele]
96
+ end
97
+
98
+ def set(*kwords_vals)
99
+ kwords_vals.each_slice(2) do |kword, val|
100
+ if CAR_DICT.check(kword, val)
101
+ @dat[kword] = val
102
+ else
103
+ puts "Couldn't set #{kword} with #{val}"
104
+ end
105
+ end
106
+ end
107
+
108
+ def display
109
+ hash_print(@dat)
110
+ end
111
+
112
+ end
113
+ ```
114
+ We can initialize a MyCar instance with some parameters if we
115
+ have them, or use {#set} afterwards. In this instance, {#set} will simply override
116
+ whatever was already stored in !{@dat[kword]}, so I'll leave it up to your
117
+ imagination how you'd proceed in a different context. For instance, using {#set}
118
+ on the *kword* for *:options* would obviously just completely overwrite the
119
+ nested hash, whereas you'd probably just intended to add or modify it. Checking
120
+ that *val* is a hash and using {Hash#merge} would be the most obvious solution.
121
+ *I'm chasing rabbits. I only mention it because it's in the ToDo to add another
122
+ private method to {KwargTypes} that let's the user set validated data on a more
123
+ granular level, such as individual keywords in a nested KwargDict.*
124
+
125
+ {#set} will also let you know which input didn't get except. Next.
126
+
127
+ ## Getting To The Point!
128
+ Here's a car you found out on an uncharted planet in the Blarglebones 9 System,
129
+ or whatever. Let's put in the information and go ahead and display it.
130
+
131
+ #### Adding the Kool Car
132
+ ```ruby
133
+ kool_car = MyCar.new(:year, 1901, :coupe, true, :nickname, "Lil Peanut", :doors, 6,
134
+ :options, {sunroof:true, sport_package:'K2', audio_package:'Loudest'},
135
+ :custom_color, ["Ugly Green", [0.51, 0.94, 0.79]])
136
+ kool_car.set(:engine, {liters:6.0, hp:500, turbo:true}, :wheels, 8)
137
+ kool_car.set(:comments, ["The shovel was a ground-breaking invention.",
138
+ "What's brown and sticky? A stick.", "Found in the Blarglebones 9 System."])
139
+
140
+ kool_car.display
141
+ ```
142
+ #### Output:
143
+ ```
144
+ year: 1901
145
+ coupe: true
146
+ nickname: Lil Peanut
147
+ doors: 6
148
+ options:
149
+ sunroof: true
150
+ sport_package: K2
151
+ audio_package: Loudest
152
+ custom_color: ["Ugly Green", [0.51, 0.94, 0.79]]
153
+ engine:
154
+ liters: 6.0
155
+ hp: 500
156
+ turbo: true
157
+ wheels: 8
158
+ comments: ["The shovel was a ground-breaking invention.", "What's brown and sticky? A stick.", "Found in the Blarglebones 9 System."]
159
+ ```
160
+
161
+ What if you're groggy from the trip and mess up the entry on another car?
162
+ When we run the snippet we'll find nothing but a list of errors and an empty MyCar
163
+ instance.
164
+
165
+ #### Programming When You Should Be Resting
166
+
167
+ ```ruby
168
+ break_car = MyCar.new
169
+ break_car.set(:doors, 7, :wheels, 3, :weight, 400, :year, 1857, :nickname, "Escort",
170
+ :coupe, 'yes', :comments, [1, 2, 3, 4], :custom_color, [0.5, 0.5, 0.5],
171
+ :engine, {supercharged:false}, :options, {heated_seats:'front only'},
172
+ :comments, ['This is why sleep is so important. And this gem. But mostly coffee.'])
173
+ break_car.display
174
+ ```
175
+ #### Output:
176
+ ```
177
+ Couldn't set doors with 7
178
+ Couldn't set wheels with 3
179
+ Couldn't set weight with 400
180
+ Couldn't set year with 1857
181
+ Couldn't set nickname with Escort
182
+ Couldn't set coupe with yes
183
+ Couldn't set comments with [1, 2, 3, 4]
184
+ Couldn't set custom_color with [0.5, 0.5, 0.5]
185
+ Couldn't set engine with {:supercharged=>false}
186
+ Couldn't set options with {:heated_seats=>"front only"}
187
+ comments: ["This is why sleep is so important. And this gem. But mostly coffee."]
188
+ ```
189
+ ## Final Thoughts On This Example
190
+ Thank goodness for that nifty KDict Gem, amiright? Now you can really put your
191
+ collection data together quickly without having to worry about writing difficult
192
+ validation code for every example. If you come across a new space-car feature,
193
+ you can just add it to the dict without rewriting any code or breaking what you've
194
+ already got! AND you can share this nifty *gem* with friends so they too can document
195
+ their collections. It's so easy a space-gerbil could do it.