Class: ShellOpts::ShellOpts

Inherits:
Object
  • Object
show all
Defined in:
lib/shellopts.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name: nil, file: $PROGRAM_NAME, help: true, version: true, silent: nil, quiet: nil, verbose: nil, debug: nil, version_number: nil, float: true, exception: ::ShellOpts.exception) ⇒ ShellOpts

Returns a new instance of ShellOpts.



144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/shellopts.rb', line 144

def initialize(name: nil, file: $PROGRAM_NAME,
    # Options
    help: true,
    version: true,
    silent: nil,
    quiet: nil,
    verbose: nil,
    debug: nil,

    # Version number (auto-detected for gem packages)
    version_number: nil,

    # Floating options
    float: true,

    exception: ::ShellOpts.exception
  )

  @name = name || File.basename(file)
  @file = file
  @help = help
  @version_number = version_number || (version && find_version_number)
  @version = !@version_number.nil? && version
  @silent = silent
  @quiet = quiet
  @verbose = verbose
  @debug = debug
  @float = float
  @exception = exception
end

Instance Attribute Details

#argsObject (readonly)

Array of remaining arguments. Initialized by #interpret



110
111
112
# File 'lib/shellopts.rb', line 110

def args
  @args
end

#argvObject (readonly)

Array of arguments. Initialized by #interpret



100
101
102
# File 'lib/shellopts.rb', line 100

def argv
  @argv
end

#debugObject (readonly)

Automatically add a –debug option if true



128
129
130
# File 'lib/shellopts.rb', line 128

def debug
  @debug
end

#exceptionObject

Use exceptions instead of ShellOpts’s error methods. Default is ShellOpts.exception that itself defalts to false. Mostly used for debug



138
139
140
# File 'lib/shellopts.rb', line 138

def exception
  @exception
end

#fileObject (readonly)

Executable. Default $PROGRAM_NAME



94
95
96
# File 'lib/shellopts.rb', line 94

def file
  @file
end

#floatObject

Floating options



134
135
136
# File 'lib/shellopts.rb', line 134

def float
  @float
end

#grammarObject (readonly) Also known as: ast

Grammar. Grammar::Program object. Initialized by #compile



103
104
105
# File 'lib/shellopts.rb', line 103

def grammar
  @grammar
end

#help(subject = nil, clear: true) ⇒ Object (readonly)

Print help for the given subject or the full documentation if subject is nil. Clears the screen beforehand if :clear is true



113
114
115
# File 'lib/shellopts.rb', line 113

def help
  @help
end

#nameObject (readonly)

Name of program. Defaults to the name of the executable



91
92
93
# File 'lib/shellopts.rb', line 91

def name
  @name
end

#quietObject (readonly)

Automatically add a -q and a –quiet option if true



122
123
124
# File 'lib/shellopts.rb', line 122

def quiet
  @quiet
end

#silentObject (readonly)

Automatically add a -s and a –silent option if true



119
120
121
# File 'lib/shellopts.rb', line 119

def silent
  @silent
end

#specObject (readonly)

Specification (String). Initialized by #compile



97
98
99
# File 'lib/shellopts.rb', line 97

def spec
  @spec
end

#tokensObject (readonly)

Debug: Internal variables made public



141
142
143
# File 'lib/shellopts.rb', line 141

def tokens
  @tokens
end

#verboseObject (readonly)

Automatically add a -v and a –verbose option if true



125
126
127
# File 'lib/shellopts.rb', line 125

def verbose
  @verbose
end

#versionObject (readonly)

Version of client program. If not nil, a –version option is added to the program



116
117
118
# File 'lib/shellopts.rb', line 116

def version
  @version
end

#version_numberObject (readonly)

Version number (this is usually detected dynamically)



131
132
133
# File 'lib/shellopts.rb', line 131

def version_number
  @version_number
end

Class Method Details

.briefObject



311
# File 'lib/shellopts.rb', line 311

def self.brief() ::ShellOpts.instance.brief end

.compare_lines(text, spec) ⇒ Object



