shellopts 2.0.0.pre.13 → 2.0.1
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/.gitignore +2 -1
- data/.ruby-version +1 -1
- data/README.md +201 -267
- data/TODO +46 -134
- data/doc/format.rb +95 -0
- data/doc/grammar.txt +27 -0
- data/doc/syntax.rb +110 -0
- data/doc/syntax.txt +10 -0
- data/lib/ext/array.rb +58 -5
- data/lib/ext/forward_to.rb +15 -0
- data/lib/ext/lcs.rb +34 -0
- data/lib/shellopts/analyzer.rb +130 -0
- data/lib/shellopts/ansi.rb +8 -0
- data/lib/shellopts/args.rb +29 -21
- data/lib/shellopts/argument_type.rb +139 -0
- data/lib/shellopts/dump.rb +158 -0
- data/lib/shellopts/formatter.rb +325 -0
- data/lib/shellopts/grammar.rb +375 -0
- data/lib/shellopts/interpreter.rb +103 -0
- data/lib/shellopts/lexer.rb +175 -0
- data/lib/shellopts/parser.rb +269 -82
- data/lib/shellopts/program.rb +279 -0
- data/lib/shellopts/renderer.rb +227 -0
- data/lib/shellopts/stack.rb +7 -0
- data/lib/shellopts/token.rb +44 -0
- data/lib/shellopts/version.rb +2 -2
- data/lib/shellopts.rb +439 -220
- data/main +1180 -0
- data/shellopts.gemspec +9 -15
- metadata +85 -42
- data/lib/main.rb +0 -1
- data/lib/shellopts/ast/command.rb +0 -41
- data/lib/shellopts/ast/node.rb +0 -37
- data/lib/shellopts/ast/option.rb +0 -21
- 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/command.rb +0 -80
- data/lib/shellopts/grammar/node.rb +0 -33
- data/lib/shellopts/grammar/option.rb +0 -66
- 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/shellopts.rb +0 -123
data/TODO
CHANGED
@@ -1,136 +1,48 @@
|
|
1
1
|
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
case opt
|
36
|
-
when '-v'; verbose = true # Return value 'true' is ok
|
37
|
-
# Unhandled option means return value is nil
|
38
|
-
end
|
39
|
-
o Consolidate some of the 3 variations of #error and #fail
|
40
|
-
o Add a option flag for solitary options (--help)
|
41
|
-
o Make a #to_yaml
|
42
|
-
o Make an official dump method for debug
|
43
|
-
o Make a note that all options are processed at once and not as-you-go
|
44
|
-
o Test that arguments with spaces work
|
45
|
-
o Long version usage strings (major release)
|
46
|
-
o Doc: Example of processing of sub-commands and sub-sub-commands
|
47
|
-
|
48
|
-
+ Add a 'mandatory' argument to #subcommand
|
49
|
-
+ More tests
|
50
|
-
+ More doc
|
51
|
-
+ Implement value-name-before-flags rule
|
52
|
-
+ Kill option array values
|
53
|
-
+ Kill key forms
|
54
|
-
+ Rename Option#opt to Option#name
|
55
|
-
+ Have all Ast objects to be on [key, name, value] form
|
56
|
-
+ Change #=>i, $=>f and introduce b (boolean)
|
57
|
-
+ Unshift program name to usage definition string before compiling
|
58
|
-
+ Rename to UsageCompiler and ArgvParser
|
59
|
-
+ Make usage-string handle commands
|
60
|
-
+ Change !cmd to cmd!
|
61
|
-
+ Clean-up terminology: Option-name is used for names with and without the prefixed dashes
|
62
|
-
+ Rename Option#has_argument? and #optional? to something else
|
63
|
-
+ Fix location reporting of compiler errors
|
64
|
-
+ Allow '--' in usage so that everything after can be used as USAGE in error messages
|
65
|
-
+ Handle pretty-printing of usage string in handling of ParserError
|
66
|
-
+ Compiler.new.compile(usage), Parser.new(compiled_program).parse(argv)
|
67
|
-
+ Check for duplicate option in the parser
|
68
|
-
+ Handle CompilerError
|
69
|
-
+ Use nil value as the name of the top 'command'
|
70
|
-
+ Refactor compilation to avoid having the Command objects throw CompilerErrors
|
71
|
-
+ Change to 'parser.parse' / 'parser.parse3'
|
72
|
-
+ Use first long option as symbolic key
|
73
|
-
+ Use full option names everywhere (eg. '--all' instead of 'all')
|
74
|
-
|
75
|
-
- Revert change from '#' -> 'i'
|
76
|
-
- Guard against reserved 'object_id' name in OpenStruct
|
77
|
-
- Default value ('=' -> ':')
|
78
|
-
Default values are better handled in the calling program
|
79
|
-
|
80
|
-
? More specialized exceptions: "MissingArgumentError" etc.
|
81
|
-
|
82
|
-
LATER
|
83
|
-
o Allow '-a' and '--aa' in usage
|
84
|
-
o Allow single-line comments
|
85
|
-
o Allow multi-line comments
|
86
|
-
o Regex as option value spec
|
87
|
-
o "FILE", "DIR", "NEWFILE", "NEWDIR" as keyword in option value spec
|
88
|
-
RFILE, RDIR
|
89
|
-
WFILE, WDIR
|
90
|
-
EFILE, EDIR
|
91
|
-
o Octal and hexadecimal integers
|
92
|
-
o Escape of separator in lists
|
93
|
-
o Handle output of subcommand usage like "cmd1 cmd1.cmd2 cmd2"
|
94
|
-
o Command-specific arguments: clone! o,opt ++ ARG1 ARG2...
|
95
|
-
o Hostname and email as basic types
|
96
|
-
|
97
|
-
ON TO_H BRANCH
|
98
|
-
ShellOpts.process(usage, argv) { |opt,val| ... } => args
|
99
|
-
ShellOpts.process(usage, argv) { |key,opt,val| ... } => args
|
100
|
-
|
101
|
-
opts = ShellOpts.new(usage, argv, defaults = {})
|
102
|
-
opts = ShellOpts.new(usage, argv, defaults = OpenStruct.new)
|
103
|
-
|
104
|
-
opts.args
|
105
|
-
opts.to_a
|
106
|
-
opts.to_h
|
107
|
-
opts.to_openstruct
|
108
|
-
|
109
|
-
opts.each { |opt,val| ... }
|
110
|
-
opts.each { |key,opt,val| ... }
|
111
|
-
|
112
|
-
LONG FORMAT
|
113
|
-
|
114
|
-
PROGRAM = File.basename(ARGV.first)
|
115
|
-
USAGE = "-a -f FILE -lvh FILE..."
|
116
|
-
DESCR = %(
|
117
|
-
Short description
|
118
|
-
|
119
|
-
Longer description
|
120
|
-
)
|
121
|
-
OPTIONS = %(
|
122
|
-
-a,--all
|
123
|
-
Process all files
|
124
|
-
|
125
|
-
-f, --file=FILE
|
126
|
-
Process file
|
127
|
-
|
128
|
-
!command
|
129
|
-
This is a command
|
130
|
-
|
131
|
-
--this-is-a-command-option
|
132
|
-
Options for commands are nested
|
133
|
-
|
2
|
+
o Ignore all text after ' # ' (doesn't conflict with option flag)
|
3
|
+
o Command aliases
|
4
|
+
o Add user-defined setions
|
5
|
+
o Add a SOURCE section with link to git repo
|
6
|
+
o Bullet-lists
|
7
|
+
o Allow a USAGE section (and NAME)
|
8
|
+
o Find source in code an adjust line number in error messages
|
9
|
+
o Rename line and char to lineno and charno
|
10
|
+
o Client-defined argument types
|
11
|
+
o Rename Expr -> ?
|
12
|
+
o Find clean(er) procedural object model
|
13
|
+
o Allow assignment to options (this makes practical stuff easier)
|
14
|
+
o Special handling of --help arguments so that '--help command' is possible
|
15
|
+
o Support for paging of help:
|
16
|
+
begin
|
17
|
+
file = Tempfile.new("prick")
|
18
|
+
file.puts HELP.split("\n").map { |l| l.sub(/^ /, "") }
|
19
|
+
file.flush
|
20
|
+
system "less #{file.path}"
|
21
|
+
ensure
|
22
|
+
file.close
|
23
|
+
end
|
24
|
+
|
25
|
+
+ Bold text output
|
26
|
+
+ Recursive format of commands
|
27
|
+
+ Rename Compiler -> Interpreter
|
28
|
+
|
29
|
+
|
30
|
+
OLD
|
31
|
+
|
32
|
+
o Fix that it is near unsable when the user doesn't do 'include ShellOpts'
|
33
|
+
o Subcommands can be parsed as well:
|
34
|
+
opts, args = Shellopts.process(OPTIONS, ARGV)
|
134
35
|
...
|
135
|
-
|
136
|
-
|
36
|
+
opts, command = opts.process
|
37
|
+
case command
|
38
|
+
when ...
|
39
|
+
end
|
40
|
+
o 'help' should list commands in declaration order
|
41
|
+
o 'help' should use all levels by default
|
42
|
+
o 'help' should always include top-level options (try setting levels: 10 and
|
43
|
+
see top-level options are gone
|
44
|
+
o A ShellOpts#shift command that makes it possible for Array#expect to emit
|
45
|
+
relevant error messages
|
46
|
+
|
47
|
+
+ Somehow escape comments where a line starts with an option name
|
48
|
+
+ Parse and check enumeration arguments ('--debug=tokens|ast|idr|...')
|
data/doc/format.rb
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
# One-line format in prioritized order
|
2
|
+
# ------------------------------------
|
3
|
+
#
|
4
|
+
# cmd -a -b -c [CMD|CMD] ARGS
|
5
|
+
# cmd -a -b -c <command> ARGS
|
6
|
+
# cmd <options> [CMD|CMD] <ARGS>
|
7
|
+
# cmd <options> <command> <ARGS>
|
8
|
+
#
|
9
|
+
# Multiline format
|
10
|
+
# ----------------
|
11
|
+
#
|
12
|
+
# cmd -a -b -c [CMD|CMD] ARGS # <- only if no subcommand options or arguments
|
13
|
+
#
|
14
|
+
# cmd -a -b -c <command> ARGS
|
15
|
+
# subcmd -e -f -g ARGS
|
16
|
+
# subcmd -h -i -j ARGS
|
17
|
+
#
|
18
|
+
# cmd -a -b -c
|
19
|
+
# -d -e
|
20
|
+
# [CMD|CMD] ARGS
|
21
|
+
#
|
22
|
+
# cmd -a -b -c
|
23
|
+
# -d -e
|
24
|
+
# <command> ARGS
|
25
|
+
#
|
26
|
+
# Brief format
|
27
|
+
# ------------
|
28
|
+
#
|
29
|
+
# Name - Brief
|
30
|
+
#
|
31
|
+
# Usage:
|
32
|
+
# cmd -a -b -c [CMD|CMD] ARGS
|
33
|
+
#
|
34
|
+
# Options:
|
35
|
+
# -a Brief
|
36
|
+
# -b Brief
|
37
|
+
# -c Brief
|
38
|
+
#
|
39
|
+
# Commands:
|
40
|
+
# CMD --opts ARGS Brief
|
41
|
+
# CMD --opts ARGS_THAT_TAKES_UP_A_LOT_OF_SPACE
|
42
|
+
# Brief
|
43
|
+
#
|
44
|
+
# Brief Command
|
45
|
+
# CMD --opts ARGS Brief
|
46
|
+
# CMD --opts ARGS_THAT_TAKES_UP_A_LOT_OF_SPACE
|
47
|
+
# Brief
|
48
|
+
#
|
49
|
+
# Brief Option
|
50
|
+
# -a Brief
|
51
|
+
# -b=a_very_long_option
|
52
|
+
# Brief
|
53
|
+
#
|
54
|
+
#
|
55
|
+
# Doc format
|
56
|
+
# ----------
|
57
|
+
#
|
58
|
+
# Name
|
59
|
+
# Name - Brief
|
60
|
+
#
|
61
|
+
# Usage:
|
62
|
+
# cmd -a -b -c [CMD|CMD] ARGS
|
63
|
+
#
|
64
|
+
# Description
|
65
|
+
# Descr
|
66
|
+
#
|
67
|
+
# Options:
|
68
|
+
# -a
|
69
|
+
# Descr
|
70
|
+
# -b
|
71
|
+
# Descr
|
72
|
+
# -c, --aliases
|
73
|
+
# Descr
|
74
|
+
#
|
75
|
+
# Commands:
|
76
|
+
# CMD -d -e -f ARGS
|
77
|
+
# Descr
|
78
|
+
#
|
79
|
+
# -d
|
80
|
+
# Descr
|
81
|
+
# -e
|
82
|
+
# Descr
|
83
|
+
# -f
|
84
|
+
# Descr
|
85
|
+
#
|
86
|
+
# CMD -g -h -i ARGS
|
87
|
+
# Descr
|
88
|
+
#
|
89
|
+
# -g
|
90
|
+
# Descr
|
91
|
+
# -h
|
92
|
+
# Descr
|
93
|
+
# -i
|
94
|
+
# Descr
|
95
|
+
#
|
data/doc/grammar.txt
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
|
2
|
+
Optionn Grammar
|
3
|
+
[ "+" ] name-list [ "=" [ label ] [ ":" [ "#" | "$" | enum | special-constant ] ] [ "?" ] ]
|
4
|
+
|
5
|
+
-a= # Renders as -a=
|
6
|
+
-a=# # Renders as -a=INT
|
7
|
+
-b=$ # Renders as -b=NUM
|
8
|
+
-c=a,b,c # Renders as -c=a|b|c
|
9
|
+
-d=3..5 # Renders as -d=3..5
|
10
|
+
-e=:DIR # Renders as -e=DIR
|
11
|
+
-f=:FILE # Renders as -f=FILE
|
12
|
+
|
13
|
+
-o=COUNT
|
14
|
+
-a=COUNT:#
|
15
|
+
-b=COUNT:$
|
16
|
+
-c=COUNT:a,b,c
|
17
|
+
-e=DIR:DIRPATH
|
18
|
+
-f=FILE:FILEPATH
|
19
|
+
|
20
|
+
Special constants
|
21
|
+
|
22
|
+
Exist Missing Optional
|
23
|
+
|
24
|
+
File FILE - FILEPATH
|
25
|
+
Dir DIR - DIRPATH
|
26
|
+
Node NODE NEW PATH
|
27
|
+
|
data/doc/syntax.rb
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
|
2
|
+
# MAN pages
|
3
|
+
#
|
4
|
+
# NAME
|
5
|
+
# #{$PROGRAM_NAME} - #{BRIEF || spec.summary}
|
6
|
+
#
|
7
|
+
# USAGE
|
8
|
+
# #{USAGE || shellopts.usage}
|
9
|
+
#
|
10
|
+
# DESCRIPTION
|
11
|
+
# #{DESCRIPTION || spec.description}
|
12
|
+
#
|
13
|
+
# OPTIONS
|
14
|
+
# #{OPTIONS || shellopts.options}
|
15
|
+
#
|
16
|
+
# COMMANDS
|
17
|
+
# #{COMMANDS || shellopts.commands}
|
18
|
+
#
|
19
|
+
|
20
|
+
# Help output
|
21
|
+
#
|
22
|
+
# #{BRIEF}
|
23
|
+
#
|
24
|
+
# Usage: #{USAGE || shellopts.usage}
|
25
|
+
#
|
26
|
+
# Options:
|
27
|
+
# #{OPTIONS_IN_SHORT_FORMAT | shellopts.options_in_short_format}
|
28
|
+
#
|
29
|
+
# Commands
|
30
|
+
# #{COMMANDS_IN_SHORT_FORMMAT | shellopts.commands_in_short_format}
|
31
|
+
#
|
32
|
+
|
33
|
+
h = %(
|
34
|
+
-a,all # Include all files
|
35
|
+
-f=FILE # Use this file
|
36
|
+
)
|
37
|
+
|
38
|
+
# Default options
|
39
|
+
# <defaults>
|
40
|
+
# Can be processed by ShellOpts.process_defaults
|
41
|
+
# ShellOpts.make(SPEC, ARGV, defaults: true)
|
42
|
+
#
|
43
|
+
# Options
|
44
|
+
# -a,b,long
|
45
|
+
# --long
|
46
|
+
# -a=FILE
|
47
|
+
# -e,env,environment=ENVIRONMENT:p,d,t,prod,dev,test,production,development
|
48
|
+
# -i=# # Integer
|
49
|
+
# -o=optional?
|
50
|
+
# --verbose* # Repeated
|
51
|
+
# -a=V* # Repeated with argument
|
52
|
+
# -a=V?* # Repeated, optionally with argument
|
53
|
+
#
|
54
|
+
# Arguments
|
55
|
+
# Arguments has to be on one line (per ++ or --)
|
56
|
+
#
|
57
|
+
# ++ ARG...
|
58
|
+
# Subcommand arguments
|
59
|
+
# -- ARG...
|
60
|
+
# Command arguments. Multiple -- definitions are allowed
|
61
|
+
#
|
62
|
+
# [ARG]
|
63
|
+
# Optional argument
|
64
|
+
# ARG
|
65
|
+
# Mandatory argument
|
66
|
+
# ARG...
|
67
|
+
# Repeated argument. At least one argument is mandatory
|
68
|
+
# [ARG...]
|
69
|
+
# Optionally repeated argument
|
70
|
+
#
|
71
|
+
# SPECIAL ALTERNATE VALUES OR ARGUMENTS
|
72
|
+
# FILE:EFILE
|
73
|
+
# Existing file. "FILE" will be used as name of the value
|
74
|
+
# PATH:EPATH
|
75
|
+
# Existing file or directory. "PATH" will be used as the name of the value
|
76
|
+
# DIRECTORY:EDIR
|
77
|
+
# Existing directory. "DIRECTORY" will be used as name of the value
|
78
|
+
#
|
79
|
+
# SPEC = %(
|
80
|
+
# # Common options
|
81
|
+
# -f,file=FILE
|
82
|
+
# -- ARG ARG
|
83
|
+
#
|
84
|
+
# # More options
|
85
|
+
# -m,mode=MODE
|
86
|
+
# -- ARG ARG ARG
|
87
|
+
# )
|
88
|
+
#
|
89
|
+
# How to make
|
90
|
+
# cp [OPTION]... [-T] SOURCE DEST
|
91
|
+
# cp [OPTION]... SOURCE... DIRECTORY
|
92
|
+
# cp [OPTION]... -t DIRECTORY SOURCE...
|
93
|
+
#
|
94
|
+
# USAGE = %(
|
95
|
+
# cp [OPTION]... [-T] SOURCE DEST
|
96
|
+
# cp [OPTION]... SOURCE... DIRECTORY
|
97
|
+
# cp [OPTION]... -t DIRECTORY SOURCE...
|
98
|
+
# )
|
99
|
+
#
|
100
|
+
# SPEC = %(
|
101
|
+
# -r,recursive
|
102
|
+
#
|
103
|
+
# -T,no-target-directory
|
104
|
+
#
|
105
|
+
# -t,target_directory=DIRECTORY:EDIR
|
106
|
+
# )
|
107
|
+
#
|
108
|
+
|
109
|
+
|
110
|
+
|
data/doc/syntax.txt
ADDED
data/lib/ext/array.rb
CHANGED
@@ -1,9 +1,62 @@
|
|
1
1
|
|
2
|
-
module
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
2
|
+
module Ext
|
3
|
+
module Array
|
4
|
+
module ShiftWhile
|
5
|
+
refine ::Array do
|
6
|
+
# The algorithm ensures that the block sees the array as if the current
|
7
|
+
# element has already been removed
|
8
|
+
def shift_while(&block)
|
9
|
+
r = []
|
10
|
+
while value = self.shift
|
11
|
+
if !block.call(value)
|
12
|
+
self.unshift value
|
13
|
+
break
|
14
|
+
else
|
15
|
+
r << value
|
16
|
+
end
|
17
|
+
end
|
18
|
+
r
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
module PopWhile
|
24
|
+
refine ::Array do
|
25
|
+
# The algorithm ensures that the block sees the array as if the current
|
26
|
+
# element has already been removed
|
27
|
+
def pop_while(&block)
|
28
|
+
r = []
|
29
|
+
while value = self.pop
|
30
|
+
if !block.call(value)
|
31
|
+
self.push value
|
32
|
+
break
|
33
|
+
else
|
34
|
+
r << value
|
35
|
+
end
|
36
|
+
end
|
37
|
+
r
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
module Wrap
|
43
|
+
refine ::Array do
|
44
|
+
# Concatenate strings into lines that are at most +width+ characters wide
|
45
|
+
def wrap(width, curr = 0)
|
46
|
+
lines = [[]]
|
47
|
+
curr -= 1 # Simplifies conditions below
|
48
|
+
each { |word|
|
49
|
+
if curr + 1 + word.size <= width
|
50
|
+
lines.last << word
|
51
|
+
curr += 1 + word.size
|
52
|
+
else
|
53
|
+
lines << [word]
|
54
|
+
curr = word.size
|
55
|
+
end
|
56
|
+
}
|
57
|
+
lines.map! { |words| words.join(" ") }
|
58
|
+
end
|
59
|
+
end
|
7
60
|
end
|
8
61
|
end
|
9
62
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
|
2
|
+
require 'forward_to'
|
3
|
+
|
4
|
+
module ForwardTo
|
5
|
+
def forward_self_to(target, *methods)
|
6
|
+
for method in Array(methods).flatten
|
7
|
+
if method =~ /=$/
|
8
|
+
class_eval("def self.#{method}(*args) #{target}.#{method}(*args) end")
|
9
|
+
else
|
10
|
+
class_eval("def self.#{method}(*args, &block) #{target}.#{method}(*args, &block) end")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
data/lib/ext/lcs.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
|
2
|
+
# https://gist.github.com/Joseph-N/fbf061aa2347ed2c104f0b3fe1a5b9f2
|
3
|
+
#
|
4
|
+
# TODO: Move to String and make find_longest_command_substring_index return a
|
5
|
+
# range
|
6
|
+
module LCS
|
7
|
+
def self.find_longest_common_substring(s1, s2)
|
8
|
+
i, z = find_longest_command_substring_index(s1, s2)
|
9
|
+
s1[i .. i + z]
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.find_longest_common_substring_index(s1, s2)
|
13
|
+
if (s1 == "" || s2 == "")
|
14
|
+
return [0,0]
|
15
|
+
end
|
16
|
+
m = Array.new(s1.length){ [0] * s2.length }
|
17
|
+
longest_length, longest_end_pos = 0,0
|
18
|
+
(0 .. s1.length - 1).each do |x|
|
19
|
+
(0 .. s2.length - 1).each do |y|
|
20
|
+
if s1[x] == s2[y]
|
21
|
+
m[x][y] = 1
|
22
|
+
if (x > 0 && y > 0)
|
23
|
+
m[x][y] += m[x-1][y-1]
|
24
|
+
end
|
25
|
+
if m[x][y] > longest_length
|
26
|
+
longest_length = m[x][y]
|
27
|
+
longest_end_pos = x
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
[longest_end_pos - longest_length + 1, longest_length]
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
|
2
|
+
module ShellOpts
|
3
|
+
module Grammar
|
4
|
+
class Node
|
5
|
+
def remove_brief_nodes
|
6
|
+
children.delete_if { |node| node.is_a?(Brief) }
|
7
|
+
end
|
8
|
+
|
9
|
+
def remove_arg_descr_nodes
|
10
|
+
children.delete_if { |node| node.is_a?(ArgDescr) }
|
11
|
+
end
|
12
|
+
|
13
|
+
def remove_arg_spec_nodes
|
14
|
+
children.delete_if { |node| node.is_a?(ArgSpec) }
|
15
|
+
end
|
16
|
+
|
17
|
+
def analyzer_error(token, message)
|
18
|
+
raise AnalyzerError.new(token), message
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class Command
|
23
|
+
def set_supercommand
|
24
|
+
commands.each { |child| child.instance_variable_set(:@supercommand, self) }
|
25
|
+
end
|
26
|
+
|
27
|
+
def collect_options
|
28
|
+
@options = option_groups.map(&:options).flatten
|
29
|
+
end
|
30
|
+
|
31
|
+
# Move options before first command
|
32
|
+
def reorder_options
|
33
|
+
if commands.any?
|
34
|
+
if i = children.find_index { |child| child.is_a?(Command) }
|
35
|
+
options, rest = children[i+1..-1].partition { |child| child.is_a?(OptionGroup) }
|
36
|
+
@children = children[0, i] + options + children[i..i] + rest
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def compute_option_hashes
|
42
|
+
options.each { |option|
|
43
|
+
option.idents.zip(option.names).each { |ident, name|
|
44
|
+
!@options_hash.key?(name) or
|
45
|
+
analyzer_error option.token, "Duplicate option name: #{name}"
|
46
|
+
@options_hash[name] = option
|
47
|
+
!@options_hash.key?(ident) or
|
48
|
+
analyzer_error option.token, "Can't use both #{@options_hash[ident].name} and #{name}"
|
49
|
+
@options_hash[ident] = option
|
50
|
+
}
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
def compute_command_hashes
|
55
|
+
commands.each { |command|
|
56
|
+
# TODO Check for dash-collision
|
57
|
+
!@commands_hash.key?(command.name) or
|
58
|
+
analyzer_error command.token, "Duplicate command name: #{command.name}"
|
59
|
+
@commands_hash[command.name] = command
|
60
|
+
@commands_hash[command.ident] = command
|
61
|
+
command.compute_command_hashes
|
62
|
+
}
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class Analyzer
|
68
|
+
include Grammar
|
69
|
+
|
70
|
+
attr_reader :grammar
|
71
|
+
|
72
|
+
def initialize(grammar)
|
73
|
+
@grammar = grammar
|
74
|
+
end
|
75
|
+
|
76
|
+
# Move commands that are nested within a different command than it belongs to
|
77
|
+
def move_commands
|
78
|
+
# We can't use Command#[] at this point so we collect the commands here
|
79
|
+
h = {}
|
80
|
+
@grammar.traverse(Grammar::Command) { |command|
|
81
|
+
h[command.path] = command
|
82
|
+
}
|
83
|
+
|
84
|
+
# Find commands to move
|
85
|
+
#
|
86
|
+
# Commands are moved in two steps because the behaviour of #traverse is
|
87
|
+
# not defined when the data structure changes beneath it
|
88
|
+
move = []
|
89
|
+
@grammar.traverse(Grammar::Command) { |command|
|
90
|
+
if command.path.size > 1 && command.parent && command.parent.path != command.path[0..-2]
|
91
|
+
move << command
|
92
|
+
else
|
93
|
+
command.instance_variable_set(:@command, command.parent)
|
94
|
+
end
|
95
|
+
}
|
96
|
+
|
97
|
+
# Move commands but do not change parent/child relationship
|
98
|
+
move.each { |command|
|
99
|
+
supercommand = h[command.path[0..-2]] or analyzer_error "Can't find #{command.ident}!"
|
100
|
+
command.parent.commands.delete(command)
|
101
|
+
supercommand.commands << command
|
102
|
+
command.instance_variable_set(:@command, supercommand)
|
103
|
+
}
|
104
|
+
end
|
105
|
+
|
106
|
+
def analyze()
|
107
|
+
move_commands
|
108
|
+
|
109
|
+
@grammar.traverse(Grammar::Command) { |command|
|
110
|
+
command.set_supercommand
|
111
|
+
command.reorder_options
|
112
|
+
command.collect_options
|
113
|
+
command.compute_option_hashes
|
114
|
+
}
|
115
|
+
|
116
|
+
@grammar.compute_command_hashes
|
117
|
+
|
118
|
+
@grammar.traverse { |node|
|
119
|
+
node.remove_brief_nodes
|
120
|
+
node.remove_arg_descr_nodes
|
121
|
+
node.remove_arg_spec_nodes
|
122
|
+
}
|
123
|
+
|
124
|
+
@grammar
|
125
|
+
end
|
126
|
+
|
127
|
+
def Analyzer.analyze(source) self.new(source).analyze end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|