claide 0.7.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,7 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
- require 'claide/command/banner/prettifier'
4
-
5
3
  module CLAide
6
4
  class Command
7
5
  # Creates the formatted banner to present as help of the provided command
@@ -57,7 +55,9 @@ module CLAide
57
55
  #
58
56
  def formatted_usage_description
59
57
  message = command.description || command.summary || ''
60
- message = Helper.format_markdown(message, TEXT_INDENT, MAX_WIDTH)
58
+ message = TextWrapper.wrap_formatted_text(message,
59
+ TEXT_INDENT,
60
+ MAX_WIDTH)
61
61
  message = prettify_message(command, message)
62
62
  "#{signature}\n\n#{message}"
63
63
  end
@@ -128,7 +128,9 @@ module CLAide
128
128
  result << name
129
129
  result << ' ' * DESCRIPTION_SPACES
130
130
  result << ' ' * (max_name_width - name_width)
131
- result << Helper.wrap_with_indent(description, desc_start, MAX_WIDTH)
131
+ result << TextWrapper.wrap_with_indent(description,
132
+ desc_start,
133
+ MAX_WIDTH)
132
134
  end
133
135
 
134
136
  # @!group Overrides
@@ -137,33 +139,50 @@ module CLAide
137
139
  # @return [String] A decorated title.
138
140
  #
139
141
  def prettify_title(title)
140
- Prettifier.prettify_title(title)
141
- end
142
-
143
- # @return [String] A decorated textual representation of the command.
144
- #
145
- def prettify_signature(command, subcommand, argument)
146
- Prettifier.prettify_signature(command, subcommand, argument)
147
- end
148
-
149
- # @return [String] A decorated command description.
150
- #
151
- def prettify_message(command, message)
152
- Prettifier.prettify_message(command, message)
142
+ title.ansi.underline
153
143
  end
154
144
 
155
145
  # @return [String] A decorated textual representation of the subcommand
156
146
  # name.
157
147
  #
158
148
  def prettify_subcommand(name)
159
- Prettifier.prettify_subcommand(name)
149
+ name.chomp.ansi.green
160
150
  end
161
151
 
162
152
  # @return [String] A decorated textual representation of the option name.
163
153
  #
164
154
  #
165
155
  def prettify_option_name(name)
166
- Prettifier.prettify_option_name(name)
156
+ name.chomp.ansi.blue
157
+ end
158
+
159
+ # @return [String] A decorated textual representation of the command.
160
+ #
161
+ def prettify_signature(command, subcommand, argument)
162
+ components = [
163
+ [command, :green],
164
+ [subcommand, :green],
165
+ [argument, :magenta],
166
+ ]
167
+ components.reduce('') do |memo, (string, ansi_key)|
168
+ next memo if !string || string.empty?
169
+ memo << ' ' << string.ansi.apply(ansi_key)
170
+ end.lstrip
171
+ end
172
+
173
+ # @return [String] A decorated command description.
174
+ #
175
+ def prettify_message(command, message)
176
+ message = message.dup
177
+ command.arguments.each do |arg|
178
+ arg.names.each do |name|
179
+ message.gsub!("`#{name.gsub(/\.{3}$/, '')}`", '\0'.ansi.magenta)
180
+ end
181
+ end
182
+ command.options.each do |(name, _description)|
183
+ message.gsub!("`#{name}`", '\0'.ansi.blue)
184
+ end
185
+ message
167
186
  end
168
187
 
169
188
  # @!group Private helpers
@@ -189,6 +208,95 @@ module CLAide
189
208
  end.max
190
209
  widths.flatten.compact.max || 1
191
210
  end
