On Wed, Aug 5, 2015 at 2:54 PM, Karthik Nayak <karthik....@gmail.com> wrote:
> Implement an `align` atom which will act as a modifier atom and align
> any string with or without an %(atom) appearing before a %(end) atom
> to the right, left or middle.

For someone not familiar with the evolution of this patch series,
"align any string with or without an %(atom)" is confusing and
distracting and seems to imply that something extra mysterious is
going on behind the scenes. Keeping it simple makes it easier to
understand:

    Add an `align` atom which left-, middle-, or right-aligns the
    content between %(align:...) and %(end).

> It is followed by `:<type>,<paddinglength>`, where the `<type>` is

'type' may not be the best name. Perhaps 'style' or 'position' or
something else would be better?

> either left, right or middle and `<paddinglength>` is the total length

'paddinglength' is misleading. You're really describing the container
width in which alignment takes places, so perhaps call it 'width' or
'fieldwidth' or something.

> of the padding to be performed. If the atom length is more than the
> padding length then no padding is performed. e.g. to pad a succeeding
> atom to the middle with a total padding size of 40 we can do a

It's odd to have alignment described in terms of "padding" and
"padding length", especially in the case of "center" alignment. It
might be better to rephrase the discussion in terms of field width or
such.

> --format="%(align:middle,40).."

I may have missed the discussion, but why was comma chosen as a
separator here, rather than, say, colon? For instance:

    %(align:middle:40)

> Add documentation and tests for the same.
>
> Signed-off-by: Karthik Nayak <karthik....@gmail.com>
> ---
> diff --git a/Documentation/git-for-each-ref.txt 
> b/Documentation/git-for-each-ref.txt
> index e49d578..d865f98 100644
> --- a/Documentation/git-for-each-ref.txt
> +++ b/Documentation/git-for-each-ref.txt
> @@ -127,6 +127,14 @@ color::
> +align::
> +       Align any string with or without %(atom) before the %(end)
> +       atom to the right, left or middle. Followed by

Ditto regarding "any string with or without %(atom)" being confusing
to someone not familiar with the evolution of this patch series.
Instead, perhaps:

    Left-, middle-, or right-align content between %(align:...)
    and %(end).

> +       `:<type>,<paddinglength>`, where the `<type>` is either left,
> +       right or middle and `<paddinglength>` is the total length of
> +       the padding to be performed. If the string length is more than
> +       the padding length then no padding is performed.

Ditto regarding above observations about 'type', 'paddinglength', and "padding".

> diff --git a/ref-filter.c b/ref-filter.c
> index 2c074a1..d123299 100644
> --- a/ref-filter.c
> +++ b/ref-filter.c
> @@ -620,7 +623,7 @@ static void populate_value(struct ref_array_item *ref)
>                 const char *name = used_atom[i];
>                 struct atom_value *v = &ref->value[i];
>                 int deref = 0;
> -               const char *refname;
> +               const char *refname = NULL;

What is this change about?

