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.
- 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
|