This is PerlMonks "Mobile"

Beefy Boxes and Bandwidth Generously Provided by pair Networks
P is for Practical
 
PerlMonks  

When doing some tests for "Emoji can be hard to see on the command line", I typed in an incorrect character for a Unicode® code point and was presented with a clock face. That reminded me of something I've been meaning to do for quite some time, so here it is.

The basic idea is to have a progress spinner that is a little more visually appealing than the usual text versions which cycle through "| / - \".

There are two versions: one with clock faces and one with phases of the moon. I've provided all of the code points, so that's one job anyone wanting to use this doesn't need to do. The code points for the clock faces are not sequential, so I've provided the order from 12:00 to 11:30; again, another fiddly job taken care of. The logic is simple and probably well-known to many; but, if not, that's done as well.

I've specified 'use 5.018;'. All of the characters were introduced in Unicode® v6.0 (determined via Unicode::UCD::charprops_all()). You may actually get away with 'use 5.014;'. I chose 5.18 based on the deltas (my emphasis throughout):

The following script is barebones and is really only intended as an example demo. Anyone wishing to use this will likely want additional output text — e.g. percentages, "done X of Y", and the like — so I saw no point in trying to guess such requirements.

#!/usr/bin/env perl use 5.018; use warnings; use open qw{:std :encoding(UTF-8)}; use Time::HiRes 'usleep'; { my @code_points = qw{ 1f55b 1f567 1f550 1f55c 1f551 1f55d 1f552 1f55e 1f553 1f55f 1f554 1f560 1f555 1f561 1f556 1f562 1f557 1f563 1f558 1f564 1f559 1f565 1f55a 1f566 }; my @chars = map chr hex, @code_points; my $total = @chars; my $index = $total; for (1 .. 50) { local $| = 1; $index %= $total; print "\b\b", $chars[$index++]; usleep 250_000; } print "\n"; } { my @code_points = qw{ 1f311 1f312 1f313 1f314 1f315 1f316 1f317 1f318 }; my @chars = map chr hex, @code_points; my $total = @chars; my $index = $total; for (1 .. 25) { local $| = 1; $index %= $total; print "\b\b", $chars[$index++]; usleep 500_000; } print "\n"; }

— Ken

Replies are listed 'Best First'.
Re: Emoji Progress Spinners
by Tux (Canon) on Feb 08, 2021 at 09:10 UTC

    The moon-phase is cool. Personally I don't like the clock version.

    Some more variations to play with:

    use 5.18.1; use warnings; use Time::HiRes "usleep"; binmode STDOUT, ":encoding(utf-8)"; $| = 1; for ([ 0x1f55b, 0x1f567, 0x1f550, 0x1f55c, 0x1f551, 0x1f55d, 0x1f552, +0x1f55e, 0x1f553, 0x1f55f, 0x1f554, 0x1f560, 0x1f555, 0x1f561, 0x1f556, +0x1f562, 0x1f557, 0x1f563, 0x1f558, 0x1f564, 0x1f559, 0x1f565, 0x1f55a, +0x1f566 ], [ 0x1f311 .. 0x1f318 ], [ 20, 0x2581..0x2588, 0x2588 .. 0x258f ], [ 0x2596, 0x2598, 0x259d, 0x2597 +], [ 0x2596, 0x258c, 0x2598, 0x2580, 0x259d, 0x2590, 0x2597, 0x2584 +], [ 0x259c, 0x259f, 0x2599, 0x259b ], [ 0x2b14, 0x25e8, 0x25ea, 0x2b13, 0x2b15, 0x25e7, 0x25e9, 0x2b12 +], [ 0x25f1, 0x25f0, 0x25f3, 0x25f2 ], [ 0x25d3, 0x25d1, 0x25d2, 0x25d0 ], [ 0x25f5, 0x25f4, 0x25f7, 0x25f6 ], [ 0x2b63, 0x2b69, 0x2b64, 0x2b66, 0x2b61, 0x2b67, 0x2b62, 0x2b68 +], ) { my @chars = map { chr } @$_; for (0 .. 50) { print " ", $chars[$_ % @chars], "\r"; usleep 250_000; } print "\n"; }

    Enjoy, Have FUN! H.Merijn

      G'day Tux,

      ++ Nice variations.

      My favourite was [ 0x2b14, 0x25e8, 0x25ea, 0x2b13, 0x2b15, 0x25e7, 0x25e9, 0x2b12 ]:

      ⬔◨◪⬓⬕◧◩⬒
      

      — Ken

