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