Skip to content

Commit

Permalink
chore: improve why command output
Browse files Browse the repository at this point in the history
  • Loading branch information
elbywan committed Sep 21, 2023
1 parent 05069d2 commit 6ba1119
Show file tree
Hide file tree
Showing 16 changed files with 154 additions and 88 deletions.
2 changes: 1 addition & 1 deletion src/cli/cli.cr
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ module Zap
end

private macro subSeparator(text, *, early_line_break = true)
prefix = "#{ {% if early_line_break %}"\n"{% else %}nil{% end %} }"
prefix = "#{ {% if early_line_break %}NEW_LINE{% else %}nil{% end %} }"
parser.separator("#{prefix} #{ {{text}}.colorize.light_blue }\n")
end

Expand Down
21 changes: 19 additions & 2 deletions src/cli/why.cr
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ alias Helpers = Zap::Utils::Various
alias Semver = Zap::Utils::Semver

struct Zap::Config
record Why < CommandConfig, packages : Array({Regex, Semver::Range?}) = [] of {Regex, Semver::Range?} do
record(Why < CommandConfig,
packages : Array({Regex, Semver::Range?}) = [] of {Regex, Semver::Range?},
short : Bool = false,
) do
def from_args(args : Array(String)) : self
if args.size > 0
args.map { |arg|
Expand All @@ -25,6 +28,20 @@ class Zap::CLI
private def on_why(parser : OptionParser)
@command_config = Config::Why.new

parser.stop
separator("Options")

parser.on("--short", "Do not display the dependencies paths.") do
@command_config = why_config.copy_with(short: true)
end

parser.before_each do |arg|
unless arg.starts_with?("-")
parser.stop
end
end
end

private macro why_config
@command_config.as(Config::Why)
end
end
2 changes: 1 addition & 1 deletion src/commands/exec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ module Zap::Commands::Exec
#{"scope".colorize.blue}: #{inferred_context.command_scope.size} package(s) • #{targets.map(&.[0].name).sort.join(", ")}
TERM
end
puts "\n"
puts NEW_LINE
end

scripts = targets.map do |package, path|
Expand Down
4 changes: 2 additions & 2 deletions src/commands/install.cr
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ module Zap::Commands::Install
#{"add/remove scope".colorize.blue}: #{inferred_context.command_scope.size} package(s)#{suffix}
TERM
end
puts "\n"
puts NEW_LINE
end
end

Expand Down Expand Up @@ -344,7 +344,7 @@ module Zap::Commands::Install
pipeline: state.pipeline
)

puts "\n" if scripts.size > 0
puts NEW_LINE if scripts.size > 0
end
end

Expand Down
2 changes: 1 addition & 1 deletion src/commands/rebuild.cr
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ module Zap::Commands::Rebuild
#{"scope".colorize.blue}: #{context.command_scope.size} package(s) • #{scope_names.sort.join(", ")}
TERM
end
puts "\n"
puts NEW_LINE
end

scripts = [] of Utils::Scripts::ScriptData
Expand Down
2 changes: 1 addition & 1 deletion src/commands/run.cr
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ module Zap::Commands::Run
#{"scope".colorize.blue}: #{inferred_context.command_scope.size} package(s) • #{targets.map(&.[0].name).sort.join(", ")}
TERM
end
puts "\n"
puts NEW_LINE
end

scripts = targets.flat_map do |package, path|
Expand Down
111 changes: 79 additions & 32 deletions src/commands/why.cr
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
module Zap::Commands::Why
alias PackageResult = {root: Lockfile::Root, ancestors: Deque({Package, DependencyType}), type: DependencyType}

LEFT_ARROW_CHAR = '←'
DEPENDS_ON_CHAR = '←'
ANCESTOR_PATH_PREFIX_CHAR = '├'
ANCESTOR_PATH_END_PREFIX_CHAR = '└'

def self.run(config : Config, why_config : Config::Why)
why_config = why_config.from_args(ARGV)
Expand All @@ -17,54 +19,99 @@ module Zap::Commands::Why
results = Hash(Package, Array(PackageResult)).new