>                 const char *formatp;
>                 struct branch *branch = NULL;
>
> @@ -687,6 +690,29 @@ static void populate_value(struct ref_array_item *ref)
>                         else
>                                 v->s = " ";
>                         continue;
> +               } else if (starts_with(name, "align:")) {
> +                       const char *valp = NULL;

Unnecessary NULL assignment.

> +                       struct align *align = xmalloc(sizeof(struct align));
> +
> +                       skip_prefix(name, "align:", &valp);

You could simplify the code by combining this skip_prefix() with the
earlier starts_with() in the conditional:

    } else if (skip_prefix(name, "align:", &valp)) {
        struct align *align = xmalloc(sizeof(struct align));
        ...

> +                       if (skip_prefix(valp, "left,", &valp))
> +                               align->align_type = ALIGN_LEFT;
> +                       else if (skip_prefix(valp, "right,", &valp))
> +                               align->align_type = ALIGN_RIGHT;
> +                       else if (skip_prefix(valp, "middle,", &valp))
> +                               align->align_type = ALIGN_MIDDLE;
> +                       else
> +                               die(_("align: improper format"));

You could do a better job of helping the user track down the offending
"improper format" by actually including it in the error message.

> +                       if (strtoul_ui(valp, 10, &align->align_value))
> +                               die(_("align: positive value expected"));

Ditto.

> +                       v->align = align;
> +                       v->modifier_atom = 1;
> +                       continue;
> +               } else if (starts_with(name, "end")) {
> +                       v->end = 1;
> +                       v->modifier_atom = 1;
> +                       continue;
>                 } else
>                         continue;
>
> @@ -1251,12 +1277,48 @@ static void emit(const char *cp, const char *ep, 
> struct ref_formatting_state *st
>  static void apply_formatting_state(struct ref_formatting_state *state, 
> struct strbuf *final)
>  {
> -       /* More formatting options to be evetually added */
> +       if (state->align && state->end) {
> +               struct strbuf *value = state->output;
> +               int len = 0, buf_len = value->len;
> +               struct align *align = state->align;
> +
> +               if (!value->buf)
> +                       return;
> +               if (!is_utf8(value->buf)) {
> +                       len = value->len - utf8_strwidth(value->buf);

What is this doing, exactly? If the string is *not* utf-8, then you're
asking it for its utf-8 width. Am I reading that correctly?

Also, what is 'len' supposed to represent? I guess you want it to be
the difference between the byte length and the display length, but the
name 'len' doesn't convey that at all, nor does it help the reader
understand the code below where you do the actual formatting.

In fact, if I'm reading this correctly, then 'len' is always zero in
your tests (because the tests never trigger this conditional), so this
functionality is never being exercised.

> +                       buf_len -= len;
> +               }
> +
> +               if (align->align_value < buf_len) {

Shouldn't this be '<=' rather than '<'? There's no point going through
the formatting gyrations below if the string fills the alignment width
exactly.

Also, what's the purpose of 'buf_len'? It's value is always
(value->len - len), so you could just as easily say:

    if (align->align_value <= value->len - len) {

In fact, the misleading name 'len', along with 'buf_len' and
value->len tend to make this code difficult to comprehend. If,
instead, you had a variable named 'display_len', then its meaning
would be obvious, and computations involving it would be more easily
understood. The value of 'display_len' could be computed via:

    display_len = utf8_strnwidth(value->buf, value->len, 0);

which would give you the utf-8 width if utf-8, or value->len if not.

And, the above conditional would become the more readable:

    if (align->align_value <= display_len) {

although, I find it easier to understand the logic with the condition
switched around:

    if (display_len >= align->align_value) {
        ...don't bother with alignment gyrations...

(but that's just subjective)

> +                       state->align = NULL;
> +                       strbuf_addbuf(final, value);
> +                       strbuf_release(value);
> +                       return;
> +               }
> +
> +               if (align->align_type == ALIGN_LEFT)
> +                       strbuf_addf(final, "%-*s", len + align->align_value, 
> value->buf);
> +               else if (align->align_type == ALIGN_MIDDLE) {
> +                       int right = (align->align_value - buf_len)/2;
> +                       strbuf_addf(final, "%*s%-*s", align->align_value - 
> right + len,
> +                                   value->buf, right, "");

Why do you use the left-justifying format "%-*s" when interpolating
the zero-length string considering that "%*s" works just as well?

An aesthetic aside: When (align_value - buf_len) is an odd number,
this implementation favors placing more whitespace to the left of the
string, and less to the right. In practice, this often tends to look a
bit more awkward than the inverse of placing more whitespace to the
right, and less to the left (but that again is subjective).

> +               } else if (align->align_type == ALIGN_RIGHT)
> +                       strbuf_addf(final, "%*s", align->align_value, 
> value->buf);

Why doesn't this case need to take 'len' into account like the other cases?

This is a tangent, but I could easily see all of the code from 'if
(align->align_value < buf_len)' down to this point being placed in
utf8.c as a general alignment utility function. Doing so would make
this function shorter, and the patch easier to review overall (which
might be an important consideration -- especially given that I've
already spent several hours reviewing this one patch).

> +               strbuf_release(value);
> +               state->align = NULL;
> +               return;
> +       } else if (state->align)
> +               return;

Do you expect additional types of state processing in the future? If
so, this function is likely to get very long. It might make sense to
break the alignment logic out into its own function which is called
from this one.

>         strbuf_addbuf(final, state->output);
>         strbuf_release(state->output);
>  }
> @@ -1301,6 +1372,7 @@ void show_ref_array_item(struct ref_array_item *info, 
> const char *format, int qu
>                 print_value(&resetv, &state);
>                 apply_formatting_state(&state, &final_buf);
>         }
> +

Sneaking in a whitespace change which an earlier patch perhaps should
have formatted correctly in the first place?

>         for (i = 0; i < final_buf.len; i++)
>                 printf("%c", final_buf.buf[i]);
>         putchar('\n');
> diff --git a/ref-filter.h b/ref-filter.h
> index 9e6c2d4..5575fe9 100644
> --- a/ref-filter.h
> +++ b/ref-filter.h
> @@ -16,14 +16,30 @@
>  struct ref_formatting_state {
> -       int quote_style;
>         struct strbuf *output;
> +       struct align *align;
> +       int quote_style;

Perhaps decide where you'd like 'quote_style' to reside from the start
so that you don't have to add it at one location in its introductory
patch and then move it in a later patch. Also, why move it here?

> +       unsigned int end : 1;
> +};
> +
> +struct align {
> +       align_type align_type;

No need for an "align_" prefix on the members of 'struct align'.
That's just unneeded verbosity.

As mentioned above, 'type' may not be the best name. Perhaps 'style'
or 'position' or something better.

> +       unsigned int align_value;

Ditto. 'value' doesn't say much, whereas 'width' or 'fieldwidth' would
be more meaningful.

>  };
> diff --git a/t/t6302-for-each-ref-filter.sh b/t/t6302-for-each-ref-filter.sh
> index 505a360..76041a2 100755
> --- a/t/t6302-for-each-ref-filter.sh
> +++ b/t/t6302-for-each-ref-filter.sh
> @@ -81,4 +81,52 @@ test_expect_success 'filtering with --contains' '
>         test_cmp expect actual
>  '
>
> +test_expect_success 'left alignment' '
> +       cat >expect <<-\EOF &&
> +       refname is refs/heads/master  |refs/heads/master
> +       refname is refs/heads/side    |refs/heads/side
> +       refname is refs/odd/spot      |refs/odd/spot
> +       refname is refs/tags/double-tag|refs/tags/double-tag
> +       refname is refs/tags/four     |refs/tags/four
> +       refname is refs/tags/one      |refs/tags/one
> +       refname is refs/tags/signed-tag|refs/tags/signed-tag
> +       refname is refs/tags/three    |refs/tags/three
> +       refname is refs/tags/two      |refs/tags/two
> +       EOF
> +       git for-each-ref --format="%(align:left,30)refname is 
> %(refname)%(end)|%(refname)" >actual &&
> +       test_cmp expect actual
> +'

Alluding to a previous review comment, for this left-alignment test,
perhaps add a third column to prove that the %(align:) atom isn't
leaking from column 1 to column 2 since it's not obvious to the
reader, given that trailing whitespace is not otherwise visible.

> +test_expect_success 'middle alignment' '
> +       cat >expect <<-\EOF &&
> +       | refname is refs/heads/master |refs/heads/master
> +       |  refname is refs/heads/side  |refs/heads/side
> +       |   refname is refs/odd/spot   |refs/odd/spot
> +       |refname is refs/tags/double-tag|refs/tags/double-tag
> +       |   refname is refs/tags/four  |refs/tags/four
> +       |   refname is refs/tags/one   |refs/tags/one
> +       |refname is refs/tags/signed-tag|refs/tags/signed-tag
> +       |  refname is refs/tags/three  |refs/tags/three
> +       |   refname is refs/tags/two   |refs/tags/two
> +       EOF
> +       git for-each-ref --format="|%(align:middle,30)refname is 
> %(refname)%(end)|%(refname)" >actual &&
> +       test_cmp expect actual
> +'
> +
> +test_expect_success 'right alignment' '
> +       cat >expect <<-\EOF &&
> +       |  refname is refs/heads/master|refs/heads/master
> +       |    refname is refs/heads/side|refs/heads/side
> +       |      refname is refs/odd/spot|refs/odd/spot
> +       |refname is refs/tags/double-tag|refs/tags/double-tag
> +       |     refname is refs/tags/four|refs/tags/four
> +       |      refname is refs/tags/one|refs/tags/one
> +       |refname is refs/tags/signed-tag|refs/tags/signed-tag
> +       |    refname is refs/tags/three|refs/tags/three
> +       |      refname is refs/tags/two|refs/tags/two
> +       EOF
> +       git for-each-ref --format="|%(align:right,30)refname is 
> %(refname)%(end)|%(refname)" >actual &&
> +       test_cmp expect actual
> +'
> +
>  test_done
> --
> 2.5.0
--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to