shellopts 2.0.0.pre.4 → 2.0.0.pre.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.ruby-version +1 -1
- data/TODO +4 -1
- data/lib/shellopts.rb +137 -44
- data/lib/shellopts/args.rb +5 -17
- data/lib/shellopts/ast/command.rb +6 -6
- data/lib/shellopts/compiler.rb +19 -21
- data/lib/shellopts/generator.rb +3 -3
- data/lib/shellopts/grammar/command.rb +12 -13
- data/lib/shellopts/grammar/node.rb +11 -3
- data/lib/shellopts/grammar/option.rb +2 -1
- data/lib/shellopts/grammar/program.rb +4 -4
- data/lib/shellopts/idr.rb +71 -44
- data/lib/shellopts/option_struct.rb +62 -159
- data/lib/shellopts/parser.rb +11 -11
- data/lib/shellopts/shellopts.rb +49 -33
- data/lib/shellopts/version.rb +1 -1
- metadata +2 -5
- data/lib/shellopts/messenger.rb +0 -71
- data/lib/shellopts/utils.rb +0 -16
- data/rs +0 -40
data/lib/shellopts/parser.rb
CHANGED
@@ -16,7 +16,7 @@ module ShellOpts
|
|
16
16
|
end
|
17
17
|
|
18
18
|
private
|
19
|
-
# Parse a
|
19
|
+
# Parse a subcommand line
|
20
20
|
class Parser
|
21
21
|
class Error < RuntimeError; end
|
22
22
|
|
@@ -27,24 +27,24 @@ module ShellOpts
|
|
27
27
|
|
28
28
|
def call
|
29
29
|
program = Ast::Program.new(@grammar)
|
30
|
-
|
30
|
+
parse_subcommand(program)
|
31
31
|
program.arguments = @argv
|
32
32
|
program
|
33
33
|
end
|
34
34
|
|
35
35
|
private
|
36
|
-
def
|
37
|
-
@seen_options = {} # Every new
|
36
|
+
def parse_subcommand(subcommand)
|
37
|
+
@seen_options = {} # Every new subcommand resets the seen options
|
38
38
|
while arg = @argv.first
|
39
39
|
if arg == "--"
|
40
40
|
@argv.shift
|
41
41
|
break
|
42
42
|
elsif arg.start_with?("-")
|
43
|
-
parse_option(
|
44
|
-
elsif cmd =
|
43
|
+
parse_option(subcommand)
|
44
|
+
elsif cmd = subcommand.grammar.subcommands[arg]
|
45
45
|
@argv.shift
|
46
|
-
|
47
|
-
|
46
|
+
subcommand.subcommand = Ast::Command.new(cmd, arg)
|
47
|
+
parse_subcommand(subcommand.subcommand)
|
48
48
|
break
|
49
49
|
else
|
50
50
|
break
|
@@ -52,7 +52,7 @@ module ShellOpts
|
|
52
52
|
end
|
53
53
|
end
|
54
54
|
|
55
|
-
def parse_option(
|
55
|
+
def parse_option(subcommand)
|
56
56
|
# Split into name and argument
|
57
57
|
case @argv.first
|
58
58
|
when /^(--.+?)(?:=(.*))?$/
|
@@ -62,7 +62,7 @@ module ShellOpts
|
|
62
62
|
end
|
63
63
|
@argv.shift
|
64
64
|
|
65
|
-
option =
|
65
|
+
option = subcommand.grammar.options[name] or raise Error, "Unknown option '#{name}'"
|
66
66
|
!@seen_options.key?(option.key) || option.repeated? or raise Error, "Duplicate option '#{name}'"
|
67
67
|
@seen_options[option.key] = true
|
68
68
|
|
@@ -83,7 +83,7 @@ module ShellOpts
|
|
83
83
|
raise Error, "No argument allowed for option '#{name}'"
|
84
84
|
end
|
85
85
|
|
86
|
-
|
86
|
+
subcommand.options << Ast::Option.new(option, name, arg)
|
87
87
|
end
|
88
88
|
|
89
89
|
def parse_arg(option, name, arg)
|
data/lib/shellopts/shellopts.rb
CHANGED
@@ -6,7 +6,7 @@ require "shellopts/args.rb"
|
|
6
6
|
# TODO
|
7
7
|
#
|
8
8
|
# PROCESSING
|
9
|
-
# 1. Compile
|
9
|
+
# 1. Compile spec string and yield a grammar
|
10
10
|
# 2. Parse the options using the grammar and yield an AST
|
11
11
|
# 3. Construct the Program model from the AST
|
12
12
|
# 4. Apply defaults to the model
|
@@ -17,19 +17,20 @@ require "shellopts/args.rb"
|
|
17
17
|
module ShellOpts
|
18
18
|
# The command line processing object
|
19
19
|
class ShellOpts
|
20
|
-
# One of :key, :name, :option
|
21
|
-
#
|
22
|
-
# Option Command
|
23
|
-
# :key key #command! (no collision)
|
24
|
-
# :name name #command (possible collision)
|
25
|
-
# :option --option #command (multihash, no collision) (TODO)
|
26
|
-
#
|
27
|
-
DEFAULT_USE = :key
|
28
|
-
|
29
20
|
# Name of program
|
30
|
-
|
21
|
+
attr_accessor :name
|
22
|
+
|
23
|
+
# Usage string. If #usage is nil, the auto-generated default is used
|
24
|
+
def usage() @usage || @grammar.usage end
|
25
|
+
def usage=(usage) @usage = usage end
|
31
26
|
|
32
|
-
#
|
27
|
+
# Specification of the command
|
28
|
+
attr_reader :spec
|
29
|
+
|
30
|
+
# Original argv argument
|
31
|
+
attr_reader :argv
|
32
|
+
|
33
|
+
# The grammar compiled from the spec string
|
33
34
|
attr_reader :grammar
|
34
35
|
|
35
36
|
# The AST parsed from the command line arguments
|
@@ -38,32 +39,33 @@ module ShellOpts
|
|
38
39
|
# The IDR generated from the Ast
|
39
40
|
attr_reader :idr
|
40
41
|
|
41
|
-
#
|
42
|
-
# standard error and exit with status 1
|
43
|
-
attr_accessor :messenger
|
44
|
-
|
45
|
-
# Compile a usage string into a grammar and use that to parse command line
|
42
|
+
# Compile a spec string into a grammar and use that to parse command line
|
46
43
|
# arguments
|
47
44
|
#
|
48
|
-
# +
|
45
|
+
# +spec+ is the spec string, and +argv+ the command line (typically the
|
49
46
|
# global ARGV array). +name+ is the name of the program and defaults to the
|
50
47
|
# basename of the program
|
51
48
|
#
|
52
|
-
# Syntax errors in the
|
53
|
-
# +ShellOpts::CompilerError+ exception.
|
49
|
+
# Syntax errors in the spec string are caused by the developer and raise a
|
50
|
+
# +ShellOpts::CompilerError+ exception. Errors in the +argv+ arguments are
|
54
51
|
# caused by the user and terminates the program with an error message and a
|
55
|
-
# short description of its
|
56
|
-
|
52
|
+
# short description of its spec
|
53
|
+
#
|
54
|
+
# TODO: Change to (name, spec, argv, usage: nil) because
|
55
|
+
# ShellOpts::ShellOpts isn't a magician like the ShellOpts module
|
56
|
+
def initialize(spec, argv, name: ::ShellOpts.default_name, usage: ::ShellOpts.default_usage)
|
57
57
|
@name = name
|
58
|
+
@spec = spec
|
59
|
+
@usage = usage
|
60
|
+
@argv = argv
|
58
61
|
begin
|
59
|
-
@grammar = Grammar.compile(name,
|
60
|
-
@
|
61
|
-
@
|
62
|
-
@idr = Idr.generate(@ast, @messenger)
|
62
|
+
@grammar = Grammar.compile(@name, @spec)
|
63
|
+
@ast = Ast.parse(@grammar, @argv)
|
64
|
+
@idr = Idr.generate(self)
|
63
65
|
rescue Grammar::Compiler::Error => ex
|
64
66
|
raise CompilerError.new(5, ex.message)
|
65
67
|
rescue Ast::Parser::Error => ex
|
66
|
-
|
68
|
+
raise UserError.new(ex.message)
|
67
69
|
end
|
68
70
|
end
|
69
71
|
|
@@ -75,10 +77,18 @@ module ShellOpts
|
|
75
77
|
def to_a() idr.to_a end
|
76
78
|
|
77
79
|
# Return a hash representation of the options. See {ShellOpts::OptionsHash}
|
78
|
-
def to_h(
|
80
|
+
def to_h(key_type: ::ShellOpts.default_key_type, aliases: {})
|
81
|
+
@idr.to_h(key_type: :key_type, aliases: aliases)
|
82
|
+
end
|
83
|
+
|
84
|
+
# TODO
|
85
|
+
# Return OptionHash object
|
86
|
+
# def to_hash(...)
|
79
87
|
|
80
88
|
# Return a struct representation of the options. See {ShellOpts::OptionStruct}
|
81
|
-
def to_struct(
|
89
|
+
def to_struct(key_type: ::ShellOpts.default_key_type, aliases: {})
|
90
|
+
@idr.to_struct(key_type: key_type, aliases: aliases)
|
91
|
+
end
|
82
92
|
|
83
93
|
# List of remaining non-option command line arguments. Returns a Argv object
|
84
94
|
def args() Args.new(self, ast&.arguments) end
|
@@ -86,15 +96,21 @@ module ShellOpts
|
|
86
96
|
# Iterate options and commands as name/value pairs. Same as +to_a.each+
|
87
97
|
def each(&block) to_a.each(&block) end
|
88
98
|
|
89
|
-
# Print error messages and
|
99
|
+
# Print error messages and spec string and exit with status 1. This method
|
90
100
|
# should be called in response to user-errors (eg. specifying an illegal
|
91
101
|
# option)
|
92
|
-
def error(*msgs
|
102
|
+
def error(*msgs, exit: true)
|
103
|
+
msg = "#{name}: #{msgs.join}\n" + (@usage ? usage : "Usage: #{name} #{usage}")
|
104
|
+
$stderr.puts msg.rstrip
|
105
|
+
exit(1) if exit
|
106
|
+
end
|
93
107
|
|
94
108
|
# Print error message and exit with status 1. This method should called in
|
95
109
|
# response to system errors (like disk full)
|
96
|
-
def fail(*msgs
|
110
|
+
def fail(*msgs, exit: true)
|
111
|
+
$stderr.puts "#{name}: #{msgs.join}"
|
112
|
+
exit(1) if exit
|
113
|
+
end
|
97
114
|
end
|
98
115
|
end
|
99
116
|
|
100
|
-
|
data/lib/shellopts/version.rb
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.7
|
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
|
+
date: 2020-10-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -118,13 +118,10 @@ files:
|
|
118
118
|
- lib/shellopts/grammar/option.rb
|
119
119
|
- lib/shellopts/grammar/program.rb
|
120
120
|
- lib/shellopts/idr.rb
|
121
|
-
- lib/shellopts/messenger.rb
|
122
121
|
- lib/shellopts/option_struct.rb
|
123
122
|
- lib/shellopts/parser.rb
|
124
123
|
- lib/shellopts/shellopts.rb
|
125
|
-
- lib/shellopts/utils.rb
|
126
124
|
- lib/shellopts/version.rb
|
127
|
-
- rs
|
128
125
|
- shellopts.gemspec
|
129
126
|
homepage: http://github.com/clrgit/shellopts
|
130
127
|
licenses: []
|
data/lib/shellopts/messenger.rb
DELETED
@@ -1,71 +0,0 @@
|
|
1
|
-
|
2
|
-
module ShellOpts
|
3
|
-
# Service object for output of messages
|
4
|
-
#
|
5
|
-
# Messages are using the common command line formats
|
6
|
-
#
|
7
|
-
class Messenger
|
8
|
-
# Name of the program. When assigning to +name+ prefixed and suffixed
|
9
|
-
# whitespaces are removed
|
10
|
-
attr_accessor :name
|
11
|
-
|
12
|
-
# :nodoc:
|
13
|
-
def name=(name) @name = name.strip end
|
14
|
-
# :nodoc:
|
15
|
-
|
16
|
-
# Usage string. If not nil the usage string is printed by #error. When
|
17
|
-
# assigning to +usage+ suffixed whitespaces are removed and the format
|
18
|
-
# automatically set to +:custom+
|
19
|
-
attr_accessor :usage
|
20
|
-
|
21
|
-
# :nodoc:
|
22
|
-
def usage=(usage)
|
23
|
-
@format = :custom
|
24
|
-
@usage = usage&.rstrip
|
25
|
-
end
|
26
|
-
# :nodoc:
|
27
|
-
|
28
|
-
# Format of the usage string: +:default+ prefixes the +usage+ with 'Usage:
|
29
|
-
# #{name} ' before printing. +:custom+ prints +usage+ as is
|
30
|
-
attr_accessor :format
|
31
|
-
|
32
|
-
# Initialize a Messenger object. +name+ is the name of the name and +usage+
|
33
|
-
# is a short description of the options (eg. '-a -b') or a longer multiline
|
34
|
-
# explanation. The +:format+ option selects bewtween the two: +short+ (the
|
35
|
-
# default) or :long. Note that
|
36
|
-
#
|
37
|
-
def initialize(name, usage, format: :default)
|
38
|
-
@name = name
|
39
|
-
@usage = usage
|
40
|
-
@format = format
|
41
|
-
end
|
42
|
-
|
43
|
-
# Print error message and usage string and exit with status 1. Output is
|
44
|
-
# using the following format
|
45
|
-
#
|
46
|
-
# <name name>: <message>
|
47
|
-
# Usage: <name name> <options and arguments>
|
48
|
-
#
|
49
|
-
def error(*msgs)
|
50
|
-
$stderr.print "#{name}: #{msgs.join}\n"
|
51
|
-
if usage
|
52
|
-
$stderr.print "Usage: #{name} " if format == :default
|
53
|
-
$stderr.print "#{usage}\n"
|
54
|
-
end
|
55
|
-
exit 1
|
56
|
-
end
|
57
|
-
|
58
|
-
# Print error message and exit with status 1. It use the current ShellOpts
|
59
|
-
# object if defined. This method should not be called in response to
|
60
|
-
# user-errors but system errors (like disk full). Output is using the
|
61
|
-
# following format:
|
62
|
-
#
|
63
|
-
# <name name>: <message>
|
64
|
-
#
|
65
|
-
def fail(*msgs)
|
66
|
-
$stderr.puts "#{name}: #{msgs.join}"
|
67
|
-
exit 1
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
data/lib/shellopts/utils.rb
DELETED
@@ -1,16 +0,0 @@
|
|
1
|
-
|
2
|
-
module ShellOpts
|
3
|
-
# Use `include ShellOpts::Utils` to include ShellOpts utility methods in the
|
4
|
-
# global namespace
|
5
|
-
module Utils
|
6
|
-
# Forwards to `ShellOpts.error`
|
7
|
-
def error(*msgs)
|
8
|
-
::ShellOpts.error(*msgs)
|
9
|
-
end
|
10
|
-
|
11
|
-
# Forwards to `ShellOpts.fail`
|
12
|
-
def fail(*msgs)
|
13
|
-
::ShellOpts.fail(*msgs)
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
data/rs
DELETED
@@ -1,40 +0,0 @@
|
|
1
|
-
#!/usr/bin/bash
|
2
|
-
|
3
|
-
PROGRAM=$(basename $0)
|
4
|
-
USAGE="SOURCE-FILE"
|
5
|
-
|
6
|
-
function error() {
|
7
|
-
echo "$PROGRAM: $@"
|
8
|
-
echo "Usage: $PROGRAM $USAGE"
|
9
|
-
exit 1
|
10
|
-
} >&2
|
11
|
-
|
12
|
-
[ $# = 1 ] || error "Illegal number of arguments"
|
13
|
-
SOURCE_NAME=${1%.rb}.rb
|
14
|
-
|
15
|
-
GEM_FILE=$(ls *.gemspec 2>/dev/null)
|
16
|
-
[ -n "$GEM_FILE" ] || error "Can't find gemspec file"
|
17
|
-
GEM_NAME=${GEM_FILE%.gemspec}
|
18
|
-
|
19
|
-
if [ -f lib/$SOURCE_NAME ]; then
|
20
|
-
SOURCE_FILE=lib/$SOURCE_NAME
|
21
|
-
elif [ -f lib/$GEM_NAME/$SOURCE_NAME ]; then
|
22
|
-
SOURCE_FILE=lib/$GEM_NAME/$SOURCE_NAME
|
23
|
-
else
|
24
|
-
SOURCE_FILE=$(find lib/$GEM_NAME -type f -path $SOURCE_NAME | head -1)
|
25
|
-
if [ -z "$SOURCE_FILE" ]; then
|
26
|
-
SOURCE_FILE=lib/$GEM_NAME/$SOURCE_NAME
|
27
|
-
fi
|
28
|
-
fi
|
29
|
-
|
30
|
-
SPEC_FILE=spec/${SOURCE_NAME%.rb}_spec.rb
|
31
|
-
[ -f $SPEC_FILE ] || error "Can't find spec file '$SPEC_FILE'"
|
32
|
-
|
33
|
-
rspec --fail-fast $SPEC_FILE || {
|
34
|
-
# rcov forgets a newline when rspec fails
|
35
|
-
status=$?; echo; exit $status;
|
36
|
-
}
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|