lockfile.crawl do |dependency, type, root, ancestors|
# for each package in the lockfile, check if it matches the provided pattern
next unless why_config.packages.any? { |name_pattern, version|
name_pattern =~ dependency.name && (!version || version.satisfies?(dependency.version))
}
result = results[dependency] ||= [] of PackageResult
# add to the results the path from the root to the package
result << {
root: root,
ancestors: ancestors.dup,
type: type,
}
end

output_by_package = results.map do |package, result|
# for each package matching the pattern, get the results and format them
output_by_package = results.map do |package, results|
output = String.build do |str|
str << "#{package.key}".colorize.yellow.bold.underline << "\n"
str << "\n"
results_by_root = result.group_by { |result| result[:root] }
results_by_root.each do |root, results|
str << results.sort { |result1, result2|
# Display the root package first
next -1 if result1[:ancestors].size == 0
next 1 if result2[:ancestors].size == 0
# Sort by the direct ancestor key in ascending order
ancestor_diff = result1[:ancestors].last[0].key <=> result2[:ancestors].last[0].key
if ancestor_diff == 0
result1[:ancestors].size - result2[:ancestors].size
else
ancestor_diff
end
}.map { |result|
ancestors_str = result[:ancestors].reverse!.map_with_index { |(ancestor, type), index|
name, version = Utils::Various.parse_key(ancestor.key)
"#{index == 0 ? name.colorize.bold.cyan : name.colorize.dim}#{index == 0 ? "@#{version}" : "@#{version}".colorize.dim}#{type.dependency? ? "" : " (#{type.to_s.camelcase(lower: true)})".colorize.dim}"
}.join(" #{LEFT_ARROW_CHAR} ")

if ancestors_str.empty?
"#{root.name.colorize.magenta.bold}#{"@#{root.version}".colorize.dim} #{"(#{result[:type].to_s.camelcase(lower: true)})".colorize.dim}"
else
"#{ancestors_str} #{LEFT_ARROW_CHAR} #{"#{root.name}@#{root.version}".colorize.magenta}"
end
}.join('\n') << "\n"
str << "\n"
end
str << "#{package.key}".colorize.yellow.bold.underline << NEW_LINE
str << NEW_LINE
results
.group_by { |result| result[:root] }
.each do |root, root_results|
str << format_root_results(root, root_results, config: why_config)
str << NEW_LINE
end
end
{ package.key, output }
{package.key, output}
end

# sort the final ouput by package key
output_by_package.sort { |(key1, _), (key2, _)| key1 <=> key2 }.each do |_, output|
puts output
end
end

private def self.format_root_results(root, root_results, *, config : Config::Why)
String.build do |str|
str << root_results
.sort(&->sort_by_direct_ancestor(PackageResult, PackageResult))
.group_by(&->group_by_direct_ancestor(PackageResult))
.map { |ancestor, results| direct_ancestor_output(ancestor, results, config: config) }
.join(NEW_LINE)
str << NEW_LINE
end
end

private def self.sort_by_direct_ancestor(result1 : PackageResult, result2 : PackageResult)
# Display the root package first
return -1 if result1[:ancestors].size == 0
return 1 if result2[:ancestors].size == 0
# Sort by the direct ancestor key in ascending order
ancestor_diff = result1[:ancestors].last[0].key <=> result2[:ancestors].last[0].key
if ancestor_diff == 0
result1[:ancestors].size - result2[:ancestors].size
else
ancestor_diff
end
end

private def self.group_by_direct_ancestor(result : PackageResult)
if result[:ancestors].size == 0
"#{result[:root].name}@#{result[:root].version}"
else
result[:ancestors].last[0].key
end
end

private def self.direct_ancestor_output(direct_ancestor : String, direct_ancestor_results : Array(PackageResult), *, config : Config::Why)
String.build do |result_str|
direct_ancestor_name, direct_ancestor_version = Utils::Various.parse_key(direct_ancestor)
result_str << "#{direct_ancestor_name.colorize.bold.cyan}#{"@#{direct_ancestor_version}"}"
unless config.short
result_str << NEW_LINE
result_str << direct_ancestor_results.map_with_index { |result, index|
ancestor_path_output(result, last_ancestor: index == direct_ancestor_results.size - 1)
}.join(NEW_LINE)
end
end
end

