shellopts 2.0.0.pre.14 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -1
  3. data/.ruby-version +1 -1
  4. data/README.md +201 -267
  5. data/TODO +37 -5
  6. data/doc/format.rb +95 -0
  7. data/doc/grammar.txt +27 -0
  8. data/doc/syntax.rb +110 -0
  9. data/doc/syntax.txt +10 -0
  10. data/lib/ext/array.rb +62 -0
  11. data/lib/ext/forward_to.rb +15 -0
  12. data/lib/ext/lcs.rb +34 -0
  13. data/lib/shellopts/analyzer.rb +130 -0
  14. data/lib/shellopts/ansi.rb +8 -0
  15. data/lib/shellopts/args.rb +25 -15
  16. data/lib/shellopts/argument_type.rb +139 -0
  17. data/lib/shellopts/dump.rb +158 -0
  18. data/lib/shellopts/formatter.rb +292 -92
  19. data/lib/shellopts/grammar.rb +375 -0
  20. data/lib/shellopts/interpreter.rb +103 -0
  21. data/lib/shellopts/lexer.rb +175 -0
  22. data/lib/shellopts/parser.rb +293 -0
  23. data/lib/shellopts/program.rb +279 -0
  24. data/lib/shellopts/renderer.rb +227 -0
  25. data/lib/shellopts/stack.rb +7 -0
  26. data/lib/shellopts/token.rb +44 -0
  27. data/lib/shellopts/version.rb +1 -1
  28. data/lib/shellopts.rb +359 -3
  29. data/main +1180 -0
  30. data/shellopts.gemspec +8 -14
  31. metadata +86 -41
  32. data/lib/ext/algorithm.rb +0 -14
  33. data/lib/ext/ruby_env.rb +0 -8
  34. data/lib/shellopts/ast/command.rb +0 -112
  35. data/lib/shellopts/ast/dump.rb +0 -28
  36. data/lib/shellopts/ast/option.rb +0 -15
  37. data/lib/shellopts/ast/parser.rb +0 -106
  38. data/lib/shellopts/constants.rb +0 -88
  39. data/lib/shellopts/exceptions.rb +0 -21
  40. data/lib/shellopts/grammar/analyzer.rb +0 -76
  41. data/lib/shellopts/grammar/command.rb +0 -87
  42. data/lib/shellopts/grammar/dump.rb +0 -56
  43. data/lib/shellopts/grammar/lexer.rb +0 -56
  44. data/lib/shellopts/grammar/option.rb +0 -55
  45. data/lib/shellopts/grammar/parser.rb +0 -78
