summaryrefslogtreecommitdiff
path: root/contrib/scripts/nm-code-format.sh
blob: cd45874f160cfd92385df2b471ba0beef5fb55b3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
#!/bin/bash

set -e

die() {
    printf '%s\n' "$*" >&2
    exit 1
}

EXCLUDE_PATHS_TOPLEVEL=(
    "src/c-list"
    "src/c-rbtree"
    "src/c-siphash"
    "src/c-stdaux"
    "src/libnm-std-aux/unaligned.h"
    "src/libnm-systemd-core/src"
    "src/libnm-systemd-shared/src"
    "src/linux-headers"
    "src/n-acd"
    "src/n-dhcp4"
)

NM_ROOT="$(git rev-parse --show-toplevel)" || die "not inside a git repository"
NM_PREFIX="$(git rev-parse --show-prefix)" || die "not inside a git repository"

if [ ! -f "$NM_ROOT/.clang-format" ]; then
    die "Error: the clang-format file in \"$NM_ROOT\" does not exist"
fi

if ! command -v clang-format &> /dev/null; then
    die "Error: clang-format is not installed. On RHEL/Fedora/CentOS run 'dnf install clang-tools-extra'"
fi

if test -n "$NM_PREFIX"; then
    EXCLUDE_PATHS=()
    for e in "${EXCLUDE_PATHS_TOPLEVEL[@]}"; do
        REGEX="^$NM_PREFIX([^/].*)$"
        if [[ "$e" =~ $REGEX ]]; then
            EXCLUDE_PATHS+=("${BASH_REMATCH[1]}")
        fi
    done
else
    EXCLUDE_PATHS=("${EXCLUDE_PATHS_TOPLEVEL[@]}")
fi

FILES=()
HAS_EXPLICIT_FILES=0
SHOW_FILENAMES=0
TEST_ONLY=0
CHECK_ALL=1

usage() {
    printf "Usage: %s [OPTION]... [FILE]...\n" "$(basename "$0")"
    printf "Reformat source files using NetworkManager's code-style.\n\n"
    printf "If no file is given the script runs on the whole codebase.\n"
    printf "OPTIONS:\n"
    printf "    -h                 Print this help message.\n"
    printf "    -i                 Reformat files (the default).\n"
    printf "    -n|--dry-run       Only check the files (contrary to \"-i\").\n"
    printf "    -a|--all           Check all files (the default).\n"
    printf "    -F|--fast          Check only files from \`git diff --name-only HEAD^\` (contrary to \"-a\").\n"
    printf "                       This also affects directories given in the [FILE] list, but not files.\n"
    printf "    --show-filenames   Only print the filenames that would be checked/formatted\n"
    printf "    --                 Separate options from filenames/directories\n"
}

ls_files_filter() {
    local OLD_IFS="$IFS"

    IFS=$'\n'
    for f in $(cat) ; do
        local found=1
        local p
        for p; do
            [[ "$f" = "$p/"* ]] && found=
            [[ "$f" = "$p" ]] && found=
        done
        test -n "$found" && printf '%s\n' "$f"
    done
    IFS="$OLD_IFS"
}

g_ls_files() {
    local pattern="$1"
    shift

    if [ $CHECK_ALL = 1 ]; then
        git ls-files -- "$pattern"
    else
        git diff --name-only HEAD^ -- "$pattern"
    fi | ls_files_filter "$@"
}

HAD_DASHDASH=0
while (( $# )); do
    if [ "$HAD_DASHDASH" = 0 ]; then
        case "$1" in
            -h)
                usage
                exit 0
                ;;
            --show-filenames)
                SHOW_FILENAMES=1
                shift
                continue
                ;;
            -a|--all)
                CHECK_ALL=1
                shift
                continue
                ;;
            -F|--fast)
                CHECK_ALL=0
                shift
                continue
                ;;
            -n|--dry-run)
                TEST_ONLY=1
                shift
                continue
                ;;
            -i)
                TEST_ONLY=0
                shift
                continue
                ;;
            --)
                HAD_DASHDASH=1
                shift
                continue
                ;;
        esac
    fi
    if [ -d "$1" ]; then
        while IFS='' read -r line;
            do FILES+=("$line")
        done < <(CHECK_ALL=$CHECK_ALL g_ls_files "${1}/*.[hc]" "${EXCLUDE_PATHS[@]}")
    elif [ -f "$1" ]; then
        FILES+=("$1")
    else
        usage >&2
        echo >&2
        die "Unknown argument \"$1\" which also is neither a file nor a directory."
    fi
    shift
    HAS_EXPLICIT_FILES=1
done

if [ $HAS_EXPLICIT_FILES = 0 ]; then
    while IFS='' read -r line; do
        FILES+=("$line")
    done < <(CHECK_ALL=$CHECK_ALL g_ls_files '*.[ch]' "${EXCLUDE_PATHS[@]}")
fi

if [ $SHOW_FILENAMES = 1 ]; then
    for f in "${FILES[@]}" ; do
        printf '%s\n' "$f"
    done
    exit 0
fi

if [ "${#FILES[@]}" = 0 ]; then
    if [ $CHECK_ALL = 1 ]; then
        die "Error: no files to check"
    fi
    exit 0
fi

FLAGS_TEST=( --Werror -n --ferror-limit=1 )

if [ $TEST_ONLY = 1 ]; then
    # We assume that all formatting is correct. In that mode, passing
    # all filenames to clang-format is significantly faster.
    #
    # Only in case of an error, we iterate over the files one by one
    # until we find the first invalid file.
    for f in "${FILES[@]}"; do
        [ -f "$f" ] || die "Error: file \"$f\" does not exist (or is not a regular file)"
    done
    clang-format "${FLAGS_TEST[@]}" "${FILES[@]}" &>/dev/null && exit 0
    for f in "${FILES[@]}"; do
        [ -f "$f" ] || die "Error: file \"$f\" does not exist (or is not a regular file)"
        if ! clang-format "${FLAGS_TEST[@]}" "$f" &>/dev/null; then
            FF="$(mktemp)"
            trap 'rm -f "$FF"' EXIT
            clang-format "$f" 2>/dev/null > "$FF"
            git --no-pager diff "$f" "$FF" || :
            die "Error: file \"$f\" has code-style is wrong. Fix it by running "'`'"\"$0\" -i \"$f\""'`'
        fi
    done
    die "an unknown error happened."
fi

clang-format -i "${FILES[@]}"