private def self.ancestor_path_output(result : PackageResult, *, last_ancestor : Bool = false)
root = result[:root]

ancestors_str = result[:ancestors].reverse!.map_with_index { |(ancestor, type), index|
name, version = Utils::Various.parse_key(ancestor.key)
"#{name.colorize.dim}#{"@#{version}".colorize.dim}#{type.dependency? ? "" : " (#{type.to_s.camelcase(lower: true)})".colorize.dim}"
}.join(" #{DEPENDS_ON_CHAR} ")

prefix = last_ancestor ? ANCESTOR_PATH_END_PREFIX_CHAR : ANCESTOR_PATH_PREFIX_CHAR

if ancestors_str.empty?
" #{prefix.colorize.cyan.bold.dim} #{root.name.colorize.magenta.bold}#{"@#{root.version}".colorize.dim} #{"(#{result[:type].to_s.camelcase(lower: true)})".colorize.dim}"
else
" #{prefix.colorize.cyan.bold.dim} #{ancestors_str} #{DEPENDS_ON_CHAR} #{"#{root.name}@#{root.version}".colorize.magenta}"
end
end
end
2 changes: 2 additions & 0 deletions src/constants.cr
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
module Zap
NEW_LINE = '\n'

enum ErrorCodes : Int32
EARLY_EXIT = 1
INSTALL_COMMAND_FAILED
Expand Down
2 changes: 1 addition & 1 deletion src/ext/option_parser.cr
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
class OptionParser
private def append_flag(flag, description, flag_formatter : String -> String)
indent = " " * 37
description = description.gsub("\n", "\n#{indent}")
description = description.gsub('\n', "\n#{indent}")
if flag.size >= 33
@flags << " #{flag_formatter.call(flag)}\n#{indent}#{description}"
else
Expand Down
48 changes: 24 additions & 24 deletions src/reporters/interactive.cr
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ class Zap::Reporter::Interactive < Zap::Reporter
@update_channel.close
if @written
@out.flush
@out.print "\n"
@out.print NEW_LINE
end
@written = false
@lines.set(0)
Expand All @@ -117,39 +117,39 @@ class Zap::Reporter::Interactive < Zap::Reporter