211
+
212
+ module TextWrapper
213
+ # @return [String] Wraps a formatted string (e.g. markdown) by stripping
214
+ # heredoc indentation and wrapping by word to the terminal width
215
+ # taking into account a maximum one, and indenting the string.
216
+ # Code lines (i.e. indented by four spaces) are not wrapped.
217
+ #
218
+ # @param [String] string
219
+ # The string to format.
220
+ #
221
+ # @param [Fixnum] indent
222
+ # The number of spaces to insert before the string.
223
+ #
224
+ # @param [Fixnum] max_width
225
+ # The maximum width to use to format the string if the terminal
226
+ # is too wide.
227
+ #
228
+ def self.wrap_formatted_text(string, indent = 0, max_width = 80)
229
+ paragraphs = strip_heredoc(string).split("\n\n")
230
+ paragraphs = paragraphs.map do |paragraph|
231
+ if paragraph.start_with?(' ' * 4)
232
+ paragraph.gsub!(/\n/, "\n#{' ' * indent}")
233
+ else
234
+ paragraph = wrap_with_indent(paragraph, indent, max_width)
235
+ end
236
+ paragraph.insert(0, ' ' * indent).rstrip
237
+ end
238
+ paragraphs.join("\n\n")
239
+ end
240
+
241
+ # @return [String] Wraps a string to the terminal width taking into
242
+ # account the given indentation.
243
+ #
244
+ # @param [String] string
245
+ # The string to indent.
246
+ #
247
+ # @param [Fixnum] indent
248
+ # The number of spaces to insert before the string.
249
+ #
250
+ # @param [Fixnum] max_width
251
+ # The maximum width to use to format the string if the terminal
252
+ # is too wide.
253
+ #
254
+ def self.wrap_with_indent(string, indent = 0, max_width = 80)
255
+ if terminal_width == 0
256
+ width = max_width
257
+ else
258
+ width = [terminal_width, max_width].min
259
+ end
260
+
261
+ full_line = string.gsub("\n", ' ')
262
+ available_width = width - indent
263
+ space = ' ' * indent
264
+ word_wrap(full_line, available_width).split("\n").join("\n#{space}")
265
+ end
266
+
267
+ # @return [String] Lifted straight from ActionView. Thanks guys!
268
+ #
269
+ def self.word_wrap(line, line_width)
270
+ line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1\n").strip
271
+ end
272
+
273
+ # @return [String] Lifted straight from ActiveSupport. Thanks guys!
274
+ #
275
+ def self.strip_heredoc(string)
276
+ if min = string.scan(/^[ \t]*(?=\S)/).min
277
+ string.gsub(/^[ \t]{#{min.size}}/, '')
278
+ else
279
+ string
280
+ end
281
+ end
282
+
283
+ # @!group Private helpers
284
+ #---------------------------------------------------------------------#
285
+
286
+ # @return [Fixnum] The width of the current terminal unless being piped.
287
+ #
288
+ def self.terminal_width
289
+ unless @terminal_width
290
+ if !ENV['CLAIDE_DISABLE_AUTO_WRAP'] &&
291
+ STDOUT.tty? && system('which tput > /dev/null 2>&1')
292
+ @terminal_width = `tput cols`.to_i
293
+ else
294
+ @terminal_width = 0
295
+ end
296
+ end
297
+ @terminal_width
298
+ end
299
+ end
192
300
  end
193
301
  end
194
302
  end
@@ -12,12 +12,12 @@ module CLAide
12
12
  # `lib/#{plugin_prefix}_plugin` relative path.
13
13
  # - Be stored in a folder named after the plugin.
14
14
  #
15
- class PluginsHelper
16
- class << self
17
- # @return [Array<Pathname>] The list of the root directories of the
18
- # loaded plugins.
19
- #
20
- attr_reader :plugin_paths
15
+ class PluginManager
16
+ # @return [Array<Pathname>] The list of the root directories of the
17
+ # loaded plugins.
18
+ #
19
+ def self.plugin_paths
20
+ @plugin_paths ||= {}
21
21
  end
22
22
 
23
23
  # @return [Array<String>] Loads plugins via RubyGems looking for files
@@ -25,36 +25,36 @@ module CLAide
25
25
  # the gems loaded successfully. Plugins are required safely.
26
26
  #
27
27
  def self.load_plugins(plugin_prefix)
28
- return if plugin_paths
29
- paths = PluginsHelper.plugin_load_paths(plugin_prefix)
30
- plugin_paths = []
31
- paths.each do |path|
32
- if PluginsHelper.safe_require(path.to_s)
33
- plugin_paths << Pathname(path + './../../').cleanpath
28
+ return if plugin_paths[plugin_prefix]
29
+
30
+ loaded_paths = []
31
+ plugin_load_paths(plugin_prefix).each do |path|
32
+ if safe_require(path.to_s)
33
+ loaded_paths << Pathname(path + './../../').cleanpath
34
34
  end
35
35
  end
36
36
 
37
- @plugin_paths = plugin_paths
37
+ plugin_paths[plugin_prefix] = loaded_paths
38
38
  end
39
39
 
40
40
  # @return [Array<Specification>] The RubyGems specifications for the
41
41
  # loaded plugins.
42
42
  #
43
43
  def self.specifications
44
- PluginsHelper.plugin_paths.map do |path|
44
+ plugin_paths.values.flatten.map do |path|
45
45
  specification(path)
46
46
  end.compact
47
47
  end
48
48
 
49
- # @return [Array<Specification>] The RubyGems specifications for the
50
- # plugin with the given root path.
49
+ # @return [Specification] The RubyGems specification for the plugin at the
50
+ # given path.
51
51
  #
52
52
  # @param [#to_s] path
53
53
  # The root path of the plugin.
54
54
  #
55
55
  def self.specification(path)
56
- glob = Dir.glob("#{path}/*.gemspec")
57
- spec = Gem::Specification.load(glob.first) if glob.count == 1
56
+ matches = Dir.glob("#{path}/*.gemspec")
57
+ spec = Gem::Specification.load(matches.first) if matches.count == 1
58
58
  unless spec
59
59
  warn '[!] Unable to load a specification for the plugin ' \
60
60
  "`#{path}`".ansi.yellow
@@ -69,7 +69,7 @@ module CLAide
69
69
  # The exception to analyze.
70
70
  #
71
71
  def self.plugins_involved_in_exception(exception)
72
- paths = plugin_paths.select do |plugin_path|
72
+ paths = plugin_paths.values.flatten.select do |plugin_path|
73
73
  exception.backtrace.any? { |line| line.include?(plugin_path.to_s) }
74
74
  end
75
75
  paths.map { |path| path.to_s.split('/').last }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: claide
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eloy Duran
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-09-11 00:00:00.000000000 Z
12
+ date: 2014-12-25 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description:
15
15
  email:
@@ -24,21 +24,13 @@ files:
24
24
  - lib/claide/ansi/string_escaper.rb
25
25
  - lib/claide/ansi.rb
26
26
  - lib/claide/argument.rb
27
- - lib/claide/argv/parser.rb
28
27
  - lib/claide/argv.rb
29
- - lib/claide/command/banner/prettifier.rb
28
+ - lib/claide/command/argument_suggester.rb
30
29
  - lib/claide/command/banner.rb
31
- - lib/claide/command/options.rb
32
- - lib/claide/command/parser.rb
33
- - lib/claide/command/plugins_helper.rb
34
- - lib/claide/command/shell_completion_helper/zsh_completion_generator.rb
35
- - lib/claide/command/shell_completion_helper.rb
36
- - lib/claide/command/validation_helper.rb
30
+ - lib/claide/command/plugin_manager.rb
37
31
  - lib/claide/command.rb
38
32
  - lib/claide/help.rb
39
- - lib/claide/helper.rb
40
33
  - lib/claide/informative_error.rb
41
- - lib/claide/mixins.rb
42
34
  - lib/claide.rb
43
35
  - README.markdown
44
36
  - LICENSE
@@ -67,4 +59,3 @@ signing_key:
67
59
  specification_version: 3
68
60
  summary: A small command-line interface framework.
69
61
  test_files: []
70
- has_rdoc:
@@ -1,83 +0,0 @@
1
- module CLAide
2
- class ARGV
3
- module Parser
4
- # @return [Array<Array<Symbol, String, Array>>] A list of tuples for each
5
- # parameter, where the first entry is the `type` and the second
6
- # entry the actual parsed parameter.
7
- #
8
- # @example
9
- #
10
- # list = parse(['tea', '--no-milk', '--sweetner=honey'])
11
- # list # => [[:arg, "tea"],
12
- # [:flag, ["milk", false]],
13
- # [:option, ["sweetner", "honey"]]]
14
- #
15
- def self.parse(argv)
16
- entries = []
17
- copy = argv.map(&:to_s)
18
- while argument = copy.shift
19
- type = argument_type(argument)
20
- parameter = argument_parameter(type, argument)
21
- entries << [type, parameter]
22
- end
23
- entries
24
- end
25
-
26
- # @return [Symbol] Returns the type of an argument. The types can be
27
- # either: `:arg`, `:flag`, `:option`.
28
- #
29
- # @param [String] argument
30
- # The argument to check.
31
- #
32
- def self.argument_type(argument)
33
- if argument.start_with?('--')
34
- if argument.include?('=')
35
- :option
36
- else
37
- :flag
38
- end
39
- else
40
- :arg
41
- end
42
- end
43
-
44
- # @return [String, Array<String, String>] Returns argument itself for
45
- # normal arguments (like commands) and a tuple with they key and
46
- # the value for options and flags.
47
- #
48
- # @param [Symbol] type
49
- # The type of the argument.
50
- #
51
- # @param [String] argument
52
- # The argument to check.
53
- #
54
- def self.argument_parameter(type, argument)
55
- case type
56
- when :arg
57
- return argument
58
- when :flag
59
- return flag_paramenter(argument)
60
- when :option
61
- return argument[2..-1].split('=', 2)
62
- end
63
- end
64
-
65
- # @return [String, Array<String, String>] Returns the parameter
66
- # describing a flag arguments.
67
- #
68
- # @param [String] argument
69
- # The flag argument to check.
70
- #
71
- def self.flag_paramenter(argument)
72
- if argument.start_with?('--no-')
73
- key = argument[5..-1]
74
- value = false
75
- else
76
- key = argument[2..-1]
77
- value = true
78
- end
79
- [key, value]
80
- end
81
- end
82
- end
83
- end
@@ -1,61 +0,0 @@
1
- # encoding: utf-8
2
-
3
- module CLAide
4
- class Command
5
- class Banner
6
- # Implements the default logic to prettify the Banner.
7
- #
8
- module Prettifier
9
- # @return [String] A decorated title.
10
- #
11
- def self.prettify_title(title)
12
- title.ansi.underline
13
- end
14
-
15
- # @return [String] A decorated textual representation of the command.
16
- #
17
- def self.prettify_signature(command, subcommand, argument)
18
- components = [
19
- [command, :green],
20
- [subcommand, :green],
21
- [argument, :magenta],
22
- ]
23
- components.reduce('') do |memo, (string, ansi_key)|
24
- next memo if !string || string.empty?
25
- memo << ' ' << string.ansi.apply(ansi_key)
26
- end.lstrip
27
- end
28
-
29
- # @return [String] A decorated command description.
30
- #
31
- def self.prettify_message(command, message)
32
- message = message.dup
33
- command.arguments.each do |arg|
34
- arg.names.each do |name|
35
- message.gsub!("`#{name.gsub(/\.{3}$/, '')}`", '\0'.ansi.magenta)
36
- end
37
- end
38
- command.options.each do |(name, _description)|
39
- message.gsub!("`#{name}`", '\0'.ansi.blue)
40
- end
41
- message
42
- end
43
-
44
- # @return [String] A decorated textual representation of the subcommand
45
- # name.
46
- #
47
- def self.prettify_subcommand(name)
48
- name.chomp.ansi.green
49
- end
50
-
51
- # @return [String] A decorated textual representation of the option
52
- # name.
53
- #
54
- #
55
- def self.prettify_option_name(name)
56
- name.chomp.ansi.blue
57
- end
58
- end
59
- end
60
- end
61
- end