noms-optconfig 1.5.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []