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.
- 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 -228
- data/lib/shellopts/args.rb +1 -1
- 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 -15
- data/lib/ext/array.rb +0 -9
- data/lib/main.rb +0 -1
- 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/main.rb +0 -10
- data/lib/shellopts/option_struct.rb +0 -148
- data/lib/shellopts/parser.rb +0 -106
- data/lib/shellopts/shellopts.rb +0 -123
@@ -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,25 +104,23 @@ files:
|
|
104
104
|
- bin/mkdoc
|
105
105
|
- bin/setup
|
106
106
|
- doc/stylesheet.css
|
107
|
-
- lib/ext/
|
108
|
-
- lib/
|
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/
|
112
|
+
- lib/shellopts/ast/dump.rb
|
113
113
|
- lib/shellopts/ast/option.rb
|
114
|
-
- lib/shellopts/ast/
|
115
|
-
- lib/shellopts/
|
116
|
-
- 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
|
117
119
|
- lib/shellopts/grammar/command.rb
|
118
|
-
- lib/shellopts/grammar/
|
120
|
+
- lib/shellopts/grammar/dump.rb
|
121
|
+
- lib/shellopts/grammar/lexer.rb
|
119
122
|
- lib/shellopts/grammar/option.rb
|
120
|
-
- lib/shellopts/grammar/
|
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
data/lib/main.rb
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
hej
|
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
|
data/lib/shellopts/generator.rb
DELETED
@@ -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
|