shellopts 2.0.0.pre.7 → 2.0.0.pre.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,66 +1,55 @@
1
1
  module ShellOpts
2
2
  module Grammar
3
- # Models an Option
4
- #
5
- # Sets Node#key to the first long option name if present or else the first short option
6
- class Option < Node
7
- # List of short names (incl. '-')
8
- attr_reader :short_names
9
-
10
- # List of long names (incl. '--')
11
- attr_reader :long_names
12
-
13
- # Name of the key attribute (eg. if key is :all then key_name is '--all'
14
- attr_reader :key_name
15
-
16
- # List of flags (Symbol)
17
- def flags() @flags.keys end
18
-
19
- # Informal name of argument (eg. 'FILE'). nil if not present
20
- attr_reader :label
21
-
22
- # Initialize an option. Short and long names are arrays of the short/long
23
- # option names (incl. the '-'/'--' prefix). It is assumed that at least
24
- # one name is given. Flags is a list of symbolic flags. Allowed flags are
25
- # :repeated, :argument, :optional, :integer, and :float. Note that
26
- # there's no :string flag, it's status is inferred. label is the optional
27
- # informal name of the option argument (eg. 'FILE') or nil if not present
28
- def initialize(short_names, long_names, flags, label = nil)
29
- @key_name = long_names.first || short_names.first
30
- name = @key_name.sub(/^-+/, "")
31
- super(name.to_sym, name)
32
- @short_names, @long_names = short_names, long_names
33
- @flags = flags.map { |flag| [flag, true] }.to_h
34
- @label = label
35
- end
36
-
37
- # Array of option names with short names first and then the long names
38
- def names() @short_names + @long_names end
39
-
40
- # Array of names and the key
41
- def identifiers() names + [key] end
42
-
43
- # Return true if +ident+ is equal to any name or to key
44
- def match?(ident) names.include?(ident) || ident == key end
45
-
46
- # Flag query methods. Returns true if the flag is present and otherwise nil
47
- def repeated?() @flags[:repeated] || false end
48
- def argument?() @flags[:argument] || false end
49
- def optional?() argument? && @flags[:optional] || false end
50
- def string?() argument? && !integer? && !float? end
51
- def integer?() argument? && @flags[:integer] || false end
52
- def float?() argument? && @flags[:float] || false end
53
-
54
- # :nocov:
55
- def dump
56
- super {
57
- puts "short_names: #{short_names.inspect}"
58
- puts "long_names: #{long_names.inspect}"
59
- puts "flags: #{flags.inspect}"
60
- puts "label: #{label.inspect}"
61
- }
3
+ class Option
4
+ # Symbolic identifier. This is the name of the option with dashes ('-')
5
+ # replaced with underscores ('_')
6
+ attr_reader :ident
7
+
8
+ # Name of option. This is the name of the first long option or the name
9
+ # of the first short option if there is no long option name. It is used
10
+ # to compute #ident
11
+ attr_reader :name
12
+
13
+ # Long name of option or nil if not present
14
+ attr_reader :longname
15
+
16
+ # Short name of option or nil if not present
17
+ attr_reader :shortname
18
+
19
+ # List of all names
20
+ attr_reader :names
21
+
22
+ # Name of argument or nil if not present
23
+ attr_reader :argument_name
24
+
25
+ # Comment
26
+ attr_reader :text
27
+
28
+ def repeatable?() @repeatable end
29
+ def argument?() @argument end
30
+ def integer?() @integer end
31
+ def float?() @float end
32
+ def string?() !@integer && !@float end
33
+ def optional?() @optional end
34
+
35
+ def initialize(names, repeatable: nil, argument: nil, integer: nil, float: nil, optional: nil)
36
+ @names = names.dup
37
+ @longname = @names.find { |name| name.length > 1 }
38
+ @shortname = @names.find { |name| name.length == 1 }
39
+ @name = @longname || @shortname
40
+ @ident = @name.gsub("-", "_").to_sym
41
+ @repeatable = repeatable || false
42
+ if argument
43
+ @argument = true
44
+ @argument_name = argument if argument.is_a?(String)
45
+ else
46
+ @argument = false
47
+ end
48
+ @integer = integer || false
49
+ @float = float || false
50
+ @optional = optional || false
51
+ @text = []
62
52
  end
