shellopts 2.0.21 → 2.0.24

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a2e9d4e1a3ad5eca9fd21a3bad085a252ff7b6921ec889e4da404c989304bf9c
4
- data.tar.gz: fa9f67483fcfb303e76f8b6b2282948f117f2497b734b6fdd6ca6f91a29b3d96
3
+ metadata.gz: fff67178855b9164ee7e432d2074040c4c7d8249231e83f27ce1b6a345dbad4c
4
+ data.tar.gz: 9c9816fda6e7361e310080fd9d150d0c9e7419de929e42a552cfa8dd4c9d7740
5
5
  SHA512:
6
- metadata.gz: 29c8d07d8fdf63c8b4e04d031ec1c7b779ed3410265103514ade6f8e5fd2b45d3f5b804de934722e0f49d11119e14e463507b7d63c0c6d2059018b75fec281a6
7
- data.tar.gz: 2f7cad783bd109dc264574cb6ed76ec1f947d1591e15267bf071ebd243b5b646039bdec9fc2dac7983db38463fb6eddb328d920beaa35052647e647f9020ed49
6
+ metadata.gz: 4c6f20dd0afbf624cc671ffc2468977861a3533c5f447acadf3082cdf577dbb9547a96e5ece6cc78fc9f93045dea99581d37f6f7e521e2bcc43261bec07cf653
7
+ data.tar.gz: 773fde53cd201e58c8d860d19861c5d33c2ce0214d5c5986cc9ce5d91d5b920e33f54398a2b6e9185c7300504ce82169c94e1d92854d68ce76c8cf9050b035f8
@@ -18,7 +18,8 @@ module ShellOpts
18
18
  # if type is an IntegerArgument
19
19
  def value?(value) true end
20
20
 
21
- # Convert value to Ruby type
21
+ # Convert value to Ruby type. This method can also be used to translate
22
+ # special keywords
22
23
  def convert(value) value end
23
24
 
24
25
  # String representation. Equal to #name
@@ -60,69 +61,120 @@ module ShellOpts
60
61
  attr_reader :kind
61
62
 
62
63
  def initialize(kind)
63
- constrain kind, :file, :dir, :path, :efile, :edir, :epath, :nfile, :ndir, :npath
64
+ constrain kind, :file, :dir, :path, :efile, :edir, :epath, :nfile, :ndir, :npath, :ifile, :ofile
64
65
  @kind = kind
65
66
  end
66
67
 
67
68
  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)
69
+ # Special-case '-' keyword
70
+ if literal == '-' && [:ifile, :ofile].include?(kind)
71
+ true
72
+
73
+ # Special-case standard I/O files
74
+ elsif %w(/dev/stdin /dev/stdout /dev/stderr /dev/null).include?(literal)
75
+ case kind
76
+ when :file, :path, :efile, :epath, :nfile, :npath
77
+ true
78
+ when :ifile
79
+ %w(/dev/stdin /dev/null).include? literal or
80
+ set_message "Can't read #{literal}"
81
+ when :ofile
82
+ %w(/dev/stdout /dev/stderr /dev/null).include?(literal) or
83
+ set_message "Can't write to #{literal}"
84
+ when :dir, :edir, :ndir
85
+ set_message "Expected file, got directory #{literal}"
86
+ else
87
+ raise ArgumentError, "Unhandled kind: #{kind.inspect}"
88
+ end
89
+
90
+ # All other files or directories
80
91
  else
81
- raise InternalError, "Illegal kind: #{kind.inspect}"
92
+ case kind # TODO: node, enode, npath - special files: character, block, socket, etc.
93
+ when :file; match_path(name, literal, kind, :file?, :default)
94
+ when :dir; match_path(name, literal, kind, :directory?, :default)
95
+ when :path; match_path(name, literal, kind, :exist?, :default)
96
+
97
+ when :efile; match_path(name, literal, kind, :file?, :exist)
98
+ when :edir; match_path(name, literal, kind, :directory?, :exist)
99
+ when :epath; match_path(name, literal, kind, :exist?, :exist)
100
+
101
+ when :nfile; match_path(name, literal, kind, :file?, :new)
102
+ when :ndir; match_path(name, literal, kind, :directory?, :new)
103
+ when :npath; match_path(name, literal, kind, :exist?, :new)
104
+
105
+ when :ifile; match_path(name, literal, kind, :readable?, :default)
106
+ when :ofile; match_path(name, literal, kind, :writable?, :default)
107
+ else
108
+ raise InternalError, "Illegal kind: #{kind.inspect}"
109
+ end
82
110
  end
