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.
- checksums.yaml +4 -4
- data/lib/claide.rb +1 -3
- data/lib/claide/argument.rb +1 -1
- data/lib/claide/argv.rb +82 -4
- data/lib/claide/command.rb +127 -24
- data/lib/claide/command/argument_suggester.rb +102 -0
- data/lib/claide/command/banner.rb +127 -19
- data/lib/claide/command/{plugins_helper.rb → plugin_manager.rb} +19 -19
- metadata +4 -13
- data/lib/claide/argv/parser.rb +0 -83
- data/lib/claide/command/banner/prettifier.rb +0 -61
- data/lib/claide/command/options.rb +0 -86
- data/lib/claide/command/parser.rb +0 -47
- data/lib/claide/command/shell_completion_helper.rb +0 -39
- data/lib/claide/command/shell_completion_helper/zsh_completion_generator.rb +0 -191
- data/lib/claide/command/validation_helper.rb +0 -102
- data/lib/claide/helper.rb +0 -115
- data/lib/claide/mixins.rb +0 -25
@@ -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 =
|
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 <<
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
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
|
-
|
30
|
-
|
31
|
-
|
32
|
-
if
|
33
|
-
|
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
|
-
|
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
|
-
|
44
|
+
plugin_paths.values.flatten.map do |path|
|
45
45
|
specification(path)
|
46
46
|
end.compact
|
47
47
|
end
|
48
48
|
|
49
|
-
# @return [
|
50
|
-
#
|
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
|
-
|
57
|
-
spec = Gem::Specification.load(
|
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.
|
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-
|
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/
|
28
|
+
- lib/claide/command/argument_suggester.rb
|
30
29
|
- lib/claide/command/banner.rb
|
31
|
-
- lib/claide/command/
|
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:
|
data/lib/claide/argv/parser.rb
DELETED
@@ -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
|