shellopts 2.0.9 → 2.0.10

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a9d576d24bd0aec0dd43a2a3645ede04751d80ae22a4c88e8f66ed53bbe7e405
4
- data.tar.gz: 9027ac55689a345099f6515e5280808528603dfafe385219e6cce77d1c166d96
3
+ metadata.gz: 812078e64c677a6aff78081e25a04b81be3b3a1fe9496709838e2abb80573b4a
4
+ data.tar.gz: 92c14741c409697f543820d656683b9552d4c6c7ce5c29b01a30677a174549c2
5
5
  SHA512:
6
- metadata.gz: b44a4d95aa0585dd0ec31abccde4942c98ad712d9e3bdf9f0c0b6ead7333ff4c5b555ec5b45786a6f539388b2460c93d12f3755b2e80cbb81fd269c42a9b4955
7
- data.tar.gz: 3c948a743d809df7d22bc142dcfa9aa4e943fed144004c4fbf5d1995c314f4aea3f91b032533222eed953e5344a45c55666339797e5ae2b1be262eea17071cd5
6
+ metadata.gz: 3b1ac7453a534a50cf250e489688aa82ad1928220a615b9e4f7076c27b588d19a7b355e866f3e3cc789fd2259bb2e5de14ac92e905b7b033528c11be26564356
7
+ data.tar.gz: d50433ec49a4bf67fd9947d2028ed81be9c8310e7410d93c691e2cd59ed36ef622937c861ef1f4608e7ef5869804cd0f1a4197abf03fd4c2d96cef4985d5dab4
data/TODO CHANGED
@@ -1,18 +1,13 @@
1
1
 
2
- o Add brackets to optional option arguments: '--all=FILE?' -> '--all[=FILE]'
3
2
  o Ignore all text after ' # ' (doesn't conflict with option flag)
4
3
  o Command aliases
5
4
  o Add user-defined setions
6
5
  o Add a SOURCE section with link to git repo
7
6
  o Bullet-lists
8
7
  o Allow a USAGE section (and NAME)
9
- o Find source in code an adjust line number in error messages
10
- o Rename line and char to lineno and charno
11
8
  o Client-defined argument types
12
9
  o Rename Expr -> ?
13
10
  o Find clean(er) procedural object model
14
- o Allow assignment to options (this makes practical stuff easier)
15
- o Special handling of --help arguments so that '--help command' is possible
16
11
  o Support for paging of help:
17
12
  begin
18
13
  file = Tempfile.new("prick")
@@ -23,6 +18,11 @@ o Support for paging of help:
23
18
  file.close
24
19
  end
25
20
 
21
+ + Special handling of --help arguments so that '--help command' is possible
22
+ + Allow assignment to options (this makes practical stuff easier)
23
+ + Rename line and char to lineno and charno
24
+ + Find source in code an adjust line number in error messages
25
+ + Add brackets to optional option arguments: '--all=FILE?' -> '--all[=FILE]'
26
26
  + Bold text output
27
27
  + Recursive format of commands
28
28
  + Rename Compiler -> Interpreter
@@ -20,10 +20,6 @@ module ShellOpts
20
20
  end
21
21
 
22
22
  class Command
23
- def set_supercommand
24
- commands.each { |child| child.instance_variable_set(:@supercommand, self) }
25
- end
26
-
27
23
  def collect_options
28
24
  @options = option_groups.map(&:options).flatten
29
25
  end
@@ -54,9 +50,9 @@ module ShellOpts
54
50
  }
55
51
  end
56
52
 
53
+ # TODO Check for dash-collision
57
54
  def compute_command_hashes