83
111
  end
84
112
 
85
113
  # Note: No checks done, not sure if it is a feature or a bug
86
114
  def value?(value) value.is_a?(String) end
87
115
 
116
+ def convert(value)
117
+ if value == "-"
118
+ case kind
119
+ when :ifile; "/dev/stdin"
120
+ when :ofile; "/dev/stdout"
121
+ else
122
+ value
123
+ end
124
+ else
125
+ value
126
+ end
127
+ end
128
+
88
129
  protected
89
130
  def match_path(name, literal, kind, method, mode)
90
131
  subject =
91
132
  case kind
92
- when :file, :efile, :nfile; "regular file"
133
+ when :file, :efile, :nfile, :ifile, :ofile; "file"
93
134
  when :dir, :edir, :ndir; "directory"
94
135
  when :path, :epath, :npath; "path"
95
136
  else
96
137
  raise ArgumentError
97
138
  end
98
139
 
99
- if File.send(method, literal) # exists?
140
+ # file exists and is the rigth type?
141
+ if File.send(method, literal)
100
142
  if mode == :new
101
143
  set_message "#{subject.capitalize} already exists in #{name}: #{literal}"
102
144
  elsif kind == :path || kind == :epath
103
145
  if File.file?(literal) || File.directory?(literal)
104
146
  true
105
147
  else
106
- set_message "Expected regular file or directory as #{name} argument: #{literal}"
148
+ set_message "Expected file or directory as #{name} argument: #{literal}"
107
149
  end
150
+ elsif (kind == :ifile || kind == :ofile) && File.directory?(literal)
151
+ set_message "File expected, got directory: #{literal}"
108
152
  else
109
153
  true
110
154
  end
111
- elsif File.exist?(literal) # exists but not the right type
112
- if mode == :new
113
- set_message "#{subject.capitalize} already exists"
155
+
156
+ # file exists but not the right type?
157
+ elsif File.exist?(literal)
158
+ if kind == :ifile
159
+ set_message "Can't read #{literal}"
160
+ elsif kind == :ofile
161
+ set_message "Can't write to #{literal}"
162
+ elsif mode == :new
163
+ set_message "#{subject.capitalize} already exists - #{literal}"
114
164
  else
115
165
  set_message "Expected #{subject} as #{name} argument: #{literal}"
116
166
  end
117
- else # does not exist
167
+
168
+ # file does not exist
169
+ else
118
170
  if [:default, :new].include? mode
119
- if File.exist?(File.dirname(literal))
171
+ if File.exist?(File.dirname(literal)) || kind == :ofile
120
172
  true
121
173
  else
122
174
  set_message "Illegal path in #{name}: #{literal}"
123
175
  end
124
176
  else
125
- set_message "Error in #{name} argument: Can't find #{literal}"
177
+ set_message "Can't find #{literal}"
126
178
  end
127
179
  end
128
180
  end
@@ -30,8 +30,8 @@ module ShellOpts
30
30
  class Command
31
31
  using Ext::Array::Wrap
32
32
 
33
- def puts_usage(bol: false)
34
- width = [Formatter.rest, Formatter::USAGE_MAX_WIDTH].min
33
+ def puts_usage(bol: false, max_width: Formatter::USAGE_MAX_WIDTH)
34
+ width = [Formatter.rest, max_width].min
35
35
  if descrs.size == 0
36
36
  print (lead = Formatter.command_prefix || "")
