noms-optconfig 1.5.3

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.
@@ -0,0 +1,53 @@
1
+ #!/bin/bash
2
+
3
+ #
4
+ # Copyright 2014 Evernote Corp. All rights reserved.
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+
20
+ domain="$1"
21
+ shift 1
22
+
23
+ SCRIPT_VERSION=$(ruby -rubygems -e 'require "optconfig/version"; puts Optconfig::VERSION')
24
+
25
+ echo -n "Using "; which optconfig.sh
26
+ . optconfig.sh
27
+ opt_new_gen $domain "$@"
28
+
29
+ :<<EOF
30
+ =head1 NAME
31
+
32
+ bash_showconfig - Display the standard configuration resulting from given options
33
+
34
+ =head1 SYNOPSIS
35
+
36
+ bash_showconfig domain optspec [options]
37
+
38
+ =head1 DESCRIPTION
39
+
40
+ The standard Optconfig system provides a way to configure program execution
41
+ in the context specified by domain. Optionally the "perl" or "ruby" languages
42
+ can be specified to use those code paths.
43
+
44
+ The optspec is a JSON-serialized option specifier (see L<Optconfig>). Other
45
+ arguments are passed to the optconfig module (perl or ruby) to determine the
46
+ final configuration.
47
+
48
+ =head1 AUTHOR
49
+
50
+ Jeremy Brinkley, E<lt>jbrinkley@evernote.comE<gt>
51
+
52
+ =cut
53
+ EOF
data/bin/optconfig.sh ADDED
@@ -0,0 +1,128 @@
1
+ #!bash
2
+
3
+ # Copyright 2013 Proofpoint, Inc. All rights reserved.
4
+ # Copyright 2014 Evernote Corp. All rights reserved.
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ # TODO:
20
+ # * --version doesn't really work, calling script continues after printing
21
+ # version.
22
+ # * --help doesn't work (because a) it prints the help on the stdout
23
+ # which is what gets eval'd, and b) it'll never find "-e"
24
+ # opt_new doesn't work, you have to eval it yourself, because otherwise
25
+ # the 'set' command which rearranges the script arguments doesn't work
26
+ # -jdb/20100921
27
+
28
+ opt_new_gen()
29
+ {
30
+ local domain optspec
31
+ domain="$1"
32
+ shift 1
33
+ optspec="$1"
34
+ shift 1
35
+ ruby -rubygems \
36
+ -e 'require "optconfig"' \
37
+ -e 'require "json"' \
38
+ -e 'require "bashon"' \
39
+ -e '$VERSION = "'$SCRIPT_VERSION'"' \
40
+ -e '$0 = "'"$0"'"' \
41
+ -e 'd = ARGV.shift' \
42
+ -e 'opttext = ARGV.shift' \
43
+ -e 'optspec = JSON.load(opttext)' \
44
+ -e 'opt = Optconfig.new(d, optspec)' \
45
+ -e 'puts opt.map { |k,v| v.to_bashon("opt", d, k) }.join(";") + ";" +' \
46
+ -e ' "opt_#{d}_vrb() { local l=\"$1\"; shift 1; test $l -le $(opt_#{d}_verbose) && echo $*; };" + ' \
47
+ -e ' "opt_#{d}_dbg() { local l=\"$1\"; shift 1; test $l -le $(opt_#{d}_debug) && echo \"DBG(#{d}):\" $*; };" + ' \
48
+ -e ' "opt_#{d}_dry() { if opt_#{d}_dry_run; then echo $*; else \"$@\"; fi; };" + ' \
49
+ -e ' "opt_#{d}() { c=\"${1//[^a-zA-Z0-9]/_}\"; shift 1; opt_#{d}_$c \"$@\"; };" + ' \
50
+ -e ' "set -- #{ARGV.map {|a| 39.chr + a + 39.chr}.join(%q( ))}"' \
51
+ "$domain" "$optspec" "$@"
52
+ }
53
+
54
+ json2bashon_gen()
55
+ {
56
+ ruby -rubygems \
57
+ -e 'require "bashon"' \
58
+ -e 'require "json"' \
59
+ -e 'name = ARGV.shift' \
60
+ -e 'json_text = ARGV.shift' \
61
+ -e 'obj = JSON.load(json_text)' \
62
+ -e 'puts obj.to_bashon(name)' "$@"
63
+ }
64
+
65
+ json2bashon()
66
+ {
67
+ eval `json2bashon_gen "$@"`
68
+ }
69
+
70
+ opt_new()
71
+ {
72
+ # This doesn't work, but this is how it should work
73
+ eval `opt_new_gen "$@"`
74
+ echo opt_$1
75
+ }
76
+
77
+ :<<'EOF'
78
+ =head1 NAME
79
+
80
+ optconfig - Bash functions for option parsing
81
+
82
+ =head1 SYNOPSIS
83
+
84
+ . optconfig.sh
85
+ domain=domain
86
+ optspec='{ "force!": false,
87
+ "logfile=s": "/var/log/foo",
88
+ "define=s%": { } }'
89
+ # opt=`opt_new $domain $optspec`
90
+ eval `opt_new_gen $domain "$optspec" "$@"`
91
+ opt=opt_$domain
92
+
93
+ if $opt force
94
+ then
95
+ rm -f $filefoo
96
+ fi
97
+
98
+ echo "Message" >>`$opt logfile`
99
+
100
+ for key in `$opt define`
101
+ do
102
+ valfun=`$opt define $key`
103
+ val=`$valfun` # Note this call--all hash values are funs
104
+ echo "$key = $val"
105
+ done
106
+
107
+ =head1 DESCRIPTION
108
+
109
+ This bash "module" implements a common config file and command-line option
110
+ parsing interface, including Optconfig standard options, that is shared with
111
+ the Optconfig Perl module. See that module for details.
112
+
113
+ The initial call is a wrapper around the Ruby optconfig option and config
114
+ parsing library, and the results are serialized using the L<bashon> module.
115
+
116
+ =head1 NOTES
117
+
118
+ Pay careful attention to how L<bashon> serializes values, especially
119
+ collections. In particular the "leaf" value of a collection is always itself a
120
+ function. If the leaf is itself a collection, this will result in many command
121
+ invocations before you reach a simple value.
122
+
123
+ =head1 AUTHOR
124
+
125
+ Jeremy Brinkley, E<lt>jbrinkley@evernote.comE<gt>
126
+
127
+ =cut
128
+ EOF
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env ruby
2
+ # Copyright 2014 Evernote Corp. All rights reserved.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ require 'optconfig'
18
+
19
+ $VERSION = Optconfig::VERSION
20
+
21
+ lib_location = $LOADED_FEATURES.find { |f| f =~ /optconfig.rb$/ }
22
+
23
+ puts "Using #{lib_location}"
24
+
25
+ domain = ARGV.shift
26
+ optspec = JSON.load(ARGV.shift)
27
+ opt = Optconfig.new(domain, optspec)
28
+ puts opt.to_json
29
+
30
+ # = NAME
31
+ #
32
+ # showconfig - Display the standard configuration resulting from given options
33
+ #
34
+ # = SYNOPSIS
35
+ #
36
+ # ruby-showconfig domain optspec [options]
37
+ #
38
+ # = DESCRIPTION
39
+ #
40
+ # The standard Optconfig system provides a way to configure program execution
41
+ # in the context specified by domain.
42
+ #
43
+ # The optspec is a JSON-serialized option specifier (see Optconfig). Other
44
+ # arguments are passed to the optconfig module to determine the
45
+ # final configuration.
46
+ #
47
+ # = AUTHOR
48
+ #
49
+ # Jeremy Brinkley, <jbrinkley@evernote.com>
50
+ #
data/lib/bashon.rb ADDED
@@ -0,0 +1,346 @@
1
+ #!ruby
2
+ # /* Copyright 2013 Proofpoint, Inc. All rights reserved.
3
+ # Copyright 2014 Evernote Corp. All rights reserved.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ # */
17
+
18
+ module BashOn
19
+ def name_key(name)
20
+ name.join('_').gsub(/[^a-zA-Z0-9\_]/, '_')
21
+ end
22
+
23
+ @@manpage = <<EOF
24
+ =head1 NAME
25
+
26
+ bashon - Serialization library for bash object notation
27
+
28
+ =head1 SYNOPSIS
29
+
30
+ require 'bashon'
31
+
32
+ puts var.to_bashon('var')
33
+
34
+ =head1 DESCRIPTION
35
+
36
+ This module enables you to serialize some ruby objects as "bashon" objects
37
+ ("bashon meaning bash object notation"). Mostly, this means that bash code is
38
+ emitted that creates functions returning (echoing) the proper value.
39
+
40
+ The name that is passed to the B<to_bashon> method is the name of the function
41
+ that will output the serialized value. It also forms the root of a hierarchy of
42
+ function names that are used for serializing complex (array and hash) types.
43
+
44
+ The output is expected to be evaluated using the L<eval> command in bash.
45
+
46
+ =head2 Types
47
+
48
+ =head3 Nils
49
+
50
+ An unset command is emitted.
51
+
52
+ =head3 Booleans
53
+
54
+ A function is created with the specified name that can be used as a truth
55
+ test. That is, the function exits with a true value when serializing the
56
+ C<true> literal and with a false value when serializing the C<false>
57
+ literal. This enables you to use the resulting functions directly in
58
+ conditionals in bash.
59
+
60
+ =head3 Other simple types
61
+
62
+ A function is created with the specified name that outputs the value of the
63
+ type using B<to_s>.
64
+
65
+ =head3 Complex types
66
+
67
+ The important thing to note about complex types (hashes and arrays) is that
68
+ the values in the collection are always the I<names of functions> that, when
69
+ invoked, will yield a bashon-serialized value. This is especially important
70
+ to remember when the value of a hash entry or array element is itself a
71
+ complex type.
72
+
73
+ =head4 Arrays
74
+
75
+ A function is created with the specified name that outputs a space-separated,
76
+ ordered list of function names. Each function name is also created; when the
77
+ function is invoked, it will yield the value of that element.
78
+
79
+ head4 Hashes
80
+
81
+ A function is created with the specified name that outputs a space-separated
82
+ list of hash keys. When the same function is invoked with a hash key as an
83
+ argument, it outputs the name of a function. That function, when invoked,
84
+ will yield the value of that element.
85
+
86
+ =head1 EXAMPLES
87
+
88
+ In the following example, each example is shown with three blocks: the ruby
89
+ code snippet emitting the serialization, its standard output, and a shell
90
+ session in which the resulting code has been eval'd
91
+
92
+ This shows how a boolean value is serialized:
93
+
94
+ puts true.to_bashon('var')
95
+
96
+ function var { return 0; }
97
+
98
+ $ if var; then echo got it; fi
99
+ got it
100
+
101
+ This shows how a nil value is serialized:
102
+
103
+ puts nil.to_bashon('var')
104
+
105
+ unset var
106
+
107
+ $ if [ -z "$(var)" ]; then echo No var; fi
108
+ No var
109
+
110
+ This shows how a string is serialized and used:
111
+
112
+ puts "catalog data".to_bashon('var')
113
+
114
+ function var { echo catalog data; }
115
+
116
+ $ string=$(var)
117
+ $ echo $string
118
+ catalago data
119
+
120
+ This shows how an array of strings is serialized:
121
+
122
+ puts ["file1", "file2", "file3"].to_bashon('var')
123
+
124
+ function var { echo var_0 var_1 var_2; } ; function var_0 { echo file1; };function var_1 { echo file2; };function var_2 { echo file3; }
125
+
126
+ $ for filefun in $(var); do file=$($filefun); touch $file; done; ls file*
127
+ file1 file2 file3
128
+
129
+ And a hash of strings:
130
+
131
+ var = { "log" => "logfile.txt",
132
+ "err" => "errfile.txt",
133
+ "input" => "data.txt" }
134
+ puts var.to_bashon('var')
135
+
136
+ function var { case "$1" in log) echo var_log;; err) echo var_err;; input) echo var_input;; '') echo log err input;; esac; }; function var_log { echo logfile.txt; };function var_err { echo errfile.txt; };function var_input { echo data.txt; }
137
+
138
+ $ for key in $(var); do valfun=$(var $key); val=$($valfun); echo $key=$val; done
139
+ log=logfile.txt
140
+ err=errfile.txt
141
+ input=data.txt
142
+ $ echo "Error message" >>$($(var err))
143
+
144
+ This shows how to deal with a heterogeneous array:
145
+
146
+ var = ['one', { 'type' => 'number', 'value' => 2 }, false]
147
+ puts var.to_bashon('var')
148
+
149
+ function var { echo var_0 var_1 var_2; } ; function var_0 { echo one; };function var_1 { case "$1" in value) echo var_1_value;; type) echo var_1_type;; '') echo value type;; esac; }; function var_1_value { echo 2; };function var_1_type { echo number; };function var_2 { return 1; }
150
+
151
+ $ for elfun $(var)
152
+ > do
153
+ > $elfun
154
+
155
+ A deeply nested hash:
156
+
157
+ cfg = { "prod" => { "db" => { "host" => "dbhost001",
158
+ "port" => 3389 },
159
+ "url" => "https://api/",
160
+ "log" => { "file" => "/var/log/app.log",
161
+ "debug" => false },
162
+ "notify" => [ "www", "ops" ] },
163
+ "qa" => { "db" => { "host" => "qa02",
164
+ "port" => 18009 },
165
+ "url" => "https://qa02:18008/v2/",
166
+ "log" => { "file" => "~qa/build9/log/app.log",
167
+ "debug" => true },
168
+ "notify" => [ "build", "test" ] }
169
+ }
170
+ puts cfg.to_bashon('cfg')
171
+
172
+ function cfg { case "$1" in prod) echo cfg_prod;; qa) echo cfg_qa;; '') echo prod qa;; esac; }; function cfg_prod { case "$1" in notify) echo cfg_prod_notify;; log) echo cfg_prod_log;; url) echo cfg_prod_url;; db) echo cfg_prod_db;; '') echo notify log url db;; esac; }; function cfg_prod_notify { echo cfg_prod_notify_0 cfg_prod_notify_1; } ; function cfg_prod_notify_0 { echo www; };function cfg_prod_notify_1 { echo ops; };function cfg_prod_log { case "$1" in debug) echo cfg_prod_log_debug;; file) echo cfg_prod_log_file;; '') echo debug file;; esac; }; function cfg_prod_log_debug { return 1; };function cfg_prod_log_file { echo /var/log/app.log; };function cfg_prod_url { echo https://api/; };function cfg_prod_db { case "$1" in port) echo cfg_prod_db_port;; host) echo cfg_prod_db_host;; '') echo port host;; esac; }; function cfg_prod_db_port { echo 3389; };function cfg_prod_db_host { echo dbhost001; };function cfg_qa { case "$1" in notify) echo cfg_qa_notify;; log) echo cfg_qa_log;; url) echo cfg_qa_url;; db) echo cfg_qa_db;; '') echo notify log url db;; esac; }; function cfg_qa_notify { echo cfg_qa_notify_0 cfg_qa_notify_1; } ; function cfg_qa_notify_0 { echo build; };function cfg_qa_notify_1 { echo test; };function cfg_qa_log { case "$1" in debug) echo cfg_qa_log_debug;; file) echo cfg_qa_log_file;; '') echo debug file;; esac; }; function cfg_qa_log_debug { return 0; };function cfg_qa_log_file { echo ~qa/build9/log/app.log; };function cfg_qa_url { echo https://qa02:18008/v2/; };function cfg_qa_db { case "$1" in port) echo cfg_qa_db_port;; host) echo cfg_qa_db_host;; '') echo port host;; esac; }; function cfg_qa_db_port { echo 18009; };function cfg_qa_db_host { echo qa02; }
173
+
174
+ $ env=prod
175
+ $ curl $($(cfg $env) url)/report.sql | \
176
+ > mysql -h $($($(cfg $env) db) host) -p $($($(cfg $env) db) port) | \
177
+ > tee -a $($($(cfg $env) log) file) | \
178
+ > mailx -s "Report" `for m in $($($(cfg $env) notify)); do echo $($m); done`
179
+ $ $($($(cfg $env) log) debug) && echo `date` `whoami`>>$($($(cfg $env) log) file)
180
+
181
+ =head1 BUGS
182
+
183
+ Right now string serialization makes no attempt to quote strings. If the
184
+ string contains a shell metacharacter, results can be unexpected. In fact,
185
+ results can be a bit unexpected anyway. I've tried different quoting schemes
186
+ and they all get real ugly real fast. In fact, things can be unexpected with
187
+ just a run of spaces or a newline.
188
+
189
+ You can't have a space in hash keys, but there's no helpful error message
190
+ until you eval the result. The problem is that it's really hard to return a
191
+ list of things that is useful for a 'for' loop where the list of things might
192
+ contain a space.
193
+
194
+ There's no good way of determining the "type" of something you encounter. You
195
+ can test the output of a function, and if each of the words in it is itself a
196
+ function, it's an array. However, if they're not, there's no way to
197
+ distinguish between a string that has multiple words and a list of hash keys.
198
+
199
+ Possibly arrays should work exactly like hashes, returning a list of indexes
200
+ instead of keys. Right now you can't access an array by index at all.
201
+
202
+ =head1 NOTES
203
+
204
+ Why not use variables instead of functions? I went down that road, but the
205
+ problem is that even bash 4, with its associative arrays, doesn't provide
206
+ enough flexibility to do nested structures, and you run into all kinds of
207
+ quoting difficulties and whatnot when you try to serialize to variables.
208
+
209
+ For example, you can serialize an array of strings in bash like this:
210
+
211
+ declare -a var
212
+ var=([0]=one [1]=two [2]=three)
213
+
214
+ But what if the values in the array are themselves arrays? Or associative
215
+ arrays? Bash can't handle that. Also, all your quoting has to be right in
216
+ order for that assignment to work, which is really difficult to get right
217
+ in a general solution with the possibility of arbitrarily nested structures.
218
+
219
+ Also, I'll note that arrays and associative arrays aren't really that
220
+ convenient in bash. I'm not sure that $(var key1) is really any harder to use
221
+ or read than ${var[key1]}.
222
+
223
+ So, I went with functions, and for consistency's sake serialized everything as
224
+ functions. That way you always know that when you get something back from
225
+ fetching a hash entry, you have to call it, there's no ambiguity about whether
226
+ it's a raw value or a hash.
227
+
228
+ There's another approach to this, which is to serialize everything into some
229
+ kind of text structure and then provide bash functions/commands to operate on
230
+ this blob of text. For example:
231
+
232
+ puts var.to_json
233
+
234
+ var='{"key1":"val1","key2":["val2one","val2two"],"key3":{"subkey1":1,"subkey2":2}}'
235
+
236
+ $ json_get "$var" key1
237
+ val1
238
+ $ json_get "$var" key2
239
+ ["val2one","val2two"]
240
+ $ json_get "$var" key2 0
241
+ val2one
242
+ $ json_get "$var" key3
243
+ {"subkey1":1,"subkey2":2}
244
+ $ json_keys "$var"
245
+ key1
246
+ key2
247
+ key3
248
+ $ json_keys "$var" key3
249
+ subkey1
250
+ subkey2
251
+ $ json_keys "$var" key3 subkey2
252
+ 2
253
+
254
+ This is viable but I think it's less convenient to use from bash.
255
+
256
+ =head1 AUTHOR
257
+
258
+ Jeremy Brinkley, E<lt>jbrinkley@evernote.comE<gt>
259
+
260
+ =cut
261
+ EOF
262
+ end
263
+
264
+ class Object
265
+ include BashOn
266
+ def to_bashon(*name)
267
+ if name.empty?
268
+ "#{self.to_s}"
269
+ else
270
+ "function #{name_key(name)} { echo #{self.to_bashon()}; }"
271
+ end
272
+ end
273
+ end
274
+
275
+ class TrueClass
276
+ include BashOn
277
+ def to_bashon(*name)
278
+ if name.empty?
279
+ "true"
280
+ else
281
+ "function #{name_key(name)} { return 0; }"
282
+ end
283
+ end
284
+ end
285
+
286
+ class FalseClass
287
+ include BashOn
288
+ def to_bashon(*name)
289
+ if name.empty?
290
+ "false"
291
+ else
292
+ "function #{name_key(name)} { return 1; }"
293
+ end
294
+ end
295
+ end
296
+
297
+ class NilClass
298
+ include BashOn
299
+ def to_bashon(*name)
300
+ if name.empty?
301
+ ""
302
+ else
303
+ "unset #{name_key(name)}"
304
+ end
305
+ end
306
+ end
307
+
308
+ class String
309
+ include BashOn
310
+
311
+ def bq
312
+ self.gsub("'", "'\''")
313
+ end
314
+
315
+ def to_bashon(*name)
316
+ if name.empty?
317
+ self
318
+ else
319
+ "function #{name_key(name)} { echo '#{self.to_bashon.bq}'; }"
320
+ end
321
+ end
322
+ end
323
+
324
+ # Enumerable?
325
+ class Array
326
+ include BashOn
327
+ def to_bashon(*name)
328
+ "function #{name_key(name)} { echo " +
329
+ (0 .. (self.nitems-1)).map { |i|
330
+ name_key(name + [i.to_s]) }.join(' ') + "; } " +
331
+ (self.empty? ? ' ' : '; ') +
332
+ (0 .. (self.nitems-1)).map { |i|
333
+ self[i].to_bashon(*(name + [i.to_s])) }.join(';')
334
+ end
335
+ end
336
+
337
+ class Hash
338
+ include BashOn
339
+ def to_bashon(*name)
340
+ "function #{name_key(name)} { case \"$1\" in " +
341
+ self.map { |k, v| "#{k}) echo '#{name_key(name + [k])}';;" }.join(' ') +
342
+ " '') echo '#{self.keys.join(' ')}';;" +
343
+ " esac; }" + (self.empty? ? ' ' : '; ') +
344
+ self.map { |k, v| self[k].to_bashon(*(name + [k])) }.join(';')
345
+ end
346
+ end
data/lib/longopt.rb ADDED
@@ -0,0 +1,239 @@
1
+ #!ruby
2
+ # /* Copyright 2013 Proofpoint, Inc. All rights reserved.
3
+ # Copyright 2014 Evernote Corp. All rights reserved.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ # */
17
+
18
+ require 'getoptlong'
19
+
20
+ class Longopt < Hash
21
+
22
+ attr_accessor :optspec
23
+
24
+ def Longopt.new_with_args(argv, *optspecs)
25
+ obj = Longopt.allocate
26
+ obj.getopts_with_argv(argv, *optspecs)
27
+ obj
28
+ end
29
+
30
+ def getopts_with_args(argv, *optspeclist)
31
+ saveargv = ARGV.dup
32
+ # produces warning
33
+ ARGV.replace(argv)
34
+ getopts(*optspeclist)
35
+ ARGV.replace(saveargv)
36
+ self
37
+ end
38
+
39
+ def Longopt.default(opth)
40
+ obj = Longopt.allocate
41
+ obj.merge(opth)
42
+ end
43
+
44
+ def initialize(*optspecs)
45
+ getopts(*optspecs)
46
+ end
47
+
48
+ def getopts(*optspecs)
49
+ @optspec = { }
50
+
51
+ if optspecs.size == 1 and optspecs[0].respond_to? :to_ary
52
+ optspecs = optspecs[0]
53
+ end
54
+
55
+ optspecs.each do |optspec|
56
+ if m = /^([\w_\-]+)([=:!+].*)/.match(optspec)
57
+ opt = m[1]
58
+ @optspec[opt] = { }
59
+
60
+ act, typ, collect_typ = m[2].split('')
61
+
62
+ case act
63
+
64
+ when '='
65
+ @optspec[opt]['gol-argument-type'] =
66
+ GetoptLong::REQUIRED_ARGUMENT
67
+ @optspec[opt]['type-letter'] = typ
68
+ @optspec[opt]['collection-type'] = collect_typ
69
+
70
+ when ':'
71
+ @optspec[opt]['gol-argument-type'] =
72
+ GetoptLong::OPTIONAL_ARGUMENT
73
+ @optspec[opt]['type-letter'] = typ
74
+ @optspec[opt]['collection-type'] = collect_typ
75
+
76
+ when '!'
77
+ @optspec[opt]['gol-argument-type'] = GetoptLong::NO_ARGUMENT
78
+ @optspec[opt]['type-letter'] = 'b'
79
+ @optspec['no' + opt] = { }
80
+ @optspec['no' + opt]['gol-argument-type'] =
81
+ GetoptLong::NO_ARGUMENT
82
+
83
+ when '+'
84
+ @optspec[opt]['gol-argument-type'] = GetoptLong::NO_ARGUMENT
85
+ @optspec[opt]['type-letter'] = 'a'
86
+
87
+ end
88
+ else
89
+ @optspec[optspec] = { }
90
+ @optspec[optspec]['gol-argument-type'] = GetoptLong::NO_ARGUMENT
91
+ @optspec[optspec]['type-letter'] = 'b'
92
+ end
93
+ end
94
+
95
+ gol_arguments = @optspec.map { |opt, optspec|
96
+ ['--' + opt,
97
+ optspec['gol-argument-type']]
98
+ }
99
+
100
+ GetoptLong.new(*gol_arguments).each do |k, v|
101
+ k = k.sub(/^--/, '')
102
+
103
+ case k
104
+
105
+ when *booleans
106
+ self[k] = true
107
+
108
+ when *nobooleans
109
+ self[k[2..-1]] = false
110
+
111
+ when *simples
112
+ self[k] = simple_value(k, v)
113
+
114
+ when *lists
115
+ self[k] = [ ] unless key? k
116
+ self[k].push(simple_value(k, v))
117
+
118
+ when *mappings
119
+ self[k] = { } unless key? k
120
+ mkey, mvalue = v.split(/=/, 2)
121
+ self[k][mkey] = simple_value(k, mvalue)
122
+
123
+ end
124
+ end
125
+
126
+ end
127
+
128
+ def simple_value(opt, optarg)
129
+ case @optspec[opt]['type-letter']
130
+ when 's'
131
+ optarg
132
+ when 'i'
133
+ Integer(optarg)
134
+ when 'f'
135
+ Float(optarg)
136
+ when 'a'
137
+ if key? opt
138
+ rv = self[opt] + 1
139
+ else
140
+ rv = 1
141
+ end
142
+ rv
143
+ else
144
+ nil
145
+ end
146
+ end
147
+
148
+ def booleans
149
+ @optspec.reject { |k, v| v['type-letter'] != 'b' }.keys
150
+ end
151
+
152
+ def nobooleans
153
+ @optspec.reject { |k, v| v['type-letter'] != 'b' }.keys.map { |el| 'no' + el }
154
+ end
155
+
156
+ def simples
157
+ @optspec.reject { |k, v| ! v['collection-type'].nil? ||
158
+ v['type-letter'].nil? ||
159
+ v['type-letter'] == 'b' }.keys
160
+ end
161
+
162
+ def lists
163
+ @optspec.reject { |k, v| v['collection-type'] != '@' }.keys
164
+ end
165
+
166
+ def mappings
167
+ @optspec.reject { |k, v| v['collection-type'] != '%' }.keys
168
+ end
169
+
170
+ @@manpage = <<'EOF'
171
+ =head1 NAME
172
+
173
+ Longopt - Convenience class similar to Perl Getopt::Long
174
+
175
+ =head1 SYNOPSIS
176
+
177
+ require 'longoopt'
178
+
179
+ opt = Longopt.new('verbose', 'force!', 'logfile=s',
180
+ 'define=s%', 'user=s@')
181
+
182
+ print "Verbose enabled" if opt['verbose']
183
+
184
+ unlink(filefoo) if opt['force']
185
+
186
+ File.open(opt['logfile'], 'w') { |fh| fh << "Log message" }
187
+
188
+ opt['user'].each { |user| notify_user(user) }
189
+
190
+ opt['define'].each { |key, value| puts "#{key} = #{value}" }
191
+
192
+ =head1 DESCRIPTION
193
+
194
+ The Longopt class wraps GetoptLong so that you can use option specifiers
195
+ similar to the Perl Getopt::Long module.
196
+
197
+ Longopt is a Hash subclass.
198
+
199
+ =head2 Class Methods
200
+
201
+ =over 4
202
+
203
+ =item new(*optspecs)
204
+
205
+ Parse ARGV for options and set options according to provided optspecs.
206
+
207
+ =item new_with_args(args, *optspecs)
208
+
209
+ Like new(), but use provided arguments rather than ARGV.
210
+
211
+ =item default(opthash)
212
+
213
+ Create a Longopt object using the provided default option values in the
214
+ opthash. Use getopts(), below, to parse arguments.
215
+
216
+ =back
217
+
218
+ =head2 Object Methods
219
+
220
+ =item getopts(*optspecs)
221
+
222
+ Parse ARGV to set option values according to provided optspecs.
223
+
224
+ =item getopts_with_args(args, *optspecs)
225
+
226
+ Parse args to set option values according to provided optspecs.
227
+
228
+ =back
229
+
230
+ =head1 AUTHOR
231
+
232
+ Jeremy Brinkley, E<lt>jbrinkley@evernote.comE<gt>
233
+
234
+ =cut
235
+
236
+ EOF
237
+
238
+ end
239
+
@@ -0,0 +1,5 @@
1
+ class Optconfig
2
+
3
+ VERSION = '1.5.3'
4
+
5
+ end
data/lib/optconfig.rb ADDED
@@ -0,0 +1,182 @@
1
+ #!ruby
2
+ # /* Copyright 2013 Proofpoint, Inc. All rights reserved.
3
+ # Copyright 2014 Evernote Corp. All rights reserved.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ # */
17
+
18
+ # = NAME
19
+ #
20
+ # Optconfig - Parse options and config files
21
+ #
22
+ # = DESCRIPTION
23
+ #
24
+ # This module implements the Optconfig standardized approach to
25
+ # command-line option parsing and JSON-based configuration file
26
+ # interpretation. See the reference documentation in the
27
+ # Optconfig master distribution for details.
28
+ #
29
+
30
+ require 'rubygems'
31
+ require 'longopt'
32
+ require 'json'
33
+
34
+ class Optconfig < Hash
35
+
36
+ require 'optconfig/version'
37
+
38
+ attr_accessor :domain, :optspec, :config, :default
39
+
40
+ def add_standard_opts(submitted_optspec)
41
+ optspec = submitted_optspec
42
+ standard_opts = {
43
+ 'config=s' => nil,
44
+ 'debug+' => 0,
45
+ 'verbose+' => 0,
46
+ 'version' => false,
47
+ 'help' => false,
48
+ 'dry-run!' => false }
49
+ standard_opts.each_pair do |opt, defval|
50
+ if ! optspec.has_key? opt
51
+ optspec[opt] = defval
52
+ end
53
+ end
54
+ optspec
55
+ end
56
+
57
+ def initialize(domain, submitted_optspec, version=nil)
58
+ @domain = domain
59
+ @optspec = add_standard_opts(submitted_optspec)
60
+ @caller_version = version || $VERSION || 'Unknown version'
61
+
62
+ submitted_optspec.each_pair do |optspec, val|
63
+ opt, dummy = optspec.split(/[\=\+\!]/, 2)
64
+ self[opt] = val
65
+ end
66
+
67
+ cfgfilepath = [ '/usr/local/etc/' + domain + '.conf',
68
+ '/etc/' + domain + '.conf' ]
69
+
70
+ if ENV.has_key? 'HOME' and ! ENV['HOME'].nil?
71
+ cfgfilepath.unshift(ENV['HOME'] + '/.' + domain)
72
+ end
73
+ @config = nil
74
+
75
+ cmdlineopt = Longopt.new(optspec.keys)
76
+
77
+ if cmdlineopt.has_key? 'config'
78
+ @config = cmdlineopt['config']
79
+ raise "File not found: #{cmdlineopt['config']}" unless
80
+ File.exist? cmdlineopt['config']
81
+ read_config(cmdlineopt['config'])
82
+ else
83
+ cfgfilepath.each do |file|
84
+ if File.readable? file
85
+ @config = file
86
+ read_config(file)
87
+ break
88
+ end
89
+ end
90
+ end
91
+
92
+ cmdlineopt.each_pair do |opt, val|
93
+ merge_cmdlineopt(opt, val)
94
+ end
95
+
96
+ if self.has_key? 'version' and self['version']
97
+ puts @caller_version
98
+ Process.exit(0)
99
+ end
100
+
101
+ if self.has_key? 'help' and self['help']
102
+ help_pattern = /(?:^=head1 +SYNOPSIS|^# *=+ *SYNOPSIS)(.*?)(?:^=head1|^# *=+)/m
103
+ myscript = File.expand_path($0)
104
+ begin
105
+ script_text = File.open(myscript, 'r') { |fh| fh.read }
106
+ if /This file was generated by RubyGems/.match script_text
107
+ # Well, thank you RubyGems. Now I have to guess where
108
+ # my code ended up.
109
+ #
110
+ # The ruby gems wrapper for these scripts has some
111
+ # special way of restricting versions here; I'm hoping
112
+ # that since that has been done (and I no longer have
113
+ # access to the special first argument that overrides
114
+ # the version) that the fact that the 'gem' call has
115
+ # already been called with that version will help
116
+ # the following work correctly. Because there's no
117
+ # real other alternative.
118
+ #
119
+ # This 'parsing' is really a load of crap. Sure hope
120
+ # Rubygems never changes anything.
121
+ # -jdb/20141010
122
+ if m = /^\s*load +(.*)$/.match(script_text)
123
+ script_file = eval("version = nil\n" + m[1])
124
+ script_text = File.open(script_file, 'r') { |fh| fh.read }
125
+ end
126
+ end
127
+ m = help_pattern.match(script_text)
128
+ if m
129
+ puts m[1].gsub(/^# ?/m, '').strip
130
+ else
131
+ puts 'No help'
132
+ end
133
+ rescue Errno::ENOENT
134
+ puts "No help (could not search #{myscript})"
135
+ end
136
+ Process.exit(0)
137
+ end
138
+ end
139
+
140
+ def read_config(file)
141
+ fileconfig = File.open(file) { |fh| JSON.load(fh) }
142
+ fileconfig.each_pair do |opt, val|
143
+ self[opt] = val
144
+ end
145
+ end
146
+
147
+ def merge_cmdlineopt(opt, val)
148
+ if self.has_key? opt
149
+ if self[opt].respond_to? :keys
150
+ if val.respond_to? :keys
151
+ # Both hashes, merge
152
+ val.each_pair { |k, v| self[opt][k] = v }
153
+ else
154
+ self[opt] = val
155
+ end
156
+ elsif self[opt].respond_to? :unshift
157
+ if val.respond_to? :each and val.respond_to? :reverse
158
+ val.reverse.each { |v| self[opt].unshift(v) }
159
+ else
160
+ self[opt] = val
161
+ end
162
+ else
163
+ self[opt] = val
164
+ end
165
+ else
166
+ self[opt] = val
167
+ end
168
+
169
+ self[opt]
170
+ end
171
+
172
+ def vrb(level, *msg)
173
+ puts msg.join("\n") if self['verbose'] >= level
174
+ end
175
+
176
+ def dbg(level, *msg)
177
+ if self['debug'] >= level
178
+ puts "DBG(#{@domain}): " + msg.join("DBG(#{@domain}): ")
179
+ end
180
+ end
181
+
182
+ end
metadata ADDED
@@ -0,0 +1,103 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: noms-optconfig
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.5.3
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jeremy Brinkley
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2016-02-03 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '1.7'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '1.7'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: '10.0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '10.0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: '2.11'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '2.11'
62
+ description: Optconfig presents a standardized way to parse configuration files and
63
+ command-line arguments
64
+ email:
65
+ - jbrinkley@evernote.com
66
+ executables:
67
+ - ruby-showconfig
68
+ extensions: []
69
+ extra_rdoc_files: []
70
+ files:
71
+ - lib/optconfig.rb
72
+ - lib/optconfig/version.rb
73
+ - lib/longopt.rb
74
+ - lib/bashon.rb
75
+ - bin/ruby-showconfig
76
+ - bin/optconfig.sh
77
+ - bin/bash-showconfig
78
+ homepage: http://github.com/evernote/optconfig
79
+ licenses:
80
+ - Apache-2
81
+ post_install_message:
82
+ rdoc_options: []
83
+ require_paths:
84
+ - lib
85
+ required_ruby_version: !ruby/object:Gem::Requirement
86
+ none: false
87
+ requirements:
88
+ - - ! '>='
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ required_rubygems_version: !ruby/object:Gem::Requirement
92
+ none: false
93
+ requirements:
94
+ - - ! '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ requirements: []
98
+ rubyforge_project:
99
+ rubygems_version: 1.8.23
100
+ signing_key:
101
+ specification_version: 3
102
+ summary: Parse commmand-line options and config files
103
+ test_files: []