def info(str : String)
@lock.synchronize do
@out << %(#{str.colorize(:blue)}) << "\n"
@out << %(#{str.colorize(:blue)}) << NEW_LINE
end
end

def warning(error : Exception, location : String? = "")
@lock.synchronize do
@out << header("⚠️", "Warning", :yellow) + location
@out << "\n"
@out << NEW_LINE
@out << "\n#{error.message}".colorize.yellow
@out << "\n"
Zap::Log.debug { error.backtrace?.try &.map { |line| "\t#{line}" }.join("\n").colorize.yellow }
@out << NEW_LINE
Zap::Log.debug { error.backtrace?.try &.map { |line| "\t#{line}" }.join(NEW_LINE).colorize.yellow }
end
end

def error(error : Exception, location : String? = "")
@lock.synchronize do
@out << "\n"
@out << header("", "Error(s):", :red) + location << "\n" << "\n"
@out << "#{error.message.try &.split("\n").join("\n ")}" << "\n"
@out << "\n"
Zap::Log.debug { error.backtrace.map { |line| "\t#{line}" }.join("\n").colorize.red }
@out << NEW_LINE
@out << header("", "Error(s):", :red) + location << NEW_LINE << NEW_LINE
@out << "#{error.message.try &.split(NEW_LINE).join("\n ")}" << NEW_LINE
@out << NEW_LINE
Zap::Log.debug { error.backtrace.map { |line| "\t#{line}" }.join(NEW_LINE).colorize.red }
end
end

def errors(errors : Array({Exception, String}))
@lock.synchronize do
@out << "\n"
@out << header("", "Error(s):", :red) << "\n" << "\n"
@out << NEW_LINE
@out << header("", "Error(s):", :red) << NEW_LINE << NEW_LINE
errors.each do |(error, message)|
@out << "#{message.try &.split("\n").join("\n ")}" << "\n"
Zap::Log.debug { error.backtrace.map { |line| "\t#{line}" }.join("\n").colorize.red }
@out << "#{message.try &.split(NEW_LINE).join("\n ")}" << NEW_LINE
Zap::Log.debug { error.backtrace.map { |line| "\t#{line}" }.join(NEW_LINE).colorize.red }
end
@out << "\n"
@out << NEW_LINE
end
end

Expand All @@ -171,7 +171,7 @@ class Zap::Reporter::Interactive < Zap::Reporter
@io_lock.synchronize do
@out << @cursor.clear_lines(@lines.get, :up)
@out << String.new(bytes)
@out << "\n"
@out << NEW_LINE
@out.flush
end
update
Expand All @@ -181,7 +181,7 @@ class Zap::Reporter::Interactive < Zap::Reporter
@io_lock.synchronize do
@out << @cursor.clear_lines(@lines.get, :up)
@out << str
@out << "\n"
@out << NEW_LINE
@out.flush
end
update
Expand Down Expand Up @@ -213,13 +213,13 @@ class Zap::Reporter::Interactive < Zap::Reporter
str << %([#{@resolved_packages.get}/#{@resolving_packages.get}])
@lines.set(1)
if (downloading = @downloading_packages.get) > 0
str << "\n"
str << NEW_LINE
str << downloading_header
str << %([#{@downloaded_packages.get}/#{downloading}])
@lines.add(1)
end
if (packing = @packing_packages.get) > 0
str << "\n"
str << NEW_LINE
str << packing_header
str << %([#{@packed_packages.get}/#{packing}])
@lines.add(1)
Expand Down Expand Up @@ -274,7 +274,7 @@ class Zap::Reporter::Interactive < Zap::Reporter
@io_lock.synchronize do
if install_config.print_logs && @logs.size > 0
@out << header("📝", "Logs", :blue)
@out << "\n"
@out << NEW_LINE
separator = "\n".colorize(:default)
@out << separator
@out << @logs.join(separator)
Expand All @@ -284,7 +284,7 @@ class Zap::Reporter::Interactive < Zap::Reporter
# print missing peers
if unmet_peers && !unmet_peers.empty?
@out << header("❗️", "Unmet Peers", :light_red)
@out << "\n"
@out << NEW_LINE
separator = "\n".colorize(:red)
@out << separator
@out << unmet_peers.to_a.flat_map { |name, versions|
Expand All @@ -302,7 +302,7 @@ class Zap::Reporter::Interactive < Zap::Reporter
if install_version
%("#{name}@#{install_version}")
else
incompatible_versions << {name, versions.map { |v, _| " #{v}" }.join("\n")}
incompatible_versions << {name, versions.map { |v, _| " #{v}" }.join(NEW_LINE)}
nil
end
end.compact!
Expand Down Expand Up @@ -342,7 +342,7 @@ class Zap::Reporter::Interactive < Zap::Reporter
@out << " #{"".colorize.red.bold} #{pkg_key}\n"
end
end
@out << "\n"
@out << NEW_LINE
end

@out << header("👌", "Done!", :green)
Expand All @@ -352,7 +352,7 @@ class Zap::Reporter::Interactive < Zap::Reporter
if memory
@out << "total memory allocated #{memory.humanize}B".colorize.dim
end
@out << "\n"
@out << NEW_LINE
end
end

Expand Down
2 changes: 1 addition & 1 deletion src/reporters/reporter.cr
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ abstract class Zap::Reporter
end

class ReporterFormattedAppendPipe < IO
def initialize(@reporter : Reporter, @separator = "\n", @prefix = " ")
def initialize(@reporter : Reporter, @separator = NEW_LINE, @prefix = " ")
@separator_and_prefix = @separator + @prefix
end

Expand Down
Loading

0 comments on commit 6ba1119

Please sign in to comment.