Created
May 15, 2024 04:48
-
-
Save japhb/8dc8046b5718313a1629801d1fe3279b to your computer and use it in GitHub Desktop.
Demonstration of progress bar alternating with terminal output (synchronous version)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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