37
37
  indent(lead.size, ' ', bol: bol && lead == "") {
@@ -41,7 +41,7 @@ module ShellOpts
41
41
  lead = Formatter.command_prefix || ""
42
42
  descrs.each { |descr|
43
43
  print lead
44
- puts render(:single, width, args: [descr.text])
44
+ puts render(:multi, width, args: descr.text.split(' '))
45
45
  }
46
46
  end
47
47
  end
@@ -58,7 +58,7 @@ module ShellOpts
58
58
  end
59
59
 
60
60
  puts "Usage"
61
- indent { puts_usage(bol: true) }
61
+ indent { puts_usage(bol: true, max_width: Formatter::HELP_MAX_WIDTH) }
62
62
 
63
63
  if options.any?
64
64
  puts
@@ -112,14 +112,13 @@ module ShellOpts
112
112
  puts
113
113
 
114
114
  puts Ansi.bold "USAGE"
115
- indent { puts_usage(bol: true) }
115
+ indent { puts_usage(bol: true, max_width: Formatter::HELP_MAX_WIDTH) }
116
116
 
117
117
  section = {
118
118
  Paragraph => "DESCRIPTION",
119
119
  OptionGroup => "OPTION",
120
120
  Command => "COMMAND"
121
121
  }
122
-
123
122
  seen_sections = {}
124
123
  newline = false # True if a newline should be printed before child
125
124
  indent {
@@ -173,7 +172,10 @@ module ShellOpts
173
172
  end
174
173
 
175
174
  module WrappedNode
176
- def puts_descr(width = Formatter.rest) puts lines(width) end
175
+ def puts_descr
176
+ width = [Formatter.rest, Formatter::HELP_MAX_WIDTH].min
177
+ puts lines(width)
178
+ end
177
179
  end
178
180
 
179
181
  class Code
@@ -217,6 +219,9 @@ module ShellOpts
217
219
  # Indent to use in help output
218
220
  HELP_INDENT = 4
219
221
 
222
+ # Max. width of help text (not including indent)
223
+ HELP_MAX_WIDTH = 85
224
+
220
225
  # Command prefix when subject is a sub-command
221
226
  def self.command_prefix() @command_prefix end
222
227
 
@@ -83,8 +83,8 @@ module ShellOpts
83
83
  when "$"
84
84
  @argument_name ||= "NUM"
85
85
  @argument_type = FloatArgument.new
86
- when "FILE", "DIR", "PATH", "EFILE", "EDIR", "EPATH", "NFILE", "NDIR", "NPATH"
87
- @argument_name ||= arg.sub(/^(?:E|N)/, "")
86
+ when "FILE", "DIR", "PATH", "EFILE", "EDIR", "EPATH", "NFILE", "NDIR", "NPATH", "IFILE", "OFILE"
87
+ @argument_name ||= arg.sub(/^(?:E|N|I|O)/, "")
88
88
  @argument_type = FileArgument.new(arg.downcase.to_sym)
89
89
  when /,/
90
90
  @argument_name ||= arg
@@ -15,9 +15,9 @@ require 'terminfo'
15
15
  #
16
16
  # Command rendering
17
17
  # cmd --all --beta [cmd1|cmd2] ARG1 ARG2 # Single-line formats (:single)
18
- # cmd --all --beta [cmd1|cmd2] ARGS...
18
+ # cmd --all --beta [cmd1|cmd2] ARGS... # Not used
19
19
  # cmd -a -b [cmd1|cmd2] ARG1 ARG2
20
- # cmd -a -b [cmd1|cmd2] ARGS...
20
+ # cmd -a -b [cmd1|cmd2] ARGS... # Not used
21
21
  #
22
22
  # cmd -a -b [cmd1|cmd2] ARG1 ARG2 # One line for each argument description (:enum)
23
23
  # cmd -a -b [cmd1|cmd2] ARG3 ARG4 # (used in the USAGE section)
@@ -108,7 +108,7 @@ module ShellOpts
108
108
  def get_args(args: nil)
109
109
  case descrs.size
110
110
  when 0; []
111
- when 1; [descrs.first.text]
111
+ when 1; descrs.first.text.split(' ')
112
112
  else [DESCRS_ABBR]
113
113
  end
114
114
  end
@@ -120,6 +120,7 @@ module ShellOpts
120
120
  end
121
121
 
122
122
  # Force one line. Compact options, commands, arguments if needed
123
+ #
123
124
  def render_single(width, args: nil)
124
125
  long_options = options.map { |option| option.render(:long) }
125
126
  short_options = options.map { |option| option.render(:short) }
@@ -164,32 +165,30 @@ module ShellOpts
164
165
  def render_multi(width, args: nil)
165
166
  long_options = options.map { |option| option.render(:long) }
166
167
  short_options = options.map { |option| option.render(:short) }
167
- short_commands = commands.empty? ? [] : ["[#{commands.map(&:name).join("|")}]"]
168
- compact_commands = [COMMANDS_ABBR]
168
+ compact_options = options.empty? ? [] : [OPTIONS_ABBR]
169
+
170
+ # Only compact commands if they can't fit on one line
171
+ if commands.empty?
172
+ use_commands = []
173
+ else
174
+ short_command = "[#{commands.map(&:name).join("|")}]"
175
+ use_commands = [short_command.size > width ? COMMANDS_ABBR : short_command]
176
+ end
169
177
 
170
178
  args ||= get_args
171
179
 
172
180
  # On one line
173
- words = [name] + long_options + short_commands + args
181
+ words = [name] + long_options + use_commands + args
174
182
  return [words.join(" ")] if pass?(words, width)
175
- words = [name] + short_options + short_commands + args
183
+ words = [name] + short_options + use_commands + args
184
+ return [words.join(" ")] if pass?(words, width)
185
+ words = [name] + compact_options + use_commands + args
176
186
  return [words.join(" ")] if pass?(words, width)
177
187
 
178
188
  # On multiple lines
179
189
  lead = name + " "
180
- options = long_options.wrap(width - lead.size)
181
- options = [lead + options[0]] + indent_lines(lead.size, options[1..-1])
182
-
183
- begin
184
- words = short_commands + args
185
- break if pass?(words, width)
186
- words = compact_commands + args
187
- break if pass?(words, width)
188
- words = compact_commands + [DESCRS_ABBR]
189
- end while false
190
-
191
- cmdargs = words.empty? ? [] : [words.join(" ")]
192
- options + indent_lines(lead.size, cmdargs)
190
+ words = (long_options + use_commands + args).wrap(width - lead.size)
191
+ lines = [lead + words[0]] + indent_lines(lead.size, words[1..-1])
193
192
  end
194
193
 
195
194
  protected
@@ -1,3 +1,3 @@
1
1
  module ShellOpts
2
- VERSION = "2.0.21"
2
+ VERSION = "2.0.24"
3
3
  end
data/lib/shellopts.rb CHANGED
@@ -140,7 +140,7 @@ module ShellOpts
140
140
  float: true,
141
141
 
142
142
  # Let exceptions through
143
- exceptions: false
143
+ exception: false
144
144
  )
145
145
 
146
146
  @name = name || File.basename($PROGRAM_NAME)
data/shellopts.gemspec CHANGED
@@ -28,8 +28,7 @@ Gem::Specification.new do |spec|
28
28
  spec.add_dependency "ruby-terminfo"
29
29
  spec.add_dependency "indented_io"
30
30
 
31
- spec.add_development_dependency "bundler", "~> 2.2.10"
32
- spec.add_development_dependency "rake", ">= 12.3.3"
33
- spec.add_development_dependency "rspec", "~> 3.0"
31
+ spec.add_development_dependency "rake"
32
+ spec.add_development_dependency "rspec"
34
33
  spec.add_development_dependency "simplecov"
35
34
  end
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.21
4
+ version: 2.0.24
5
5
  platform: ruby
6
6
  authors:
7
7
  - Claus Rasmussen
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-04-21 00:00:00.000000000 Z
11
+ date: 2022-05-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: forward_to
@@ -66,48 +66,34 @@ dependencies:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
- - !ruby/object:Gem::Dependency
70
- name: bundler
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - "~>"
74
- - !ruby/object:Gem::Version
75
- version: 2.2.10
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - "~>"
81
- - !ruby/object:Gem::Version
82
- version: 2.2.10
83
69
  - !ruby/object:Gem::Dependency
84
70
  name: rake
85
71
  requirement: !ruby/object:Gem::Requirement
86
72
  requirements:
87
73
  - - ">="
88
74
  - !ruby/object:Gem::Version
89
- version: 12.3.3
75
+ version: '0'
90
76
  type: :development
91
77
  prerelease: false
92
78
  version_requirements: !ruby/object:Gem::Requirement
93
79
  requirements:
94
80
  - - ">="
95
81
  - !ruby/object:Gem::Version
96
- version: 12.3.3
82
+ version: '0'
97
83
  - !ruby/object:Gem::Dependency
98
84
  name: rspec
99
85
  requirement: !ruby/object:Gem::Requirement
100
86
  requirements:
101
- - - "~>"
87
+ - - ">="
102
88
  - !ruby/object:Gem::Version
103
- version: '3.0'
89
+ version: '0'
104
90
  type: :development
105
91
  prerelease: false
106
92
  version_requirements: !ruby/object:Gem::Requirement
107
93
  requirements:
108
- - - "~>"
94
+ - - ">="
109
95
  - !ruby/object:Gem::Version
110
- version: '3.0'
96
+ version: '0'
111
97
  - !ruby/object:Gem::Dependency
112
98
  name: simplecov
113
99
  requirement: !ruby/object:Gem::Requirement