1#!/usr/bin/env python3
2#
3# Arm SCP/MCP Software
4# Copyright (c) 2015-2024, Arm Limited and Contributors. All rights reserved.
5#
6# SPDX-License-Identifier: BSD-3-Clause
7#
8"""
9    Check for tabs in the source code.
10"""
11import argparse
12import os
13import shutil
14import subprocess
15import sys
16import tempfile
17import glob
18from utils import banner, get_filtered_files
19
20#
21# Directories to exclude
22#
23
24# Exclude all mod_test 'mocks' directories
25UNIT_TEST_MOCKS = glob.glob('module/**/test/**/mocks', recursive=True) +\
26                  glob.glob('product/**/test/**/mocks', recursive=True)
27
28EXCLUDE_DIRECTORIES = [
29    '.git',
30    'build',
31    'contrib',
32    'product/rcar/src/CMSIS-FreeRTOS',
33    'unit_test/unity_mocks',
34] + UNIT_TEST_MOCKS
35
36#
37# File types to check
38#
39FILE_TYPES = [
40    '*.c',
41    '*.h',
42    '*.py',
43    'CMakeLists.txt',
44]
45
46
47def convert_tabs_to_spaces(path):
48    print(f'\tConverting all tabs in {path} into spaces...')
49    try:
50        _, temp_file = tempfile.mkstemp(prefix='tabs_to_spaces_')
51        print(f'Using {temp_file}')
52        subprocess.check_call(f'expand -t4 {path} > {temp_file}',
53                              shell=True)
54        shutil.copyfile(temp_file, path)
55    except Exception as e:
56        print(f'Error: Failed to convert file {path} with {e}')
57        sys.exit(1)
58    finally:
59        if os.path.exists(temp_file):
60            os.remove(temp_file)
61
62
63def check_files(file_paths, convert):
64    tabs_found_count = 0
65
66    for path in file_paths:
67        try:
68            with open(path, encoding='utf-8') as file:
69                file_has_tabs = False
70                for line, string in enumerate(file):
71                    if '\t' in string:
72                        print(f'{path}:{line + 1} has tab instead of spaces')
73                        tabs_found_count += 1
74                        file_has_tabs = True
75                if convert and file_has_tabs:
76                    convert_tabs_to_spaces(path)
77        except UnicodeDecodeError:
78            print(f'Invalid file format {path}')
79    return tabs_found_count
80
81
82def run(convert=False):
83    print(banner('Checking the presence of tabs in the code...'))
84
85    if convert:
86        print('Conversion mode is enabled.')
87
88    files = get_filtered_files(EXCLUDE_DIRECTORIES, FILE_TYPES)
89    tabs_found_count = check_files(files, convert)
90    if tabs_found_count == 0:
91        print('No tabs found')
92        return True
93
94    print(f'{tabs_found_count} tab(s) found.')
95    return False
96
97
98def parse_args(argv, prog_name):
99    parser = argparse.ArgumentParser(
100        prog=prog_name,
101        description='Perform checks for presence of tabs in the code')
102
103    parser.add_argument('-c', '--convert', dest='convert',
104                        required=False, default=False, action='store_true',
105                        help='Convert tabs to 4 spaces.')
106
107    return parser.parse_args(argv)
108
109
110def main(argv=[], prog_name=''):
111    args = parse_args(argv, prog_name)
112    return 0 if run(args.convert) else 1
113
114
115if __name__ == '__main__':
116    sys.exit(main(sys.argv[1:], sys.argv[0]))
117