claide 0.7.0 → 0.8.0

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