sfp 0.1.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -2,7 +2,7 @@ SFP Parser and Planner for Ruby
2
2
  ===============================
3
3
  - Author: Herry (herry13@gmail.com)
4
4
  - Last update: 06 May 2013
5
- - Version: 0.1.3
5
+ - Version: 0.2.0
6
6
  - License: [BSD License](https://github.com/herry13/sfp-ruby/blob/master/LICENSE)
7
7
 
8
8
  A Ruby gem that provides a Ruby interface to parse SFP language. It also provides
@@ -29,6 +29,7 @@ To install
29
29
 
30
30
  Requirements
31
31
  ------------
32
+ - Ruby (>= 1.8.7)
32
33
  - Rubygems
33
34
  - antlr3
34
35
  - json
@@ -44,7 +45,7 @@ To use as a command line to solve a planning task
44
45
  -------------------------------------------------
45
46
  - parse a SFP file, and then print the output in JSON
46
47
 
47
- $ sfp -p <sfp-file>
48
+ $ sfp --json <sfp-file>
48
49
 
49
50
  - solve a planning task, and then print the plan in JSON
50
51
 
@@ -86,15 +87,23 @@ To use as Ruby library
86
87
 
87
88
  To solve planning task and execute it using Bash scripts
88
89
  --------------------------------------------------------
89
- - Parse a SFP file, print the plan in JSON, and then execute the plan by
90
- invoking Bash scripts the current directory or as specified in environment
91
- variable SFP_HOME. *Note*: the namespaces are represented by directories
92
- e.g. executing procedure *$.a.b.foo* will be invoking a Bash script
93
- *./a/b/foo* (or *$SFP_HOME/a/b/foo*).
90
+ Parse a SFP file, print the plan in JSON, and then execute the plan by
91
+ invoking Bash scripts in directory **modules**. *Note*: the namespaces are
92
+ represented by directories e.g. executing procedure *$.a.b.foo* will be
93
+ invoking a Bash script *modules/a/b/foo*.
94
94
 
95
- $ sfp --exec-bash <sfp-file>
95
+ $ sfp --solve-execute <sfp-file>
96
96
 
97
97
 
98
+ To execute previously generated plan file in JSON format
99
+ --------------------------------------------------------
100
+ If you have generated a plan saved in a file, then you could execute the plan by
101
+ invoking Bash scripts in directory **modules**. *Note*: the namespaces are
102
+ represented by directories e.g. executing procedure *$.a.b.foo* will be
103
+ invoking a Bash script *modules/a/b/foo*.
104
+
105
+ $ sfp --execute <plan-file>
106
+
98
107
 
99
108
 
100
109
  Example
@@ -228,7 +237,7 @@ Example
228
237
  command with an option *--exec-bash* and with an argument the path of
229
238
  the task file:
230
239
 
231
- $ sfp --exec-bash task,sfp
240
+ $ sfp --solve-execute task,sfp
232
241
 
233
242
  It will generate and execute the plan by invoking the Bash scripts in
234
243
  the current directory (or as specified in environment variable SFP_HOME)
@@ -237,10 +246,3 @@ Example
237
246
  2. *modules/pc/redirect "$.b"*
238
247
  3. *modules/a/stop*
239
248
 
240
-
241
- About Nuri
242
- ----------
243
- [Nuri](https://github.com/herry13/nuri) is a configuration tool that
244
- binds these procedures with particular shell commands and Ruby objects
245
- in order to implement the required configuration changes based on the
246
- goal and global constraints given by the user.
data/bin/sfp CHANGED
@@ -3,8 +3,59 @@
3
3
  libdir = File.expand_path(File.dirname(__FILE__))
4
4
  require "#{libdir}/../lib/sfp"
5
5
 
6
+ opts = Trollop::options do
7
+ version "sfp 0.2.0 (c) 2013 Herry"
8
+ banner <<-EOS
9
+ Parse a SFP file, solve the planning task, and print the plan (if found) in JSON format.
10
+
11
+ Usage:
12
+ sfp [options] <file>
13
+
14
+ where [options] are:
15
+ EOS
16
+
17
+ opt :json, "parse a SFP file and print it in JSON format"
18
+ opt :solve_execute, "parse a SFP File, solve the planning task, " +
19
+ "and execute the plan with given execution framework"
20
+ opt :bash, "set Bash as the execution framework", :default => true
21
+ opt :execute, "execute a plan in given file"
22
+ end
23
+
24
+ if opts[:json]
25
+ home_dir = File.expand_path(File.dirname(ARGV[0]))
26
+ parser = Sfp::Parser.new({:home_dir => home_dir})
27
+ parser.parse(File.read(ARGV[0]))
28
+ puts parser.to_json({:pretty => true})
29
+
30
+ elsif opts[:solve_execute]
31
+ abort "There is no available execution framework!" if not opts[:bash]
32
+
33
+ planner = Sfp::Planner.new
34
+ plan = planner.solve({:file => ARGV[0]})
35
+ puts "Plan: #{plan.inspect}"
36
+ executor = Sfp::BashExecutor.new
37
+ puts "Execution:"
38
+ executor.execute_plan({:plan => plan, :print_output => true})
39
+
40
+ elsif opts[:execute]
41
+ abort "There is no available execution framework!" if not opts[:bash]
42
+
43
+ plan = JSON.parse(File.read(ARGV[0]))
44
+ executor = Sfp::BashExecutor.new
45
+ executor.execute_plan({:plan => plan, :print_output => true})
46
+
47
+ elsif ARGV.length > 0
48
+ planner = Sfp::Planner.new
49
+ puts planner.solve({:file => ARGV[0], :pretty_json => true})
50
+
51
+ else
52
+ Trollop::help
53
+
54
+ end
55
+
56
+ =begin
6
57
  # application mode
7
- if ARGV.length > 1 and ARGV[0] == '-p'
58
+ if ARGV.length > 1 and (ARGV[0] == '-p' or ARGV[0] == '-json')
8
59
  home_dir = File.expand_path(File.dirname(ARGV[1]))
9
60
  parser = Sfp::Parser.new({:home_dir => home_dir})
10
61
  parser.parse(File.read(ARGV[1]))
@@ -30,15 +81,15 @@ elsif ARGV.length > 0
30
81
  puts planner.solve({:file => ARGV[0], :pretty_json => true})
31
82
 
32
83
  else
33
- puts "Usage: #{$0} [options] <sfp-file>
84
+ puts "Usage: #{$0} [options] <file>
34
85
 
35
86
  options:
36
- <none> parse file and solve the planning task
37
- -p parse file and print it in JSON format
38
- --exec-ruby parse file, solve the planning task, and
39
- execute the plan with Sfp::RubyExecutor
40
- --exec-bash parse file, solve the planning task, and
87
+ <none> parse a SFP file and solve the planning task
88
+ -p, -json parse a SFP file and print it in JSON format
89
+ -x
90
+ --exec-bash parse a SFP file, solve the planning task, and
41
91
  execute the plan with Sfp::BashExecutor
42
92
 
43
93
  "
44
94
  end
95
+ =end
@@ -0,0 +1,786 @@
1
+ ## lib/trollop.rb -- trollop command-line processing library
2
+ ## Author:: William Morgan (mailto: wmorgan-trollop@masanjin.net)
3
+ ## Copyright:: Copyright 2007 William Morgan
4
+ ## License:: the same terms as ruby itself
5
+
6
+ require 'date'
7
+
8
+ module Trollop
9
+
10
+ VERSION = "2.0"
11
+
12
+ ## Thrown by Parser in the event of a commandline error. Not needed if
13
+ ## you're using the Trollop::options entry.
14
+ class CommandlineError < StandardError; end
15
+
16
+ ## Thrown by Parser if the user passes in '-h' or '--help'. Handled
17
+ ## automatically by Trollop#options.
18
+ class HelpNeeded < StandardError; end
19
+
20
+ ## Thrown by Parser if the user passes in '-h' or '--version'. Handled
21
+ ## automatically by Trollop#options.
22
+ class VersionNeeded < StandardError; end
23
+
24
+ ## Regex for floating point numbers
25
+ FLOAT_RE = /^-?((\d+(\.\d+)?)|(\.\d+))([eE][-+]?[\d]+)?$/
26
+
27
+ ## Regex for parameters
28
+ PARAM_RE = /^-(-|\.$|[^\d\.])/
29
+
30
+ ## The commandline parser. In typical usage, the methods in this class
31
+ ## will be handled internally by Trollop::options. In this case, only the
32
+ ## #opt, #banner and #version, #depends, and #conflicts methods will
33
+ ## typically be called.
34
+ ##
35
+ ## If you want to instantiate this class yourself (for more complicated
36
+ ## argument-parsing logic), call #parse to actually produce the output hash,
37
+ ## and consider calling it from within
38
+ ## Trollop::with_standard_exception_handling.
39
+ class Parser
40
+
41
+ ## The set of values that indicate a flag option when passed as the
42
+ ## +:type+ parameter of #opt.
43
+ FLAG_TYPES = [:flag, :bool, :boolean]
44
+
45
+ ## The set of values that indicate a single-parameter (normal) option when
46
+ ## passed as the +:type+ parameter of #opt.
47
+ ##
48
+ ## A value of +io+ corresponds to a readable IO resource, including
49
+ ## a filename, URI, or the strings 'stdin' or '-'.
50
+ SINGLE_ARG_TYPES = [:int, :integer, :string, :double, :float, :io, :date]
51
+
52
+ ## The set of values that indicate a multiple-parameter option (i.e., that
53
+ ## takes multiple space-separated values on the commandline) when passed as
54
+ ## the +:type+ parameter of #opt.
55
+ MULTI_ARG_TYPES = [:ints, :integers, :strings, :doubles, :floats, :ios, :dates]
56
+
57
+ ## The complete set of legal values for the +:type+ parameter of #opt.
58
+ TYPES = FLAG_TYPES + SINGLE_ARG_TYPES + MULTI_ARG_TYPES
59
+
60
+ INVALID_SHORT_ARG_REGEX = /[\d-]/ #:nodoc:
61
+
62
+ ## The values from the commandline that were not interpreted by #parse.
63
+ attr_reader :leftovers
64
+
65
+ ## The complete configuration hashes for each option. (Mainly useful
66
+ ## for testing.)
67
+ attr_reader :specs
68
+
69
+ ## Initializes the parser, and instance-evaluates any block given.
70
+ def initialize *a, &b
71
+ @version = nil
72
+ @leftovers = []
73
+ @specs = {}
74
+ @long = {}
75
+ @short = {}
76
+ @order = []
77
+ @constraints = []
78
+ @stop_words = []
79
+ @stop_on_unknown = false
80
+
81
+ #instance_eval(&b) if b # can't take arguments
82
+ cloaker(&b).bind(self).call(*a) if b
83
+ end
84
+
85
+ ## Define an option. +name+ is the option name, a unique identifier
86
+ ## for the option that you will use internally, which should be a
87
+ ## symbol or a string. +desc+ is a string description which will be
88
+ ## displayed in help messages.
89
+ ##
90
+ ## Takes the following optional arguments:
91
+ ##
92
+ ## [+:long+] Specify the long form of the argument, i.e. the form with two dashes. If unspecified, will be automatically derived based on the argument name by turning the +name+ option into a string, and replacing any _'s by -'s.
93
+ ## [+:short+] Specify the short form of the argument, i.e. the form with one dash. If unspecified, will be automatically derived from +name+.
94
+ ## [+:type+] Require that the argument take a parameter or parameters of type +type+. For a single parameter, the value can be a member of +SINGLE_ARG_TYPES+, or a corresponding Ruby class (e.g. +Integer+ for +:int+). For multiple-argument parameters, the value can be any member of +MULTI_ARG_TYPES+ constant. If unset, the default argument type is +:flag+, meaning that the argument does not take a parameter. The specification of +:type+ is not necessary if a +:default+ is given.
95
+ ## [+:default+] Set the default value for an argument. Without a default value, the hash returned by #parse (and thus Trollop::options) will have a +nil+ value for this key unless the argument is given on the commandline. The argument type is derived automatically from the class of the default value given, so specifying a +:type+ is not necessary if a +:default+ is given. (But see below for an important caveat when +:multi+: is specified too.) If the argument is a flag, and the default is set to +true+, then if it is specified on the the commandline the value will be +false+.
96
+ ## [+:required+] If set to +true+, the argument must be provided on the commandline.
97
+ ## [+:multi+] If set to +true+, allows multiple occurrences of the option on the commandline. Otherwise, only a single instance of the option is allowed. (Note that this is different from taking multiple parameters. See below.)
98
+ ##
99
+ ## Note that there are two types of argument multiplicity: an argument
100
+ ## can take multiple values, e.g. "--arg 1 2 3". An argument can also
101
+ ## be allowed to occur multiple times, e.g. "--arg 1 --arg 2".
102
+ ##
103
+ ## Arguments that take multiple values should have a +:type+ parameter
104
+ ## drawn from +MULTI_ARG_TYPES+ (e.g. +:strings+), or a +:default:+
105
+ ## value of an array of the correct type (e.g. [String]). The
106
+ ## value of this argument will be an array of the parameters on the
107
+ ## commandline.
108
+ ##
109
+ ## Arguments that can occur multiple times should be marked with
110
+ ## +:multi+ => +true+. The value of this argument will also be an array.
111
+ ## In contrast with regular non-multi options, if not specified on
112
+ ## the commandline, the default value will be [], not nil.
113
+ ##
114
+ ## These two attributes can be combined (e.g. +:type+ => +:strings+,
115
+ ## +:multi+ => +true+), in which case the value of the argument will be
116
+ ## an array of arrays.
117
+ ##
118
+ ## There's one ambiguous case to be aware of: when +:multi+: is true and a
119
+ ## +:default+ is set to an array (of something), it's ambiguous whether this
120
+ ## is a multi-value argument as well as a multi-occurrence argument.
121
+ ## In thise case, Trollop assumes that it's not a multi-value argument.
122
+ ## If you want a multi-value, multi-occurrence argument with a default
123
+ ## value, you must specify +:type+ as well.
124
+
125
+ def opt name, desc="", opts={}
126
+ raise ArgumentError, "you already have an argument named '#{name}'" if @specs.member? name
127
+
128
+ ## fill in :type
129
+ opts[:type] = # normalize
130
+ case opts[:type]
131
+ when :boolean, :bool; :flag
132
+ when :integer; :int
133
+ when :integers; :ints
134
+ when :double; :float
135
+ when :doubles; :floats
136
+ when Class
137
+ case opts[:type].name
138
+ when 'TrueClass', 'FalseClass'; :flag
139
+ when 'String'; :string
140
+ when 'Integer'; :int
141
+ when 'Float'; :float
142
+ when 'IO'; :io
143
+ when 'Date'; :date
144
+ else
145
+ raise ArgumentError, "unsupported argument type '#{opts[:type].class.name}'"
146
+ end
147
+ when nil; nil
148
+ else
149
+ raise ArgumentError, "unsupported argument type '#{opts[:type]}'" unless TYPES.include?(opts[:type])
150
+ opts[:type]
151
+ end
152
+
153
+ ## for options with :multi => true, an array default doesn't imply
154
+ ## a multi-valued argument. for that you have to specify a :type
155
+ ## as well. (this is how we disambiguate an ambiguous situation;
156
+ ## see the docs for Parser#opt for details.)
157
+ disambiguated_default = if opts[:multi] && opts[:default].is_a?(Array) && !opts[:type]
158
+ opts[:default].first
159
+ else
160
+ opts[:default]
161
+ end
162
+
163
+ type_from_default =
164
+ case disambiguated_default
165
+ when Integer; :int
166
+ when Numeric; :float
167
+ when TrueClass, FalseClass; :flag
168
+ when String; :string
169
+ when IO; :io
170
+ when Date; :date
171
+ when Array
172
+ if opts[:default].empty?
173
+ raise ArgumentError, "multiple argument type cannot be deduced from an empty array for '#{opts[:default][0].class.name}'"
174
+ end
175
+ case opts[:default][0] # the first element determines the types
176
+ when Integer; :ints
177
+ when Numeric; :floats
178
+ when String; :strings
179
+ when IO; :ios
180
+ when Date; :dates
181
+ else
182
+ raise ArgumentError, "unsupported multiple argument type '#{opts[:default][0].class.name}'"
183
+ end
184
+ when nil; nil
185
+ else
186
+ raise ArgumentError, "unsupported argument type '#{opts[:default].class.name}'"
187
+ end
188
+
189
+ raise ArgumentError, ":type specification and default type don't match (default type is #{type_from_default})" if opts[:type] && type_from_default && opts[:type] != type_from_default
190
+
191
+ opts[:type] = opts[:type] || type_from_default || :flag
192
+
193
+ ## fill in :long
194
+ opts[:long] = opts[:long] ? opts[:long].to_s : name.to_s.gsub("_", "-")
195
+ opts[:long] = case opts[:long]
196
+ when /^--([^-].*)$/; $1
197
+ when /^[^-]/; opts[:long]
198
+ else; raise ArgumentError, "invalid long option name #{opts[:long].inspect}"
199
+ end
200
+ raise ArgumentError, "long option name #{opts[:long].inspect} is already taken; please specify a (different) :long" if @long[opts[:long]]
201
+
202
+ ## fill in :short
203
+ opts[:short] = opts[:short].to_s if opts[:short] unless opts[:short] == :none
204
+ opts[:short] = case opts[:short]
205
+ when /^-(.)$/; $1
206
+ when nil, :none, /^.$/; opts[:short]
207
+ else raise ArgumentError, "invalid short option name '#{opts[:short].inspect}'"
208
+ end
209
+
210
+ if opts[:short]
211
+ raise ArgumentError, "short option name #{opts[:short].inspect} is already taken; please specify a (different) :short" if @short[opts[:short]]
212
+ raise ArgumentError, "a short option name can't be a number or a dash" if opts[:short] =~ INVALID_SHORT_ARG_REGEX
213
+ end
214
+
215
+ ## fill in :default for flags
216
+ opts[:default] = false if opts[:type] == :flag && opts[:default].nil?
217
+
218
+ ## autobox :default for :multi (multi-occurrence) arguments
219
+ opts[:default] = [opts[:default]] if opts[:default] && opts[:multi] && !opts[:default].is_a?(Array)
220
+
221
+ ## fill in :multi
222
+ opts[:multi] ||= false
223
+
224
+ opts[:desc] ||= desc
225
+ @long[opts[:long]] = name
226
+ @short[opts[:short]] = name if opts[:short] && opts[:short] != :none
227
+ @specs[name] = opts
228
+ @order << [:opt, name]
229
+ end
230
+
231
+ ## Sets the version string. If set, the user can request the version
232
+ ## on the commandline. Should probably be of the form "<program name>
233
+ ## <version number>".
234
+ def version s=nil; @version = s if s; @version end
235
+
236
+ ## Adds text to the help display. Can be interspersed with calls to
237
+ ## #opt to build a multi-section help page.
238
+ def banner s; @order << [:text, s] end
239
+ alias :text :banner
240
+
241
+ ## Marks two (or more!) options as requiring each other. Only handles
242
+ ## undirected (i.e., mutual) dependencies. Directed dependencies are
243
+ ## better modeled with Trollop::die.
244
+ def depends *syms
245
+ syms.each { |sym| raise ArgumentError, "unknown option '#{sym}'" unless @specs[sym] }
246
+ @constraints << [:depends, syms]
247
+ end
248
+
249
+ ## Marks two (or more!) options as conflicting.
250
+ def conflicts *syms
251
+ syms.each { |sym| raise ArgumentError, "unknown option '#{sym}'" unless @specs[sym] }
252
+ @constraints << [:conflicts, syms]
253
+ end
254
+
255
+ ## Defines a set of words which cause parsing to terminate when
256
+ ## encountered, such that any options to the left of the word are
257
+ ## parsed as usual, and options to the right of the word are left
258
+ ## intact.
259
+ ##
260
+ ## A typical use case would be for subcommand support, where these
261
+ ## would be set to the list of subcommands. A subsequent Trollop
262
+ ## invocation would then be used to parse subcommand options, after
263
+ ## shifting the subcommand off of ARGV.
264
+ def stop_on *words
265
+ @stop_words = [*words].flatten
266
+ end
267
+
268
+ ## Similar to #stop_on, but stops on any unknown word when encountered
269
+ ## (unless it is a parameter for an argument). This is useful for
270
+ ## cases where you don't know the set of subcommands ahead of time,
271
+ ## i.e., without first parsing the global options.
272
+ def stop_on_unknown
273
+ @stop_on_unknown = true
274
+ end
275
+
276
+ ## Parses the commandline. Typically called by Trollop::options,
277
+ ## but you can call it directly if you need more control.
278
+ ##
279
+ ## throws CommandlineError, HelpNeeded, and VersionNeeded exceptions.
280
+ def parse cmdline=ARGV
281
+ vals = {}
282
+ required = {}
283
+
284
+ opt :version, "Print version and exit" if @version unless @specs[:version] || @long["version"]
285
+ opt :help, "Show this message" unless @specs[:help] || @long["help"]
286
+
287
+ @specs.each do |sym, opts|
288
+ required[sym] = true if opts[:required]
289
+ vals[sym] = opts[:default]
290
+ vals[sym] = [] if opts[:multi] && !opts[:default] # multi arguments default to [], not nil
291
+ end
292
+
293
+ resolve_default_short_options!
294
+
295
+ ## resolve symbols
296
+ given_args = {}
297
+ @leftovers = each_arg cmdline do |arg, params|
298
+ ## handle --no- forms
299
+ arg, negative_given = if arg =~ /^--no-([^-]\S*)$/
300
+ ["--#{$1}", true]
301
+ else
302
+ [arg, false]
303
+ end
304
+
305
+ sym = case arg
306
+ when /^-([^-])$/; @short[$1]
307
+ when /^--([^-]\S*)$/; @long[$1] || @long["no-#{$1}"]
308
+ else; raise CommandlineError, "invalid argument syntax: '#{arg}'"
309
+ end
310
+
311
+ sym = nil if arg =~ /--no-/ # explicitly invalidate --no-no- arguments
312
+
313
+ raise CommandlineError, "unknown argument '#{arg}'" unless sym
314
+
315
+ if given_args.include?(sym) && !@specs[sym][:multi]
316
+ raise CommandlineError, "option '#{arg}' specified multiple times"
317
+ end
318
+
319
+ given_args[sym] ||= {}
320
+ given_args[sym][:arg] = arg
321
+ given_args[sym][:negative_given] = negative_given
322
+ given_args[sym][:params] ||= []
323
+
324
+ # The block returns the number of parameters taken.
325
+ num_params_taken = 0
326
+
327
+ unless params.nil?
328
+ if SINGLE_ARG_TYPES.include?(@specs[sym][:type])
329
+ given_args[sym][:params] << params[0, 1] # take the first parameter
330
+ num_params_taken = 1
331
+ elsif MULTI_ARG_TYPES.include?(@specs[sym][:type])
332
+ given_args[sym][:params] << params # take all the parameters
333
+ num_params_taken = params.size
334
+ end
335
+ end
336
+
337
+ num_params_taken
338
+ end
339
+
340
+ ## check for version and help args
341
+ raise VersionNeeded if given_args.include? :version
342
+ raise HelpNeeded if given_args.include? :help
343
+
344
+ ## check constraint satisfaction
345
+ @constraints.each do |type, syms|
346
+ constraint_sym = syms.find { |sym| given_args[sym] }
347
+ next unless constraint_sym
348
+
349
+ case type
350
+ when :depends
351
+ syms.each { |sym| raise CommandlineError, "--#{@specs[constraint_sym][:long]} requires --#{@specs[sym][:long]}" unless given_args.include? sym }
352
+ when :conflicts
353
+ syms.each { |sym| raise CommandlineError, "--#{@specs[constraint_sym][:long]} conflicts with --#{@specs[sym][:long]}" if given_args.include?(sym) && (sym != constraint_sym) }
354
+ end
355
+ end
356
+
357
+ required.each do |sym, val|
358
+ raise CommandlineError, "option --#{@specs[sym][:long]} must be specified" unless given_args.include? sym
359
+ end
360
+
361
+ ## parse parameters
362
+ given_args.each do |sym, given_data|
363
+ arg, params, negative_given = given_data.values_at :arg, :params, :negative_given
364
+
365
+ opts = @specs[sym]
366
+ raise CommandlineError, "option '#{arg}' needs a parameter" if params.empty? && opts[:type] != :flag
367
+
368
+ vals["#{sym}_given".intern] = true # mark argument as specified on the commandline
369
+
370
+ case opts[:type]
371
+ when :flag
372
+ vals[sym] = (sym.to_s =~ /^no_/ ? negative_given : !negative_given)
373
+ when :int, :ints
374
+ vals[sym] = params.map { |pg| pg.map { |p| parse_integer_parameter p, arg } }
375
+ when :float, :floats
376
+ vals[sym] = params.map { |pg| pg.map { |p| parse_float_parameter p, arg } }
377
+ when :string, :strings
378
+ vals[sym] = params.map { |pg| pg.map { |p| p.to_s } }
379
+ when :io, :ios
380
+ vals[sym] = params.map { |pg| pg.map { |p| parse_io_parameter p, arg } }
381
+ when :date, :dates
382
+ vals[sym] = params.map { |pg| pg.map { |p| parse_date_parameter p, arg } }
383
+ end
384
+
385
+ if SINGLE_ARG_TYPES.include?(opts[:type])
386
+ unless opts[:multi] # single parameter
387
+ vals[sym] = vals[sym][0][0]
388
+ else # multiple options, each with a single parameter
389
+ vals[sym] = vals[sym].map { |p| p[0] }
390
+ end
391
+ elsif MULTI_ARG_TYPES.include?(opts[:type]) && !opts[:multi]
392
+ vals[sym] = vals[sym][0] # single option, with multiple parameters
393
+ end
394
+ # else: multiple options, with multiple parameters
395
+ end
396
+
397
+ ## modify input in place with only those
398
+ ## arguments we didn't process
399
+ cmdline.clear
400
+ @leftovers.each { |l| cmdline << l }
401
+
402
+ ## allow openstruct-style accessors
403
+ class << vals
404
+ def method_missing(m, *args)
405
+ self[m] || self[m.to_s]
406
+ end
407
+ end
408
+ vals
409
+ end
410
+
411
+ def parse_date_parameter param, arg #:nodoc:
412
+ begin
413
+ begin
414
+ time = Chronic.parse(param)
415
+ rescue NameError
416
+ # chronic is not available
417
+ end
418
+ time ? Date.new(time.year, time.month, time.day) : Date.parse(param)
419
+ rescue ArgumentError
420
+ raise CommandlineError, "option '#{arg}' needs a date"
421
+ end
422
+ end
423
+
424
+ ## Print the help message to +stream+.
425
+ def educate stream=$stdout
426
+ width # hack: calculate it now; otherwise we have to be careful not to
427
+ # call this unless the cursor's at the beginning of a line.
428
+ left = {}
429
+ @specs.each do |name, spec|
430
+ left[name] = "--#{spec[:long]}" +
431
+ (spec[:type] == :flag && spec[:default] ? ", --no-#{spec[:long]}" : "") +
432
+ (spec[:short] && spec[:short] != :none ? ", -#{spec[:short]}" : "") +
433
+ case spec[:type]
434
+ when :flag; ""
435
+ when :int; " <i>"
436
+ when :ints; " <i+>"
437
+ when :string; " <s>"
438
+ when :strings; " <s+>"
439
+ when :float; " <f>"
440
+ when :floats; " <f+>"
441
+ when :io; " <filename/uri>"
442
+ when :ios; " <filename/uri+>"
443
+ when :date; " <date>"
444
+ when :dates; " <date+>"
445
+ end
446
+ end
447
+
448
+ leftcol_width = left.values.map { |s| s.length }.max || 0
449
+ rightcol_start = leftcol_width + 6 # spaces
450
+
451
+ unless @order.size > 0 && @order.first.first == :text
452
+ stream.puts "#@version\n" if @version
453
+ stream.puts "Options:"
454
+ end
455
+
456
+ @order.each do |what, opt|
457
+ if what == :text
458
+ stream.puts wrap(opt)
459
+ next
460
+ end
461
+
462
+ spec = @specs[opt]
463
+ stream.printf " %#{leftcol_width}s: ", left[opt]
464
+ desc = spec[:desc] + begin
465
+ default_s = case spec[:default]
466
+ when $stdout; "<stdout>"
467
+ when $stdin; "<stdin>"
468
+ when $stderr; "<stderr>"
469
+ when Array
470
+ spec[:default].join(", ")
471
+ else
472
+ spec[:default].to_s
473
+ end
474
+
475
+ if spec[:default]
476
+ if spec[:desc] =~ /\.$/
477
+ " (Default: #{default_s})"
478
+ else
479
+ " (default: #{default_s})"
480
+ end
481
+ else
482
+ ""
483
+ end
484
+ end
485
+ stream.puts wrap(desc, :width => width - rightcol_start - 1, :prefix => rightcol_start)
486
+ end
487
+ end
488
+
489
+ def width #:nodoc:
490
+ @width ||= if $stdout.tty?
491
+ begin
492
+ require 'curses'
493
+ Curses::init_screen
494
+ x = Curses::cols
495
+ Curses::close_screen
496
+ x
497
+ rescue Exception
498
+ 80
499
+ end
500
+ else
501
+ 80
502
+ end
503
+ end
504
+
505
+ def wrap str, opts={} # :nodoc:
506
+ if str == ""
507
+ [""]
508
+ else
509
+ str.split("\n").map { |s| wrap_line s, opts }.flatten
510
+ end
511
+ end
512
+
513
+ ## The per-parser version of Trollop::die (see that for documentation).
514
+ def die arg, msg
515
+ if msg
516
+ $stderr.puts "Error: argument --#{@specs[arg][:long]} #{msg}."
517
+ else
518
+ $stderr.puts "Error: #{arg}."
519
+ end
520
+ $stderr.puts "Try --help for help."
521
+ exit(-1)
522
+ end
523
+
524
+ private
525
+
526
+ ## yield successive arg, parameter pairs
527
+ def each_arg args
528
+ remains = []
529
+ i = 0
530
+
531
+ until i >= args.length
532
+ if @stop_words.member? args[i]
533
+ remains += args[i .. -1]
534
+ return remains
535
+ end
536
+ case args[i]
537
+ when /^--$/ # arg terminator
538
+ remains += args[(i + 1) .. -1]
539
+ return remains
540
+ when /^--(\S+?)=(.*)$/ # long argument with equals
541
+ yield "--#{$1}", [$2]
542
+ i += 1
543
+ when /^--(\S+)$/ # long argument
544
+ params = collect_argument_parameters(args, i + 1)
545
+ unless params.empty?
546
+ num_params_taken = yield args[i], params
547
+ unless num_params_taken
548
+ if @stop_on_unknown
549
+ remains += args[i + 1 .. -1]
550
+ return remains
551
+ else
552
+ remains += params
553
+ end
554
+ end
555
+ i += 1 + num_params_taken
556
+ else # long argument no parameter
557
+ yield args[i], nil
558
+ i += 1
559
+ end
560
+ when /^-(\S+)$/ # one or more short arguments
561
+ shortargs = $1.split(//)
562
+ shortargs.each_with_index do |a, j|
563
+ if j == (shortargs.length - 1)
564
+ params = collect_argument_parameters(args, i + 1)
565
+ unless params.empty?
566
+ num_params_taken = yield "-#{a}", params
567
+ unless num_params_taken
568
+ if @stop_on_unknown
569
+ remains += args[i + 1 .. -1]
570
+ return remains
571
+ else
572
+ remains += params
573
+ end
574
+ end
575
+ i += 1 + num_params_taken
576
+ else # argument no parameter
577
+ yield "-#{a}", nil
578
+ i += 1
579
+ end
580
+ else
581
+ yield "-#{a}", nil
582
+ end
583
+ end
584
+ else
585
+ if @stop_on_unknown
586
+ remains += args[i .. -1]
587
+ return remains
588
+ else
589
+ remains << args[i]
590
+ i += 1
591
+ end
592
+ end
593
+ end
594
+
595
+ remains
596
+ end
597
+
598
+ def parse_integer_parameter param, arg
599
+ raise CommandlineError, "option '#{arg}' needs an integer" unless param =~ /^\d+$/
600
+ param.to_i
601
+ end
602
+
603
+ def parse_float_parameter param, arg
604
+ raise CommandlineError, "option '#{arg}' needs a floating-point number" unless param =~ FLOAT_RE
605
+ param.to_f
606
+ end
607
+
608
+ def parse_io_parameter param, arg
609
+ case param
610
+ when /^(stdin|-)$/i; $stdin
611
+ else
612
+ require 'open-uri'
613
+ begin
614
+ open param
615
+ rescue SystemCallError => e
616
+ raise CommandlineError, "file or url for option '#{arg}' cannot be opened: #{e.message}"
617
+ end
618
+ end
619
+ end
620
+
621
+ def collect_argument_parameters args, start_at
622
+ params = []
623
+ pos = start_at
624
+ while args[pos] && args[pos] !~ PARAM_RE && !@stop_words.member?(args[pos]) do
625
+ params << args[pos]
626
+ pos += 1
627
+ end
628
+ params
629
+ end
630
+
631
+ def resolve_default_short_options!
632
+ @order.each do |type, name|
633
+ next unless type == :opt
634
+ opts = @specs[name]
635
+ next if opts[:short]
636
+
637
+ c = opts[:long].split(//).find { |d| d !~ INVALID_SHORT_ARG_REGEX && !@short.member?(d) }
638
+ if c # found a character to use
639
+ opts[:short] = c
640
+ @short[c] = name
641
+ end
642
+ end
643
+ end
644
+
645
+ def wrap_line str, opts={}
646
+ prefix = opts[:prefix] || 0
647
+ width = opts[:width] || (self.width - 1)
648
+ start = 0
649
+ ret = []
650
+ until start > str.length
651
+ nextt =
652
+ if start + width >= str.length
653
+ str.length
654
+ else
655
+ x = str.rindex(/\s/, start + width)
656
+ x = str.index(/\s/, start) if x && x < start
657
+ x || str.length
658
+ end
659
+ ret << (ret.empty? ? "" : " " * prefix) + str[start ... nextt]
660
+ start = nextt + 1
661
+ end
662
+ ret
663
+ end
664
+
665
+ ## instance_eval but with ability to handle block arguments
666
+ ## thanks to _why: http://redhanded.hobix.com/inspect/aBlockCostume.html
667
+ def cloaker &b
668
+ (class << self; self; end).class_eval do
669
+ define_method :cloaker_, &b
670
+ meth = instance_method :cloaker_
671
+ remove_method :cloaker_
672
+ meth
673
+ end
674
+ end
675
+ end
676
+
677
+ ## The easy, syntactic-sugary entry method into Trollop. Creates a Parser,
678
+ ## passes the block to it, then parses +args+ with it, handling any errors or
679
+ ## requests for help or version information appropriately (and then exiting).
680
+ ## Modifies +args+ in place. Returns a hash of option values.
681
+ ##
682
+ ## The block passed in should contain zero or more calls to +opt+
683
+ ## (Parser#opt), zero or more calls to +text+ (Parser#text), and
684
+ ## probably a call to +version+ (Parser#version).
685
+ ##
686
+ ## The returned block contains a value for every option specified with
687
+ ## +opt+. The value will be the value given on the commandline, or the
688
+ ## default value if the option was not specified on the commandline. For
689
+ ## every option specified on the commandline, a key "<option
690
+ ## name>_given" will also be set in the hash.
691
+ ##
692
+ ## Example:
693
+ ##
694
+ ## require 'trollop'
695
+ ## opts = Trollop::options do
696
+ ## opt :monkey, "Use monkey mode" # a flag --monkey, defaulting to false
697
+ ## opt :name, "Monkey name", :type => :string # a string --name <s>, defaulting to nil
698
+ ## opt :num_limbs, "Number of limbs", :default => 4 # an integer --num-limbs <i>, defaulting to 4
699
+ ## end
700
+ ##
701
+ ## ## if called with no arguments
702
+ ## p opts # => {:monkey=>false, :name=>nil, :num_limbs=>4, :help=>false}
703
+ ##
704
+ ## ## if called with --monkey
705
+ ## p opts # => {:monkey=>true, :name=>nil, :num_limbs=>4, :help=>false, :monkey_given=>true}
706
+ ##
707
+ ## See more examples at http://trollop.rubyforge.org.
708
+ def options args=ARGV, *a, &b
709
+ @last_parser = Parser.new(*a, &b)
710
+ with_standard_exception_handling(@last_parser) { @last_parser.parse args }
711
+ end
712
+
713
+ ## If Trollop::options doesn't do quite what you want, you can create a Parser
714
+ ## object and call Parser#parse on it. That method will throw CommandlineError,
715
+ ## HelpNeeded and VersionNeeded exceptions when necessary; if you want to
716
+ ## have these handled for you in the standard manner (e.g. show the help
717
+ ## and then exit upon an HelpNeeded exception), call your code from within
718
+ ## a block passed to this method.
719
+ ##
720
+ ## Note that this method will call System#exit after handling an exception!
721
+ ##
722
+ ## Usage example:
723
+ ##
724
+ ## require 'trollop'
725
+ ## p = Trollop::Parser.new do
726
+ ## opt :monkey, "Use monkey mode" # a flag --monkey, defaulting to false
727
+ ## opt :goat, "Use goat mode", :default => true # a flag --goat, defaulting to true
728
+ ## end
729
+ ##
730
+ ## opts = Trollop::with_standard_exception_handling p do
731
+ ## o = p.parse ARGV
732
+ ## raise Trollop::HelpNeeded if ARGV.empty? # show help screen
733
+ ## o
734
+ ## end
735
+ ##
736
+ ## Requires passing in the parser object.
737
+
738
+ def with_standard_exception_handling parser
739
+ begin
740
+ yield
741
+ rescue CommandlineError => e
742
+ $stderr.puts "Error: #{e.message}."
743
+ $stderr.puts "Try --help for help."
744
+ exit(-1)
745
+ rescue HelpNeeded
746
+ parser.educate
747
+ exit
748
+ rescue VersionNeeded
749
+ puts parser.version
750
+ exit
751
+ end
752
+ end
753
+
754
+ ## Informs the user that their usage of 'arg' was wrong, as detailed by
755
+ ## 'msg', and dies. Example:
756
+ ##
757
+ ## options do
758
+ ## opt :volume, :default => 0.0
759
+ ## end
760
+ ##
761
+ ## die :volume, "too loud" if opts[:volume] > 10.0
762
+ ## die :volume, "too soft" if opts[:volume] < 0.1
763
+ ##
764
+ ## In the one-argument case, simply print that message, a notice
765
+ ## about -h, and die. Example:
766
+ ##
767
+ ## options do
768
+ ## opt :whatever # ...
769
+ ## end
770
+ ##
771
+ ## Trollop::die "need at least one filename" if ARGV.empty?
772
+ def die arg, msg=nil
773
+ if @last_parser
774
+ @last_parser.die arg, msg
775
+ else
776
+ raise ArgumentError, "Trollop::die can only be called after Trollop::options"
777
+ end
778
+ end
779
+
780
+ def help stream=$stdout
781
+ @last_parser.educate(stream)
782
+ end
783
+
784
+ module_function :options, :die, :with_standard_exception_handling, :help
785
+
786
+ end # module
data/lib/sfp.rb CHANGED
@@ -5,6 +5,8 @@ require 'json'
5
5
  # internal dependencies
6
6
  libdir = File.expand_path(File.dirname(__FILE__))
7
7
 
8
+ require libdir + '/sfp/trollop'
9
+
8
10
  require libdir + '/sfp/Sfplib'
9
11
  require libdir + '/sfp/SfpLangParser'
10
12
  require libdir + '/sfp/SfpLangLexer'
data/sfp.gemspec CHANGED
@@ -1,7 +1,7 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'sfp'
3
- s.version = '0.1.3'
4
- s.date = '2013-05-06'
3
+ s.version = '0.2.0'
4
+ s.date = '2013-05-15'
5
5
  s.summary = 'SFP Parser and Planner'
6
6
  s.description = 'A Ruby gem that provides a Ruby API to SFP language parser and ' +
7
7
  'SFP planner. It also provides a planner sript to solve a planning problem ' +
metadata CHANGED
@@ -1,65 +1,50 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: sfp
3
- version: !ruby/object:Gem::Version
4
- prerelease: false
5
- segments:
6
- - 0
7
- - 1
8
- - 3
9
- version: 0.1.3
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ prerelease:
10
6
  platform: ruby
11
- authors:
7
+ authors:
12
8
  - Herry
13
9
  autorequire:
14
10
  bindir: bin
15
11
  cert_chain: []
16
-
17
- date: 2013-05-06 00:00:00 +01:00
18
- default_executable:
19
- dependencies:
20
- - !ruby/object:Gem::Dependency
12
+ date: 2013-05-15 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
21
15
  name: json
22
- prerelease: false
23
- requirement: &id001 !ruby/object:Gem::Requirement
24
- requirements:
16
+ requirement: &5016880 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
25
19
  - - ~>
26
- - !ruby/object:Gem::Version
27
- segments:
28
- - 1
29
- - 7
30
- - 5
20
+ - !ruby/object:Gem::Version
31
21
  version: 1.7.5
32
22
  type: :runtime
33
- version_requirements: *id001
34
- - !ruby/object:Gem::Dependency
35
- name: antlr3
36
23
  prerelease: false
37
- requirement: &id002 !ruby/object:Gem::Requirement
38
- requirements:
24
+ version_requirements: *5016880
25
+ - !ruby/object:Gem::Dependency
26
+ name: antlr3
27
+ requirement: &5015880 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
39
30
  - - ~>
40
- - !ruby/object:Gem::Version
41
- segments:
42
- - 1
43
- - 8
44
- - 12
31
+ - !ruby/object:Gem::Version
45
32
  version: 1.8.12
46
33
  type: :runtime
47
- version_requirements: *id002
48
- description: A Ruby gem that provides a Ruby API to SFP language parser and SFP planner. It also provides a planner sript to solve a planning problem written in SFP language.
34
+ prerelease: false
35
+ version_requirements: *5015880
36
+ description: A Ruby gem that provides a Ruby API to SFP language parser and SFP planner.
37
+ It also provides a planner sript to solve a planning problem written in SFP language.
49
38
  email: herry13@gmail.com
50
- executables:
39
+ executables:
51
40
  - sfp
52
41
  extensions: []
53
-
54
42
  extra_rdoc_files: []
55
-
56
- files:
43
+ files:
57
44
  - .gitignore
58
45
  - LICENSE
59
46
  - README.md
60
47
  - bin/sfp
61
- - bin/solver/linux-arm/downward
62
- - bin/solver/linux-arm/preprocess
63
48
  - bin/solver/linux-x86/downward
64
49
  - bin/solver/linux-x86/preprocess
65
50
  - bin/solver/macos/downward
@@ -74,6 +59,7 @@ files:
74
59
  - lib/sfp/sas.rb
75
60
  - lib/sfp/sas_translator.rb
76
61
  - lib/sfp/sfw2graph.rb
62
+ - lib/sfp/trollop.rb
77
63
  - lib/sfp/visitors.rb
78
64
  - sfp.gemspec
79
65
  - src/SfpLang.g
@@ -97,37 +83,31 @@ files:
97
83
  - test/test3.sfp
98
84
  - test/test4.inc
99
85
  - test/test4.sfp
100
- has_rdoc: true
101
86
  homepage: https://github.com/herry13/sfp-ruby
102
87
  licenses: []
103
-
104
88
  post_install_message:
105
89
  rdoc_options: []
106
-
107
- require_paths:
90
+ require_paths:
108
91
  - lib
109
- required_ruby_version: !ruby/object:Gem::Requirement
110
- requirements:
111
- - - ">="
112
- - !ruby/object:Gem::Version
113
- segments:
114
- - 0
115
- version: "0"
116
- required_rubygems_version: !ruby/object:Gem::Requirement
117
- requirements:
118
- - - ">="
119
- - !ruby/object:Gem::Version
120
- segments:
121
- - 0
122
- version: "0"
92
+ required_ruby_version: !ruby/object:Gem::Requirement
93
+ none: false
94
+ requirements:
95
+ - - ! '>='
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ required_rubygems_version: !ruby/object:Gem::Requirement
99
+ none: false
100
+ requirements:
101
+ - - ! '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
123
104
  requirements: []
124
-
125
105
  rubyforge_project: sfp
126
- rubygems_version: 1.3.6
106
+ rubygems_version: 1.8.11
127
107
  signing_key:
128
108
  specification_version: 3
129
109
  summary: SFP Parser and Planner
130
- test_files:
110
+ test_files:
131
111
  - test/cloud-schemas.sfp
132
112
  - test/future/test1.sfp
133
113
  - test/nd-cloud1.sfp
Binary file
Binary file