Skip to content

Instantly share code, notes, and snippets.

@japhb
Created May 15, 2024 04:48
Show Gist options
  • Save japhb/8dc8046b5718313a1629801d1fe3279b to your computer and use it in GitHub Desktop.
Save japhb/8dc8046b5718313a1629801d1fe3279b to your computer and use it in GitHub Desktop.
Demonstration of progress bar alternating with terminal output (synchronous version)
#!/usr/bin/env raku
use v6.d;
use File::Find; # To support recursive repo searching
use Terminal::ANSIColor; # Or don't use colored(), or hardcode SGR codes
use Text::MiscUtils::Layout; # Or change duospace-width to .chars
#| Summarize status and problems in many git checkouts
sub MAIN(
Str:D $dir = %*ENV<HOME> ~ '/foss/git', #= Parent dir containing all checkouts
Bool:D :$progress = True, #= Show live progress bar
Bool:D :$recurse = True, #= Recurse into deeper directories
Bool:D :$fetch = False, #= Fetch remotes before checking status
) {
# Width of progress bar itself (not including showing percentage at end)
my $bar-width = 50;
# Find all relevant checkouts, and exit if none
my $exclude = *.basename eq '.git' | '.github' | '.precomp';
my @checkouts = $recurse ?? find(:$dir, :$exclude, type => 'dir')
.grep(*.child('.git').d).sort
!! dir($dir, test => { "$dir/$_/.git".IO.d }).sort;
my $dir-width = @checkouts.map(*.relative($dir).&duospace-width).max;
my $count = +@checkouts;
unless $count {
note "No checkouts found in directory '$dir'";
exit 1;
}
#| Print a single table row
my sub print-row($first, *@rest) {
printf "%-{$dir-width}s %s\n", $first, join "\t", @rest;
}
#| Display a progress bar
my sub show-progress-bar(Real:D $delta = 0, Str:D :$note = '') {
# Skip unless showing progress bar
return unless $progress;
# Unicode partial blocks and color config
constant @partial = « '' ▏ ▎ ▍ ▌ ▋ ▊ ▉ »;
constant $fg-color = 'blue';
constant $bg-color = 'red';
# Track overall progress
state $current = 0;
$current += $delta;
# Calculate completion fraction
my $fraction = $current / $count;
my $percent = $fraction * 100;
my $cells = $fraction * $bar-width;
# Join bar segments, Unicode 1.1 "smooth" version
my $partial = @partial[floor(@partial * ($cells - floor($cells)))];
my $bar = '█' x floor($cells)
~ $partial
~ ' ' x ($bar-width - floor($cells) - $partial.chars);
# Add color to Unicode 1.1 version if desired
$bar = colored($bar, "$fg-color on_$bg-color");
# # Join bar segments, colored ASCII version
# my $bar = colored(' ' x floor($cells), 'on_' ~ $fg-color)
# ~ colored(' ' x ($bar-width - floor($cells)), 'on_' ~ $bg-color);
# # Join bar segments, *UN*colored ASCII version
# my $bar = '=' x floor($cells)
# ~ ' ' x ($bar-width - floor($cells));
printf "\r%s %.1f%%%s", $bar, $percent, $note;
}
#| Hide the progress bar temporarily
my sub hide-progress-bar() {
# Skip unless showing progress bar
return unless $progress;
# General ANSI/VT CSI commands: Clear full line, go to start of line
print "\e[2K" ~ "\e[1G";
# # Basic ASCII: Carriage Return, space over progress to clear, Carriage Return
# # display width = bar + (percent display) + (longest note)
# my $width = $bar-width + 7 + 10;
# print "\r" ~ ' ' x $width ~ "\r";
}
# Print table header
print-row '', 'Needs', 'HTTP', 'Needs', 'Needs', 'On main', 'Extra', 'Pending', 'Other';
print-row '', 'origin', 'origin', 'update', 'push', 'branch', 'files', 'changes', 'issues';
print-row '', |('-------' xx 8);
# Iterate over checkouts showing progress and results
show-progress-bar;
my $subtasks = 3;
my \Δ = 1 / $subtasks;
for @checkouts {
# Check git remotes for missing origin or legacy HTTP GitHub origin
my $proc = run < git remote -v >, :cwd($_), :out;
my @lines = $proc.out.slurp(:close).lines;
my $has-origin = @lines.grep(/^origin/);
my $http-origin = @lines.grep(/^'origin' \s+ 'http' 's'? '://github.com/'/);
show-progress-bar(Δ);
# Fetch updates if requested and remotes can be safely fetched
if $fetch && $has-origin && !$http-origin {
show-progress-bar(note => ' Fetching');
$proc = run < git fetch --all --tags --quiet >, :cwd($_), :err;
if $proc.err.slurp(:close) -> $errors {
hide-progress-bar;
my $error = "\n"
~ colored("ERROR in {.relative($dir)}:", 'bold red')
~ "\n"
~ colored($errors.indent(4), 'red');
put $error;
}
}
show-progress-bar(Δ, note => '');
# Examine git status for common problems
$proc = run < git status >, :cwd($_), :out;
@lines = $proc.out.slurp(:close).lines;
my $on-main = @lines.grep(/^'On branch ' ['main'|'master']/);
my $needs-push = @lines.grep(/^'Your branch is ahead of'/);
my $up-to-date = @lines.grep(/^'Your branch is up' . 'to' . 'date with '/);
my $clean = @lines.grep(/^'nothing to commit, working ' ['directory'|'tree'] ' clean'/);
my $untracked = @lines.grep(/^'Untracked files:'/);
my $unstaged = @lines.grep(/^'Changes not staged for commit:'/);
show-progress-bar(Δ);
# Hide progress bar and print a new table row
hide-progress-bar;
print-row .relative($dir),
($has-origin ?? '✓' !! 'ORIGIN'),
($http-origin ?? 'HTTP' !! '✓'),
($up-to-date || !$has-origin || $needs-push ||
$clean && !$on-main ?? '✓' !! 'UPDATE'),
($needs-push ?? 'PUSH' !! '✓'),
($on-main ?? '✓' !! 'BRANCH'),
($untracked ?? 'EXTRA' !! '✓'),
($unstaged ?? 'CHANGES' !! '✓'),
(!$clean && !$untracked && !$unstaged ?? 'OTHER' !! '✓');
}
# Show finished progress bar for a moment before hiding it and exiting
show-progress-bar;
sleep .1;
hide-progress-bar;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment