1. 24
    hg prompt design vcs

This is a text post rather than a direct link because I want to talk about hg prompt’s optional prefix/postfix syntax. Links at the bottom.

So, hg prompt is a Mercurial extension by Steve Losh that lets you format all Mercurial-related info in a single command like this, so you can use the output in your prompt:

$ hg prompt "{on {bookmark}}{ at {rev}}{ ({tags})}{{status|modified|unknown}}"
on main at 1471 (tip)!?

The part I want to discuss here is the syntax for the placeholder/format string/string interpolation, such as

{on {bookmark}}

That format string means:

  • if there is an active bookmark (Git users: think ‘active branch [pointer]’), then:
    • first print “on “,
    • then print the bookmark’s name
    • then print nothing.
  • If there is no active bookmark, print nothing.

The innovation is here is the first-class syntax for “use this prefix/suffix, but omit them if the placeholder turns out to be empty”:

 {prefix{placeholder}suffix}

In every other formatting DSL I know of, you’d have to separately make the prefix and the suffix conditional, something like this (on multiple lines for clarity):

{if(placeholder, prefix, "")}
{placeholder}
{if(placeholder, suffix, "")}

So! If you’re ever in a position to create a formatting DSL where some placeholders might be empty, this syntax is a wonderful way for users to specify parentheses or spaces that must disappear if the placeholder is empty.

Links:

