You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1126 lines
35 KiB

#!/usr/bin/python
"""
option_reducer.py
reduces options in a given config file to the minimum while still maintaining
desired formatting
:author: Daniel Chumak
:license: GPL v2+
"""
# Possible improvements:
# - parallelize add_back()
# - (maybe) reduce amount of written config file, see Uncrustify --set
from __future__ import print_function # python >= 2.6
import argparse
from os import name as os_name, sep as os_path_sep, fdopen as os_fdopen, \
remove as os_remove
from os.path import exists, join as path_join
from subprocess import Popen, PIPE
from sys import exit as sys_exit, stderr, stdout
from shutil import rmtree
from multiprocessing import cpu_count
from tempfile import mkdtemp, mkstemp
from contextlib import contextmanager
from collections import OrderedDict
from threading import Timer
from multiprocessing.pool import Pool
from itertools import combinations
FLAGS = None
NULL_DEV = "/dev/null" if os_name != "nt" else "nul"
def enum(**enums):
return type('Enum', (), enums)
RESTULTSFLAG = enum(NONE=0, REMOVE=1, KEEP=2)
ERROR_CODE = enum(NONE=0, FLAGS=200, SANITY0=201, SANITY1=202)
MODES = ("reduce", "no-default")
@contextmanager
def make_temp_directory():
"""
Wraps tempfile.mkdtemp to use it inside a with statement that auto deletes
the temporary directory with its content after the with block closes
:return: str
----------------------------------------------------------------------------
path to the generated directory
"""
temp_dir = mkdtemp()
try:
yield temp_dir
finally:
rmtree(temp_dir)
@contextmanager
def make_raw_temp_file(*args, **kwargs):
"""
Wraps tempfile.mkstemp to use it inside a with statement that auto deletes
the file after the with block closes
Parameters
----------------------------------------------------------------------------
:param args, kwargs:
arguments passed to mkstemp
:return: int, str
----------------------------------------------------------------------------
the file descriptor and the file path of the created temporary file
"""
fd, tmp_file_name = mkstemp(*args, **kwargs)
try:
yield (fd, tmp_file_name)
finally:
os_remove(tmp_file_name)
@contextmanager
def open_fd(*args, **kwargs):
"""
Wraps os.fdopen to use it inside a with statement that auto closes the
generated file descriptor after the with block closes
Parameters
----------------------------------------------------------------------------
:param args, kwargs:
arguments passed to os.fdopen
:return: TextIOWrapper
----------------------------------------------------------------------------
open file object connected to the file descriptor
"""
fp = os_fdopen(*args, **kwargs)
try:
yield fp
finally:
fp.close()
def term_proc(proc, timeout):
"""
helper function to terminate a process
Parameters
----------------------------------------------------------------------------
:param proc: process object
the process object that is going to be terminated
:param timeout: dictionary
a dictionary (used as object reference) to set a flag that indicates
that the process is going to be terminated
"""
timeout["value"] = True
proc.terminate()
def uncrustify(unc_bin_path, cfg_file_path, unformatted_file_path,
lang=None, debug_file=None, check=False):
"""
executes Uncrustify and captures its stdout
Parameters
----------------------------------------------------------------------------
:param unc_bin_path: str
path to the Uncrustify binary
:param cfg_file_path: str
path to a config file for Uncrustify
:param unformatted_file_path: str
path to a file that is going to be formatted
:param lang: str / None
Uncrustifys -l argument
:param debug_file: str / None
Uncrustifys -p argument
:param check: bool
Used to control whether Uncrustifys --check is going to be used
:return: str / None
----------------------------------------------------------------------------
returns the stdout from Uncrustify or None if the process takes to much
time (set to 5 sec)
"""
args = [unc_bin_path, "-q", "-c", cfg_file_path, '-f',
unformatted_file_path]
if lang:
args.extend(("-l", lang))
if debug_file:
args.extend(('-p', debug_file))
if check:
args.append('--check')
proc = Popen(args, stdout=PIPE, stderr=PIPE)
timeout = {"value": False}
timer = Timer(5, term_proc, [proc, timeout])
timer.start()
output_b, error_txt_b = proc.communicate()
timer.cancel()
if timeout["value"]:
print("uncrustify proc timeout: %s" % ' '.join(args), file=stderr)
return None
error = error_txt_b.decode("UTF-8")
if error:
print("Uncrustify %s stderr:\n %s" % (unformatted_file_path, error),
file=stderr)
return output_b
def same_expected_generated(formatted_path, unc_bin_path, cfg_file_path,
input_path, lang=None):
"""
Calls uncrustify and compares its generated output with the content of a
file
Parameters
----------------------------------------------------------------------------
:param formatted_path: str
path to a file containing the expected content
:params unc_bin_path, cfg_file_path, input_path, lang: str, str, str,
str / None
see uncrustify()
:return: bool
----------------------------------------------------------------------------
True if the strings match, False otherwise
"""
expected_string = ''
with open(formatted_path, 'rb') as f:
expected_string = f.read()
formatted_string = uncrustify(unc_bin_path, cfg_file_path, input_path, lang)
return True if formatted_string == expected_string else False
def process_uncrustify(args):
"""
special wrapper for same_expected_generated()
accesses global var(s): RESTULTSFLAG
Parameters
----------------------------------------------------------------------------
:param args: list / tuple< int, ... >
this function is intended to be called by multiprocessing.pool.map()
therefore all arguments are inside a list / tuple:
id: int
an index number needed by the caller to differentiate runs
other parameters:
see same_expected_generated()
:return: tuple< int, RESTULTSFLAG >
----------------------------------------------------------------------------
returns a tuple containing the id and a RESTULTSFLAG, REMOVE if both
strings are equal, KEEP if not
"""
id = args[0]
res = same_expected_generated(*args[1:])
return id, RESTULTSFLAG.REMOVE if res else RESTULTSFLAG.KEEP
def write_config_file(args):
"""
Writes all but one excluded option into a config file
Parameters
----------------------------------------------------------------------------
:param args: list / tuple< list< tuple< str, str > >, str, int >
this function is intended to be called by multiprocessing.pool.map()
therefore all arguments are inside a list / tuple:
config_list: list< tuple< str, str > >
a list of tuples containing option names and values
tmp_dir: str
path to a directory in which the config file is going to be
written
exclude_idx: int
index for an option that is not going to be written into the
config file
"""
config_list, tmp_dir, exclude_idx = args
with open("%s%suncr-%d.cfg" % (tmp_dir, os_path_sep, exclude_idx),
'w') as f:
print_config(config_list, target_file_obj=f, exclude_idx=exclude_idx)
def write_config_file2(args):
"""
Writes two option lists into a config file
Parameters
----------------------------------------------------------------------------
:param args: list< tuple< str, str > >,
list< tuple< str, str > >, str, int
this function is intended to be called by multiprocessing.pool.map()
therefore all arguments are inside a list / tuple:
config_list: list< tuple< str, str > >
the first list of tuples containing option names and values
test_list: list< tuple< str, str > >
the second list of tuples containing option names and values
tmp_dir: str
path to a directory in which the config file is going to be
written
idx: int
index that is going to be used for the filename
"""
config_list0, config_list1, tmp_dir, idx = args
with open("%s%suncr-r-%d.cfg" % (tmp_dir, os_path_sep, idx), 'w') as f:
print_config(config_list0, target_file_obj=f)
print("", end='\n', file=f)
print_config(config_list1, target_file_obj=f)
def gen_multi_combinations(elements, N):
"""
generator function that generates, based on a set of elements, all
combinations of 1..N elements
Parameters
----------------------------------------------------------------------------
:param elements: list / tuple
a list of elements from which the combinations will be generated
:param N:
the max number of element in a combination
:return: list
----------------------------------------------------------------------------
yields a single combination of the elements
>>> gen_multi_combinations(["a", "b", "c"], 3)
(a); (b); (c); (a,b); (a,c); (b,c); (a,b,c)
"""
fields = len(elements)
if N > fields:
raise Exception("Error: N > len(options)")
if N <= 0:
raise Exception("Error: N <= 0")
for n in range(1, N + 1):
yield combinations(elements, n)
def add_back(unc_bin_path, input_files, formatted_files, langs, options_r,
options_k, tmp_dir):
"""
lets Uncrustify format files with generated configs files until all
formatted files match their according expected files.
Multiple config files are generated based on a (base) list of Uncrustify
options combined with additional (new) options derived from combinations of
another list of options.
accesses global var(s): RESTULTSFLAG
Parameters
----------------------------------------------------------------------------
:param unc_bin_path: str
path to the Uncrustify binary
:param input_files: list / tuple< str >
a list containing paths to a files that are going to be formatted
:param formatted_files: list / tuple< str >
a list containing paths to files containing the expected contents
:param langs: list / tuple< str > / None
a list of languages the files, used as Uncrustifys -l argument
can be None or shorter than the amount of provided files
:param options_r: list< tuple< str, str > >
the list of options from which combinations will be derived
:param options_k: list< tuple< str, str > >
the (base) list of Uncrustify options
:param tmp_dir: str
the directory in which the config files will be written to
:return: list< tuple< str, str > > / None
----------------------------------------------------------------------------
list of additional option that were needed to generate matching file
contents
"""
lang_max_idx = -1 if langs is None else len(langs) - 1
file_len = len(input_files)
if len(formatted_files) != file_len:
raise Exception("len(input_files) != len(formatted_files)")
for m_combination in gen_multi_combinations(options_r, len(options_r)):
for idx, (r_combination) in enumerate(m_combination):
write_config_file2((options_k, r_combination, tmp_dir, idx))
cfg_file_path = "%s%suncr-r-%d.cfg" % (tmp_dir, os_path_sep, idx)
res = []
for file_idx in range(file_len):
lang = None if idx > lang_max_idx else langs[file_idx]
r = process_uncrustify(
(0, formatted_files[file_idx], unc_bin_path, cfg_file_path,
input_files[file_idx], lang))
res.append(r[1])
# all files, flag = remove -> option can be removed -> equal output
if res.count(RESTULTSFLAG.REMOVE) == len(res):
return r_combination
return None
def sanity_raw_run(args):
"""
wrapper for same_expected_generated(), prints error message if the config
file does not generate the expected result
Parameters
----------------------------------------------------------------------------
:param args:
see same_expected_generated
:return:
----------------------------------------------------------------------------
see same_expected_generated
"""
res = same_expected_generated(*args)
if not res:
formatted_file_path = args[0]
config_file_path = args[2]
input_file_path = args[3]
print("\nprovided config does not create formatted source file:\n"
" %s\n %s\n->| %s"
% (input_file_path, config_file_path, formatted_file_path),
file=stderr)
return res
def sanity_run(args):
"""
wrapper for same_expected_generated(), prints error message if the config
file does not generate the expected result
Parameters
----------------------------------------------------------------------------
:param args:
see same_expected_generated
:return:
----------------------------------------------------------------------------
see same_expected_generated
"""
res = same_expected_generated(*args)
if not res:
formatted_file_path = args[0]
input_file_path = args[3]
print("\ngenerated config does not create formatted source file:\n"
" %s\n %s"
% (input_file_path, formatted_file_path), file=stderr)
return res
def sanity_run_splitter(uncr_bin, config_list, input_files, formatted_files,
langs, tmp_dir, jobs):
"""
writes config option into a file and tests if every input file is formatted
so that is matches the content of the according expected file
Parameters
----------------------------------------------------------------------------
:param uncr_bin: str
path to the Uncrustify binary
:param config_list: list< tuple< str, str > >
a list of tuples containing option names and values
:param input_files: list / tuple< str >
a list containing paths to a files that are going to be formatted
:param formatted_files: list / tuple< str >
a list containing paths to files containing the expected contents
:param langs: list / tuple< str > / None
a list of languages the files, used as Uncrustifys -l argument
can be None or shorter than the amount of provided files
:param tmp_dir: str
the directory in which the config files will be written to
:param jobs: int
number of processes to use
:return: bool
----------------------------------------------------------------------------
True if all files generate correct results, False oterhwise
"""
file_len = len(input_files)
if len(formatted_files) != file_len:
raise Exception("len(input_files) != len(formatted_files)")
gen_cfg_path = path_join(tmp_dir, "gen.cfg")
with open(gen_cfg_path, 'w') as f:
print_config(config_list, target_file_obj=f)
lang_max_idx = -1 if langs is None else len(langs) - 1
args = []
for idx in range(file_len):
lang = None if idx > lang_max_idx else langs[idx]
args.append((formatted_files[idx], uncr_bin, gen_cfg_path,
input_files[idx], lang))
pool = Pool(processes=jobs)
sr = pool.map(sanity_run, args)
return False not in sr
def print_config(config_list, target_file_obj=stdout, exclude_idx=()):
"""
prints config options into a config file
Parameters
----------------------------------------------------------------------------
:param config_list: list< tuple< str, str > >
a list containing pairs of option names and option values
:param target_file_obj: file object
see file param of print()
:param exclude_idx: int / list< int >
index of option(s) that are not going to be printed
"""
if not config_list:
return
config_list_len = len(config_list)
# check if exclude_idx list is empty -> assign len
if type(exclude_idx) in (list, tuple) and not exclude_idx:
exclude_idx = [config_list_len]
else:
# sort it, unless it is an int -> transform into a list
try:
exclude_idx = sorted(exclude_idx)
except TypeError:
exclude_idx = [exclude_idx]
# extracted first loop round:
# do not print '\n' for the ( here non-existing) previous line
if exclude_idx[0] != 0:
print("%s = %s" % (config_list[0][0].ljust(31, ' '), config_list[0][1]),
end='', file=target_file_obj)
# also print space if a single option was provided and it is going to be
# excluded. This is done in order to be able to differentiate between
# --empty-nochange and the case where all options can be removed
elif config_list_len == 1:
print(' ', end='', file=target_file_obj)
return
start_idx = 1
for end in exclude_idx:
end = min(end, config_list_len)
for idx in range(start_idx, end):
print("\n%s = %s"
% (config_list[idx][0].ljust(31, ' '), config_list[idx][1]),
end='', file=target_file_obj)
start_idx = min(end + 1, config_list_len)
# after
for idx in range(start_idx, config_list_len):
print("\n%s = %s"
% (config_list[idx][0].ljust(31, ' '), config_list[idx][1]),
end='', file=target_file_obj)
def get_non_default_options(unc_bin_path, cfg_file_path):
"""
calls Uncrustify to generate a debug file from which a config only with
non default valued options are extracted
accesses global var(s): NULL_DEV
Parameters
----------------------------------------------------------------------------
:param unc_bin_path: str
path to the Uncrustify binary
:param cfg_file_path: str
path to a config file for Uncrustify
:return: list< str >
----------------------------------------------------------------------------
amount of lines in the provided and shortened config
"""
lines = []
with make_raw_temp_file(suffix='.unc') as (fd, file_path):
# make debug file
uncrustify(unc_bin_path, cfg_file_path, NULL_DEV, debug_file=file_path,
check=True)
# extract non comment lines -> non default config lines
with open_fd(fd, 'r') as fp:
lines = fp.read().splitlines()
lines = [line for line in lines if not line[:1] == '#']
return lines
def parse_config_file(file_obj):
"""
Reads in a Uncrustify config file
Parameters
----------------------------------------------------------------------------
:param file_obj:
the file object of an opened config file
:return: list< tuple< str, str > >
----------------------------------------------------------------------------
a list containing pairs of option names and option values
"""
# dict used to only save the last option setting if the same option occurs
# multiple times, without this:
# optionA0 can be removed because optionA1 = s0, and
# optionA1 can be removed because optionA0 = s0
# -> optionA0, optionA1 are both removed
config_map = OrderedDict()
# special keys may not have this limitation, as for example
# 'set x y' and 'set x z' do not overwrite each other
special_keys = {'macro-open', 'macro-else', 'macro-close', 'set', 'type',
'file_ext', 'define'}
special_list = []
for line in file_obj:
# cut comments
pound_pos = line.find('#')
if pound_pos != -1:
line = line[:pound_pos]
split_pos = line.find('=')
if split_pos == -1:
split_pos = line.find(' ')
if split_pos == -1:
continue
key = line[:split_pos].strip()
value = line[split_pos + 1:].strip()
if key in special_keys:
special_list.append((key, value))
else:
config_map[key] = value
config_list = list(config_map.items())
config_list += special_list
return config_list
def count_lines(file_path):
"""
returns the count of lines in a file by counting '\n' chars
Parameters
----------------------------------------------------------------------------
:param file_path: str
file in which the lines will be counted
:return: int
----------------------------------------------------------------------------
number a lines
"""
in_count = 0
with open(file_path, 'r') as f:
in_count = f.read().count('\n') + 1
return in_count
def reduce(options_list):
"""
Reduces the given options to a minimum
accesses global var(s): FLAGS, RESTULTSFLAG, ERROR_CODE
Parameters
----------------------------------------------------------------------------
:param options_list: list< tuple< str, str > >
the list of options that are going to be reduced
:return: int, list< tuple< str, str > >
status return code, reduced options
"""
config_list_len = len(options_list)
ret_flag = ERROR_CODE.NONE
file_count = len(FLAGS.input_file_path)
lang_max_idx = -1 if FLAGS.lang is None else len(FLAGS.lang) - 1
pool = Pool(processes=FLAGS.jobs)
with make_temp_directory() as tmp_dir:
# region sanity run ----------------------------------------------------
args = []
for idx in range(file_count):
lang = None if idx > lang_max_idx else FLAGS.lang[idx]
args.append((FLAGS.formatted_file_path[idx],
FLAGS.uncrustify_binary_path, FLAGS.config_file_path,
FLAGS.input_file_path[idx], lang))
sr = pool.map(sanity_raw_run, args)
del args[:]
if False in sr:
return ERROR_CODE.SANITY0, []
del sr[:]
# endregion
# region config generator loop -----------------------------------------
args = []
for e_idx in range(config_list_len):
args.append((options_list, tmp_dir, e_idx))
pool.map(write_config_file, args)
del args[:]
# endregion
# region main loop -----------------------------------------------------
args = []
jobs = config_list_len * file_count
for idx in range(jobs):
file_idx = idx // config_list_len
option_idx = idx % config_list_len
cfg_file_path = "%s%suncr-%d.cfg" \
% (tmp_dir, os_path_sep, option_idx)
lang = None if idx > lang_max_idx else FLAGS.lang[file_idx]
args.append((idx, FLAGS.formatted_file_path[file_idx],
FLAGS.uncrustify_binary_path, cfg_file_path,
FLAGS.input_file_path[file_idx], lang))
results = pool.map(process_uncrustify, args)
del args[:]
# endregion
# region clean results -------------------------------------------------
option_flags = [RESTULTSFLAG.NONE] * config_list_len
for r in results:
idx = r[0]
flag = r[1]
option_idx = idx % config_list_len
if option_flags[option_idx] == RESTULTSFLAG.KEEP:
continue
option_flags[option_idx] = flag
del results[:]
# endregion
options_r = [options_list[idx] for idx, x in enumerate(option_flags)
if x == RESTULTSFLAG.REMOVE]
options_list = [options_list[idx] for idx, x in enumerate(option_flags)
if x == RESTULTSFLAG.KEEP]
del option_flags[:]
# region sanity run ----------------------------------------------------
# options can be removed one at a time generating appropriate results,
# oddly enough sometimes a config generated this way can fail when a
# combination of multiple options is missing
s_flag = True
if options_r:
s_flag = sanity_run_splitter(
FLAGS.uncrustify_binary_path, options_list,
FLAGS.input_file_path, FLAGS.formatted_file_path, FLAGS.lang,
tmp_dir, FLAGS.jobs)
if not s_flag:
ret_flag = ERROR_CODE.SANITY1
print("\n\nstumbled upon complex option dependencies in \n"
" %s\n"
"trying to add back minimal amount of removed options\n"
% FLAGS.config_file_path, file=stderr)
ret_options = add_back(
FLAGS.uncrustify_binary_path, FLAGS.input_file_path,
FLAGS.formatted_file_path, FLAGS.lang, options_r,
options_list, tmp_dir)
if ret_options:
options_list.extend(ret_options)
s_flag = sanity_run_splitter(
FLAGS.uncrustify_binary_path, options_list,
FLAGS.input_file_path, FLAGS.formatted_file_path,
FLAGS.lang, tmp_dir, FLAGS.jobs)
if s_flag:
print("Success!", file=stderr)
ret_flag = ERROR_CODE.NONE
# endregion
return ret_flag, options_list if ret_flag == ERROR_CODE.NONE else []
def reduce_mode():
"""
the mode that minimizes a config file as much as possible
accesses global var(s): FLAGS, ERROR_CODE
"""
ret_flag = ERROR_CODE.NONE
option_list = {}
# gen & parse non default config
lines = get_non_default_options(FLAGS.uncrustify_binary_path,
FLAGS.config_file_path)
option_list = parse_config_file(lines)
config_list_len = len(option_list)
config_lines_init = count_lines(FLAGS.config_file_path)
config_lines_ndef = len(lines)
del lines[:]
# early return if all options are already removed at this point
if config_list_len == 0:
if not FLAGS.empty_nochange \
or (config_lines_init - config_lines_ndef) > 0:
if not FLAGS.quiet:
print("\n%s" % '# '.ljust(78, '-'))
print(" ")
if not FLAGS.quiet:
print("%s" % '# '.ljust(78, '-'))
print("# initial config lines: %d,\n"
"# default options and unneeded lines: %d,\n"
"# unneeded options: 0,\n"
"# kept options: 0"
% (config_lines_init, config_lines_init))
print("ret_flag: 0", file=stderr)
return ERROR_CODE.NONE
# gen reduced options
config_lines_redu = -1
for i in range(FLAGS.passes):
old_config_lines_redu = config_lines_redu
ret_flag, option_list = reduce(option_list)
config_lines_redu = len(option_list)
if ret_flag != ERROR_CODE.NONE \
or config_lines_redu == old_config_lines_redu:
break
if ret_flag == ERROR_CODE.NONE:
# use the debug file trick again to get correctly sorted options
with make_raw_temp_file(suffix='.unc') as (fd, file_path):
with open_fd(fd, 'w') as f:
print_config(option_list, target_file_obj=f)
lines = get_non_default_options(FLAGS.uncrustify_binary_path,
file_path)
option_list = parse_config_file(lines)
# print output + stats
if not FLAGS.empty_nochange or config_lines_ndef != config_lines_redu:
if not FLAGS.quiet:
print("\n%s" % '# '.ljust(78, '-'))
print_config(option_list)
if not FLAGS.quiet:
print("\n%s" % '# '.ljust(78, '-'))
print("# initial config lines: %d,\n"
"# default options and unneeded lines: %d,\n"
"# unneeded options: %d,\n"
"# kept options: %d"
% (config_lines_init,
config_lines_init - config_lines_ndef,
config_lines_ndef - config_lines_redu,
config_lines_redu))
print("ret_flag: %d" % ret_flag, file=stderr)
return ret_flag
def no_default_mode():
"""
the mode removes all unnecessary lines and options with default values
accesses global var(s): FLAGS, ERROR_CODE
"""
lines = get_non_default_options(FLAGS.uncrustify_binary_path,
FLAGS.config_file_path, )
config_lines_ndef = len(lines)
config_lines_init = count_lines(FLAGS.config_file_path)
if not FLAGS.empty_nochange or (config_lines_ndef != config_lines_init):
if not FLAGS.quiet:
print("%s" % '# '.ljust(78, '-'))
options_str = '\n'.join(lines)
if not options_str:
print(" ")
else:
print(options_str, file=stdout)
if not FLAGS.quiet:
print("%s" % '# '.ljust(78, '-'))
print("# initial config lines: %d,\n"
"# default options and unneeded lines: %d,\n"
% (config_lines_init, config_lines_init - config_lines_ndef))
return ERROR_CODE.NONE
def main():
"""
calls the mode that was specified by the -m script argument,
defaults to reduce_mode if not provided or unknown mode
accesses global var(s): MODES, FLAGS
:return: int
----------------------------------------------------------------------------
return code
"""
if FLAGS.mode == MODES[1]:
return no_default_mode()
return reduce_mode()
def valid_file(arg_parser, *args):
"""
checks if on of the provided paths is a file
Parameters
----------------------------------------------------------------------------
:param arg_parser:
argument parser object that is called if no file is found
:param args: list< str >
a list of file path that is going to be checked
:return: str
----------------------------------------------------------------------------
path to an existing file
"""
arg = None
found_flag = False
for arg in args:
if exists(arg):
found_flag = True
break
if not found_flag:
arg_parser.error("file(s) do not exist: %s" % args)
return arg
if __name__ == "__main__":
"""
parses all script arguments and calls main()
accesses global var(s): FLAGS, ERROR_CODE, MODES
"""
arg_parser = argparse.ArgumentParser()
group_general = arg_parser.add_argument_group(
'general options', 'Options used by both modes')
group_general.add_argument(
'-q', '--quiet',
default=False,
action='store_true',
help='Whether or not messages, other than the actual config output, '
'should be printed to stdout.'
)
group_general.add_argument(
'--empty-nochange',
default=False,
action='store_true',
help='Do not print anything to stdout if no options could be removed'
)
group_general.add_argument(
'-m', '--mode',
type=str,
choices=MODES,
default=MODES[0],
help="The script operation mode. Defaults to '%s'" % MODES[0]
)
group_general.add_argument(
'-b', '--uncrustify_binary_path',
metavar='<path>',
type=lambda x: valid_file(
arg_parser, x,
"../build/uncrustify.exe",
"../build/Debug/uncrustify",
"../build/Debug/uncrustify.exe",
"../build/Release/uncrustify",
"../build/Release/uncrustify.exe"),
default="../build/uncrustify",
help="The Uncrustify binary file path. Is searched in known locations "
"in the 'Uncrustify/build/' directory if no <path> is provided."
)
group_general.add_argument(
'-c', '--config_file_path',
metavar='<path>',
type=lambda x: valid_file(arg_parser, x),
required=True,
help='Path to the config file.'
)
group_reduce = arg_parser.add_argument_group(
'reduce mode', 'Options to reduce configuration file options')
group_reduce.add_argument(
'-i', '--input_file_path',
metavar='<path>',
type=lambda x: valid_file(arg_parser, x),
nargs='+',
action='append',
help="Path to the unformatted source file. "
"Required if mode '%s' is used" % MODES[0]
)
group_reduce.add_argument(
'-f', '--formatted_file_path',
metavar='<path>',
type=lambda x: valid_file(arg_parser, x),
nargs='+',
action='append',
help="Path to the formatted source file. "
"Required if mode '%s' is used" % MODES[0]
)
group_reduce.add_argument(
'-l', '--lang',
metavar='<str>',
nargs='+',
required=False,
action='append',
help='Uncrustify processing language for each input file'
)
group_reduce.add_argument(
'-j', '--jobs',
metavar='<nr>',
type=int,
default=cpu_count(),
help='Number of concurrent jobs.'
)
group_reduce.add_argument(
'-p', '--passes',
metavar='<nr>',
type=int,
default=5,
help='Max. number of cleaning passes.'
)
group_no_default = arg_parser.add_argument_group(
'no-default mode', 'Options to remove configuration file option with '
'default values: ~~_Currently only the general'
' options are used for this mode_~~')
FLAGS, unparsed = arg_parser.parse_known_args()
if FLAGS.lang is not None:
FLAGS.lang = [j for i in FLAGS.lang for j in i]
if FLAGS.mode == MODES[0]:
if not FLAGS.input_file_path or not FLAGS.formatted_file_path:
arg_parser.error("Flags -f and -i are required in Mode '%s'!"
% MODES[0])
sys_exit(ERROR_CODE.FLAGS)
# flatten 2 dimensional args: -f p -f p -f p -f p0 p1 p2 -> [[],[], ...]
FLAGS.input_file_path = [j for i in FLAGS.input_file_path for j in i]
FLAGS.formatted_file_path = [j for i in
FLAGS.formatted_file_path for j in i]
if len(FLAGS.input_file_path) != len(FLAGS.formatted_file_path):
print("Unequal amount of input and formatted file paths.",
file=stderr)
sys_exit(ERROR_CODE.FLAGS)
sys_exit(main())