summaryrefslogtreecommitdiff
path: root/contrib/scripts/nm-code-format.sh
blob: 0aaa14752c8b9fe6ed8b94daf5df392ba8e88740 (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
#!/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

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 "    -i                 Reformat files (this is the default)\n"
    printf "    -n|--dry-run       Only check the files (contrary to \"-i\")\n"
    printf "    -h                 Print this help message\n"
    printf "    --show-filenames   Only print the filenames that would be checked/formatted\n"
    printf "    --                 Separate options from filenames/directories\n"
}

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

    IFS=$'\n'
    for f in $(git ls-files -- "$pattern") ; 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"
}

HAD_DASHDASH=0
while (( $# )); do
    if [ "$HAD_DASHDASH" = 0 ]; then
        case "$1" in
            -h)
                usage
                exit 0
                ;;
            --show-filenames)
                SHOW_FILENAMES=1
                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 < <(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 < <(g_ls_files '*.[ch]' "${EXCLUDE_PATHS[@]}")
fi

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

if [ "${#FILES[@]}" = 0 ]; then
    die "Error: no files to check"
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[@]}"