aboutsummaryrefslogblamecommitdiffstatshomepage
path: root/tools/clang-format/clang-format.sh
blob: 00fe7129da31b4baa749a2e69b1d103280ef0f17 (plain) (tree)

























































































































































































                                                                                                                                                                
#!/usr/bin/env bash

# Copyright (c) 2020 Egor Tensin <Egor.Tensin@gmail.com>
# This file is part of the "cmake-common" project.
# For details, see https://github.com/egor-tensin/cmake-common.
# Distributed under the MIT License.

set -o errexit -o nounset -o pipefail

# Utility

script_name="$( basename -- "${BASH_SOURCE[0]}" )"
readonly script_name

dump() {
    local prefix="${FUNCNAME[0]}"
    [ "${#FUNCNAME[@]}" -gt 1 ] && prefix="${FUNCNAME[1]}"

    local msg
    for msg; do
        echo "$script_name: $prefix: $msg"
    done
}

# Settings

clang_format='clang-format'
clang_format_style='file'
clang_format_diff=

update_clang_format() {
    if [ "$#" -ne 1 ]; then
        echo "usage: ${FUNCNAME[0]} CLANG_FORMAT_PATH" >&2
        return 1
    fi

    local new_clang_format="$1"

    if ! command -v "$new_clang_format" > /dev/null 2>&1; then
        dump "couldn't find clang-format at: $new_clang_format" >&2
        return 1
    fi

    clang_format="$new_clang_format"
}

# Command line parsing

script_usage() {
    local msg
    for msg; do
        echo "$script_name: $msg"
    done

    echo "usage: $script_name [-h|--help] [-b|--clang-format PATH] [-s|--style STYLE] [--diff]
  -h,--help            show this message and exit
  -b,--clang-format    set path to clang-format executable
  -s,--style           clang-format -style parameter argument
  --diff               don't edit the files, just show the diff"
}

parse_script_options() {
    while [ "$#" -gt 0 ]; do
        local key="$1"
        shift

        case "$key" in
            -h|--help)
                script_usage
                exit 0
                ;;
            --diff)
                clang_format_diff=1
                continue
                ;;
            -b|--clang-format|-s|--style)
                ;;
            *)
                script_usage "unrecognized parameter: $key" >&2
                exit 1
                ;;
        esac

        if [ "$#" -eq 0 ]; then
            script_usage "missing argument for parameter: $key" >&2
            exit 1
        fi

        local value="$1"
        shift

        case "$key" in
            -b|--clang-format)
                update_clang_format "$value"
                ;;
            -s|--style)
                clang_format_style="$value"
                ;;
            *)
                script_usage "unrecognized parameter: $key" >&2
                exit 1
                ;;
        esac
    done
}

# Routines for running clang-format

run_clang_format_diff() {
    local exit_code=0
    local file

    for file; do
        if "$clang_format" "-style=$clang_format_style" -- "$file" | diff --unified --label="$file (original)" --label="$file (clang-format)" -- "$file" -; then
            continue
        else
            exit_code="$?"
            [ "$exit_code" -eq 1 ] && continue
            break
        fi
    done

    return "$exit_code"
}

run_clang_format_edit() {
    "$clang_format" -i "-style=$clang_format_style" -- "$@"
}

run_clang_format() {
    if [ -z "$clang_format_diff" ]; then
        run_clang_format_edit "$@"
    else
        run_clang_format_diff "$@"
    fi
}

# File traversal

list_all_files() {
    git ls-tree -r -z --name-only HEAD
}

declare -a cpp_extensions=(c h cc hh cpp hpp cxx hxx cp c++)

list_cpp_files() {
    local -A cpp_extension_set
    local ext

    for ext in ${cpp_extensions[@]+"${cpp_extensions[@]}"}; do
        cpp_extension_set[$ext]=1
    done

    local -a files=()
    local file

    while IFS= read -d '' -r file; do
        basename="$( basename -- "$file" )"
        ext="${basename##*.}"
        [ "$ext" = "$basename" ] && continue # No .EXTension

        [ -n "${cpp_extension_set[$ext]+x}" ] && files+=("$file")
    done < <( list_all_files )

    printf -- '%s\0' ${files[@]+"${files[@]}"}
}

# Main routines

process_cpp_files() {
    local -a files=()
    local file

    while IFS= read -d '' -r file; do
        files+=("$file")
    done < <( list_cpp_files )

    run_clang_format ${files[@]+"${files[@]}"}
}

main() {
    parse_script_options "$@"
    process_cpp_files
}

main "$@"