Re: Emoji Progress Spinners
by parv (Parson) on Feb 07, 2021 at 07:27 UTC

    Thanks, Ken.

    On MS Windows 10, in PowerShell 7.0.2 command window (not cmd.exe), I see shit via Strawberry Perl ... well not quite. Its properties show "Unicode65001 UTF-8" codepage, with no obvious way to change that. Executing "bash.exe" did not change the behaviour much via WSL perl.

    Then one way or another I came upon Windows Terminal in search of better Unicode support. PowerShell output was still crappy as before. But after executing "bash.exe", I could see proper clock & moon cycles. Finally!

      Meanwhile, if I need Unicode output on a Windows console, I put the following near the top of the script:
      use Win32::Console; my $codepage = Win32::Console::OutputCP(); Win32::Console::OutputCP(65000); binmode STDOUT, ':encoding(cp65000)';
      AFAIK, these settings are per-process, otherwise I'd change this into a BEGIN block with a corresponding END block - that's why I saved the $codepage, you can discard this line if you don't need to restore it within the script.

        Thank you for the clue.

      G'day parv,

      Following the link you provided, I see quite a fair way down the page, where it has an old vs. new comparison, it says about the "old":

      "... to maintain backward compatibility, we have been unable to add ... unicode text, and emoji."

      That possibly explains why your earlier attempts had problems. Also, if I recall correctly, MSWin uses UTF-16 natively; so that's just another fly in the ointment.

      I've never been a big fan of cmd.exe; I wasn't overly impressed with PowerShell when I first encountered it a couple of years ago; this Windows Terminal looks a bit more promising and I may try it out at some point — I'm not rushing to do that, but I did at least bookmark that link. I also have Win10, but I use Cygwin on top of that, so these MS shell issues are somewhat moot.

      Anyway, I'm pleased to see you finally got it working.

      — Ken

      On my Windows 10 (Version 2004, although I was quite sure it were 20H2) if I chcp 65001, it looks OK.

      I'm calling Strawberry Perl via its "portableshell.bat" and then call powershell on top of it, but I assume chcp is more or less independent from the way it is invoked.

        soonix, that is good and surprising to read.

        Version of Windows 10 Home I have is 20H2, OS build 19042.746. "Use Legacy console" option has been turned off. My experiment in 5 kinds of console windows ...

        Powershell 7.0.2 (was listed as "PowerShell 7-x64" in "Control Panel: Programs and Feature")
        ran chcp, then portableshell.bat, perl.exe; printed was ...
        ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒòº
        ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒ≡ƒîæ
        
        Cmd
        an "improvement" is that cursor stays in place and "?" is displayed for both clock and moon.
        Windows Terminal; chosen from "Tasks" menu after right click on the icon ...
        Powershell (icon with black background)
        same as before
        Cmd
        (ran only chcp and perl) success with Strawberry Perl, not needed to use WSL.
        Windows PowerShell (icon with blue background)
        (ran only chcp and perl) success with Strawberry Perl

        Nested dl parsing seems to be busted here.

Re: Emoji Progress Spinners
by hippo (Bishop) on Feb 07, 2021 at 12:28 UTC

    While the code runs for me and I do use unicode-enabled terminals for pretty much everything, the problem I encounter is that the fonts don't necessarily cover the codepoints. Most of the time I use urxvt256c with xft:Hack-Regular and it has none of these characters. The default font in terminology (which is DejaVu Sans Mono (Book)*) does have all of the clockface characters but only about half (!) of the moon phase ones.

    There is probably no way to be able to tell reliably from within the script whether these codepoints will be rendered correctly by the terminal so there's no fall-back option. While this is a cool demo, I don't think it can be used universally just yet.

    * I can't recall if this was the default when it was installed or something I configured at the time. It was years ago :-)


    🦛

      G'day hippo,

      As you can see, I made some serious attempts to choose the best Perl version. There are, of course, other considerations such as platforms and available fonts; for instance, check parv's shell problems.

      "There is probably no way to be able to tell reliably from within the script whether these codepoints will be rendered correctly by the terminal ..."

      As an absolute statement, I would have to agree; however, there are ways to mitigate this. In a recent module (Tk::Aux::Font) that I wrote — purely for my own use — I had this subroutine:

      sub _best_family { my ($mw, $type) = @_; state $families = { sans => ['Verdana', 'DejaVu Sans', 'Helvetica'], serif => ['Times New Roman', 'DejaVu Serif', 'Times'], mono => [ 'Menlo', 'Andale Mono', 'Lucida Console', 'Consolas', 'Courier New', 'Courier' ], emoji => ['Times New Roman', 'DejaVu Serif', 'Times'], }; my $actual_family; for my $family ($families->{$type}->@*) { my $test_font = $mw->fontCreate(-family => $family); $actual_family = $mw->fontActual($test_font, '-family'); last if $actual_family eq $family; } return $actual_family; }

      Whilst developing, I tweaked this multiple times, especially with respect to the actual font order for various families. My overall findings were that "sans" and "mono" failed to produce many glyphs; "serif" and "emoji" (in this instance, the same lists) rendered many more. On my Win10/Cygwin, "Times New Roman" was the best; "DejaVu Serif" came a very close second (it seemed to render glyphs a pixel or two below the optimal position); "Times" was OK but resolution was poor and I'd rank it as a poor third cousin.

      Based on all of this, I'd recommend that you render with "DejaVu Serif", instead of "DejaVu Sans".

      Please bear in mind that was, as stated, intended to be "a little more visually appealing"; it solves no bugs nor improves any optimisations. It's possibly a nice-to-have but never essential.

      If it's of any use, here's a relevant extract from my local CSS:

      :root { --font-pref-sans-serif: "Verdana"; --font-pref-serif: "Times New Roman"; --font-pref-monospace: "Menlo", "Andale Mono", "Lucida Console", +"Courier New"; --font-manuscript-base: "Junicode"; --font-alchemy-base: "Newton Sans Regular"; --font-emoji-base: "Apple Color Emoji", "Segoe UI Emoji", "EmojiOne", "AndroidEmoji", "emojidex", "fxemoji", "NotoColorEmoji", "Twemoji"; --font-symbol-base: "Symbola"; } :root { --font-std-sans-serif: var(--font-pref-sans-serif), sans-serif; --font-std-serif: var(--font-pref-serif), serif; --font-std-monospace: var(--font-pref-monospace), monospace; --font-std-cursive: cursive; --font-std-fantasy: fantasy; } :root { --font-manuscript: var(--font-manuscript-base), var(--font-std-serif); --font-alchemy: var(--font-alchemy-base), var(--font-std-sans-serif); --font-emoji: var(--font-emoji-base), var(--font-std-monospace), var(--font-std-sans-serif); --font-symbol: var(--font-symbol-base), var(--font-std-monospace), var(--font-std-sans-serif); --font-emoji-or-symbol: var(--font-emoji-base), var(--font-std-monospace), var(--font-std-sans-serif); /* var(--font-symbol-base); */ --font-unicode-raw: var(--font-pref-serif), var(--font-symbol-base), serif, var(--font-std-sans-serif), var(--font-std-monospace), var(--font-std-cursive), var(--font-std-fantasy); }

      — Ken

        Based on all of this, I'd recommend that you render with "DejaVu Serif", instead of "DejaVu Sans".

        Having just tried that I regret to report that it has made no difference. There's also no real mono DejaVu Serif available so the terminal just looks awful too. :-)

        It will doubtless all work fine when I get around to upgrading.


        🦛