63
- # :nocov:
64
53
  end
65
54
  end
66
55
  end
@@ -0,0 +1,78 @@
1
+ module ShellOpts
2
+ module Grammar
3
+ class Parser
4
+ def self.parse(program_name, exprs)
5
+ @commands = []
6
+ @commands << (@current = @cmd = Program.new(program_name))
7
+ @exprs = exprs.dup
8
+
9
+ while !@exprs.empty?
10
+ type, value = @exprs.shift
11
+ case type
12
+ when "OPT"
13
+ parse_option(value)
14
+ when "CMD"
15
+ parse_command(value)
16
+ when "ARG"
17
+ parse_argument(value)
18
+ when "TXT"
19
+ parse_text(value)
20
+ else
21
+ raise
22
+ end
23
+ end
24
+
25
+ @commands.each { |cmd| # Remove empty last-lines in comments and options
26
+ while cmd.text.last =~ /^\s*$/
27
+ cmd.text.pop
28
+ end
29
+ cmd.opts.each { |opt|
30
+ while opt.text.last =~ /^\s*$/
31
+ opt.text.pop
32
+ end
33
+ }
34
+ }
35
+
36
+ @commands
37
+ end
38
+
39
+ def self.parse_option_names(names)
40
+ names.split(",")
41
+ end
42
+
43
+ def self.parse_option(source)
44
+ OPTION_RE =~ source or raise CompilerError, "Illegal option: #{source}"
45
+ option_group = $1
46
+ argument = $4 || $2 && true
47
+ type = $3
48
+ optional = $5
49
+
50
+ option_group =~ /^(\+\+?|--?)(.*)/
51
+ repeatable = ($1 == '+' || $1 == '++' ? '+' : nil)
52
+ names = parse_option_names($2)
53
+
54
+ @cmd.opts << (@current = Option.new(
55
+ names,
56
+ repeatable: repeatable, argument: argument,
57
+ integer: (type == '#'), float: (type == '$'),
58
+ optional: optional))
59
+ !OPTION_RESERVED_WORDS.include?(@current.name) or
60
+ raise CompilerError, "Reserved option name: #{@current.name}"
61
+ end
62
+
63
+ def self.parse_argument(source)
64
+ @cmd.args << source
65
+ end
66
+
67
+ def self.parse_command(value)
68
+ @commands << (@current = @cmd = Command.new(value))
69
+ !COMMAND_RESERVED_WORDS.include?(@current.name) or
70
+ raise CompilerError, "Reserved command name: #{@current.name}"
71
+ end
72
+
73
+ def self.parse_text(value)
74
+ @current.text << value
75
+ end
76
+ end
77
+ end
78
+ end
@@ -1,3 +1,3 @@
1
- module Shellopts
2
- VERSION = "2.0.0.pre.7"
1
+ module ShellOpts
2
+ VERSION = "2.0.0.pre.14"
3
3
  end
data/shellopts.gemspec CHANGED
@@ -5,7 +5,7 @@ require "shellopts/version"
5
5
 
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = "shellopts"
8
- spec.version = Shellopts::VERSION
8
+ spec.version = ShellOpts::VERSION
9
9
  spec.authors = ["Claus Rasmussen"]
10
10
  spec.email = ["claus.l.rasmussen@gmail.com"]
11
11
 
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.0.pre.7
4
+ version: 2.0.0.pre.14
5
5
  platform: ruby
6
6
  authors:
7
7
  - Claus Rasmussen
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-10-06 00:00:00.000000000 Z
11
+ date: 2021-02-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -104,23 +104,23 @@ files:
104
104
  - bin/mkdoc
105
105
  - bin/setup
106
106
  - doc/stylesheet.css
107
- - lib/ext/array.rb
107
+ - lib/ext/algorithm.rb
108
+ - lib/ext/ruby_env.rb
108
109
  - lib/shellopts.rb
109
110
  - lib/shellopts/args.rb
110
111
  - lib/shellopts/ast/command.rb
