ws.con commit sending errors to stdout under $PAGER (61b8050)
   1/*
   2 * Whitespace rules
   3 *
   4 * Copyright (c) 2007 Junio C Hamano
   5 */
   6
   7#include "cache.h"
   8#include "attr.h"
   9
  10static struct whitespace_rule {
  11        const char *rule_name;
  12        unsigned rule_bits;
  13} whitespace_rule_names[] = {
  14        { "trailing-space", WS_TRAILING_SPACE },
  15        { "space-before-tab", WS_SPACE_BEFORE_TAB },
  16        { "indent-with-non-tab", WS_INDENT_WITH_NON_TAB },
  17};
  18
  19unsigned parse_whitespace_rule(const char *string)
  20{
  21        unsigned rule = WS_DEFAULT_RULE;
  22
  23        while (string) {
  24                int i;
  25                size_t len;
  26                const char *ep;
  27                int negated = 0;
  28
  29                string = string + strspn(string, ", \t\n\r");
  30                ep = strchr(string, ',');
  31                if (!ep)
  32                        len = strlen(string);
  33                else
  34                        len = ep - string;
  35
  36                if (*string == '-') {
  37                        negated = 1;
  38                        string++;
  39                        len--;
  40                }
  41                if (!len)
  42                        break;
  43                for (i = 0; i < ARRAY_SIZE(whitespace_rule_names); i++) {
  44                        if (strncmp(whitespace_rule_names[i].rule_name,
  45                                    string, len))
  46                                continue;
  47                        if (negated)
  48                                rule &= ~whitespace_rule_names[i].rule_bits;
  49                        else
  50                                rule |= whitespace_rule_names[i].rule_bits;
  51                        break;
  52                }
  53                string = ep;
  54        }
  55        return rule;
  56}
  57
  58static void setup_whitespace_attr_check(struct git_attr_check *check)
  59{
  60        static struct git_attr *attr_whitespace;
  61
  62        if (!attr_whitespace)
  63                attr_whitespace = git_attr("whitespace", 10);
  64        check[0].attr = attr_whitespace;
  65}
  66
  67unsigned whitespace_rule(const char *pathname)
  68{
  69        struct git_attr_check attr_whitespace_rule;
  70
  71        setup_whitespace_attr_check(&attr_whitespace_rule);
  72        if (!git_checkattr(pathname, 1, &attr_whitespace_rule)) {
  73                const char *value;
  74
  75                value = attr_whitespace_rule.value;
  76                if (ATTR_TRUE(value)) {
  77                        /* true (whitespace) */
  78                        unsigned all_rule = 0;
  79                        int i;
  80                        for (i = 0; i < ARRAY_SIZE(whitespace_rule_names); i++)
  81                                all_rule |= whitespace_rule_names[i].rule_bits;
  82                        return all_rule;
  83                } else if (ATTR_FALSE(value)) {
  84                        /* false (-whitespace) */
  85                        return 0;
  86                } else if (ATTR_UNSET(value)) {
  87                        /* reset to default (!whitespace) */
  88                        return whitespace_rule_cfg;
  89                } else {
  90                        /* string */
  91                        return parse_whitespace_rule(value);
  92                }
  93        } else {
  94                return whitespace_rule_cfg;
  95        }
  96}
  97
  98/* The returned string should be freed by the caller. */
  99char *whitespace_error_string(unsigned ws)
 100{
 101        struct strbuf err;
 102        strbuf_init(&err, 0);
 103        if (ws & WS_TRAILING_SPACE)
 104                strbuf_addstr(&err, "trailing whitespace");
 105        if (ws & WS_SPACE_BEFORE_TAB) {
 106                if (err.len)
 107                        strbuf_addstr(&err, ", ");
 108                strbuf_addstr(&err, "space before tab in indent");
 109        }
 110        if (ws & WS_INDENT_WITH_NON_TAB) {
 111                if (err.len)
 112                        strbuf_addstr(&err, ", ");
 113                strbuf_addstr(&err, "indent with spaces");
 114        }
 115        return strbuf_detach(&err, NULL);
 116}
 117
 118/* If stream is non-NULL, emits the line after checking. */
 119unsigned check_and_emit_line(const char *line, int len, unsigned ws_rule,
 120                             FILE *stream, const char *set,
 121                             const char *reset, const char *ws)
 122{
 123        unsigned result = 0;
 124        int written = 0;
 125        int trailing_whitespace = -1;
 126        int trailing_newline = 0;
 127        int i;
 128
 129        /* Logic is simpler if we temporarily ignore the trailing newline. */
 130        if (len > 0 && line[len - 1] == '\n') {
 131                trailing_newline = 1;
 132                len--;
 133        }
 134
 135        /* Check for trailing whitespace. */
 136        if (ws_rule & WS_TRAILING_SPACE) {
 137                for (i = len - 1; i >= 0; i--) {
 138                        if (isspace(line[i])) {
 139                                trailing_whitespace = i;
 140                                result |= WS_TRAILING_SPACE;
 141                        }
 142                        else
 143                                break;
 144                }
 145        }
 146
 147        /* Check for space before tab in initial indent. */
 148        for (i = 0; i < len; i++) {
 149                if (line[i] == ' ')
 150                        continue;
 151                if (line[i] != '\t')
 152                        break;
 153                if ((ws_rule & WS_SPACE_BEFORE_TAB) && written < i) {
 154                        result |= WS_SPACE_BEFORE_TAB;
 155                        if (stream) {
 156                                fputs(ws, stream);
 157                                fwrite(line + written, i - written, 1, stream);
 158                                fputs(reset, stream);
 159                        }
 160                } else if (stream)
 161                        fwrite(line + written, i - written, 1, stream);
 162                if (stream)
 163                        fwrite(line + i, 1, 1, stream);
 164                written = i + 1;
 165        }
 166
 167        /* Check for indent using non-tab. */
 168        if ((ws_rule & WS_INDENT_WITH_NON_TAB) && i - written >= 8) {
 169                result |= WS_INDENT_WITH_NON_TAB;
 170                if (stream) {
 171                        fputs(ws, stream);
 172                        fwrite(line + written, i - written, 1, stream);
 173                        fputs(reset, stream);
 174                }
 175                written = i;
 176        }
 177
 178        if (stream) {
 179                /* Now the rest of the line starts at written.
 180                 * The non-highlighted part ends at trailing_whitespace. */
 181                if (trailing_whitespace == -1)
 182                        trailing_whitespace = len;
 183
 184                /* Emit non-highlighted (middle) segment. */
 185                if (trailing_whitespace - written > 0) {
 186                        fputs(set, stream);
 187                        fwrite(line + written,
 188                            trailing_whitespace - written, 1, stream);
 189                        fputs(reset, stream);
 190                }
 191
 192                /* Highlight errors in trailing whitespace. */
 193                if (trailing_whitespace != len) {
 194                        fputs(ws, stream);
 195                        fwrite(line + trailing_whitespace,
 196                            len - trailing_whitespace, 1, stream);
 197                        fputs(reset, stream);
 198                }
 199                if (trailing_newline)
 200                        fputc('\n', stream);
 201        }
 202        return result;
 203}