shellopts 2.0.0.pre.13 → 2.0.0.pre.14

Sign up to get free protection for your applications and to get access to all the features.
@@ -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.13"
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.13
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-11-25 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,25 +104,23 @@ files:
104
104
  - bin/mkdoc
105
105
  - bin/setup
106
106
  - doc/stylesheet.css
107
- - lib/ext/array.rb
108
- - lib/main.rb
107
+ - lib/ext/algorithm.rb
108
+ - lib/ext/ruby_env.rb
109
109
  - lib/shellopts.rb
110
110
  - lib/shellopts/args.rb
111
111
  - lib/shellopts/ast/command.rb
112
- - lib/shellopts/ast/node.rb
112
+ - lib/shellopts/ast/dump.rb
113
113
  - lib/shellopts/ast/option.rb
114
- - lib/shellopts/ast/program.rb
115
- - lib/shellopts/compiler.rb
116
- - 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
117
119
  - lib/shellopts/grammar/command.rb
118
- - lib/shellopts/grammar/node.rb
120
+ - lib/shellopts/grammar/dump.rb
121
+ - lib/shellopts/grammar/lexer.rb
119
122
  - lib/shellopts/grammar/option.rb
120
- - lib/shellopts/grammar/program.rb
121
- - lib/shellopts/idr.rb
122
- - lib/shellopts/main.rb
123
- - lib/shellopts/option_struct.rb
124
- - lib/shellopts/parser.rb
125
- - lib/shellopts/shellopts.rb
123
+ - lib/shellopts/grammar/parser.rb
126
124
  - lib/shellopts/version.rb
127
125
  - shellopts.gemspec
128
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
data/lib/main.rb DELETED
@@ -1 +0,0 @@
1
- hej
@@ -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
@@ -1,15 +0,0 @@
1
-
2
- require 'shellopts/idr.rb'
3
-
4
- module ShellOpts
5
- module Idr
6
- # Generates an Idr::Program from a ShellOpts object
7
- def self.generate(shellopts)
8
- Idr::Program.new(shellopts)
9
- end
10
- end
11
- end
12
-
13
-
14
-
15
-
@@ -1,33 +0,0 @@
1
- module ShellOpts
2
- module Grammar
3
- # Root class for Grammar objects
4
- #
5
- # Node objects are created by ShellOpts::Grammar.compile that returns a
6
- # Program object that in turn contains other node objects in a hierarchical
7
- # structure that reflects the grammar of the program. Only
8
- # ShellOpts::Grammar.compile should create node objects
9
- class Node
10
- # Key (Symbol) of node. Unique within the enclosing command
11
- attr_reader :key
12
-
13
- # Name of node. The name of an option is without the prefixed '-' or
14
- # '--', the name of a command is without the suffixed '!'. Note that name
15
- # collisions can happen between options and commands names
16
- attr_reader :name
17
-
18
- def initialize(key, name)
19
- @key, @name = key, name
20
- end
21
-
22
- # :nocov:
23
- def dump(&block)
24
- puts key.inspect
25
- indent {
26
- puts "name: #{name.inspect}"
27
- yield if block_given?
28
- }
29
- end
30
- # :nocov:
31
- end
32
- end
33
- end
@@ -1,65 +0,0 @@
1
- module ShellOpts
2
- module Grammar
3
- # Program is the root object of the grammar
4
- class Program < Command
5
- # Array of non-option litteral arguments (ie. what comes after the double dash ('+--+') in
6
- # the usage definition). Initially empty but filled out during compilation
7
- attr_reader :args
8
-
9
- # Initialize a top-level Program object
10
- def initialize(name, option_list)
11
- super(nil, name, option_list)
12
- @args = []
13
- end
14
-
15
- # Usage string to be used in error messages. The string is kept short by
16
- # only listing the shortest option (if there is more than one)
17
- def usage
18
- (
19
- render_options(option_list) +
20
- subcommand_list.map { |cmd| render_subcommand(cmd) } +
21
- args
22
- ).flatten.join(" ")
23
- end
24
-
25
- # :nocov:
26
- def dump(&block)
27
- super {
28
- puts "args: #{args.inspect}"
29
- puts "usage: #{usage.inspect}"
30
- }
31
- end
32
- # :nocov:
33
-
34
- private
35
- def render_subcommand(subcommand)
36
- [subcommand.name] + render_options(subcommand.option_list) +
37
- subcommand.subcommand_list.map { |cmd| render_subcommand(cmd) }.flatten
38
- end
39
-
40
- def render_options(options)
41
- options.map { |opt|
42
- s = opt.names.first
43
- if opt.argument?
44
- arg_string =
45
- if opt.label
46
- opt.label
47
- elsif opt.integer?
48
- "INT"
49
- elsif opt.float?
50
- "FLOAT"
51
- else
52
- "ARG"
53
- end
54
- if opt.optional?
55
- s += "[=#{arg_string}]"
56
- else
57
- s += "=#{arg_string}"
58
- end
59
- end
60
- s
61
- }
62
- end
63
- end
64
- end
65
- end