111
- - lib/shellopts/ast/node.rb
112
+ - lib/shellopts/ast/dump.rb
112
113
  - lib/shellopts/ast/option.rb
113
- - lib/shellopts/ast/program.rb
114
- - lib/shellopts/compiler.rb
115
- - lib/shellopts/generator.rb
114
+ - lib/shellopts/ast/parser.rb
115
+ - lib/shellopts/constants.rb
116
+ - lib/shellopts/exceptions.rb
117
+ - lib/shellopts/formatter.rb
118
+ - lib/shellopts/grammar/analyzer.rb
116
119
  - lib/shellopts/grammar/command.rb
117
- - lib/shellopts/grammar/node.rb
120
+ - lib/shellopts/grammar/dump.rb
121
+ - lib/shellopts/grammar/lexer.rb
118
122
  - lib/shellopts/grammar/option.rb
119
- - lib/shellopts/grammar/program.rb
120
- - lib/shellopts/idr.rb
121
- - lib/shellopts/option_struct.rb
122
- - lib/shellopts/parser.rb
123
- - lib/shellopts/shellopts.rb
123
+ - lib/shellopts/grammar/parser.rb
124
124
  - lib/shellopts/version.rb
125
125
  - shellopts.gemspec
126
126
  homepage: http://github.com/clrgit/shellopts
