shellopts 2.0.0.pre.14 → 2.0.2

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 +29 -21
  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 +360 -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
@@ -0,0 +1,139 @@
1
+ module ShellOpts
2
+ module Grammar
3
+ class ArgumentType
4
+ # Name of type
5
+ def name() self.class.to_s.sub(/.*::/, "").sub(/Argument/, "") end
6
+
7
+ # Return truish if value literal (String) match the type. Returns false
8
+ # and set #message if the value doesn't match. <name> is used to
9
+ # construct the error message and is the name/alias the user specified on
10
+ # the command line
11
+ def match?(name, literal) true end
12
+
13
+ # Error message if match? returned false. Note that this method is not
14
+ # safe for concurrent processing
15
+ attr_reader :message
16
+
17
+ # Return true if .value is an "instance" of self. Ie. an Integer object
18
+ # if type is an IntegerArgument
19
+ def value?(value) true end
20
+
21
+ # Convert value to Ruby type
22
+ def convert(value) value end
23
+
24
+ # String representation. Equal to #name
25
+ def to_s() name end
26
+
27
+ protected
28
+ # it is important that #set_message return false
29
+ def set_message(msg)
30
+ @message = msg
31
+ false
32
+ end
33
+ end
34
+
35
+ class StringType < ArgumentType
36
+ end
37
+
38
+ class IntegerArgument < ArgumentType
39
+ def match?(name, literal)
40
+ literal =~ /^-?\d+$/ or
41
+ set_message "Illegal integer value in #{name}: #{literal}"
42
+ end
43
+
44
+ def value?(value) value.is_a?(Integer) end
45
+ def convert(value) value.to_i end
46
+ end
47
+
48
+ class FloatArgument < ArgumentType
49
+ def match?(name, literal)
50
+ # https://stackoverflow.com/a/21891705/2130986
51
+ literal =~ /^[+-]?(?:0|[1-9]\d*)(?:\.(?:\d*[1-9]|0))?$/ or
52
+ set_message "Illegal decimal value in #{name}: #{literal}"
53
+ end
54
+
55
+ def value?(value) value.is_a?(Numeric) end
56
+ def convert(value) value.to_f end
57
+ end
58
+
59
+ class FileArgument < ArgumentType
60
+ attr_reader :kind
61
+
62
+ def initialize(kind)
63
+ constrain kind, :file, :dir, :path, :efile, :edir, :epath, :nfile, :ndir, :npath
64
+ @kind = kind
65
+ end
66
+
67
+ def match?(name, literal)
68
+ case kind
69
+ when :file; match_path(name, literal, kind, :file?, :default)
70
+ when :dir; match_path(name, literal, kind, :directory?, :default)
71
+ when :path; match_path(name, literal, kind, :exist?, :default)
72
+
73
+ when :efile; match_path(name, literal, kind, :file?, :exist)
74
+ when :edir; match_path(name, literal, kind, :directory?, :exist)
75
+ when :epath; match_path(name, literal, kind, :exist?, :exist)
76
+
77
+ when :nfile; match_path(name, literal, kind, :file?, :new)
78
+ when :ndir; match_path(name, literal, kind, :directory?, :new)
79
+ when :npath; match_path(name, literal, kind, :exist?, :new)
80
+ else
81
+ raise InternalError, "Illegal kind: #{kind.inspect}"
82
+ end
83
+ end
84
+
85
+ # Note: No checks done, not sure if it is a feature or a bug
86
+ def value?(value) value.is_a?(String) end
87
+
88
+ protected
89
+ def match_path(name, literal, kind, method, mode)
90
+ subject =
91
+ case kind
92
+ when :file, :efile, :nfile; "regular file"
93
+ when :dir, :edir, :ndir; "directory"
94
+ when :path, :epath, :npath; "path"
95
+ else
96
+ raise ArgumentError
97
+ end
98
+
99
+ if File.send(method, literal) # exists?
100
+ if mode == :new
101
+ set_message "#{subject.capitalize} already exists in #{name}: #{literal}"
102
+ elsif kind == :path || kind == :epath
103
+ if File.file?(literal) || File.directory?(literal)
104
+ true
105
+ else
106
+ set_message "Expected regular file or directory as #{name} argument: #{literal}"
107
+ end
108
+ else
109
+ true
110
+ end
111
+ elsif File.exist?(literal) # exists but not the right type
112
+ if mode == :new
113
+ set_message "#{subject.capitalize} already exists"
114
+ else
115
+ set_message "Expected #{subject} as #{name} argument: #{literal}"
116
+ end
117
+ else # does not exist
118
+ if [:default, :new].include? mode
119
+ if File.exist?(File.dirname(literal))
120
+ true
121
+ else
122
+ set_message "Illegal path in #{name}: #{literal}"
123
+ end
124
+ else
125
+ set_message "Error in #{name} argument: Can't find #{literal}"
126
+ end
127
+ end
128
+ end
129
+ end
130
+
131
+ class EnumArgument < ArgumentType
132
+ attr_reader :values
133
+ def initialize(values) @values = values.dup end
134
+ def match?(name, literal) value?(literal) or set_message "Illegal value in #{name}: '#{literal}'" end
135
+ def value?(value) @values.include?(value) end
136
+ end
137
+ end
138
+ end
139
+
@@ -0,0 +1,158 @@
1
+ module ShellOpts
2
+ module Grammar
3
+ class IdrNode
4
+ def dump_doc
5
+ puts "#{self.class} #{ident}"
6
+ indent {
7
+ children.each(&:dump_doc)
8
+ }
9
+ end
10
+ end
11
+ end
12
+
13
+ module Grammar
14
+ class Command
15
+ # Usable after parsing
16
+ def render_structure
17
+ io = StringIO.new
18
+ dump_structure(io)
19
+ io.string
20
+ end
21
+
22
+ def dump_structure(device = $stdout)
23
+ device.puts ident
24
+ device.indent { |dev|
25
+ option_groups.each { |group| dev.puts group.options.map(&:name).join(" ") }
26
+ commands.each { |command| command.dump_structure(dev) }
27
+ descrs.each { |descr| dev.puts descr.text }
28
+ }
29
+ end
30
+ end
31
+ end
32
+
33
+ module Grammar
34
+ class Node
35
+ def dump_ast
36
+ puts "#{classname} @ #{token.pos} #{token.source}"
37
+ indent { children.each(&:dump_ast) }
38
+ end
39
+
40
+ def dump_idr(short = false)
41
+ puts "#{classname}" if !short
42
+ end
43
+
44
+ def dump_attrs(*attrs)
45
+ indent {
46
+ Array(attrs).flatten.select { |attr| attr.is_a?(Symbol) }.each { |attr|
47
+ value = self.send(attr)
48
+ case value
49
+ when Brief
50
+ puts "#{attr}: #{value.text}"
51
+ when Node
52
+ puts "#{attr}:"
53
+ indent { value.dump_idr }
54
+ when Array
55
+ case value.first
56
+ when nil
57
+ puts "#{attr}: []"
58
+ when Node
59
+ puts "#{attr}:"
60
+ indent { value.each(&:dump_idr) }
61
+ else
62
+ puts "#{attr}: #{value.inspect}"
63
+ end
64
+ when ArgumentType
65
+ puts "#{attr}: #{value}"
66
+ else
67
+ # value = value.inspect if value.nil? || !value.respond_to?(:to_s)
68
+ puts "#{attr}: #{value.inspect}"
69
+ end
70
+ }
71
+ }
72
+ end
73
+
74
+ protected
75
+ def classname() self.class.to_s.sub(/.*::/, "") end
76
+ end
77
+
78
+ class Option
79
+ def dump_idr(short = false)
80
+ if short
81
+ s = [
82
+ name,
83
+ argument? ? argument_type.name : nil,
84
+ optional? ? "?" : nil,
85
+ repeatable? ? "*" : nil
86
+ ].compact.join(" ")
87
+ puts s
88
+ else
89
+ puts "#{name}: #{classname}"
90
+ dump_attrs(
91
+ :uid, :path, :attr, :ident, :name, :idents, :names,
92
+ :repeatable?,
93
+ :argument?, argument? && :argument_name, argument? && :argument_type,
94
+ :enum?, enum? && :argument_enum,
95
+ :optional?)
96
+ indent { puts "brief: #{group.brief}" }
97
+ end
98
+ end
99
+ end
100
+
101
+ class Command
102
+ def dump_idr(short = false)
103
+ if short
104
+ puts name
105
+ indent {
106
+ options.each { |option| option.dump_idr(short) }
107
+ commands.each { |command| command.dump_idr(short) }
108
+ descrs.each { |descr| descr.dump_idr(short) }
109
+ }
110
+ else
111
+ puts "#{name}: #{classname}"
112
+ dump_attrs :uid, :path, :ident, :name, :options, :commands, :specs, :descrs, :brief
113
+ end
114
+ end
115
+ end
116
+
117
+ class ArgDescr
118
+ def dump_idr(short = false)
119
+ super
120
+ indent { puts token.to_s }
121
+ end
122
+ end
123
+
124
+ class ArgSpec < Node
125
+ def dump_idr(short = false)
126
+ super
127
+ dump_attrs :arguments
128
+ end
129
+ end
130
+
131
+ class Arg < Node
132
+ def dump_idr(short = false)
133
+ puts "<type>"
134
+ end
135
+ end
136
+ end
137
+
138
+ class Command
139
+ def __dump__(argv = [])
140
+ ::Kernel.puts __name__
141
+ ::Kernel.indent {
142
+ __options__.each { |ident, value| ::Kernel.puts "#{ident}: #{value.inspect}" }
143
+ __subcommand__!&.__dump__
144
+ ::Kernel.puts argv.map(&:inspect).join(" ") if !argv.empty?
145
+ }
146
+ end
147
+
148
+ # Class-level accessor methods
149
+ def self.dump(expr, argv = []) expr.__dump__(argv) end
150
+ end
151
+
152
+ class Option
153
+ def dump
154
+ ::Kernel.puts [name, argument].compact.join(" ")
155
+ end
156
+ end
157
+ end
158
+