codeclimate-fede 0.85.23
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 +7 -0
- data/bin/check +18 -0
- data/bin/codeclimate +21 -0
- data/bin/prep-release +45 -0
- data/bin/release +41 -0
- data/bin/validate-release +18 -0
- data/config/engines.yml +322 -0
- data/lib/cc/analyzer.rb +50 -0
- data/lib/cc/analyzer/bridge.rb +106 -0
- data/lib/cc/analyzer/composite_container_listener.rb +21 -0
- data/lib/cc/analyzer/container.rb +208 -0
- data/lib/cc/analyzer/container/result.rb +74 -0
- data/lib/cc/analyzer/container_listener.rb +9 -0
- data/lib/cc/analyzer/engine.rb +125 -0
- data/lib/cc/analyzer/engine_output.rb +74 -0
- data/lib/cc/analyzer/engine_output_filter.rb +36 -0
- data/lib/cc/analyzer/engine_output_overrider.rb +31 -0
- data/lib/cc/analyzer/filesystem.rb +50 -0
- data/lib/cc/analyzer/formatters.rb +21 -0
- data/lib/cc/analyzer/formatters/formatter.rb +53 -0
- data/lib/cc/analyzer/formatters/html_formatter.rb +415 -0
- data/lib/cc/analyzer/formatters/json_formatter.rb +38 -0
- data/lib/cc/analyzer/formatters/plain_text_formatter.rb +101 -0
- data/lib/cc/analyzer/formatters/spinner.rb +35 -0
- data/lib/cc/analyzer/issue.rb +69 -0
- data/lib/cc/analyzer/issue_sorter.rb +30 -0
- data/lib/cc/analyzer/issue_validations.rb +26 -0
- data/lib/cc/analyzer/issue_validations/category_validation.rb +32 -0
- data/lib/cc/analyzer/issue_validations/check_name_presence_validation.rb +15 -0
- data/lib/cc/analyzer/issue_validations/content_validation.rb +21 -0
- data/lib/cc/analyzer/issue_validations/description_presence_validation.rb +15 -0
- data/lib/cc/analyzer/issue_validations/location_format_validation.rb +72 -0
- data/lib/cc/analyzer/issue_validations/other_locations_format_validation.rb +41 -0
- data/lib/cc/analyzer/issue_validations/path_existence_validation.rb +15 -0
- data/lib/cc/analyzer/issue_validations/path_is_file_validation.rb +15 -0
- data/lib/cc/analyzer/issue_validations/path_presence_validation.rb +15 -0
- data/lib/cc/analyzer/issue_validations/relative_path_validation.rb +32 -0
- data/lib/cc/analyzer/issue_validations/remediation_points_validation.rb +25 -0
- data/lib/cc/analyzer/issue_validations/severity_validation.rb +39 -0
- data/lib/cc/analyzer/issue_validations/type_validation.rb +15 -0
- data/lib/cc/analyzer/issue_validations/validation.rb +35 -0
- data/lib/cc/analyzer/issue_validator.rb +11 -0
- data/lib/cc/analyzer/location_description.rb +45 -0
- data/lib/cc/analyzer/logging_container_listener.rb +24 -0
- data/lib/cc/analyzer/measurement.rb +22 -0
- data/lib/cc/analyzer/measurement_validations.rb +16 -0
- data/lib/cc/analyzer/measurement_validations/name_validation.rb +23 -0
- data/lib/cc/analyzer/measurement_validations/type_validation.rb +15 -0
- data/lib/cc/analyzer/measurement_validations/validation.rb +27 -0
- data/lib/cc/analyzer/measurement_validations/value_validation.rb +21 -0
- data/lib/cc/analyzer/measurement_validator.rb +11 -0
- data/lib/cc/analyzer/mounted_path.rb +80 -0
- data/lib/cc/analyzer/raising_container_listener.rb +32 -0
- data/lib/cc/analyzer/source_buffer.rb +47 -0
- data/lib/cc/analyzer/source_extractor.rb +79 -0
- data/lib/cc/analyzer/source_fingerprint.rb +40 -0
- data/lib/cc/analyzer/statsd_container_listener.rb +51 -0
- data/lib/cc/analyzer/validator.rb +38 -0
- data/lib/cc/cli.rb +39 -0
- data/lib/cc/cli/analyze.rb +90 -0
- data/lib/cc/cli/analyze/engine_failure.rb +11 -0
- data/lib/cc/cli/command.rb +85 -0
- data/lib/cc/cli/console.rb +12 -0
- data/lib/cc/cli/engines.rb +5 -0
- data/lib/cc/cli/engines/engine_command.rb +15 -0
- data/lib/cc/cli/engines/install.rb +35 -0
- data/lib/cc/cli/engines/list.rb +18 -0
- data/lib/cc/cli/file_store.rb +42 -0
- data/lib/cc/cli/global_cache.rb +47 -0
- data/lib/cc/cli/global_config.rb +35 -0
- data/lib/cc/cli/help.rb +51 -0
- data/lib/cc/cli/output.rb +34 -0
- data/lib/cc/cli/prepare.rb +98 -0
- data/lib/cc/cli/runner.rb +75 -0
- data/lib/cc/cli/validate_config.rb +84 -0
- data/lib/cc/cli/version.rb +16 -0
- data/lib/cc/cli/version_checker.rb +107 -0
- data/lib/cc/config.rb +70 -0
- data/lib/cc/config/checks_adapter.rb +40 -0
- data/lib/cc/config/default_adapter.rb +54 -0
- data/lib/cc/config/engine.rb +41 -0
- data/lib/cc/config/engine_set.rb +47 -0
- data/lib/cc/config/json_adapter.rb +17 -0
- data/lib/cc/config/prepare.rb +92 -0
- data/lib/cc/config/validation/check_validator.rb +34 -0
- data/lib/cc/config/validation/engine_validator.rb +93 -0
- data/lib/cc/config/validation/fetch_validator.rb +78 -0
- data/lib/cc/config/validation/file_validator.rb +112 -0
- data/lib/cc/config/validation/hash_validations.rb +52 -0
- data/lib/cc/config/validation/json.rb +31 -0
- data/lib/cc/config/validation/prepare_validator.rb +40 -0
- data/lib/cc/config/validation/yaml.rb +66 -0
- data/lib/cc/config/yaml_adapter.rb +73 -0
- data/lib/cc/engine_registry.rb +74 -0
- data/lib/cc/resolv.rb +39 -0
- data/lib/cc/workspace.rb +39 -0
- data/lib/cc/workspace/exclusion.rb +34 -0
- data/lib/cc/workspace/path_tree.rb +49 -0
- data/lib/cc/workspace/path_tree/dir_node.rb +67 -0
- data/lib/cc/workspace/path_tree/file_node.rb +31 -0
- metadata +277 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
require "cc/cli/command"
|
|
2
|
+
|
|
3
|
+
module CC
|
|
4
|
+
module CLI
|
|
5
|
+
class Analyze < Command
|
|
6
|
+
ARGUMENT_LIST = "[-f format] [-e engine[:channel]] [path]".freeze
|
|
7
|
+
SHORT_HELP = "Run analysis with the given arguments".freeze
|
|
8
|
+
HELP = "#{SHORT_HELP}\n" \
|
|
9
|
+
"\n" \
|
|
10
|
+
" -f <format>, --format <format> Format of output. Possible values: #{CC::Analyzer::Formatters::FORMATTERS.keys.join ", "}\n" \
|
|
11
|
+
" -e <engine[:channel]> Engine to run. Can be specified multiple times.\n" \
|
|
12
|
+
" --dev Run in development mode. Engines installed locally that are not in the manifest will be run.\n" \
|
|
13
|
+
" path Path to check. Can be specified multiple times.".freeze
|
|
14
|
+
|
|
15
|
+
autoload :EngineFailure, "cc/cli/analyze/engine_failure"
|
|
16
|
+
|
|
17
|
+
include CC::Analyzer
|
|
18
|
+
|
|
19
|
+
def run
|
|
20
|
+
# Load config here so it sees ./.codeclimate.yml
|
|
21
|
+
@config = Config.load
|
|
22
|
+
|
|
23
|
+
# process args after, so it modifies loaded configuration
|
|
24
|
+
process_args
|
|
25
|
+
|
|
26
|
+
bridge = Bridge.new(
|
|
27
|
+
config: config,
|
|
28
|
+
formatter: formatter,
|
|
29
|
+
listener: CompositeContainerListener.new(
|
|
30
|
+
LoggingContainerListener.new(Analyzer.logger),
|
|
31
|
+
RaisingContainerListener.new(EngineFailure),
|
|
32
|
+
),
|
|
33
|
+
registry: EngineRegistry.new,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
bridge.run
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
attr_reader :config, :engines_disabled, :listener, :registry
|
|
42
|
+
|
|
43
|
+
def process_args
|
|
44
|
+
while (arg = @args.shift)
|
|
45
|
+
case arg
|
|
46
|
+
when "-f", "--format"
|
|
47
|
+
@formatter = Formatters.resolve(@args.shift).new(filesystem)
|
|
48
|
+
when "-e", "--engine"
|
|
49
|
+
disable_all_engines!
|
|
50
|
+
name, channel = @args.shift.split(":", 2)
|
|
51
|
+
enable_engine(name, channel)
|
|
52
|
+
when "--dev"
|
|
53
|
+
config.development = true
|
|
54
|
+
when "--no-plugins"
|
|
55
|
+
config.disable_plugins!
|
|
56
|
+
else
|
|
57
|
+
config.analysis_paths << arg
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
rescue Formatters::Formatter::InvalidFormatterError => ex
|
|
61
|
+
fatal(ex.message)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def formatter
|
|
65
|
+
@formatter ||= Formatters::PlainTextFormatter.new(filesystem)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def disable_all_engines!
|
|
69
|
+
unless engines_disabled
|
|
70
|
+
config.engines.each { |e| e.enabled = false }
|
|
71
|
+
@engines_disabled = true
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def enable_engine(name, channel)
|
|
76
|
+
existing_engine = config.engines.detect { |e| e.name == name }
|
|
77
|
+
if existing_engine.present?
|
|
78
|
+
existing_engine.enabled = true
|
|
79
|
+
existing_engine.channel = channel if channel.present?
|
|
80
|
+
else
|
|
81
|
+
config.engines << Config::Engine.new(
|
|
82
|
+
name,
|
|
83
|
+
channel: channel,
|
|
84
|
+
enabled: true,
|
|
85
|
+
)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
require "highline"
|
|
2
|
+
require "active_support"
|
|
3
|
+
require "active_support/core_ext"
|
|
4
|
+
require "rainbow"
|
|
5
|
+
require "cc/cli/output"
|
|
6
|
+
|
|
7
|
+
module CC
|
|
8
|
+
module CLI
|
|
9
|
+
class Command
|
|
10
|
+
include CC::CLI::Output
|
|
11
|
+
|
|
12
|
+
CODECLIMATE_YAML = ".codeclimate.yml".freeze
|
|
13
|
+
NAMESPACE = name.split("::")[0..-2].join("::").freeze
|
|
14
|
+
|
|
15
|
+
def self.abstract!
|
|
16
|
+
@abstract = true
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.abstract?
|
|
20
|
+
@abstract == true
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.all
|
|
24
|
+
@@subclasses.reject(&:abstract?)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.[](name)
|
|
28
|
+
all.find { |command| command.name == "#{NAMESPACE}::#{name}" || command.command_name == name }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# rubocop: disable Style/ClassVars
|
|
32
|
+
def self.inherited(subclass)
|
|
33
|
+
@@subclasses ||= []
|
|
34
|
+
@@subclasses << subclass
|
|
35
|
+
end
|
|
36
|
+
# rubocop: enable Style/ClassVars
|
|
37
|
+
|
|
38
|
+
def self.synopsis
|
|
39
|
+
"#{command_name} #{self::ARGUMENT_LIST if const_defined?(:ARGUMENT_LIST)}".strip
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def self.short_help
|
|
43
|
+
if const_defined? :SHORT_HELP
|
|
44
|
+
self::SHORT_HELP
|
|
45
|
+
else
|
|
46
|
+
""
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def self.help
|
|
51
|
+
if const_defined? :HELP
|
|
52
|
+
self::HELP
|
|
53
|
+
else
|
|
54
|
+
short_help
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def initialize(args = [])
|
|
59
|
+
@args = args
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def run
|
|
63
|
+
$stderr.puts "unknown command #{self.class.name.split("::").last.underscore}"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def self.command_name
|
|
67
|
+
name.gsub(/^#{NAMESPACE}::/, "").split("::").map do |part|
|
|
68
|
+
part.split(/(?=[A-Z])/).map(&:downcase).join("-")
|
|
69
|
+
end.join(":")
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def execute
|
|
73
|
+
run
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
private
|
|
77
|
+
|
|
78
|
+
def filesystem
|
|
79
|
+
@filesystem ||= CC::Analyzer::Filesystem.new(
|
|
80
|
+
CC::Analyzer::MountedPath.code.container_path,
|
|
81
|
+
)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
module CC
|
|
2
|
+
module CLI
|
|
3
|
+
class Console < Command
|
|
4
|
+
SHORT_HELP = "Open a ruby console for the CLI. Useful for developing against the CLI.".freeze
|
|
5
|
+
|
|
6
|
+
def run
|
|
7
|
+
require "pry"
|
|
8
|
+
binding.pry(quiet: true, prompt: Pry::SIMPLE_PROMPT, output: $stdout)
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
module CC
|
|
2
|
+
module CLI
|
|
3
|
+
module Engines
|
|
4
|
+
class Install < EngineCommand
|
|
5
|
+
SHORT_HELP = "Pull the latest images for enabled engines in your configuration".freeze
|
|
6
|
+
|
|
7
|
+
ImagePullFailure = Class.new(StandardError)
|
|
8
|
+
|
|
9
|
+
def run
|
|
10
|
+
say "Pulling docker images."
|
|
11
|
+
pull_docker_images
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def config
|
|
17
|
+
@config ||= CC::Config.load
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def pull_docker_images
|
|
21
|
+
config.engines.each(&method(:pull_engine))
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def pull_engine(engine)
|
|
25
|
+
metadata = engine_registry.fetch_engine_details(engine)
|
|
26
|
+
unless system("docker pull #{metadata.image}")
|
|
27
|
+
raise ImagePullFailure, "unable to pull image #{metadata.image}"
|
|
28
|
+
end
|
|
29
|
+
rescue EngineRegistry::EngineDetailsNotFoundError
|
|
30
|
+
warn("unknown engine <#{engine.name}:#{engine.channel}>")
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module CC
|
|
2
|
+
module CLI
|
|
3
|
+
module Engines
|
|
4
|
+
class List < EngineCommand
|
|
5
|
+
SHORT_HELP = "List all available engines".freeze
|
|
6
|
+
|
|
7
|
+
def run
|
|
8
|
+
say "Available engines:"
|
|
9
|
+
engine_registry.
|
|
10
|
+
sort_by { |engine, _| engine.name }.
|
|
11
|
+
each do |engine, metadata|
|
|
12
|
+
say "- #{engine.name}: #{metadata.description}"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
require "fileutils"
|
|
2
|
+
require "yaml"
|
|
3
|
+
|
|
4
|
+
module CC
|
|
5
|
+
module CLI
|
|
6
|
+
class FileStore
|
|
7
|
+
# This class is not supposed to be directly used. It should be sublcassed
|
|
8
|
+
# and a few constants need to be defined on the sublass to be usable.
|
|
9
|
+
#
|
|
10
|
+
# FILE_NAME is the name of the file this class wraps.
|
|
11
|
+
|
|
12
|
+
def initialize
|
|
13
|
+
load_data
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def save
|
|
17
|
+
return false unless File.exist? self.class::FILE_NAME
|
|
18
|
+
|
|
19
|
+
File.open(self.class::FILE_NAME, "w") do |f|
|
|
20
|
+
YAML.dump data, f
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
true
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
attr_reader :data
|
|
29
|
+
|
|
30
|
+
def load_data
|
|
31
|
+
@data =
|
|
32
|
+
if File.exist? self.class::FILE_NAME
|
|
33
|
+
File.open(self.class::FILE_NAME, "r:bom|utf-8") do |f|
|
|
34
|
+
YAML.safe_load(f, [Time], [], false, self.class::FILE_NAME) || {}
|
|
35
|
+
end
|
|
36
|
+
else
|
|
37
|
+
{}
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
require "cc/cli/file_store"
|
|
2
|
+
|
|
3
|
+
module CC
|
|
4
|
+
module CLI
|
|
5
|
+
class GlobalCache < FileStore
|
|
6
|
+
FILE_NAME = "/cache.yml".freeze
|
|
7
|
+
|
|
8
|
+
# Cache entries
|
|
9
|
+
|
|
10
|
+
def last_version_check
|
|
11
|
+
data["last-version-check"] || Time.at(0)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def last_version_check=(value)
|
|
15
|
+
data["last-version-check"] =
|
|
16
|
+
if value.is_a? Time
|
|
17
|
+
value
|
|
18
|
+
else
|
|
19
|
+
Time.at(0)
|
|
20
|
+
end
|
|
21
|
+
save
|
|
22
|
+
value
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def latest_version
|
|
26
|
+
data["latest-version"]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def latest_version=(value)
|
|
30
|
+
data["latest-version"] = value
|
|
31
|
+
save
|
|
32
|
+
value
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def outdated
|
|
36
|
+
data["outdated"] == true
|
|
37
|
+
end
|
|
38
|
+
alias outdated? outdated
|
|
39
|
+
|
|
40
|
+
def outdated=(value)
|
|
41
|
+
data["outdated"] = value == true
|
|
42
|
+
save
|
|
43
|
+
value
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
require "cc/cli/file_store"
|
|
2
|
+
require "uuid"
|
|
3
|
+
|
|
4
|
+
module CC
|
|
5
|
+
module CLI
|
|
6
|
+
class GlobalConfig < FileStore
|
|
7
|
+
FILE_NAME = "/config.yml".freeze
|
|
8
|
+
|
|
9
|
+
DEFAULT_CONFIG = {
|
|
10
|
+
"check-version" => true,
|
|
11
|
+
}.freeze
|
|
12
|
+
|
|
13
|
+
# Config entries
|
|
14
|
+
|
|
15
|
+
def check_version
|
|
16
|
+
data["check-version"]
|
|
17
|
+
end
|
|
18
|
+
alias check_version? check_version
|
|
19
|
+
|
|
20
|
+
def check_version=(value)
|
|
21
|
+
data["check-version"] = value == true
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def uuid
|
|
25
|
+
data["uuid"] ||= UUID.new.generate
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def load_data
|
|
31
|
+
@data = DEFAULT_CONFIG.merge(super)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
data/lib/cc/cli/help.rb
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
require "rainbow"
|
|
2
|
+
|
|
3
|
+
module CC
|
|
4
|
+
module CLI
|
|
5
|
+
class Help < Command
|
|
6
|
+
ARGUMENT_LIST = "[command]".freeze
|
|
7
|
+
SHORT_HELP = "Display help information.".freeze
|
|
8
|
+
HELP = "#{SHORT_HELP}\n" \
|
|
9
|
+
"\n" \
|
|
10
|
+
" no arguments Show help summary for all commands.\n" \
|
|
11
|
+
" [command] Show help for specific commands. Can be specified multiple times.".freeze
|
|
12
|
+
|
|
13
|
+
def run
|
|
14
|
+
if @args.any?
|
|
15
|
+
@args.each do |command|
|
|
16
|
+
show_help(command)
|
|
17
|
+
end
|
|
18
|
+
else
|
|
19
|
+
show_help_summary
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def show_help(command_name)
|
|
26
|
+
if (command = Command[command_name])
|
|
27
|
+
say "Usage: codeclimate #{command.synopsis}\n"
|
|
28
|
+
say "\n"
|
|
29
|
+
say "#{command.help}\n"
|
|
30
|
+
say "\n\n"
|
|
31
|
+
else
|
|
32
|
+
say "Unknown command: #{command_name}"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def show_help_summary
|
|
37
|
+
short_helps =
|
|
38
|
+
Command.all.sort_by(&:command_name).map do |command|
|
|
39
|
+
[command.synopsis, command.short_help]
|
|
40
|
+
end.compact.to_h
|
|
41
|
+
|
|
42
|
+
longest_command_length = short_helps.keys.map(&:length).max
|
|
43
|
+
|
|
44
|
+
say "Usage: codeclimate COMMAND ...\n\nAvailable commands:\n"
|
|
45
|
+
short_helps.each do |command, help|
|
|
46
|
+
say format(" %-#{longest_command_length}s %s\n", command, help)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|