data/lib/ext/array.rb DELETED
@@ -1,9 +0,0 @@
1
-
2
- module XArray
3
- refine Array do
4
- # Find and return first duplicate. Return nil if not found
5
- def find_dup
6
- detect { |e| rindex(e) != index(e) }
7
- end
8
- end
9
- end
@@ -1,37 +0,0 @@
1
- module ShellOpts
2
- module Ast
3
- class Node
4
- # The associated Grammar::Node object
5
- attr_reader :grammar
6
-
7
- # Key of node. Shorthand for grammar.key
8
- def key() @grammar.key end
9
-
10
- # Name of node (either program, command, or option name)
11
- attr_reader :name
12
-
13
- # Initialize an +Ast::Node+ object. +grammar+ is the corresponding
14
- # grammar object (+Grammar::Node+) and +name+ is the name of the option
15
- # or sub-command
16
- def initialize(grammar, name)
17
- @grammar, @name = grammar, name
18
- end
19
-
20
- # Return a name/value pair
21
- def to_tuple
22
- [name, values]
23
- end
24
-
25
- # Return either a value (option value), an array of values (command), or
26
- # nil (option without a value). It must be defined in sub-classes of Ast::Node
27
- def values() raise end
28
-
29
- # :nocov:
30
- def dump(&block)
31
- puts key.inspect
32
- indent { yield } if block_given?
33
- end
34
- # :nocov:
35
- end
36
- end
37
- end
@@ -1,14 +0,0 @@
1
- module ShellOpts
2
- module Ast
3
- class Program < Command
4
- # Command line arguments. Initially nil but assigned by the parser. This array
5
- # is the same as the argument array returned by Ast.parse
6
- attr_accessor :arguments
7
-
8
- def initialize(grammar)
9
- super(grammar, grammar.name)
10
- @arguments = nil
11
- end
12
- end
13
- end
14
- end
@@ -1,128 +0,0 @@
1
- require "ext/array.rb"
2
-
3
- require 'shellopts/grammar/node.rb'
4
- require 'shellopts/grammar/option.rb'
5
- require 'shellopts/grammar/command.rb'
6
- require 'shellopts/grammar/program.rb'
7
-
8
- module ShellOpts
9
- module Grammar
10
- # Compiles an option definition string and returns a Grammar::Program
11
- # object. name is the name of the program and source is the
12
- # option definition string
13
- def self.compile(name, source)
14
- name.is_a?(String) or raise Compiler::Error, "Expected String argument, got #{name.class}"
15
- source.is_a?(String) or raise Compiler::Error, "Expected String argument, got #{source.class}"
16
- Compiler.new(name, source).call
17
- end
18
-
19
- # Service object for compiling an option definition string. Returns a
20
- # Grammar::Program object
21
- #
22
- # Compiler implements a recursive descend algorithm to compile the option
23
- # string. The algorithm uses state variables and is embedded in a
24
- # Grammar::Compiler service object
25
- class Compiler
26
- class Error < RuntimeError; end
27
-
28
- # Initialize a Compiler object. source is the option definition string
29
- def initialize(name, source)
30
- @name, @tokens = name, source.split(/\s+/).reject(&:empty?)
31
-
32
- # @subcommands_by_path is an hash from subcommand-path to Command or Program
33
- # object. The top level Program object has nil as its path.
34
- # @subcommands_by_path is used to check for uniqueness of subcommands and to
35
- # link sub-subcommands to their parents
36
- @subcommands_by_path = {}
37
- end
38
-
39
- def call
40
- compile_program
41
- end
42
-
43
- private
44
- using XArray # For Array#find_dup
45
-
46
- # Returns the current token
47
- def curr_token() @tokens.first end
48
-
49
- # Returns the current token and advance to the next token
50
- def next_token() @tokens.shift end
51
-
52
- def compile_program
53
- program = @subcommands_by_path[nil] = Grammar::Program.new(@name, compile_options)
54
- while curr_token && curr_token != "--"
55
- compile_subcommand
56
- end
57
- program.args.concat(@tokens[1..-1]) if curr_token
58
- program
59
- end
60
-
61
- def compile_subcommand
62
- path = curr_token[0..-2]
63
- ident_list = compile_ident_list(path, ".")
64
- parent_path = ident_list.size > 1 ? ident_list[0..-2].join(".") : nil
65
- name = ident_list[-1]
66
-
67
- parent = @subcommands_by_path[parent_path] or
68
- raise Compiler::Error, "No such subcommand: #{parent_path.inspect}"
69
- !@subcommands_by_path.key?(path) or raise Compiler::Error, "Duplicate subcommand: #{path.inspect}"
70
- next_token
71
- @subcommands_by_path[path] = Grammar::Command.new(parent, name, compile_options)
72
- end
73
-
74
- def compile_options
75
- option_list = []
76
- while curr_token && curr_token != "--" && !curr_token.end_with?("!")
77
- option_list << compile_option
78
- end
79
- dup = option_list.map(&:names).flatten.find_dup and
80
- raise Compiler::Error, "Duplicate option name: #{dup.inspect}"
81
- option_list
82
- end
83
-
84
- def compile_option
85
- # Match string and build flags
86
- flags = []
87
- curr_token =~ /^(\+)?(.+?)(?:(=)(\$|\#)?(.*?)(\?)?)?$/
88
- flags << :repeated if $1 == "+"
89
- names = $2
90
- flags << :argument if $3 == "="
91
- flags << :integer if $4 == "#"
92
- flags << :float if $4 == "$"
93
- label = $5 == "" ? nil : $5
94
- flags << :optional if $6 == "?"
95
-
96
- # Build names
97
- short_names = []
98
- long_names = []
99
- ident_list = compile_ident_list(names, ",")
100
- (dup = ident_list.find_dup).nil? or
101
- raise Compiler::Error, "Duplicate identifier #{dup.inspect} in #{curr_token.inspect}"
102
- ident_list.each { |ident|
103
- if ident.size == 1
104
- short_names << "-#{ident}"
105
- else
106
- long_names << "--#{ident}"
107
- end
108
- }
109
-
110
- next_token
111
- Grammar::Option.new(short_names, long_names, flags, label)
112
- end
113
-
114
- # Compile list of option names or a subcommand path
115
- def compile_ident_list(ident_list_str, sep)
116
- ident_list_str.split(sep, -1).map { |str|
117
- !str.empty? or
118
- raise Compiler::Error, "Empty identifier in #{curr_token.inspect}"
119
- !str.start_with?("-") or
120
- raise Compiler::Error, "Identifier can't start with '-' in #{curr_token.inspect}"
121
- str !~ /([^\w\d#{sep}-])/ or
122
- raise Compiler::Error, "Illegal character #{$1.inspect} in #{curr_token.inspect}"
123
- str
124
- }
125
- end
126
- end
127
- end
128
- end