Re: Emoji Progress Spinners
by Your Mother (Archbishop) on Feb 07, 2021 at 04:37 UTC

    Ha! Really fun. Runs fine in a Mac Terminal.

      G'day Your Mother,

      Glad you liked it.

      Apple's emoji glyph rendering is about the best I've seen. I expect the output looked a lot better to you than what I'm seeing on Win10/Cygwin.

      — Ken

Re: Emoji Progress Spinners
by pme (Monsignor) on Feb 07, 2021 at 13:46 UTC
    Hi Monks,

    Can someone tell me where else this function chaining EXPR (chr hex) can be used in perl? Thanks.

    my @chars = map chr hex, @code_points;
      Can someone tell me where else this function chaining EXPR (chr hex) can be used in perl?

      It's just a combination of function calls and map:

      $ perl -MO=Deparse,-p -e 'map chr hex, @code_points' map(chr(hex($_)), @code_points);

      The behavior of chr and hex can be achieved with the (_) prototype, though prototypes are one of those "only use this if you know what you are doing" kind of thing because they change how Perl code is actually parsed (see also).

      Parentheses can be omitted in calls to subs which the compiler has already seen.

      $ perl -wMstrict -le 'sub foo {} foo "bar"' # ok $ perl -wMstrict -le 'sub foo {} foo("bar")' # ok $ perl -wMstrict -le 'foo("bar"); sub foo {}' # ok $ perl -wMstrict -le 'foo "bar"; sub foo {}' # fails String found where operator expected at -e line 1, near "foo "bar"" (Do you need to predeclare foo?) syntax error at -e line 1, near "foo "bar"" Execution of -e aborted due to compilation errors.

      The only thing special here is map and grep, which have as an alternative to their map {...} ... and grep {...} ... forms the map ..., ... and grep ..., ... forms. The former two can be emulated using prototypes (e.g. (&@)), while the latter two can't.

        Parentheses can be omitted in calls to subs which the compiler has already seen.

        And to named operators such as hex and chr.

        Seeking work! You can reach me at ikegami@adaelis.com

      G'day pme,

      Thankyou for your excellent question. Those of us who've been coding Perl for a quarter of a century or more, may often rely upon muscle memory rather than using any actual brain cells. When I start a new script, I often find that I've witten

      #!/usr/bin/env perl use strict; use warnings;

      before I've even thought about the problem.

      Any Perl function that has function LIST can be expanded using function function-returning-list LIST. This is pretty much what 🦛 says.

      Many commands allow the use of $_, check the documentation.

      Hopefully, combined with other responses, that answers your question. Of course, if anything is still unclear, ask for further clarification.

      — Ken

        Thank you all for your answers. Meanwhile I found answer myself to my question.
        @a2 = map chr hex, @a1;
        can be written as
        @a2 = map chr(hex()), @a1; # or @a2 = map chr(hex($_)), @a1;
        And the original syntax can be used in a simple assignment statement like this.
        $x = char hex 41; # which means $x = 'A';

      Any function (or sub) which returns a value (scalar, list, ref, whatever) can be acted upon by another function.


      🦛