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.
- checksums.yaml +4 -4
- data/TODO +15 -135
- data/lib/ext/algorithm.rb +14 -0
- data/lib/ext/ruby_env.rb +8 -0
- data/lib/shellopts.rb +90 -213
- data/lib/shellopts/args.rb +18 -12
- data/lib/shellopts/ast/command.rb +101 -30
- data/lib/shellopts/ast/dump.rb +28 -0
- data/lib/shellopts/ast/option.rb +8 -14
- data/lib/shellopts/ast/parser.rb +106 -0
- data/lib/shellopts/constants.rb +88 -0
- data/lib/shellopts/exceptions.rb +21 -0
- data/lib/shellopts/formatter.rb +125 -0
- data/lib/shellopts/grammar/analyzer.rb +76 -0
- data/lib/shellopts/grammar/command.rb +67 -60
- data/lib/shellopts/grammar/dump.rb +56 -0
- data/lib/shellopts/grammar/lexer.rb +56 -0
- data/lib/shellopts/grammar/option.rb +49 -60
- data/lib/shellopts/grammar/parser.rb +78 -0
- data/lib/shellopts/version.rb +2 -2
- data/shellopts.gemspec +1 -1
- metadata +13 -13
- data/lib/ext/array.rb +0 -9
- data/lib/shellopts/ast/node.rb +0 -37
- data/lib/shellopts/ast/program.rb +0 -14
- data/lib/shellopts/compiler.rb +0 -128
- data/lib/shellopts/generator.rb +0 -15
- data/lib/shellopts/grammar/node.rb +0 -33
- data/lib/shellopts/grammar/program.rb +0 -65
- data/lib/shellopts/idr.rb +0 -236
- data/lib/shellopts/option_struct.rb +0 -148
- data/lib/shellopts/parser.rb +0 -106
- data/lib/shellopts/shellopts.rb +0 -116
@@ -1,66 +1,55 @@
|
|
1
1
|
module ShellOpts
|
2
2
|
module Grammar
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
#
|
11
|
-
attr_reader :
|
12
|
-
|
13
|
-
#
|
14
|
-
attr_reader :
|
15
|
-
|
16
|
-
#
|
17
|
-
|
18
|
-
|
19
|
-
#
|
20
|
-
attr_reader :
|
21
|
-
|
22
|
-
#
|
23
|
-
|
24
|
-
|
25
|
-
#
|
26
|
-
|
27
|
-
|
28
|
-
def
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
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
|
data/lib/shellopts/version.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
|
-
module
|
2
|
-
VERSION = "2.0.0.pre.
|
1
|
+
module ShellOpts
|
2
|
+
VERSION = "2.0.0.pre.14"
|
3
3
|
end
|
data/shellopts.gemspec
CHANGED
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.
|
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:
|
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/
|
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/
|
112
|
+
- lib/shellopts/ast/dump.rb
|
112
113
|
- lib/shellopts/ast/option.rb
|
113
|
-
- lib/shellopts/ast/
|
114
|
-
- lib/shellopts/
|
115
|
-
- lib/shellopts/
|
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/
|
120
|
+
- lib/shellopts/grammar/dump.rb
|
121
|
+
- lib/shellopts/grammar/lexer.rb
|
118
122
|
- lib/shellopts/grammar/option.rb
|
119
|
-
- lib/shellopts/grammar/
|
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
data/lib/shellopts/ast/node.rb
DELETED
@@ -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
|
data/lib/shellopts/compiler.rb
DELETED
@@ -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
|