shellopts 2.0.0.pre.1 → 2.0.0.pre.8
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/.ruby-version +1 -1
- data/TODO +6 -0
- data/lib/main.rb +1 -0
- data/lib/shellopts.rb +140 -44
- data/lib/shellopts/args.rb +48 -0
- 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/main.rb +10 -0
- data/lib/shellopts/option_struct.rb +62 -159
- data/lib/shellopts/parser.rb +11 -11
- data/lib/shellopts/shellopts.rb +53 -35
- data/lib/shellopts/version.rb +1 -1
- metadata +5 -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
@@ -1,10 +1,12 @@
|
|
1
1
|
|
2
2
|
require "shellopts"
|
3
3
|
|
4
|
+
require "shellopts/args.rb"
|
5
|
+
|
4
6
|
# TODO
|
5
7
|
#
|
6
8
|
# PROCESSING
|
7
|
-
# 1. Compile
|
9
|
+
# 1. Compile spec string and yield a grammar
|
8
10
|
# 2. Parse the options using the grammar and yield an AST
|
9
11
|
# 3. Construct the Program model from the AST
|
10
12
|
# 4. Apply defaults to the model
|
@@ -15,19 +17,20 @@ require "shellopts"
|
|
15
17
|
module ShellOpts
|
16
18
|
# The command line processing object
|
17
19
|
class ShellOpts
|
18
|
-
# One of :key, :name, :option
|
19
|
-
#
|
20
|
-
# Option Command
|
21
|
-
# :key key #command! (no collision)
|
22
|
-
# :name name #command (possible collision)
|
23
|
-
# :option --option #command (multihash, no collision) (TODO)
|
24
|
-
#
|
25
|
-
DEFAULT_USE = :key
|
26
|
-
|
27
20
|
# Name of program
|
28
|
-
|
21
|
+
attr_accessor :name
|
29
22
|
|
30
|
-
#
|
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
|
26
|
+
|
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
|
31
34
|
attr_reader :grammar
|
32
35
|
|
33
36
|
# The AST parsed from the command line arguments
|
@@ -36,32 +39,33 @@ module ShellOpts
|
|
36
39
|
# The IDR generated from the Ast
|
37
40
|
attr_reader :idr
|
38
41
|
|
39
|
-
#
|
40
|
-
# standard error and exit with status 1
|
41
|
-
attr_accessor :messenger
|
42
|
-
|
43
|
-
# 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
|
44
43
|
# arguments
|
45
44
|
#
|
46
|
-
# +
|
45
|
+
# +spec+ is the spec string, and +argv+ the command line (typically the
|
47
46
|
# global ARGV array). +name+ is the name of the program and defaults to the
|
48
47
|
# basename of the program
|
49
48
|
#
|
50
|
-
# Syntax errors in the
|
51
|
-
# +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
|
52
51
|
# caused by the user and terminates the program with an error message and a
|
53
|
-
# short description of its
|
54
|
-
|
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)
|
55
57
|
@name = name
|
58
|
+
@spec = spec
|
59
|
+
@usage = usage
|
60
|
+
@argv = argv
|
56
61
|
begin
|
57
|
-
@grammar = Grammar.compile(name,
|
58
|
-
@
|
59
|
-
@
|
60
|
-
@idr = Idr.generate(@ast, @messenger)
|
62
|
+
@grammar = Grammar.compile(@name, @spec)
|
63
|
+
@ast = Ast.parse(@grammar, @argv)
|
64
|
+
@idr = Idr.generate(self)
|
61
65
|
rescue Grammar::Compiler::Error => ex
|
62
66
|
raise CompilerError.new(5, ex.message)
|
63
67
|
rescue Ast::Parser::Error => ex
|
64
|
-
|
68
|
+
raise UserError.new(ex.message)
|
65
69
|
end
|
66
70
|
end
|
67
71
|
|
@@ -73,26 +77,40 @@ module ShellOpts
|
|
73
77
|
def to_a() idr.to_a end
|
74
78
|
|
75
79
|
# Return a hash representation of the options. See {ShellOpts::OptionsHash}
|
76
|
-
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(...)
|
77
87
|
|
78
88
|
# Return a struct representation of the options. See {ShellOpts::OptionStruct}
|
79
|
-
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
|
80
92
|
|
81
|
-
# List of remaining non-option command line arguments.
|
82
|
-
def args()
|
93
|
+
# List of remaining non-option command line arguments. Returns a Argv object
|
94
|
+
def args() Args.new(self, ast&.arguments) end
|
83
95
|
|
84
96
|
# Iterate options and commands as name/value pairs. Same as +to_a.each+
|
85
97
|
def each(&block) to_a.each(&block) end
|
86
98
|
|
87
|
-
# Print error messages and
|
99
|
+
# Print error messages and spec string and exit with status 1. This method
|
88
100
|
# should be called in response to user-errors (eg. specifying an illegal
|
89
101
|
# option)
|
90
|
-
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
|
91
107
|
|
92
108
|
# Print error message and exit with status 1. This method should called in
|
93
109
|
# response to system errors (like disk full)
|
94
|
-
def fail(*msgs
|
110
|
+
def fail(*msgs, exit: true)
|
111
|
+
$stderr.puts "#{name}: #{msgs.join}"
|
112
|
+
exit(1) if exit
|
113
|
+
end
|
95
114
|
end
|
96
115
|
end
|
97
116
|
|
98
|
-
|
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.8
|
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
|
@@ -105,7 +105,9 @@ files:
|
|
105
105
|
- bin/setup
|
106
106
|
- doc/stylesheet.css
|
107
107
|
- lib/ext/array.rb
|
108
|
+
- lib/main.rb
|
108
109
|
- lib/shellopts.rb
|
110
|
+
- lib/shellopts/args.rb
|
109
111
|
- lib/shellopts/ast/command.rb
|
110
112
|
- lib/shellopts/ast/node.rb
|
111
113
|
- lib/shellopts/ast/option.rb
|
@@ -117,13 +119,11 @@ files:
|
|
117
119
|
- lib/shellopts/grammar/option.rb
|
118
120
|
- lib/shellopts/grammar/program.rb
|
119
121
|
- lib/shellopts/idr.rb
|
120
|
-
- lib/shellopts/
|
122
|
+
- lib/shellopts/main.rb
|
121
123
|
- lib/shellopts/option_struct.rb
|
122
124
|
- lib/shellopts/parser.rb
|
123
125
|
- lib/shellopts/shellopts.rb
|
124
|
-
- lib/shellopts/utils.rb
|
125
126
|
- lib/shellopts/version.rb
|
126
|
-
- rs
|
127
127
|
- shellopts.gemspec
|
128
128
|
homepage: http://github.com/clrgit/shellopts
|
129
129
|
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
|
-
|