@@ -1,76 +0,0 @@
1
-
2
- module ShellOpts
3
- module Grammar
4
- class Analyzer
5
- def self.analyze(commands)
6
- @program = commands.shift
7
- @commands = commands
8
- build_options
9
- link_up
10
- @program
11
- end
12
-
13
- private
14
- def self.error(mesg, command)
15
- mesg += " in #{command.path}" if !command.program?
16
- raise CompileError, mesg
17
- end
18
-
19
- def self.program() @program end
20
- def self.commands() @commands end
21
-
22
- # Initialize Command#options
23
- def self.build_options
24
- ([program] + commands).each { |command|
25
- command.opts.each { |opt|
26
- opt.names.each { |name|
27
- !command.options.key?(name) or
28
- error "Duplicate option name '#{name}'", command
29
- command.options[name] = opt
30
- }
31
-
32
- !command.options.key?(opt.ident) or
33
- error "Duplicate option identifier '#{opt.ident}'", command
34
- command.options[opt.ident] = opt
35
- }
36
- }
37
- end
38
-
39
- # Initialize Command#commands
40
- def self.link_up
41
- # Hash from path to command
42
- cmds = { "" => program }
43
-
44
- # Add placeholders for actual commands and virtual commands for empty parent commands
45
- commands.sort.each { |cmd|
46
- # Place holder for actual command
47
- cmds[cmd.path] = nil
48
-
49
- # Add parent virtual commands
50
- curr = cmd
51
- while !cmds.key?(curr.parent_path)
52
- curr = cmds[curr.parent_path] = VirtualCommand.new(curr.parent_path)
53
- end
54
- }
55
-
56
- # Add actual commands
57
- commands.sort.each { |cmd|
58
- !cmds[cmd.path] or
59
- error "Duplicate command name '#{cmd.name}'", cmds[cmd.parent_path]
60
- cmds[cmd.path] = cmd
61
- }
62
-
63
- # Link up
64
- cmds.values.each { |cmd|
65
- next if cmd == program
66
- cmd.instance_variable_set(:@parent, cmds[cmd.parent_path])
67
- cmd.parent.commands[cmd.name] = cmd
68
- cmd.parent.cmds << cmd
69
- !cmd.parent.commands.key?(cmd.ident) or
70
- error "Duplicate command identifier '#{cmd.ident}'", cmd.parent
71
- cmd.parent.commands[cmd.ident] = cmd
72
- }
73
- end
74
- end
75
- end
76
- end
@@ -1,87 +0,0 @@
1
- module ShellOpts
2
- module Grammar
3
- # TODO: Command aliases: list.something!,list.somethingelse!
4
- class Command
5
- # Parent command. nil for the program-level Command object. Initialized
6
- # by the analyzer
7
- attr_reader :parent
8
-
9
- # Name of command. nil for the program-level Command object
10
- attr_reader :name
11
-
12
- # Ident of command. nil for the program-level Command object
13
- attr_reader :ident
14
-
15
- # Path of command. The empty string for the program-level Command object
16
- attr_reader :path
17
-
18
- # Path of parent command. nil for the program-level Command object. This
19
- # is the same as #parent&.path but is available before #parent is
20
- # intialized. It is used to build the command hierarchy in the analyzer
21
- attr_reader :parent_path
22
-
23
- # List of comments. Initialized by the parser
24
- attr_reader :text
25
-
26
- # List of options. Initialized by the parser
27
- attr_reader :opts
28
-
29
- # List of sub-commands. Initialized by the parser
30
- attr_reader :cmds
31
-
32
- # List of arguments. Initialized by the parser
33
- attr_reader :args
34
-
35
- # Hash from name/identifier to option. Note that each option has at least
36
- # two entries in the hash: One by name and one by identifier. Option
37
- # aliases are also keys in the hash. Initialized by the analyzer
38
- attr_reader :options
39
-
40
- # Hash from name to sub-command. Note that each command has two entries in
41
- # the hash: One by name and one by identifier. Initialized by the analyzer
42
- attr_reader :commands
43
-
44
- def initialize(path, virtual: false)
45
- if path == ""
46
- @path = path
47
- else
48
- @path = path.sub(/!$/, "")
49
- components = @path.split(".")
50
- @name = components.pop
51
- @parent_path = components.join(".")
52
- @ident = @name.gsub(/-/, "_").to_sym
53
- end
54
- @virtual = virtual
55
- @text = []
56
- @opts = []
57
- @cmds = []
58
- @args = []
59
-
60
- @options = {}
61
- @commands = {}
62
- end
63
-
64
- # True if this is the program-level command
65
- def program?() @path == "" end
66
-
67
- # True if this is a virtual command that cannot be called without a
68
- # sub-command
69
- def virtual?() @virtual end
70
-
71
- def <=>(other)
72
- path <=> other.path
73
- end
74
- end
75
-
76
- class Program < Command
77
- def initialize(name)
78
- super("")
79
- @name = name
80
- end
81
- end
82
-
83
- class VirtualCommand < Command
84
- def initialize(path) super(path, virtual: true) end
85
- end
86
- end
87
- end
@@ -1,56 +0,0 @@
1
- module ShellOpts
2
- module Grammar
3
- class Command
4
- def dump
5
- print (path ? "#{path}!" : 'nil')
6
- print " (virtual)" if virtual?
7
- print " [PROGRAM]" if program?
8
- puts
9
- indent {
10
- puts "name: #{name.inspect}"
11
- puts "ident: #{ident.inspect}"
12
- puts "path: #{path.inspect}"
13
- puts "parent_path: #{parent_path.inspect}"
14
- if !text.empty?
15
- puts "text"
16
- indent { text.each { |txt| puts txt } }
17
- end
18
- if !opts.empty?
19
- puts "opts"
20
- indent { opts.each(&:dump) }
21
- end
22
- if !cmds.empty?
23
- puts "cmds (#{cmds.size})"
24
- indent { cmds.each(&:dump) }
25
- end
26
- if !args.empty?
27
- puts "args"
28
- indent { args.each { |arg| puts arg } }
29
- end
30
- }
31
- end
32
- end
33
-
34
- class Option
35
- def dump
36
- puts name
37
- indent {
38
- if !text.empty?
39
- puts "text"
40
- indent { text.each { |txt| puts txt } }
41
- end
42
- puts "ident: #{ident.inspect}"
43
- puts "names: #{names.join(', ')}"
44
- puts "repeatable: #{repeatable?}"
45
- puts "argument: #{argument?}"
46
- if argument?
47
- puts "argument_name: #{argument_name}" if argument_name
48
- puts "integer: #{integer?}"
49
- puts "float: #{float?}"
50
- puts "optional: #{optional?}"
51
- end
52
- }
53
- end
54
- end
55
- end
56
- end
@@ -1,56 +0,0 @@
1
- module ShellOpts
2
- module Grammar
3
- class Lexer
4
- def self.lex(source)
5
- lines = source.split("\n").map(&:strip)
6
-
7
- # Skip initial blank lines
8
- lines = lines.drop_while { |line| line == "" }
9
-
10
- # Split lines into command, option, argument, or text
11
- res = []
12
- while line = lines.shift
13
- if line =~ SCAN_RE
14
- # Collect following comments
15
- txts = []
16
- while lines.first && lines.first !~ SCAN_RE
17
- txts << lines.shift
18
- end
19
-
20
- words = line.split(/\s+/)
21
- while word = words.shift
22
- type =
23
- case word
24
- when OPTION_RE
25
- "OPT"
26
- when COMMAND_PATH_RE
27
- "CMD"
28
- when ARGUMENT_EXPR_RE
29
- args = [word]
30
- # Scan arguments
31
- while words.first =~ ARGUMENT_EXPR_RE
32
- args << words.shift
33
- end
34
- word = args.join(" ")
35
- "ARG"
36
- when /^[a-z0-9]/
37
- raise CompileError, "Illegal argument: #{word} (should be uppercase)"
38
- else
39
- raise CompileError, "Illegal syntax: #{line}"
40
- end
41
- res << [type, word]
42
- txts.each { |txt| res << ["TXT", txt] } # Add comments after first command or option
43
- txts = []
44
- end
45
- elsif line =~ /^-|\+/
46
- raise CompileError, "Illegal short option name: #{line}"
47
- else
48
- res << ["TXT", line]
49
- end
50
- end
51
- res
52
- end
53
- end
54
- end
55
- end
56
-
@@ -1,55 +0,0 @@
1
- module ShellOpts
2
- module Grammar
3
- class Option
4
- # Symbolic identifier. This is the name of the option with dashes ('-')
5
- # replaced with underscores ('_')
6
- attr_reader :ident
7
-
8
- # Name of option. This is the name of the first long option or the name
9
- # of the first short option if there is no long option name. It is used
10
- # to compute #ident
11
- attr_reader :name
12
-
13
- # Long name of option or nil if not present
14
- attr_reader :longname
15
-
16
- # Short name of option or nil if not present
17
- attr_reader :shortname
18
-
19
- # List of all names
20
- attr_reader :names
21
-
22
- # Name of argument or nil if not present
23
- attr_reader :argument_name
24
-
25
- # Comment
26
- attr_reader :text
27
-
28
- def repeatable?() @repeatable end
29
- def argument?() @argument end
30
- def integer?() @integer end
31
- def float?() @float end
32
- def string?() !@integer && !@float end
33
- def optional?() @optional end
34
-
35
- def initialize(names, repeatable: nil, argument: nil, integer: nil, float: nil, optional: nil)
36
- @names = names.dup
37
- @longname = @names.find { |name| name.length > 1 }
38
- @shortname = @names.find { |name| name.length == 1 }
39
- @name = @longname || @shortname
40
- @ident = @name.gsub("-", "_").to_sym
41
- @repeatable = repeatable || false
42
- if argument
43
- @argument = true
44
- @argument_name = argument if argument.is_a?(String)
45
- else
46
- @argument = false
47
- end
48
- @integer = integer || false
49
- @float = float || false
50
- @optional = optional || false
51
- @text = []
52
- end
53
- end
54
- end
55
- end
@@ -1,78 +0,0 @@
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