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.



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
174
175
176
# File 'lib/shellopts.rb', line 146

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
  @clear_screen = true
end

Instance Attribute Details

#argsObject (readonly)

Array of remaining arguments. Initialized by #interpret



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

def args
  @args
end

#argvObject (readonly)

Array of arguments. Initialized by #interpret



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

def argv
  @argv
end

#debugObject (readonly)

Automatically add a --debug option if true



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

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 when you want a stack trace instead of a user-readable error message



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

def exception
  @exception
end

#fileObject (readonly)

Executable. Default $PROGRAM_NAME



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

def file
  @file
end

#floatObject

Floating options



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

def float
  @float
end

#grammarObject (readonly) Also known as: ast

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



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

def grammar
  @grammar
end

#help(subject = nil) ⇒ Object (readonly)

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



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

def help
  @help
end

#nameObject (readonly)

Name of program. Defaults to the name of the executable



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

def name
  @name
end

#quietObject (readonly)

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



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

def quiet
  @quiet
end

#silentObject (readonly)

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



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

def silent
  @silent
end

#specObject (readonly)

Specification (String). Initialized by #compile



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

def spec
  @spec
end

#tokensObject (readonly)

Debug: Internal variables made public



143
144
145
# File 'lib/shellopts.rb', line 143

def tokens
  @tokens
end

#verboseObject (readonly)

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



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

def verbose
  @verbose
end

#versionObject (readonly)

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



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

def version
  @version
end

#version_numberObject (readonly)

Version number (this is usually detected dynamically)



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

def version_number
  @version_number
end

Class Method Details

.briefObject



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

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

.compare_lines(text, spec) ⇒ Object



359
360
361
362
363
# File 'lib/shellopts.rb', line 359

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



367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
# File 'lib/shellopts.rb', line 367

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



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

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



262
263
264
265
266
# File 'lib/shellopts.rb', line 262

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

.usageObject



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

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

Instance Method Details

#briefObject

Print brief help



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

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

#compile(spec) ⇒ Object

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



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
215
216
217
# File 'lib/shellopts.rb', line 180

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:



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

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:



291
292
293
294
295
# File 'lib/shellopts.rb', line 291

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



400
401
402
# File 'lib/shellopts.rb', line 400

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



413
414
415
416
417
418
419
420
421
422
# File 'lib/shellopts.rb', line 413

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



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

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



404
405
406
407
408
409
410
411
# File 'lib/shellopts.rb', line 404

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 ShellOpts object



251
252
253
254
255
# File 'lib/shellopts.rb', line 251

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

#programObject

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



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

def program() @program end

#usageObject

Print usage



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

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