summaryrefslogtreecommitdiffstats
path: root/debian/uncrustify-trinity/uncrustify-trinity-0.75.0/scripts/option_reducer.py
diff options
context:
space:
mode:
Diffstat (limited to 'debian/uncrustify-trinity/uncrustify-trinity-0.75.0/scripts/option_reducer.py')
-rwxr-xr-xdebian/uncrustify-trinity/uncrustify-trinity-0.75.0/scripts/option_reducer.py1125
1 files changed, 1125 insertions, 0 deletions
diff --git a/debian/uncrustify-trinity/uncrustify-trinity-0.75.0/scripts/option_reducer.py b/debian/uncrustify-trinity/uncrustify-trinity-0.75.0/scripts/option_reducer.py
new file mode 100755
index 00000000..403ff92b
--- /dev/null
+++ b/debian/uncrustify-trinity/uncrustify-trinity-0.75.0/scripts/option_reducer.py
@@ -0,0 +1,1125 @@
+#!/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())