shellopts 2.0.0.pre.4 → 2.0.0.pre.13
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/main.rb +1 -0
- data/lib/shellopts.rb +154 -46
- data/lib/shellopts/args.rb +21 -27
- 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 +58 -35
- data/lib/shellopts/version.rb +1 -1
- metadata +4 -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
|
26
|
+
|
27
|
+
# Specification of the command
|
28
|
+
attr_reader :spec
|
31
29
|
|
32
|
-
#
|
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,33 +39,41 @@ 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
|
46
|
-
# arguments
|
42
|
+
# Compile a spec string into a grammar
|
47
43
|
#
|
48
|
-
# +
|
44
|
+
# +spec+ is the spec string, and +argv+ the command line (typically the
|
49
45
|
# global ARGV array). +name+ is the name of the program and defaults to the
|
50
46
|
# basename of the program
|
51
47
|
#
|
52
|
-
# Syntax errors in the
|
53
|
-
# +ShellOpts::CompilerError+ exception.
|
54
|
-
# caused by the user and
|
55
|
-
#
|
56
|
-
|
48
|
+
# Syntax errors in the spec string are caused by the developer and cause
|
49
|
+
# #initialize to raise a +ShellOpts::CompilerError+ exception. Errors in
|
50
|
+
# the +argv+ arguments are caused by the user and cause #process to raise
|
51
|
+
# ShellOpts::UserError exception
|
52
|
+
#
|
53
|
+
# TODO: Change to (name, spec, argv, usage: nil) because
|
54
|
+
# ShellOpts::ShellOpts isn't a magician like the ShellOpts module
|
55
|
+
def initialize(spec, argv, name: ::ShellOpts.default_name, usage: ::ShellOpts.default_usage)
|
57
56
|
@name = name
|
57
|
+
@spec = spec
|
58
|
+
@usage = usage
|
59
|
+
@argv = argv
|
58
60
|
begin
|
59
|
-
@grammar = Grammar.compile(name,
|
60
|
-
@messenger = messenger || Messenger.new(name, @grammar.usage)
|
61
|
-
@ast = Ast.parse(@grammar, argv)
|
62
|
-
@idr = Idr.generate(@ast, @messenger)
|
61
|
+
@grammar = Grammar.compile(@name, @spec)
|
63
62
|
rescue Grammar::Compiler::Error => ex
|
64
63
|
raise CompilerError.new(5, ex.message)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Process command line arguments and return self. Raises a
|
68
|
+
# ShellOpts::UserError in case of an error
|
69
|
+
def process
|
70
|
+
begin
|
71
|
+
@ast = Ast.parse(@grammar, @argv)
|
72
|
+
@idr = Idr.generate(self)
|
65
73
|
rescue Ast::Parser::Error => ex
|
66
|
-
|
74
|
+
raise UserError.new(ex.message)
|
67
75
|
end
|
76
|
+
self
|
68
77
|
end
|
69
78
|
|
70
79
|
# Return an array representation of options and commands in the same order
|
@@ -75,10 +84,18 @@ module ShellOpts
|
|
75
84
|
def to_a() idr.to_a end
|
76
85
|
|
77
86
|
# Return a hash representation of the options. See {ShellOpts::OptionsHash}
|
78
|
-
def to_h(
|
87
|
+
def to_h(key_type: ::ShellOpts.default_key_type, aliases: {})
|
88
|
+
@idr.to_h(key_type: :key_type, aliases: aliases)
|
89
|
+
end
|
90
|
+
|
91
|
+
# TODO
|
92
|
+
# Return OptionHash object
|
93
|
+
# def to_hash(...)
|
79
94
|
|
80
95
|
# Return a struct representation of the options. See {ShellOpts::OptionStruct}
|
81
|
-
def to_struct(
|
96
|
+
def to_struct(key_type: ::ShellOpts.default_key_type, aliases: {})
|
97
|
+
@idr.to_struct(key_type: key_type, aliases: aliases)
|
98
|
+
end
|
82
99
|
|
83
100
|
# List of remaining non-option command line arguments. Returns a Argv object
|
84
101
|
def args() Args.new(self, ast&.arguments) end
|
@@ -86,15 +103,21 @@ module ShellOpts
|
|
86
103
|
# Iterate options and commands as name/value pairs. Same as +to_a.each+
|
87
104
|
def each(&block) to_a.each(&block) end
|
88
105
|
|
89
|
-
# Print error messages and
|
106
|
+
# Print error messages and spec string and exit with status 1. This method
|
90
107
|
# should be called in response to user-errors (eg. specifying an illegal
|
91
108
|
# option)
|
92
|
-
def error(*msgs
|
109
|
+
def error(*msgs, exit: true)
|
110
|
+
msg = "#{name}: #{msgs.join}\n" + (@usage ? usage : "Usage: #{name} #{usage}")
|
111
|
+
$stderr.puts msg.rstrip
|
112
|
+
exit(1) if exit
|
113
|
+
end
|
93
114
|
|
94
115
|
# Print error message and exit with status 1. This method should called in
|
95
116
|
# response to system errors (like disk full)
|
96
|
-
def fail(*msgs
|
117
|
+
def fail(*msgs, exit: true)
|
118
|
+
$stderr.puts "#{name}: #{msgs.join}"
|
119
|
+
exit(1) if exit
|
120
|
+
end
|
97
121
|
end
|
98
122
|
end
|
99
123
|
|
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.13
|
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-11-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -105,6 +105,7 @@ 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
|
109
110
|
- lib/shellopts/args.rb
|
110
111
|
- lib/shellopts/ast/command.rb
|
@@ -118,13 +119,11 @@ files:
|
|
118
119
|
- lib/shellopts/grammar/option.rb
|
119
120
|
- lib/shellopts/grammar/program.rb
|
120
121
|
- lib/shellopts/idr.rb
|
121
|
-
- lib/shellopts/
|
122
|
+
- lib/shellopts/main.rb
|
122
123
|
- lib/shellopts/option_struct.rb
|
123
124
|
- lib/shellopts/parser.rb
|
124
125
|
- lib/shellopts/shellopts.rb
|
125
|
-
- lib/shellopts/utils.rb
|
126
126
|
- lib/shellopts/version.rb
|
127
|
-
- rs
|
128
127
|
- shellopts.gemspec
|
129
128
|
homepage: http://github.com/clrgit/shellopts
|
130
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
|
-
|