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

Sign up to get free protection for your applications and to get access to all the features.
@@ -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