337
338
339
340
341
# File 'lib/shellopts.rb', line 337

def self.compare_lines(text, spec)
  return true if text == spec
  return true if text =~ /[#\$\\]/
  false
end

.find_spec_in_text(text, spec, oneline) ⇒ Object

Find line and char index of spec in text. Returns [nil, nil] if not found



345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
# File 'lib/shellopts.rb', line 345

def self.find_spec_in_text(text, spec, oneline)
  text_lines = text.split("\n")
  spec_lines = spec.split("\n")
  spec_lines.pop_while { |line| line =~ /^\s*$/ }

  if oneline
    line_i = nil
    char_i = nil
    char_z = 0

    (0 ... text_lines.size).each { |text_i|
      curr_char_i, curr_char_z =
          LCS.find_longest_common_substring_index(text_lines[text_i], spec_lines.first.strip)
      if curr_char_z > char_z
        line_i = text_i
        char_i = curr_char_i
        char_z = curr_char_z
      end
    }
    line_i ? [line_i, char_i] : [nil, nil]
  else
    spec_string = spec_lines.first.strip
    line_i = (0 ... text_lines.size - spec_lines.size + 1).find { |text_i|
      (0 ... spec_lines.size).all? { |spec_i|
        compare_lines(text_lines[text_i + spec_i], spec_lines[spec_i])
      }
    } or return [nil, nil]
    char_i, char_z =
        LCS.find_longest_common_substring_index(text_lines[line_i], spec_lines.first.strip)
    [line_i, char_i || 0]
  end
end

.help(subject = nil) ⇒ Object



312
# File 'lib/shellopts.rb', line 312

def self.help(subject = nil) ::ShellOpts.instance.help(subject) end

.process(spec, argv, **opts) ⇒ Object

Create a ShellOpts object and sets the global instance, then process the spec and arguments. Returns a tuple of a ShellOpts::Program with the options and subcommands and a ShellOpts::Args object with the remaining arguments



260
261
262
263
264
# File 'lib/shellopts.rb', line 260

def self.process(spec, argv, **opts)
  ::ShellOpts.instance = shellopts = ShellOpts.new(**opts)
  shellopts.process(spec, argv)
  [shellopts.program, shellopts.args]
end

.usageObject



310
# File 'lib/shellopts.rb', line 310

def self.usage() ::ShellOpts.instance.usage end

Instance Method Details

#briefObject

Print brief help



299
# File 'lib/shellopts.rb', line 299

def brief() Formatter.brief(@grammar) end

#compile(spec) ⇒ Object

Compile source and return grammar object. Also sets #spec and #grammar. Returns the grammar



177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
# File 'lib/shellopts.rb', line 177

def compile(spec)
  handle_exceptions {
    @oneline = spec.index("\n").nil?
    @spec = spec.sub(/^\s*\n/, "")
    @tokens = Lexer.lex(name, @spec, @oneline)
    ast = Parser.parse(tokens)

    help_spec = (@help == true ? "-h,help" : @help)
    version_spec = (@version == true ? "--version" : @version)
    silent_spec = (@silent == true ? "--silent" : @silent)
    quiet_spec = (@quiet == true ? "-q,quiet" : @quiet)
    verbose_spec = (@verbose == true ? "+v,verbose" : @verbose)
    debug_spec = (@debug == true ? "--debug" : @debug)

    @silent_option =
        ast.inject_option(silent_spec, "Silent", "Do not write anything to standard output/error") if @silent
    @quiet_option =
        ast.inject_option(quiet_spec, "Quiet", "Do not write anything to standard output") if @quiet
    @verbose_option =
        ast.inject_option(verbose_spec, "Increase verbosity", "Write verbose output") if @verbose
    @debug_option =
        ast.inject_option(debug_spec, "Write debug information") if @debug
    @help_option =
        ast.inject_option(help_spec, "Write short or long help") { |option|
          short_option = option.short_names.first
          long_option = option.long_names.first
          [
            short_option && "#{short_option} prints a brief help text",
            long_option && "#{long_option} prints a longer man-style description of the command"
          ].compact.join(", ")
        } if @help
    @version_option =
        ast.inject_option(version_spec, "Write version number and exit") if @version

    @grammar = Analyzer.analyze(ast)
  }
  self
end

#error(message, exit: 1) ⇒ Object

Write short usage and error message to standard error and terminate program with status 1

#error is supposed to be used when the user made an error and the usage is written to help correcting the error

Raises:



271
272
273
274
275
276
277
278
279
280
281
282
# File 'lib/shellopts.rb', line 271

def error(message, exit: 1)
  raise ShellOpts::Error.new(message) if ::ShellOpts.exception
  $stderr.puts "#{name}: #{message}"
  saved = $stdout
  begin
    $stdout = $stderr
    Formatter.usage(grammar)
    ::ShellOpts.handle_exit(exit)
  ensure
    $stdout = saved
  end
end

#failure(message, exit: 1) ⇒ Object

Write error message to standard error and terminate program with status 1

#failure doesn’t print the program usage because is supposed to be used when the user specified the correct arguments but something else went wrong during processing

Raises:



289
290
291
292
293
# File 'lib/shellopts.rb', line 289

def failure(message, exit: 1)
  raise ShellOpts::Failure.new(message) if ::ShellOpts.exception
  $stderr.puts "#{name}: #{message}"
  ::ShellOpts.handle_exit(exit)
end

#find_spec_in_fileObject



378
379
380
# File 'lib/shellopts.rb', line 378

def find_spec_in_file
  self.class.find_spec_in_text(IO.read(@file), @spec, @oneline).map { |i| (i || 0) + 1 }
end

#find_subject(obj) ⇒ Object



391
392
393
394
395
396
397
398
399
400
# File 'lib/shellopts.rb', line 391

def find_subject(obj)
  case obj
    when String; lookup(obj)
    when Ast::Command; Command.grammar(obj) # FIXME
    when Grammar::Command; obj
    when NilClass; grammar
  else
    raise Internal, "Illegal object: #{obj.class}"
  end
end

#interpret(argv) ⇒ Object

Use grammar to interpret arguments. Return a ShellOpts::Program and ShellOpts::Args tuple



219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
# File 'lib/shellopts.rb', line 219

def interpret(argv)
  handle_exceptions {
    @argv = argv.dup
    @program, @args = Interpreter.interpret(grammar, argv, float: float, exception: exception)

    # Process standard options that may have been renamed or be deactivated
    if @help && @program.__send__(:"#{@help_option.ident}?")
      if @program[:help].name =~ /^--/
        ShellOpts.help
      else
        ShellOpts.brief
      end
      exit
    elsif @version && @program.__send__(:"#{@version_option.ident}?")
      puts version_number
      exit
    else
      # Assign standard options. The targets doesn't change if the option is renamed
      @program.__silent__ = @program.__send__(:"#{@silent_option.ident}?") if @silent
      @program.__quiet__ = @program.__send__(:"#{@quiet_option.ident}?") if @quiet
      @program.__verbose__ = @program.__send__(:"#{@verbose_option.ident}") if @verbose
      @program.__debug__ = @program.__send__(:"#{@debug_option.ident}?") if @debug
    end
  }
  self
end

#lookup(name) ⇒ Object



382
383
384
385
386
387
388
389
# File 'lib/shellopts.rb', line 382

def lookup(name)
  a = name.split(".")
  cmd = grammar
  while element = a.shift
    cmd = cmd.commands[element]
  end
  cmd
end

#process(spec, argv) ⇒ Object

Compile spec and interpret argv. Returns a tuple of a ShellOpts::Program and ShellOpts::Args object



249
250
251
252
253
# File 'lib/shellopts.rb', line 249

def process(spec, argv)
  compile(spec)
  interpret(argv)
  self
end

#programObject

Resulting ShellOpts::Program object containing options and optional subcommand. Initialized by #interpret



107
# File 'lib/shellopts.rb', line 107

def program() @program end

#usageObject

Print usage



296
# File 'lib/shellopts.rb', line 296

def usage() Formatter.usage(@grammar) end