58
55
  commands.each { |command|
59
- # TODO Check for dash-collision
60
56
  !@commands_hash.key?(command.name) or
61
57
  analyzer_error command.token, "Duplicate command name: #{command.name}"
62
58
  @commands_hash[command.name] = command
@@ -76,41 +72,74 @@ module ShellOpts
76
72
  @grammar = grammar
77
73
  end
78
74
 
79
- # Move commands that are nested within a different command than it belongs to
80
- def move_commands
75
+ def create_implicit_commands(cmd)
76
+ path = cmd.path[0..-2]
77
+
78
+
79
+ end
80
+
81
+ # Link up commands with supercommands. This is only done for commands that
82
+ # are nested within a different command than it belongs to. The
83
+ # parent/child relationship is not changed Example:
84
+ #
85
+ # cmd!
86
+ # cmd.subcmd!
87
+ #
88
+ # Here subcmd is added to cmd's list of commands. It keeps its position in
89
+ # the program's parent/child relationship so that documentation will print the
90
+ # commands in the given order and with the given indentation level
91
+ #
92
+ def link_commands
81
93
  # We can't use Command#[] at this point so we collect the commands here
82
94
  h = {}
83
95
  @grammar.traverse(Grammar::Command) { |command|
84
96
  h[command.path] = command
97
+ # TODO: Pick up parent-less commands
98
+ }
99
+
100
+ # Command to link
101
+ link = []
102
+
103
+ # Create implicit commands
104
+ h.sort { |l,r| l.size <=> r.size }.each { |path, command|
105
+ path = path[0..-2]
106
+ while !h.key?(path)
107
+ cmd = Grammar::Command.new(nil, command.token)
108
+ cmd.set_name(path.last.to_s.sub(/!/, ""), path.dup)
109
+ link << cmd
110
+ h[cmd.path] = cmd
111
+ path.pop
112
+ end
85
113
  }
86
114
 
87
- # Find commands to move
115
+ # Find commands to link
88
116
  #
89
- # Commands are moved in two steps because the behaviour of #traverse is
90
- # not defined when the data structure changes beneath it
91
- move = []
117
+ # Commands are linked in two steps because the behaviour of #traverse is
118
+ # not defined when the data structure changes beneath it. (FIXME: Does it
119
+ # change when we don't touch the parent/child relationship?)
92
120
  @grammar.traverse(Grammar::Command) { |command|
93
121
  if command.path.size > 1 && command.parent && command.parent.path != command.path[0..-2]
94
- move << command
122
+ # if command.path.size > 1 && command.parent.path != command.path[0..-2]
123
+ link << command
95
124
  else
96
125
  command.instance_variable_set(:@command, command.parent)
97
126
  end
98
127
  }
99
128
 
100
- # Move commands but do not change parent/child relationship
101
- move.each { |command|
102
- supercommand = h[command.path[0..-2]] or analyzer_error "Can't find #{command.ident}!"
103
- command.parent.commands.delete(command)
129
+ # Link commands but do not change parent/child relationship
130
+ link.each { |command|
131
+ path = command.path[0..-2]
132
+ path.pop while (supercommand = h[path]).nil?
133
+ command.parent.commands.delete(command) if command.parent
104
134
  supercommand.commands << command
105
135
  command.instance_variable_set(:@command, supercommand)
106
136
  }
107
137
  end
108
138
 
109
139
  def analyze()
110
- move_commands
140
+ link_commands
111
141
 
112
142
  @grammar.traverse(Grammar::Command) { |command|
113
- command.set_supercommand
114
143
  command.reorder_options
115
144
  command.collect_options
116
145
  command.compute_option_hashes
@@ -81,7 +81,15 @@ module ShellOpts
81
81
  end
82
82
 
83
83
  def puts_descr(prefix, brief: !self.brief.nil?, name: :path)
84
- puts Ansi.bold([prefix, render(:single, Formatter.rest)].flatten.compact.join(" "))
84
+ # Use one-line mode if all options are declared on one line
85
+ if options.all? { |option| option.token.lineno == token.lineno }
86
+ puts Ansi.bold([prefix, render(:single, Formatter.rest)].flatten.compact.join(" "))
87
+ puts_options = false
88
+ else
89
+ puts Ansi.bold([prefix, render(:abbr, Formatter.rest)].flatten.compact.join(" "))
90
+ puts_options = true
91
+ end
92
+
85
93
  indent {
86
94
  if brief
87
95
  puts self.brief.words.wrap(Formatter.rest)
@@ -93,7 +101,10 @@ module ShellOpts
93
101
 
94
102
  if child.is_a?(Command)
95
103
  child.puts_descr(prefix, name: :path)
96
- else
104
+ elsif child.is_a?(OptionGroup)
105
+ child.puts_descr if puts_options
106
+ newline = false
107
+ else
97
108
  child.puts_descr
98
109
  end
99
110
  }
@@ -125,9 +136,9 @@ module ShellOpts
125
136
  section.delete(klass)
126
137
  section.delete(Paragraph)
127
138
  if klass <= OptionGroup
128
- s = s + "S" if options.size > 1
139
+ s += "S" if options.size > 1
129
140
  elsif klass <= Command
130
- s = s + "S" if commands.size > 1 || commands.first&.commands&.size != 0
141
+ s += "S" if commands.size > 1 || commands.size == 1 && commands.first.commands.size > 1
131
142
  end
132
143
  puts
133
144
  indent(-1).puts Ansi.bold s
@@ -139,7 +150,7 @@ module ShellOpts
139
150
  end
140
151
 
141
152
  if child.is_a?(Command)
142
- prefix = child.supercommand == self ? nil : child.supercommand&.name
153
+ prefix = child.path[path.size..-2].map { |sym| sym.to_s.sub(/!/, "") }
143
154
  child.puts_descr(prefix, brief: false, name: :path)
144
155
  newline = true
145
156
  else
@@ -150,9 +161,10 @@ module ShellOpts
150
161
 
151
162
  # Also emit commands not declared in nested scope
152
163
  (commands - children.select { |child| child.is_a?(Command) }).each { |cmd|
164
+ next if cmd.parent.nil? # Skip implicit commands
153
165
  puts if newline
154
166
  newline = true
155
- prefix = cmd.supercommand == self ? nil : cmd.supercommand&.name
167
+ prefix = cmd.command == self ? nil : cmd.command&.name
156
168
  cmd.puts_descr(prefix, brief: false, name: path)
157
169
  }
158
170
  }
@@ -45,16 +45,18 @@ module ShellOpts
45
45
  end
46
46
 
47
47
  class IdrNode < Node
48
- # Command of this object. This is different from #parent when a
49
- # subcommand is nested on a higher level than its supercommand.
50
- # Initialized by the analyzer
48
+ # Command of this object (nil for the top-level Program object). This is
49
+ # different from #parent when a subcommand is nested textually on a
50
+ # higher level than its supercommand. Initialized by the analyzer
51
51
  attr_reader :command
52
52
 
53
53
  # Unique identifier of node (String) within the context of a program. nil
54
- # for the Program object. It is the list of path elements concatenated
55
- # with '.' and with internal '!' removed (eg. "cmd.opt" or "cmd.cmd!").
56
- # Initialized by the parser
57
- attr_reader :uid
54
+ # for the Program object. It is the dot-joined elements of path with
55
+ # internal exclamation marks removed (eg. "cmd.opt" or "cmd.cmd!").
56
+ # Initialize by the analyzer
57
+ def uid()
58
+ @uid ||= command && [command.uid, ident].compact.join(".").sub(/!\./, ".")
59
+ end
58
60
 
59
61
  # Path from Program object and down to this node. Array of identifiers.
60
62
  # Empty for the Program object. Initialized by the parser
@@ -84,6 +86,13 @@ module ShellOpts
84
86
  # #ident is a reserved word. Initialized by the parser
85
87
  attr_reader :attr
86
88
 
89
+ def set_name(name, path)
90
+ @name = name.to_s
91
+ @path = path
92
+ @ident = @path.last || :!
93
+ @attr = ::ShellOpts::Command::RESERVED_OPTION_NAMES.include?(@ident.to_s) ? nil : @ident
94
+ end
95
+
87
96
  protected
88
97
  def lookup(path)
89
98
  path.empty? or raise ArgumentError, "Argument should be empty"
@@ -189,10 +198,6 @@ module ShellOpts
189
198
  # methods are initialized by the analyzer
190
199
  #
191
200
  class Command < IdrNode
192
- # Supercommand or nil if this is the top-level Program object.
193
- # Initialized by the analyzer
194
- attr_reader :supercommand
195
-
196
201
  # Brief description of command
197
202
  attr_accessor :brief
198
203
 
@@ -16,9 +16,9 @@ module ShellOpts
16
16
  class IdrNode
17
17
  # Assumes that @name and @path has been defined
18
18
  def parse
19
- @ident = @path.last || :!
20
- @attr = ::ShellOpts::Command::RESERVED_OPTION_NAMES.include?(ident.to_s) ? nil : ident
21
- @uid = parent && @path.join(".").sub(/!\./, ".") # uid is nil for the Program object
19
+ # @ident = @path.last || :!
20
+ # @attr = ::ShellOpts::Command::RESERVED_OPTION_NAMES.include?(ident.to_s) ? nil : ident
21
+ # @uid = parent && @path.join(".").sub(/!\./, ".") # uid is nil for the Program object
22
22
  end
23
23
  end
24
24
 
@@ -56,8 +56,9 @@ module ShellOpts
56
56
  @long_names = names.map { |name| "--#{name}" }
57
57
  @long_idents = names.map { |name| name.tr("-", "_").to_sym }
58
58
 
59
- @name = @long_names.first || @short_names.first
60
- @path = command.path + [@long_idents.first || @short_idents.first]
59
+ set_name(
60
+ @long_names.first || @short_names.first,
61
+ command.path + [@long_idents.first || @short_idents.first])
61
62
 
62
63
  @argument = !arg.nil?
63
64
 
@@ -108,11 +109,11 @@ module ShellOpts
108
109
  def parse
109
110
  if parent
110
111
  path_names = token.source.sub("!", "").split(".")
111
- @name = path_names.last
112
- @path = path_names.map { |cmd| "#{cmd}!".to_sym }
112
+ set_name(
113
+ path_names.last,
114
+ path_names.map { |cmd| "#{cmd}!".to_sym })
113
115
  else
114
- @path = []
115
- @name = token.source
116
+ set_name(token.source, [])
116
117
  end
117
118
  super
118
119
  end
@@ -123,13 +124,15 @@ module ShellOpts
123
124
  super(nil, token)
124
125
  end
125
126
 
126
- def add_stdopts
127
+ def add_version_option
127
128
  option_token = Token.new(:option, 1, 1, "--version")
128
129
  brief_token = Token.new(:brief, 1, 1, "Write version number and exit")
129
130
  group = OptionGroup.new(self, option_token)
130
131
  option = Option.parse(group, option_token)
131
132
  brief = Brief.parse(group, brief_token)
133
+ end
132
134
 
135
+ def add_help_options
133
136
  option_token = Token.new(:option, 1, 1, "-h,help")
134
137
  brief_token = Token.new(:brief, 1, 1, "Write help text and exit")
135
138
  paragraph_token = Token.new(:text, 1, 1,
@@ -49,10 +49,10 @@ module ShellOpts
49
49
  singleton_method_removed singleton_method_undefined
50
50
  )
51
51
 
52
- # These methods can be overridden by an option (the value is not used -
52
+ # These methods can be overridden by an option or a command (the value is not used -
53
53
  # this is just for informational purposes)
54
- OVERRIDEABLE_METHODS = %w(
55
- subcommand
54
+ OVERRIDEABLE_METHOD_NAMES = %w(
55
+ subcommand subcommand! supercommand!
56
56
  )
57
57
 
58
58
  # Redefine ::new to call #__initialize__
@@ -107,6 +107,7 @@ module ShellOpts
107
107
  #
108
108
  # Note: Can be overridden by option, in that case use #__subcommand__ or
109
109
  # ShellOpts.subcommand(object) instead
110
+ #
110
111
  def subcommand() __subcommand__ end
111
112
 
112
113
  # The subcommand object or nil if not present. Per-subcommand methods
@@ -120,7 +121,12 @@ module ShellOpts
120
121
  def subcommand!() __subcommand__! end
121
122
 
122
123
  # The parent command or nil. Initialized by #add_command
123
- attr_accessor :__supercommand__
124
+ #
125
+ # Note: Can be overridden by a subcommand declaration (but not an
126
+ # option), in that case use #__supercommand__! or
127
+ # ShellOpts.supercommand!(object) instead
128
+ #
129
+ def supercommand!() __supercommand__ end
124
130
 
125
131
  # UID of command/program
126
132
  def __uid__() @__grammar__.uid end
@@ -149,6 +155,9 @@ module ShellOpts
149
155
  # Map from identifier to option object or to a list of option objects if
150
156
  # the option is repeatable
151
157
  attr_reader :__option_hash__
158
+
159
+ # The parent command or nil. Initialized by #add_command
160
+ attr_accessor :__supercommand__
152
161
 
153
162
  # The subcommand identifier (a Symbol incl. the exclamation mark) or nil
154
163
  # if not present. Use #subcommand!, or the dynamically generated
@@ -38,13 +38,19 @@ module ShellOpts
38
38
  #
39
39
  def render(format)
40
40
  constrain format, :enum, :long, :short
41
- case format
42
- when :enum; names.join(", ")
43
- when :long; name
44
- when :short; short_names.first || name
41
+ s =
42
+ case format
43
+ when :enum; names.join(", ")
44
+ when :long; name
45
+ when :short; short_names.first || name
46
+ else
47
+ raise ArgumentError, "Illegal format: #{format.inspect}"
48
+ end
49
+ if argument?
50
+ s + (optional? ? "[=#{argument_name}]" : "=#{argument_name}")
45
51
  else
46
- raise ArgumentError, "Illegal format: #{format.inspect}"
47
- end + (argument? ? "=#{argument_name}" : "")
52
+ s
53
+ end
48
54
  end
49
55
  end
50
56
 
@@ -75,17 +81,19 @@ module ShellOpts
75
81
  COMMANDS_ABBR = "[COMMANDS]"
76
82
  DESCRS_ABBR = "ARGS..."
77
83
 
78
- # Format can be one of :single, :enum, or :multi. :single force one-line
79
- # output and compacts options and commands if needed. :enum outputs a
80
- # :single line for each argument specification/description, :multi tries
81
- # one-line output but wrap options if needed. Multiple argument
82
- # specifications/descriptions are always compacted
84
+ # Format can be one of :abbr, :single, :enum, or :multi. :abbr
85
+ # lists the command on one line with options abbreviated. :single force
86
+ # one-line output and compacts options and commands if needed. :enum
87
+ # outputs a :single line for each argument specification/description,
88
+ # :multi tries one-line output but wrap options if needed. Multiple
89
+ # argument specifications/descriptions are always compacted
83
90
  #
84
91
  def render(format, width, root: false, **opts)
85
92
  case format
93
+ when :abbr; render_abbr
86
94
  when :single; render_single(width, **opts)
87
95
  when :enum; render_enum(width, **opts)
88
- when :multi; render_multi2(width, **opts)
96
+ when :multi; render_multi(width, **opts)
89
97
  else
90
98
  raise ArgumentError, "Illegal format: #{format.inspect}"
91
99
  end
@@ -96,6 +104,21 @@ module ShellOpts
96
104
  end
97
105
 
98
106
  protected
107
+ # TODO: Refactor and implement recursive detection of any argument
108
+ def get_args(args: nil)
109
+ case descrs.size
110
+ when 0; []
111
+ when 1; [descrs.first.text]
112
+ else [DESCRS_ABBR]
113
+ end
114
+ end
115
+
116
+ # Force one line and compact options to "[OPTIONS]"
117
+ def render_abbr
118
+ args = get_args
119
+ ([name] + [options.empty? ? nil : "[OPTIONS]"] + args).compact.join(" ")
120
+ end
121
+
99
122
  # Force one line. Compact options, commands, arguments if needed
100
123
  def render_single(width, args: nil)
101
124
  long_options = options.map { |option| option.render(:long) }
@@ -104,13 +127,7 @@ module ShellOpts
104
127
  short_commands = commands.empty? ? [] : ["[#{commands.map(&:name).join("|")}]"]
105
128
  compact_commands = commands.empty? ? [] : [COMMANDS_ABBR]
106
129
 
107
- # TODO: Refactor and implement recursive detection of any argument
108
- args ||=
109
- case descrs.size
110
- when 0; args = []
111
- when 1; [descrs.first.text]
112
- else [DESCRS_ABBR]
113
- end
130
+ args ||= get_args
114
131
 
115
132
  begin # to be able to use 'break' below
116
133
  words = [name] + long_options + short_commands + args
@@ -149,36 +166,8 @@ module ShellOpts
149
166
  short_options = options.map { |option| option.render(:short) }
150
167
  short_commands = commands.empty? ? [] : ["[#{commands.map(&:name).join("|")}]"]
151
168
  compact_commands = [COMMANDS_ABBR]
152
- args ||= self.descrs.size != 1 ? [DESCRS_ABBR] : descrs.map(&:text)
153
-
154
- # On one line
155
- words = long_options + short_commands + args
156
- return [words.join(" ")] if pass?(words, width)
157
- words = short_options + short_commands + args
158
- return [words.join(" ")] if pass?(words, width)
159
-
160
- # On multiple lines
161
- options = long_options.wrap(width)
162
- commands = [[short_commands, args].join(" ")]
163
- return options + commands if pass?(commands, width)
164
- options + [[compact_commands, args].join(" ")]
165
- end
166
-
167
- # Try to keep on one line but wrap options if needed. Multiple argument
168
- # specifications/descriptions are always compacted
169
- def render_multi2(width, args: nil)
170
- long_options = options.map { |option| option.render(:long) }
171
- short_options = options.map { |option| option.render(:short) }
172
- short_commands = commands.empty? ? [] : ["[#{commands.map(&:name).join("|")}]"]
173
- compact_commands = [COMMANDS_ABBR]
174
169
 
175
- # TODO: Refactor and implement recursive detection of any argument
176
- args ||=
177
- case descrs.size
178
- when 0; args = []
179
- when 1; [descrs.first.text]
180
- else [DESCRS_ABBR]
181
- end
170
+ args ||= get_args
182
171
 
183
172
  # On one line
184
173
  words = [name] + long_options + short_commands + args
@@ -1,3 +1,3 @@
1
1
  module ShellOpts
2
- VERSION = "2.0.9"
2
+ VERSION = "2.0.10"
3
3
  end
data/lib/shellopts.rb CHANGED
@@ -1,12 +1,6 @@
1
1
 
2
- $quiet = nil
3
- $verb = nil
4
- $debug = nil
5
- $shellopts = nil
6
-
7
2
  require 'indented_io'
8
3
 
9
- #$LOAD_PATH.unshift "../constrain/lib"
10
4
  require 'constrain'
11
5
  include Constrain
12
6
 
@@ -98,14 +92,22 @@ module ShellOpts
98
92
  # Array of remaining arguments. Initialized by #interpret
99
93
  attr_reader :args
100
94
 
101
- # Compiler flags
102
- attr_accessor :stdopts
103
- attr_accessor :msgopts
95
+ # Automatically add a -h and a --help option if true
96
+ attr_reader :help
104
97
 
105
- # Version of client program. This is only used if +stdopts+ is true
106
- attr_reader :version
98
+ # Version of client program. If not nil a --version option is added to the program
99
+ def version
100
+ return @version if @version
101
+ exe = caller.find { |line| line =~ /`<top \(required\)>'$/ }&.sub(/:.*/, "")
102
+ file = Dir.glob(File.dirname(exe) + "/../lib/*/version.rb").first
103
+ @version = IO.read(file).sub(/^.*VERSION\s*=\s*"(.*?)".*$/m, '\1') or
104
+ raise ArgumentError, "ShellOpts needs an explicit version"
105
+ end
106
+
107
+ # Add message options (TODO)
108
+ attr_accessor :msgopts
107
109
 
108
- # Interpreter flags
110
+ # Floating options
109
111
  attr_accessor :float
110
112
 
111
113
  # True if ShellOpts lets exceptions through instead of writing an error
@@ -117,11 +119,14 @@ module ShellOpts
117
119
 
118
120
  # Debug: Internal variables made public
119
121
  attr_reader :tokens
120
- alias_method :ast, :grammar # Oops - defined earlier FIXME
122
+ alias_method :ast, :grammar
121
123
 
122
- def initialize(name: nil, stdopts: true, version: nil, msgopts: false, float: true, exception: false)
124
+ def initialize(name: nil, help: true, version: true, msgopts: false, float: true, exception: false)
123
125
  @name = name || File.basename($PROGRAM_NAME)
124
- @stdopts, @version, @msgopts, @float, @exception = stdopts, version, msgopts, float, exception
126
+ @help = help
127
+ @use_version = version ? true : false
128
+ @version = @use_version && @version != true ? @version : nil
129
+ @msgopts, @float, @exception = msgopts, float, exception
125
130
  end
126
131
 
127
132
  # Compile source and return grammar object. Also sets #spec and #grammar.
@@ -133,7 +138,8 @@ module ShellOpts
133
138
  @file = find_caller_file
134
139
  @tokens = Lexer.lex(name, @spec, @oneline)
135
140
  ast = Parser.parse(tokens)
136
- ast.add_stdopts if stdopts
141
+ ast.add_version_option if @use_version
142
+ ast.add_help_options if @help
137
143
  @grammar = Analyzer.analyze(ast)
138
144
  }
139
145
  self
@@ -146,19 +152,16 @@ module ShellOpts
146
152
  handle_exceptions {
147
153
  @argv = argv.dup
148
154
  @program, @args = Interpreter.interpret(grammar, argv, float: float, exception: exception)
149
- if stdopts
150
- if @program.version?
151
- version or raise ArgumentError, "Version not specified"
152
- puts version
153
- exit
154
- elsif @program.help?
155
- if @program[:help].name == "-h"
156
- ShellOpts.brief
157
- else
158
- ShellOpts.help
159
- end
160
- exit
155
+ if @program.version?
156
+ puts version
157
+ exit
158
+ elsif @program.help?
159
+ if @program[:help].name == "-h"
160
+ ShellOpts.brief
161
+ else
162
+ ShellOpts.help
161
163
  end
164
+ exit
162
165
  end
163
166
  }
164
167
  self
@@ -364,132 +367,3 @@ module ShellOpts
364
367
  end
365
368
  end
366
369
 
367
-
368
-
369
-
370
-
371
-
372
-
373
-
374
- __END__
375
-
376
- require "shellopts/version"
377
-
378
- require "ext/algorithm.rb"
379
- require "ext/ruby_env.rb"
380
-
381
- require "shellopts/constants.rb"
382
- require "shellopts/exceptions.rb"
383
-
384
- require "shellopts/grammar/analyzer.rb"
385
- require "shellopts/grammar/lexer.rb"
386
- require "shellopts/grammar/parser.rb"
387
- require "shellopts/grammar/command.rb"
388
- require "shellopts/grammar/option.rb"
389
-
390
- require "shellopts/ast/parser.rb"
391
- require "shellopts/ast/command.rb"
392
- require "shellopts/ast/option.rb"
393
-
394
- require "shellopts/args.rb"
395
- require "shellopts/formatter.rb"
396
-
397
- if RUBY_ENV == "development"
398
- require "shellopts/grammar/dump.rb"
399
- require "shellopts/ast/dump.rb"
400
- end
401
-
402
- $verb = nil
403
- $quiet = nil
404
- $shellopts = nil
405
-
406
- module ShellOpts
407
- class ShellOpts
408
- attr_reader :name # Name of program. Defaults to the name of the executable
409
- attr_reader :spec
410
- attr_reader :argv
411
-
412
- attr_reader :grammar
413
- attr_reader :program
414
- attr_reader :arguments
415
-
416
- def initialize(spec, argv, name: nil, exception: false)
417
- @name = name || File.basename($PROGRAM_NAME)
418
- @spec, @argv = spec, argv.dup
419
- exprs = Grammar::Lexer.lex(@spec)
420
- commands = Grammar::Parser.parse(@name, exprs)
421
- @grammar = Grammar::Analyzer.analyze(commands)
422
-
423
- begin
424
- @program, @arguments = Ast::Parser.parse(@grammar, @argv)
425
- rescue Error => ex
426
- raise if exception
427
- error(ex.subject, ex.message)
428
- end
429
- end
430
-
431
- def error(subject = nil, message)
432
- $stderr.puts "#{name}: #{message}"
433
- usage(subject, device: $stderr)
434
- exit 1
435
- end
436
-
437
- def fail(message)
438
- $stderr.puts "#{name}: #{message}"
439
- exit 1
440
- end
441
-
442
- def usage(subject = nil, device: $stdout, levels: 1, margin: "")
443
- subject = find_subject(subject)
444
- device.puts Formatter.usage_string(subject, levels: levels, margin: margin)
445
- end
446
-
447
- def help(subject = nil, device: $stdout, levels: 10, margin: "", tab: " ")
448
- subject = find_subject(subject)
449
- device.puts Formatter.help_string(subject, levels: levels, margin: margin, tab: tab)
450
- end
451
-
452
- private
453
- def lookup(name)
454
- a = name.split(".")
455
- cmd = grammar
456
- while element = a.shift
457
- cmd = cmd.commands[element]
458
- end
459
- cmd
460
- end
461
-
462
- def find_subject(obj)
463
- case obj
464
- when String; lookup(obj)
465
- when Ast::Command; Command.grammar(obj)
466
- when Grammar::Command; obj
467
- when NilClass; grammar
468
- else
469
- raise Internal, "Illegal object: #{obj.class}"
470
- end
471
- end
472
- end
473
-
474
- def self.process(spec, argv, name: nil, exception: false)
475
- $shellopts = ShellOpts.new(spec, argv, name: name, exception: exception)
476
- [$shellopts.program, $shellopts.arguments]
477
- end
478
-
479
- def self.error(subject = nil, message)
480
- $shellopts.error(subject, message)
481
- end
482
-
483
- def self.fail(message)
484
- $shellopts.fail(message)
485
- end
486
-
487
- def self.help(subject = nil, device: $stdout, levels: 10, margin: "", tab: " ")
488
- $shellopts.help(subject, device: device, levels: levels, margin: margin, tab: tab)
489
- end
490
-
491
- def self.usage(subject = nil, device: $stdout, levels: 1, margin: "")
492
- $shellopts.usage(subject, device: device, levels: levels, margin: margin)
493
- end
494
- end
495
-
data/main CHANGED
@@ -7,12 +7,15 @@ require 'shellopts'
7
7
 
8
8
  include ShellOpts
9
9
 
10
+ p ShellOpts::ShellOpts.default_version
11
+ exit
12
+
10
13
  VERSION = "1.2.3"
11
14
 
12
15
  SPEC = %(
13
16
  -a @ An option
14
17
  )
15
- opts, args = ShellOpts::process(SPEC, ARGV, version: VERSION)
18
+ opts, args = ShellOpts.process(SPEC, ARGV, version: VERSION)
16
19
  #ShellOpts::ShellOpts.help
17
20
 
18
21
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shellopts
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.9
4
+ version: 2.0.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Claus Rasmussen
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-03-06 00:00:00.000000000 Z
11
+ date: 2022-03-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: forward_to