(The design tag on this story is because that is the de facto UI/UX tag. Furthermore, I am of the opinion we need a UI/UX tag.)

  1.  

  2. 1

    Doesn’t groovy formatting work the same way? I remember something along those lines when using Filebot title formatting.

    1. 1

      Apparently?!! It seems Groovy placeholders are formed from an expression inside braces; and the expression can look like {title} but also like {"$title"} or {"this is the $title"}; and if the value title is undefined the entire placeholder {...} evaluates to the empty string. Which in the last example means the prefix “this is the “ also gets suppressed.

      I’m not sure the Groovy devs themselves are aware that you can use this to get prefix-only-if-value-is-defined behaviour … the docs don’t mention it! But Okam from 2012 figured it out, and wrote it in the only post they ever made on the Filebot forum. I can’t say we’re lucky to have Google, but we’re lucky to have search engines.

    2. 1

      Whoa, I just realised I can user-define this (albeit with less elegant syntax) in the Mercurial formatting language by adding this to my .hgrc:

      [templatelalias]
      # Example usage: hg log -r . -T 'text{maybe("bookmark", " >>", bookmarks, "<<")} more text'
      maybe(mylabel, prefix, main, suffix) = '\
          {ifeq(main, "", "", label(mylabel, prefix))}\
          {label(mylabel, main)}\
          {ifeq(main, "", "", label(mylabel, suffix))}'
      
      1. 1

        Installation page seems to have a dead link to http://bitbucket.org/sjl/hg-prompt/

        1. 1

          Yep, that’s https://hg.stevelosh.com/hg-prompt/ nowadays, since Bitbucket dropped Mercurial support.

        2. 1

          Installation page seems to have a dead link to http://bitbucket.org/sjl/hg-prompt/

          Does it play nice with evolve?

          It’s just dying messily with…

          hg prompt 'test'
          Traceback (most recent call last):
            File "/usr/lib/python3/dist-packages/mercurial/extensions.py", line 251, in _runuisetup
              uisetup(ui)
            File "/usr/lib/python3/dist-packages/hgext3rd/evolve/exthelper.py", line 149, in finaluisetup
              c(ui)
            File "/usr/lib/python3/dist-packages/hgext3rd/evolve/__init__.py", line 1278, in _setuphelp
              help.helptable.sort()
          TypeError: '<' not supported between instances of 'str' and 'bytes'
          *** failed to set up extension evolve: '<' not supported between instances of 'str' and 'bytes'
          ** Unknown exception encountered with possibly-broken third-party extension prompt
          ** which supports versions unknown of Mercurial.
          ** Please disable prompt and try your action again.
          ** If that fixes the bug please report it to the extension author.
          ** Python 3.6.9 (default, Oct  8 2020, 12:12:24) [GCC 8.4.0]
          ** Mercurial Distributed SCM (version 5.5.2)
          ** Extensions loaded: graphlog, strip, mq, extdiff, rebase, convert, purge, eol, churn, hgk, record, prompt
          Traceback (most recent call last):
            File "/usr/bin/hg", line 43, in <module>
              dispatch.run()
            File "/usr/lib/python3/dist-packages/mercurial/dispatch.py", line 113, in run
              status = dispatch(req)
            File "/usr/lib/python3/dist-packages/mercurial/dispatch.py", line 303, in dispatch
              ret = _runcatch(req) or 0
            File "/usr/lib/python3/dist-packages/mercurial/dispatch.py", line 479, in _runcatch
              return _callcatch(ui, _runcatchfunc)
            File "/usr/lib/python3/dist-packages/mercurial/dispatch.py", line 488, in _callcatch
              return scmutil.callcatch(ui, func)
            File "/usr/lib/python3/dist-packages/mercurial/scmutil.py", line 152, in callcatch
              return func()
            File "/usr/lib/python3/dist-packages/mercurial/dispatch.py", line 469, in _runcatchfunc
              return _dispatch(req)
            File "/usr/lib/python3/dist-packages/mercurial/dispatch.py", line 1233, in _dispatch
              lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions
            File "/usr/lib/python3/dist-packages/mercurial/dispatch.py", line 917, in runcommand
              ret = _runcommand(ui, options, cmd, d)
            File "/usr/lib/python3/dist-packages/mercurial/dispatch.py", line 1244, in _runcommand
              return cmdfunc()
            File "/usr/lib/python3/dist-packages/mercurial/dispatch.py", line 1230, in <lambda>
              d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
            File "/usr/lib/python3/dist-packages/mercurial/util.py", line 1867, in check
              return func(*args, **kwargs)
            File "/usr/lib/python3/dist-packages/mercurial/util.py", line 1867, in check
              return func(*args, **kwargs)
            File "/usr/lib/python3/dist-packages/hgext/mq.py", line 4226, in mqcommand
              return orig(ui, repo, *args, **kwargs)
            File "/usr/lib/python3/dist-packages/mercurial/util.py", line 1867, in check
              return func(*args, **kwargs)
            File "/home/johnc/builds/hg-prompt/prompt.py", line 410, in prompt
              fs = re.sub(tag_start + tag + tag_end, repl, fs)
            File "/usr/lib/python3.6/re.py", line 191, in sub
              return _compile(pattern, flags).sub(repl, string, count)
          TypeError: cannot use a string pattern on a bytes-like object
          
          1. 2

            If you run Mercurial with Python 2, you won’t get this error. The error arises because (a) ‘…’ meant/means a bytes array in Python 2, (b) ‘…’ means an array of unicode code points in Python 3, and (c) hg-prompt dates from Mercurial’s Python 2 days. If the subject interests you, I can recommend this blog post that reflects on Mercurial’s 2-to-3 transition.

            The Python 3 fix would involve turning nearly every '...' string into a b'...' string (easy-ish). If you want to fix it for yourself,, that’s all. If you want to submit the patch upstream, you’d probably need to set up a testing harness to make sure it works in both 2 and 3 (not so easy). I see a two recent commits ([1], [2]) on this subject, so the interest is probably there.

            Hg-prompt plays nicely with evolve in the sense that it doesn’t crash, and treats hidden revs the same as non-hidden. It does not yet have keywords for topic-related info. I’ve got a draft patch for that, but I haven’t submitted it upstream yet. It defines keywords {topic} (the active topic), {revtopic|idx} (the changeset’s topic|stack number s0, s1, … even if another topic is active) and {stackidx} (stack id s0, s1, …, for the active topic’s stack).