claide 0.7.0 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- 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,86 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
module CLAide
|
4
|
-
class Command
|
5
|
-
# Provides support for the default options.
|
6
|
-
#
|
7
|
-
module Options
|
8
|
-
# @return [Array<Array<String, String>>] The default options for a root
|
9
|
-
# command implemented by CLAide.
|
10
|
-
#
|
11
|
-
DEFAULT_ROOT_OPTIONS = [
|
12
|
-
['--completion-script', 'Print the auto-completion script'],
|
13
|
-
['--version', 'Show the version of the tool'],
|
14
|
-
]
|
15
|
-
|
16
|
-
# @return [Array<Array<String, String>>] The default options implemented
|
17
|
-
# by CLAide.
|
18
|
-
#
|
19
|
-
DEFAULT_OPTIONS = [
|
20
|
-
['--verbose', 'Show more debugging information'],
|
21
|
-
['--no-ansi', 'Show output without ANSI codes'],
|
22
|
-
['--help', 'Show help banner of specified command'],
|
23
|
-
]
|
24
|
-
|
25
|
-
# @return [Array<Array<String, String>>] The list of the default
|
26
|
-
# options for the given command.
|
27
|
-
#
|
28
|
-
# @param [Class] command_class
|
29
|
-
# The command class for which the options are needed.
|
30
|
-
#
|
31
|
-
def self.default_options(command_class)
|
32
|
-
if command_class.root_command?
|
33
|
-
Options::DEFAULT_ROOT_OPTIONS + Options::DEFAULT_OPTIONS
|
34
|
-
else
|
35
|
-
Options::DEFAULT_OPTIONS
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
# Handles root commands options if appropriate.
|
40
|
-
#
|
41
|
-
# @param [Command] command
|
42
|
-
# The invoked command.
|
43
|
-
#
|
44
|
-
# @param [ARGV] argv
|
45
|
-
# The parameters of the command.
|
46
|
-
#
|
47
|
-
# @return [Bool] Whether any root command option was handled.
|
48
|
-
#
|
49
|
-
def self.handle_root_option(command, argv)
|
50
|
-
argv = ARGV.coherce(argv)
|
51
|
-
return false unless command.class.root_command?
|
52
|
-
if argv.flag?('version')
|
53
|
-
print_version(command)
|
54
|
-
return true
|
55
|
-
elsif argv.flag?('completion-script')
|
56
|
-
print_completion_template(command)
|
57
|
-
return true
|
58
|
-
end
|
59
|
-
false
|
60
|
-
end
|
61
|
-
|
62
|
-
# Prints the version of the command optionally including plugins.
|
63
|
-
#
|
64
|
-
# @param [Command] command
|
65
|
-
# The invoked command.
|
66
|
-
#
|
67
|
-
def self.print_version(command)
|
68
|
-
puts command.class.version
|
69
|
-
if command.verbose?
|
70
|
-
PluginsHelper.specifications.each do |spec|
|
71
|
-
puts "#{spec.name}: #{spec.version}"
|
72
|
-
end
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
# Prints an auto-completion script according to the user shell.
|
77
|
-
#
|
78
|
-
# @param [Command] command
|
79
|
-
# The invoked command.#
|
80
|
-
#
|
81
|
-
def self.print_completion_template(command)
|
82
|
-
puts ShellCompletionHelper.completion_template(command.class)
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|
86
|
-
end
|
@@ -1,47 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
module CLAide
|
4
|
-
class Command
|
5
|
-
# Loads a command instances from arguments.
|
6
|
-
#
|
7
|
-
module Parser
|
8
|
-
# @param [Array, ARGV] argv
|
9
|
-
# A list of (remaining) parameters.
|
10
|
-
#
|
11
|
-
# @return [Command] An instance of the command class that was matched by
|
12
|
-
# going through the arguments in the parameters and drilling down
|
13
|
-
# command classes.
|
14
|
-
#
|
15
|
-
def self.parse(command, argv)
|
16
|
-
argv = ARGV.coherce(argv)
|
17
|
-
cmd = argv.arguments.first
|
18
|
-
if cmd && subcommand = command.find_subcommand(cmd)
|
19
|
-
argv.shift_argument
|
20
|
-
parse(subcommand, argv)
|
21
|
-
elsif command.abstract_command? && command.default_subcommand
|
22
|
-
load_default_subcommand(command, argv)
|
23
|
-
else
|
24
|
-
command.new(argv)
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
# @param [Array, ARGV] argv
|
29
|
-
# A list of (remaining) parameters.#
|
30
|
-
#
|
31
|
-
# @return [Command] Returns the default subcommand initialized with the
|
32
|
-
# given arguments.
|
33
|
-
#
|
34
|
-
def self.load_default_subcommand(command, argv)
|
35
|
-
default_subcommand = command.default_subcommand
|
36
|
-
subcommand = command.find_subcommand(default_subcommand)
|
37
|
-
unless subcommand
|
38
|
-
raise 'Unable to find the default subcommand ' \
|
39
|
-
"`#{default_subcommand}` for command `#{self}`."
|
40
|
-
end
|
41
|
-
result = parse(subcommand, argv)
|
42
|
-
result.invoked_as_default = true
|
43
|
-
result
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
@@ -1,39 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
require 'claide/command/shell_completion_helper/zsh_completion_generator'
|
4
|
-
|
5
|
-
module CLAide
|
6
|
-
class Command
|
7
|
-
module ShellCompletionHelper
|
8
|
-
# Returns the completion template generated for the given command for the
|
9
|
-
# given shell. If the shell is not provided it will be inferred by the
|
10
|
-
# environment.
|
11
|
-
#
|
12
|
-
def self.completion_template(command, shell = nil)
|
13
|
-
shell ||= ENV['SHELL'].split('/').last
|
14
|
-
case shell
|
15
|
-
when 'zsh'
|
16
|
-
ZSHCompletionGenerator.generate(command)
|
17
|
-
else
|
18
|
-
raise Help, "Auto-completion generator for `#{shell}` shell not" \
|
19
|
-
' implemented.'
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
# Indents the lines of the given string except the first one to the given
|
24
|
-
# level. Uses two spaces per each level.
|
25
|
-
#
|
26
|
-
# @param [String] string
|
27
|
-
# The string to indent.
|
28
|
-
#
|
29
|
-
# @param [Fixnum] indentation
|
30
|
-
# The indentation amount.
|
31
|
-
#
|
32
|
-
# @return [String] An indented string.
|
33
|
-
#
|
34
|
-
def self.indent(string, indentation)
|
35
|
-
string.gsub("\n", "\n#{' ' * indentation * 2}")
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
@@ -1,191 +0,0 @@
|
|
1
|
-
module CLAide
|
2
|
-
class Command
|
3
|
-
module ShellCompletionHelper
|
4
|
-
# Generates a completion script for the Z shell.
|
5
|
-
#
|
6
|
-
module ZSHCompletionGenerator
|
7
|
-
# @return [String] The completion script.
|
8
|
-
#
|
9
|
-
# @param [Class] command
|
10
|
-
# The command to generate the script for.
|
11
|
-
#
|
12
|
-
# rubocop:disable MethodLength
|
13
|
-
def self.generate(command)
|
14
|
-
result = <<-DOC.strip_margin('|')
|
15
|
-
|#compdef #{command.command}
|
16
|
-
|# setopt XTRACE VERBOSE
|
17
|
-
|# vim: ft=zsh sw=2 ts=2 et
|
18
|
-
|
|
19
|
-
|local -a _subcommands
|
20
|
-
|local -a _options
|
21
|
-
|
|
22
|
-
|#{case_statement_fragment(command)}
|
23
|
-
DOC
|
24
|
-
|
25
|
-
post_process(result)
|
26
|
-
end
|
27
|
-
# rubocop:enable MethodLength
|
28
|
-
|
29
|
-
# Returns a case statement for a given command with the given nesting
|
30
|
-
# level.
|
31
|
-
#
|
32
|
-
# @param [Class] command
|
33
|
-
# The command to generate the fragment for.
|
34
|
-
#
|
35
|
-
# @param [Fixnum] nest_level
|
36
|
-
# The nesting level to detect the index of the words array.
|
37
|
-
#
|
38
|
-
# @return [String] the case statement fragment.
|
39
|
-
#
|
40
|
-
# @example
|
41
|
-
# case "$words[2]" in
|
42
|
-
# spec-file)
|
43
|
-
# [..snip..]
|
44
|
-
# ;;
|
45
|
-
# *) # bin
|
46
|
-
# _subcommands=(
|
47
|
-
# "spec-file:"
|
48
|
-
# )
|
49
|
-
# _describe -t commands "bin subcommands" _subcommands
|
50
|
-
# _options=(
|
51
|
-
# "--completion-script:Print the auto-completion script"
|
52
|
-
# "--help:Show help banner of specified command"
|
53
|
-
# "--verbose:Show more debugging information"
|
54
|
-
# "--version:Show the version of the tool"
|
55
|
-
# )
|
56
|
-
# _describe -t options "bin options" _options
|
57
|
-
# ;;
|
58
|
-
# esac
|
59
|
-
#
|
60
|
-
# rubocop:disable MethodLength
|
61
|
-
def self.case_statement_fragment(command, nest_level = 0)
|
62
|
-
entries = case_statement_entries_fragment(command, nest_level + 1)
|
63
|
-
subcommands = subcommands_fragment(command)
|
64
|
-
options = options_fragment(command)
|
65
|
-
|
66
|
-
result = <<-DOC.strip_margin('|')
|
67
|
-
|case "$words[#{nest_level + 2}]" in
|
68
|
-
| #{ShellCompletionHelper.indent(entries, 1)}
|
69
|
-
| *) # #{command.full_command}
|
70
|
-
| #{ShellCompletionHelper.indent(subcommands, 2)}
|
71
|
-
| #{ShellCompletionHelper.indent(options, 2)}
|
72
|
-
| ;;
|
73
|
-
|esac
|
74
|
-
DOC
|
75
|
-
result.gsub(/\n *\n/, "\n").chomp
|
76
|
-
end
|
77
|
-
# rubocop:enable MethodLength
|
78
|
-
|
79
|
-
# Returns a case statement for a given command with the given nesting
|
80
|
-
# level.
|
81
|
-
#
|
82
|
-
# @param [Class] command
|
83
|
-
# The command to generate the fragment for.
|
84
|
-
#
|
85
|
-
# @param [Fixnum] nest_level
|
86
|
-
# The nesting level to detect the index of the words array.
|
87
|
-
#
|
88
|
-
# @return [String] the case statement fragment.
|
89
|
-
#
|
90
|
-
# @example
|
91
|
-
# repo)
|
92
|
-
# case "$words[5]" in
|
93
|
-
# *) # bin spec-file lint
|
94
|
-
# _options=(
|
95
|
-
# "--help:Show help banner of specified command"
|
96
|
-
# "--only-errors:Skip warnings"
|
97
|
-
# "--verbose:Show more debugging information"
|
98
|
-
# )
|
99
|
-
# _describe -t options "bin spec-file lint options" _options
|
100
|
-
# ;;
|
101
|
-
# esac
|
102
|
-
# ;;
|
103
|
-
#
|
104
|
-
def self.case_statement_entries_fragment(command, nest_level)
|
105
|
-
subcommands = command.subcommands_for_command_lookup
|
106
|
-
subcommands.sort_by(&:name).map do |subcommand|
|
107
|
-
subcase = case_statement_fragment(subcommand, nest_level)
|
108
|
-
<<-DOC.strip_margin('|')
|
109
|
-
|#{subcommand.command})
|
110
|
-
| #{ShellCompletionHelper.indent(subcase, 1)}
|
111
|
-
|;;
|
112
|
-
DOC
|
113
|
-
end.join("\n")
|
114
|
-
end
|
115
|
-
|
116
|
-
# Returns the fragment of the subcommands array.
|
117
|
-
#
|
118
|
-
# @param [Class] command
|
119
|
-
# The command to generate the fragment for.
|
120
|
-
#
|
121
|
-
# @return [String] The fragment.
|
122
|
-
#
|
123
|
-
def self.subcommands_fragment(command)
|
124
|
-
subcommands = command.subcommands_for_command_lookup
|
125
|
-
list = subcommands.sort_by(&:name).map do |subcommand|
|
126
|
-
"\"#{subcommand.command}:#{subcommand.summary}\""
|
127
|
-
end
|
128
|
-
describe_fragment(command, 'subcommands', 'commands', list)
|
129
|
-
end
|
130
|
-
|
131
|
-
# Returns the fragment of the options array.
|
132
|
-
#
|
133
|
-
# @param [Class] command
|
134
|
-
# The command to generate the fragment for.
|
135
|
-
#
|
136
|
-
# @return [String] The fragment.
|
137
|
-
#
|
138
|
-
def self.options_fragment(command)
|
139
|
-
list = command.options.sort_by(&:first).map do |option|
|
140
|
-
"\"#{option[0]}:#{option[1]}\""
|
141
|
-
end
|
142
|
-
describe_fragment(command, 'options', 'options', list)
|
143
|
-
end
|
144
|
-
|
145
|
-
# Returns the fragment for a list of completions and the ZSH
|
146
|
-
# `_describe` function.
|
147
|
-
#
|
148
|
-
# @param [Class] command
|
149
|
-
# The command to generate the fragment for.
|
150
|
-
#
|
151
|
-
# @param [String] name
|
152
|
-
# The name of the list.
|
153
|
-
#
|
154
|
-
# @param [Class] tag
|
155
|
-
# The ZSH tag to use (e.g. command or option).
|
156
|
-
#
|
157
|
-
# @param [Array<String>] list
|
158
|
-
# The list of the entries.
|
159
|
-
#
|
160
|
-
# @return [String] The fragment.
|
161
|
-
#
|
162
|
-
def self.describe_fragment(command, name, tag, list)
|
163
|
-
if list && !list.empty?
|
164
|
-
<<-DOC.strip_margin('|')
|
165
|
-
|_#{name}=(
|
166
|
-
| #{ShellCompletionHelper.indent(list.join("\n"), 1)}
|
167
|
-
|)
|
168
|
-
|_describe -t #{tag} "#{command.full_command} #{name}" _#{name}
|
169
|
-
DOC
|
170
|
-
else
|
171
|
-
''
|
172
|
-
end
|
173
|
-
end
|
174
|
-
|
175
|
-
# Post processes a script to remove any artifact and escape any needed
|
176
|
-
# character.
|
177
|
-
#
|
178
|
-
# @param [String] string
|
179
|
-
# The string to post process.
|
180
|
-
#
|
181
|
-
# @return [String] The post processed script.
|
182
|
-
#
|
183
|
-
def self.post_process(string)
|
184
|
-
string.gsub!(/\n *\n/, "\n\n")
|
185
|
-
string.gsub!(/`/, '\\\`')
|
186
|
-
string
|
187
|
-
end
|
188
|
-
end
|
189
|
-
end
|
190
|
-
end
|
191
|
-
end
|
@@ -1,102 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
module CLAide
|
4
|
-
class Command
|
5
|
-
module ValidationHelper
|
6
|
-
# @return [String] Returns a message including a suggestion for the given
|
7
|
-
# unrecognized arguments.
|
8
|
-
#
|
9
|
-
# @param [Array<String>] arguments
|
10
|
-
# The unrecognized arguments.
|
11
|
-
#
|
12
|
-
# @param [Class] command_class
|
13
|
-
# The class of the command which encountered the unrecognized
|
14
|
-
# arguments.
|
15
|
-
#
|
16
|
-
def self.argument_suggestion(arguments, command_class)
|
17
|
-
string = arguments.first
|
18
|
-
type = ARGV::Parser.argument_type(string)
|
19
|
-
list = suggestion_list(command_class, type)
|
20
|
-
suggestion = ValidationHelper.suggestion(string, list)
|
21
|
-
suggestion_message(suggestion, type, string)
|
22
|
-
end
|
23
|
-
|
24
|
-
# @return [Array<String>] The list of the valid arguments for a command
|
25
|
-
# according to the type of the argument.
|
26
|
-
#
|
27
|
-
# @param [Command] command_class
|
28
|
-
# The class of the command for which the list of arguments is
|
29
|
-
# needed.
|
30
|
-
#
|
31
|
-
# @param [Symbol] type
|
32
|
-
# The type of the argument.
|
33
|
-
#
|
34
|
-
def self.suggestion_list(command_class, type)
|
35
|
-
case type
|
36
|
-
when :option, :flag
|
37
|
-
command_class.options.map(&:first)
|
38
|
-
when :arg
|
39
|
-
command_class.subcommands_for_command_lookup.map(&:command)
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
# Returns a suggestion for a string from a list of possible elements.
|
44
|
-
#
|
45
|
-
# @return [String] string
|
46
|
-
# The string for which the suggestion is needed.
|
47
|
-
#
|
48
|
-
# @param [Array<String>] list
|
49
|
-
# The list of the valid elements
|
50
|
-
#
|
51
|
-
def self.suggestion(string, list)
|
52
|
-
sorted = list.sort_by do |element|
|
53
|
-
Helper.levenshtein_distance(string, element)
|
54
|
-
end
|
55
|
-
sorted.first
|
56
|
-
end
|
57
|
-
|
58
|
-
# @return [String] Returns a message including a suggestion for the given
|
59
|
-
# suggestion.
|
60
|
-
#
|
61
|
-
# @param [String, Nil] suggestion
|
62
|
-
# The suggestion.
|
63
|
-
#
|
64
|
-
# @param [Symbol] type
|
65
|
-
# The type of the suggestion.
|
66
|
-
#
|
67
|
-
# @param [String] string
|
68
|
-
# The unrecognized string.
|
69
|
-
#
|
70
|
-
def self.suggestion_message(suggestion, type, string)
|
71
|
-
string_type = type == :arg ? 'command' : 'option'
|
72
|
-
if suggestion
|
73
|
-
pretty_suggestion = prettify_validation_suggestion(suggestion, type)
|
74
|
-
"Unknown #{string_type}: `#{string}`\n" \
|
75
|
-
"Did you mean: #{pretty_suggestion}"
|
76
|
-
else
|
77
|
-
"Unknown #{string_type}: `#{string}`"
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
# Prettifies the given validation suggestion according to the type.
|
82
|
-
#
|
83
|
-
# @param [String] suggestion
|
84
|
-
# The suggestion to prettify.
|
85
|
-
#
|
86
|
-
# @param [Type] type
|
87
|
-
# The type of the suggestion: either `:command` or `:option`.
|
88
|
-
#
|
89
|
-
# @return [String] A handsome suggestion.
|
90
|
-
#
|
91
|
-
def self.prettify_validation_suggestion(suggestion, type)
|
92
|
-
case type
|
93
|
-
when :option, :flag
|
94
|
-
suggestion = "#{suggestion}"
|
95
|
-
suggestion.ansi.blue
|
96
|
-
when :arg
|
97
|
-
suggestion.ansi.green
|
98
|
-
end
|
99
|
-
end
|
100
|
-
end
|
101
|
-
end
|
102
|
-
end
|