.agents/
.cargo/
.codex/
.config/
.gnupg/
.local/
.vim/
bin/
ag
189 B
book-counter
112 B
cargo-flow
321 B
cargo-flow-check
422 B
colors
210 B
conky-sync-status
554 B
git-delete-merged
92 B
git-forest
13.1 KiB
haskell-tags
230 B
haskell-watch-build
125 B
haskell-watch-lint
151 B
kitty-reload
35 B
kitty-switch-theme
283 B
lock
288 B
mount-tank
78 B
pinentry
46 B
pkgbuild-dl
87 B
record-window
996 B
refresh-display
38 B
screenshot
127 B
screenshot-region
176 B
seedbox-mount
621 B
seedbox-status
129 B
set-background
90 B
share-sync
577 B
spectrwm-status
22 B
start-streaming
904 B
sync-up
661 B
todo
60 B
todo-widget
235 B
toggle-displays
1.5 KiB
tty-record
200 B
twitch
68 B
waybar-audio-output-selector
2.0 KiB
waybar-clipboard
847 B
waybar-screenshot
554 B
xmobar-brightness
191 B
xmobar-dropbox-status
152 B
xmobar-sync-status
304 B
etc/
images/
.dircolors
4.2 KiB
.fzy.zsh
1.1 KiB
.ghci
501 B
.gitconfig
2.2 KiB
.gitignore
0 B
.gitsigners
282 B
.ignore
136 B
.rgrc
174 B
.vimrc
12.7 KiB
.vimrc.lua
113 B
.wacom
260 B
bin/git-forest
raw
| 1 | #!/usr/bin/perl |
| 2 | # |
| 3 | # git-森林 |
| 4 | # text-based tree visualisation |
| 5 | # Copyright © Jan Engelhardt <jengelh [at] medozas de>, 2008 |
| 6 | # |
| 7 | # This program is free software; you can redistribute it and/or modify |
| 8 | # it under the terms of the GNU General Public License as published by |
| 9 | # the Free Software Foundation; either version 2 or 3 of the license. |
| 10 | # |
| 11 | use Getopt::Long; |
| 12 | use Git; |
| 13 | use strict; |
| 14 | use utf8; |
| 15 | use Encode qw(encode); |
| 16 | my $Repo = Git->repository($ENV{"GIT_DIR"} || "."); |
| 17 | my $Pretty_fmt = "format:%s"; |
| 18 | my $Reverse_order = 0; |
| 19 | my $Show_all = 0; |
| 20 | my $Show_rebase = 1; |
| 21 | my $Style = 1; |
| 22 | my $Subvine_depth = 2; |
| 23 | my $With_sha = 0; |
| 24 | my %Color = ( |
| 25 | "default" => "\e[0m", # ] |
| 26 | "at" => "\e[1;30m", # ] |
| 27 | "hhead" => "\e[1;31m", # ] |
| 28 | "head" => "\e[1;32m", # ] |
| 29 | "ref" => "\e[1;34m", # ] |
| 30 | "remote" => "\e[1;35m", # ] |
| 31 | "sha" => "\e[0;31m", # ] |
| 32 | "tag" => "\e[1;33m", # ] |
| 33 | "tree" => "\e[0;33m", # ] |
| 34 | ); |
| 35 | |
| 36 | &main(); |
| 37 | |
| 38 | sub main |
| 39 | { |
| 40 | &Getopt::Long::Configure(qw(bundling pass_through)); |
| 41 | &GetOptions( |
| 42 | "all" => \$Show_all, |
| 43 | "no-color" => sub { %Color = (); }, |
| 44 | "no-rebase" => sub { $Show_rebase = 0; }, |
| 45 | "a" => sub { $Pretty_fmt = "format:\e[1;30m(\e[0;32m%an\e[1;30m)\e[0m %s"; }, # ]]]] |
| 46 | "pretty=s" => \$Pretty_fmt, |
| 47 | "reverse" => \$Reverse_order, |
| 48 | "svdepth=i" => \$Subvine_depth, |
| 49 | "style=i" => \$Style, |
| 50 | "sha" => \$With_sha, |
| 51 | ); |
| 52 | ++$Subvine_depth; |
| 53 | if (substr($Pretty_fmt, 0, 7) ne "format:") { |
| 54 | die "If you use --pretty, it must be in the form of --pretty=format:"; |
| 55 | } |
| 56 | $Pretty_fmt = substr($Pretty_fmt, 7); |
| 57 | while ($Pretty_fmt =~ /\%./g) { |
| 58 | if ($& eq "\%b" || $& eq "\%n" || ($&.$') =~ /^\%x0a/i) { |
| 59 | die "Cannot use \%b, \%n or \%x0a in --pretty=format:"; |
| 60 | } |
| 61 | } |
| 62 | if ($Show_all) { |
| 63 | # |
| 64 | # Give --all back. And add HEAD to include commits |
| 65 | # in the rev list that belong to a detached HEAD. |
| 66 | # |
| 67 | unshift(@ARGV, "--all", "HEAD"); |
| 68 | } |
| 69 | if ($Reverse_order) { |
| 70 | tie(*STDOUT, "ReverseOutput"); |
| 71 | } |
| 72 | &process(); |
| 73 | if ($Reverse_order) { |
| 74 | untie *STDOUT; |
| 75 | } |
| 76 | } |
| 77 | |
| 78 | sub get_line_block |
| 79 | { |
| 80 | my($fh, $max) = @_; |
| 81 | |
| 82 | while (scalar(@h::ist) < $max) { |
| 83 | my $x; |
| 84 | |
| 85 | $x = <$fh>; |
| 86 | if (!defined($x)) { |
| 87 | last; |
| 88 | } |
| 89 | push(@h::ist, $x); |
| 90 | } |
| 91 | |
| 92 | my @ret = (shift @h::ist); |
| 93 | foreach (2..$max) { |
| 94 | push(@ret, $h::ist[$_-2]); |
| 95 | } |
| 96 | return @ret; |
| 97 | } |
| 98 | |
| 99 | sub process |
| 100 | { |
| 101 | my(@vine); |
| 102 | my $refs = &get_refs(); |
| 103 | my($fh, $fhc) = $Repo->command_output_pipe("log", "--date-order", |
| 104 | "--pretty=format:<%H><%h><%P>$Pretty_fmt", @ARGV); |
| 105 | |
| 106 | while (my($line, @next_sha) = get_line_block($fh, $Subvine_depth)) { |
| 107 | if (!defined($line)) { |
| 108 | last; |
| 109 | } |
| 110 | chomp $line; |
| 111 | my($sha, $mini_sha, $parents, $msg) = |
| 112 | ($line =~ /^<(.*?)><(.*?)><(.*?)>(.*)/s); |
| 113 | my @next_sha = map { ($_) = /^<(.*?)>/ } @next_sha; |
| 114 | my @parents = split(" ", $parents); |
| 115 | |
| 116 | &vine_branch(\@vine, $sha); |
| 117 | my $ra = &vine_commit(\@vine, $sha, \@parents); |
| 118 | |
| 119 | if (exists($refs->{$sha})) { |
| 120 | print &vis_post(&vis_commit($ra), |
| 121 | $Color{at}."m".$Color{default}); |
| 122 | &ref_print($refs->{$sha}); |
| 123 | } else { |
| 124 | print &vis_post(&vis_commit($ra, " ")); |
| 125 | } |
| 126 | if ($With_sha) { |
| 127 | print $msg, $Color{at}, encode('UTF-8', "──("), $Color{sha}, $mini_sha, |
| 128 | $Color{at}, ")", $Color{default}, "\n"; |
| 129 | } else { |
| 130 | print $msg, "\n"; |
| 131 | } |
| 132 | |
| 133 | &vine_merge(\@vine, $sha, \@next_sha, \@parents); |
| 134 | } |
| 135 | $Repo->command_close_pipe($fh, $fhc); |
| 136 | } |
| 137 | |
| 138 | sub get_refs |
| 139 | { |
| 140 | my($fh, $c) = $Repo->command_output_pipe("show-ref"); |
| 141 | my $ret = {}; |
| 142 | |
| 143 | while (defined(my $ln = <$fh>)) { |
| 144 | chomp $ln; |
| 145 | if (length($ln) == 0) { |
| 146 | next; |
| 147 | } |
| 148 | |
| 149 | my($sha, $name) = ($ln =~ /^(\S+)\s+(.*)/s); |
| 150 | if (!exists($ret->{$sha})) { |
| 151 | $ret->{$sha} = []; |
| 152 | } |
| 153 | push(@{$ret->{$sha}}, $name); |
| 154 | if ($name =~ m{^refs/tags/}) { |
| 155 | my $sub_sha = $Repo->command("log", "-1", |
| 156 | "--pretty=format:%H", $name); |
| 157 | chomp $sub_sha; |
| 158 | if ($sha ne $sub_sha) { |
| 159 | push(@{$ret->{$sub_sha}}, $name); |
| 160 | } |
| 161 | } |
| 162 | } |
| 163 | |
| 164 | $Repo->command_close_pipe($fh, $c); |
| 165 | |
| 166 | my $rebase = -e $Repo->repo_path()."/.dotest-merge/git-rebase-todo" && |
| 167 | $Show_rebase; |
| 168 | if ($rebase) { |
| 169 | if (open(my $act_fh, $Repo->repo_path(). |
| 170 | "/.dotest-merge/git-rebase-todo")) { |
| 171 | my($curr) = (<$act_fh> =~ /^\S+\s+(\S+)/); |
| 172 | $curr = $Repo->command("rev-parse", $curr); |
| 173 | chomp $curr; |
| 174 | unshift(@{$ret->{$curr}}, "rebase/cn"); |
| 175 | close $act_fh; |
| 176 | } |
| 177 | |
| 178 | chomp(my $up = $Repo->command("rev-parse", ".dotest-merge/upstream")); |
| 179 | chomp(my $onto = $Repo->command("rev-parse", ".dotest-merge/onto")); |
| 180 | chomp(my $old = $Repo->command("rev-parse", ".dotest-merge/head")); |
| 181 | unshift(@{$ret->{$up}}, "rebase/upstream"); |
| 182 | unshift(@{$ret->{$onto}}, "rebase/onto"); |
| 183 | unshift(@{$ret->{$old}}, "rebase/saved-HEAD"); |
| 184 | } |
| 185 | |
| 186 | my $head = $Repo->command("rev-parse", "HEAD"); |
| 187 | chomp $head; |
| 188 | if ($rebase) { |
| 189 | unshift(@{$ret->{$head}}, "rebase/new"); |
| 190 | } |
| 191 | unshift(@{$ret->{$head}}, "HEAD"); |
| 192 | |
| 193 | return $ret; |
| 194 | } |
| 195 | |
| 196 | # |
| 197 | # ref_print - print a ref with color |
| 198 | # @s: ref name |
| 199 | # |
| 200 | sub ref_print |
| 201 | { |
| 202 | foreach my $symbol (@{shift @_}) { |
| 203 | print $Color{at}, "["; |
| 204 | if ($symbol eq "HEAD" || $symbol =~ m{^rebase/}) { |
| 205 | print $Color{hhead}, $symbol; |
| 206 | } elsif ($symbol =~ m{^refs/(remotes/[^/]+)/(.*)}s) { |
| 207 | print $Color{remote}, $1, $Color{head}, "/$2"; |
| 208 | } elsif ($symbol =~ m{^refs/heads/(.*)}s) { |
| 209 | print $Color{head}, $1; |
| 210 | } elsif ($symbol =~ m{^refs/tags/(.*)}s) { |
| 211 | print $Color{tag}, $1; |
| 212 | } elsif ($symbol =~ m{^refs/(.*)}s) { |
| 213 | print $Color{ref}, $1; |
| 214 | } |
| 215 | print $Color{at}, encode('UTF-8', "]──"), $Color{default}; |
| 216 | } |
| 217 | } |
| 218 | |
| 219 | # |
| 220 | # vine_branch - |
| 221 | # @vine: column array containing the expected parent IDs |
| 222 | # @rev: commit ID |
| 223 | # |
| 224 | # Draws the branching vine matrix between a commit K and K^ (@rev). |
| 225 | # |
| 226 | sub vine_branch |
| 227 | { |
| 228 | my($vine, $rev) = @_; |
| 229 | my $idx; |
| 230 | |
| 231 | my($matched, $master) = (0, 0); |
| 232 | my $ret; |
| 233 | |
| 234 | # Transform array into string |
| 235 | for ($idx = 0; $idx <= $#$vine; ++$idx) { |
| 236 | if (!defined($vine->[$idx])) { |
| 237 | $ret .= " "; |
| 238 | next; |
| 239 | } elsif ($vine->[$idx] ne $rev) { |
| 240 | $ret .= "I"; |
| 241 | next; |
| 242 | } |
| 243 | if (!$master && $idx % 2 == 0) { |
| 244 | $ret .= "S"; |
| 245 | $master = 1; |
| 246 | } else { |
| 247 | $ret .= "s"; |
| 248 | $vine->[$idx] = undef; |
| 249 | } |
| 250 | ++$matched; |
| 251 | } |
| 252 | |
| 253 | if ($matched < 2) { |
| 254 | return; |
| 255 | } |
| 256 | |
| 257 | &remove_trailing_blanks($vine); |
| 258 | print &vis_post(&vis_fan($ret, "branch")), "\n"; |
| 259 | } |
| 260 | |
| 261 | # |
| 262 | # vine_commit - |
| 263 | # @vine: column array containing the expected IDs |
| 264 | # @rev: commit ID |
| 265 | # @parents: array of parent IDs |
| 266 | # |
| 267 | sub vine_commit |
| 268 | { |
| 269 | my($vine, $rev, $parents) = @_; |
| 270 | my $ret; |
| 271 | |
| 272 | for (my $i = 0; $i <= $#$vine; ++$i) { |
| 273 | if (!defined($vine->[$i])) { |
| 274 | $ret .= " "; |
| 275 | } elsif ($vine->[$i] eq $rev) { |
| 276 | $ret .= "C"; |
| 277 | } else { |
| 278 | $ret .= "I"; |
| 279 | } |
| 280 | } |
| 281 | |
| 282 | if ($ret !~ /C/) { |
| 283 | # Not having produced a C before means this is a tip |
| 284 | my $i; |
| 285 | for ($i = &round_down2($#$vine); $i >= 0; $i -= 2) { |
| 286 | if (substr($ret, $i, 1) eq " ") { |
| 287 | substr($ret, $i, 1) = "t"; |
| 288 | $vine->[$i] = $rev; |
| 289 | last; |
| 290 | } |
| 291 | } |
| 292 | if ($i < 0) { |
| 293 | if (scalar(@$vine) % 2 != 0) { |
| 294 | push(@$vine, undef); |
| 295 | $ret .= " "; |
| 296 | } |
| 297 | $ret .= "t"; |
| 298 | push(@$vine, $rev); |
| 299 | } |
| 300 | } |
| 301 | |
| 302 | &remove_trailing_blanks($vine); |
| 303 | |
| 304 | if (scalar(@$parents) == 0) { |
| 305 | # tree root |
| 306 | $ret =~ tr/C/r/; |
| 307 | } |
| 308 | |
| 309 | return $ret; |
| 310 | } |
| 311 | |
| 312 | # |
| 313 | # vine_merge - |
| 314 | # @vine: column array containing the expected parent IDs |
| 315 | # @rev: commit ID |
| 316 | # @next_rev: next commit ID in the revision list |
| 317 | # @parents: parent IDs of @rev |
| 318 | # |
| 319 | # Draws the merging vine matrix between a commit K (@rev) and K^ (@parents). |
| 320 | # |
| 321 | sub vine_merge |
| 322 | { |
| 323 | my($vine, $rev, $next_rev, $parents) = @_; |
| 324 | my $orig_vine = -1; |
| 325 | my @slot; |
| 326 | my($ret, $max); |
| 327 | |
| 328 | for (my $i = 0; $i <= $#$vine; ++$i) { |
| 329 | if ($vine->[$i] eq $rev) { |
| 330 | $orig_vine = $i; |
| 331 | last; |
| 332 | } |
| 333 | } |
| 334 | |
| 335 | if ($orig_vine == -1) { |
| 336 | die "vine_commit() did not add this vine."; |
| 337 | } |
| 338 | |
| 339 | if (scalar(@$parents) <= 1) { |
| 340 | # |
| 341 | # A single parent does not need a visual. Update and return. |
| 342 | # |
| 343 | $vine->[$orig_vine] = $parents->[0]; |
| 344 | &remove_trailing_blanks($vine); |
| 345 | return; |
| 346 | } |
| 347 | |
| 348 | # |
| 349 | # Put previously seen branches in the vine subcolumns |
| 350 | # Need to keep at least one parent for the slot algorithm below. |
| 351 | # |
| 352 | for (my $j = 0; $j <= $#$parents && $#$parents > 0; ++$j) { |
| 353 | for (my $idx = 0; $idx <= $#$vine; ++$idx) { |
| 354 | if ($vine->[$idx] ne $parents->[$j] || |
| 355 | !grep { my $z = $vine->[$idx]; /^\Q$z\E$/ } |
| 356 | @$next_rev) { |
| 357 | next; |
| 358 | } |
| 359 | if ($idx == $orig_vine) { |
| 360 | die "Should not really happen"; |
| 361 | } |
| 362 | if ($idx < $orig_vine) { |
| 363 | my $p = $idx + 1; |
| 364 | if (defined($vine->[$p])) { |
| 365 | $p = $idx - 1; |
| 366 | } |
| 367 | if (defined($vine->[$p])) { |
| 368 | last; |
| 369 | } |
| 370 | $vine->[$p] = $parents->[$j]; |
| 371 | str_expand(\$ret, $p + 1); |
| 372 | substr($ret, $p, 1) = "s"; |
| 373 | } else { |
| 374 | my $p = $idx - 1; |
| 375 | if (defined($vine->[$p]) || $p < 0) { |
| 376 | $p = $idx + 1; |
| 377 | } |
| 378 | if (defined($vine->[$p])) { |
| 379 | last; |
| 380 | } |
| 381 | $vine->[$p] = $parents->[$j]; |
| 382 | str_expand(\$ret, $p + 1); |
| 383 | substr($ret, $p, 1) = "s"; |
| 384 | } |
| 385 | splice(@$parents, $j, 1); |
| 386 | --$j; # outer loop |
| 387 | last; # inner loop |
| 388 | } |
| 389 | } |
| 390 | |
| 391 | # |
| 392 | # Find some good spots to split out into and record columns |
| 393 | # that will be used soon in the @slot list. |
| 394 | # |
| 395 | push(@slot, $orig_vine); |
| 396 | my $parent = 0; |
| 397 | |
| 398 | for (my $seeker = 2; $parent < $#$parents && |
| 399 | $seeker < 2 + $#$vine; ++$seeker) |
| 400 | { |
| 401 | my $idx = ($seeker % 2 == 0) ? -1 : 1; |
| 402 | $idx *= int($seeker / 2); |
| 403 | $idx *= 2; |
| 404 | $idx += $orig_vine; |
| 405 | |
| 406 | if ($idx >= 0 && $idx <= $#$vine && !defined($vine->[$idx])) { |
| 407 | push(@slot, $idx); |
| 408 | $vine->[$idx] = "0" x 40; |
| 409 | ++$parent; |
| 410 | } |
| 411 | } |
| 412 | for (my $idx = $orig_vine + 2; $parent < $#$parents; $idx += 2) { |
| 413 | if (!defined($vine->[$idx])) { |
| 414 | push(@slot, $idx); |
| 415 | ++$parent; |
| 416 | } |
| 417 | } |
| 418 | |
| 419 | if (scalar(@slot) != scalar(@$parents)) { |
| 420 | die "Serious internal problem"; |
| 421 | } |
| 422 | |
| 423 | @slot = sort { $a <=> $b } @slot; |
| 424 | $max = scalar(@$vine) + 2 * scalar(@slot); |
| 425 | |
| 426 | for (my $i = 0; $i < $max; ++$i) { |
| 427 | str_expand(\$ret, $i + 1); |
| 428 | if ($#slot >= 0 && $i == $slot[0]) { |
| 429 | shift @slot; |
| 430 | $vine->[$i] = shift @$parents; |
| 431 | substr($ret, $i, 1) = ($i == $orig_vine) ? "S" : "s"; |
| 432 | } elsif (substr($ret, $i, 1) eq "s") { |
| 433 | ; # keep existing fanouts |
| 434 | } elsif (defined($vine->[$i])) { |
| 435 | substr($ret, $i, 1) = "I"; |
| 436 | } else { |
| 437 | substr($ret, $i, 1) = " "; |
| 438 | } |
| 439 | } |
| 440 | |
| 441 | print &vis_post(&vis_fan($ret, "merge")), "\n"; |
| 442 | } |
| 443 | |
| 444 | # |
| 445 | # vis_* - transform control string into usable graphic |
| 446 | # |
| 447 | # To cut down on code, the three vine_* functions produce only a dumb, |
| 448 | # but already unambiguous, control string which needs some processing |
| 449 | # before it is ready for public display. |
| 450 | # |
| 451 | |
| 452 | sub vis_commit |
| 453 | { |
| 454 | my $s = shift @_; |
| 455 | my $f = shift @_; |
| 456 | $s =~ s/ +$//gs; |
| 457 | if (defined $f) { |
| 458 | $s .= $f; |
| 459 | } |
| 460 | return $s; |
| 461 | } |
| 462 | |
| 463 | sub vis_fan |
| 464 | { |
| 465 | my $s = shift @_; |
| 466 | my $b = shift(@_) eq "branch"; |
| 467 | |
| 468 | $s =~ s{s.*s}{ |
| 469 | $_ = $&; |
| 470 | $_ =~ tr/ I/DO/; |
| 471 | $_; |
| 472 | }ei; |
| 473 | |
| 474 | # Transform an ODODO.. sequence into a contiguous overpass. |
| 475 | $s =~ s{O[DO]+O}{"O" x length($&)}eg; |
| 476 | |
| 477 | # Do left/right edge transformation |
| 478 | $s =~ s{(s.*)S(.*s)}{&vis_fan3($1, $2)}es || |
| 479 | $s =~ s{(s.*)S}{&vis_fan2L($1)."B"}es || |
| 480 | $s =~ s{S(.*s)}{"A".&vis_fan2R($1)}es || |
| 481 | die "Should not come here"; |
| 482 | |
| 483 | if ($b) { |
| 484 | $s =~ tr/efg/xyz/; |
| 485 | } |
| 486 | |
| 487 | return $s; |
| 488 | } |
| 489 | |
| 490 | sub vis_fan2L |
| 491 | { |
| 492 | my $l = shift @_; |
| 493 | $l =~ s/^s/e/; |
| 494 | $l =~ s/s/f/g; |
| 495 | return $l; |
| 496 | } |
| 497 | |
| 498 | sub vis_fan2R |
| 499 | { |
| 500 | my $r = shift @_; |
| 501 | $r =~ s/s$/g/; |
| 502 | $r =~ s/s/f/g; |
| 503 | return $r; |
| 504 | } |
| 505 | |
| 506 | sub vis_fan3 |
| 507 | { |
| 508 | my($l, $r) = @_; |
| 509 | $l =~ s/^s/e/; |
| 510 | $l =~ s/s/f/g; |
| 511 | $r =~ s/s$/g/; |
| 512 | $r =~ s/s/f/g; |
| 513 | return "${l}K$r"; |
| 514 | } |
| 515 | |
| 516 | sub vis_xfrm |
| 517 | { |
| 518 | # A: branch to right |
| 519 | # B: branch to right |
| 520 | # C: commit |
| 521 | # D: |
| 522 | # e: merge visual left (╔) |
| 523 | # f: merge visual center (╦) |
| 524 | # g: merge visual right (╗) |
| 525 | # I: straight line (║) |
| 526 | # K: branch visual split (╬) |
| 527 | # m: single line (─) |
| 528 | # O: overpass (≡) |
| 529 | # r: root (╙) |
| 530 | # t: tip (╓) |
| 531 | # x: branch visual left (╚) |
| 532 | # y: branch visual center (╩) |
| 533 | # z: branch visual right (╝) |
| 534 | # *: filler |
| 535 | |
| 536 | my $s = shift @_; |
| 537 | my $spc = shift @_; |
| 538 | if ($spc) { |
| 539 | $s =~ s{[Ctr].*}{ |
| 540 | $_ = $&; |
| 541 | $_ =~ s{ }{\*}g; |
| 542 | $_; |
| 543 | }esg; |
| 544 | } |
| 545 | |
| 546 | if ($Reverse_order) { |
| 547 | $s =~ tr/efg.rt.xyz/xyz.tr.efg/; |
| 548 | } |
| 549 | |
| 550 | if ($Style == 1) { |
| 551 | $s =~ tr/ABCD.efg.IKO.mrt.xyz/├┤├─.┌┬┐.│┼≡.─└┌.└┴┘/; |
| 552 | } elsif ($Style == 2) { |
| 553 | $s =~ tr/ABCD.efg.IKO.mrt.xyz/╠╣╟═.╔╦╗.║╬═.─╙╓.╚╩╝/; |
| 554 | } elsif ($Style == 10) { |
| 555 | $s =~ tr/ABCD.efg.IKO.mrt.xyz/├┤├─.╭┬╮.│┼≡.─└┌.╰┴╯/; |
| 556 | } elsif ($Style == 15) { |
| 557 | $s =~ tr/ABCD.efg.IKO.mrt.xyz/┣┫┣━.┏┳┓.┃╋☰.━┗┏.┗┻┛/; |
| 558 | } |
| 559 | return $s; |
| 560 | } |
| 561 | |
| 562 | # |
| 563 | # vis_post - post-process/transform vine graphic |
| 564 | # Apply user-specific style transformation. |
| 565 | # |
| 566 | sub vis_post |
| 567 | { |
| 568 | my $s = shift @_; |
| 569 | my $f = shift @_; |
| 570 | |
| 571 | $s = vis_xfrm($s, defined $f); |
| 572 | $f =~ s/^([^\x1b]+)/vis_xfrm($1)/e; |
| 573 | $f =~ s/(\x1b.*?m)([^\x1b]+)/$1.vis_xfrm($2)/eg; |
| 574 | if (defined $f) { |
| 575 | $s =~ s/\*/$f/g; |
| 576 | $s =~ s{\Q$Color{default}\E}{$&.$Color{tree}}egs; |
| 577 | $s .= $f; |
| 578 | } |
| 579 | |
| 580 | return $Color{tree}, encode('UTF-8', $s), $Color{default}; |
| 581 | } |
| 582 | |
| 583 | sub remove_trailing_blanks |
| 584 | { |
| 585 | my $a = shift @_; |
| 586 | |
| 587 | while (scalar(@$a) > 0 && !defined($a->[$#$a])) { |
| 588 | pop(@$a); |
| 589 | } |
| 590 | } |
| 591 | |
| 592 | sub round_down2 |
| 593 | { |
| 594 | my $i = shift @_; |
| 595 | if ($i < 0) { |
| 596 | return $i; |
| 597 | } |
| 598 | return $i & ~1; |
| 599 | } |
| 600 | |
| 601 | sub str_expand |
| 602 | { |
| 603 | my $r = shift @_; |
| 604 | my $l = shift @_; |
| 605 | |
| 606 | if (length($$r) < $l) { |
| 607 | $$r .= " " x ($l - length($$r)); |
| 608 | } |
| 609 | } |
| 610 | |
| 611 | package ReverseOutput; |
| 612 | require Tie::Handle; |
| 613 | @ReverseOutput::ISA = qw(Tie::Handle); |
| 614 | |
| 615 | sub TIEHANDLE |
| 616 | { |
| 617 | my $class = shift @_; |
| 618 | my $self = {}; |
| 619 | |
| 620 | open($self->{fh}, ">&STDOUT"); |
| 621 | |
| 622 | return bless $self, $class; |
| 623 | } |
| 624 | |
| 625 | sub PRINT |
| 626 | { |
| 627 | my $self = shift @_; |
| 628 | my $fh = $self->{fh}; |
| 629 | |
| 630 | $self->{saved} .= join($\, @_); |
| 631 | } |
| 632 | |
| 633 | sub UNTIE |
| 634 | { |
| 635 | my $self = shift @_; |
| 636 | my $fh = $self->{fh}; |
| 637 | |
| 638 | print $fh join($/, reverse split(/\n/s, $self->{saved})); |
| 639 | print $fh "\n"; |
| 640 | undef $self->{saved}; |
| 641 | } |