shellopts 2.0.0.pre.14 → 2.0.2

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.
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
+