shellopts 2.0.0.pre.13 → 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.
@@ -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