Source code for inicheck.utilities

import dateparser
from datetime import date, datetime
import sys
import os


[docs]def parse_date(value): """ Function used to cast items to datetime from string. Meant to prevent the importing of pandas just for the to_datetime function. Args: value: string of a datetime Returns: converted: Datetime object representing the value passed. """ if isinstance(value, datetime): return value elif isinstance(value, date): return datetime(value.year, value.month, value.day) else: converted = dateparser.parse(value, settings={'STRICT_PARSING': True}) if converted is None: raise TypeError("{} is not a date".format(value)) return converted
[docs]def find_options_in_recipes(recipes, choice_search, action_kw, condition_position=0): """ Looks through the master config at recipes and entries to determine if there are place the developer made distince choices, this is used in the inimake script to walk users through making their own config file from scratch. This function looks through and finds sections or items that match the conditional. Then it looks through the adj_config to determine if any sections/items are being removed as a result, then it finds those. If a section/item appears in a recipe condition and a removed by another trigger it is extremely likely these are choices. Args: mcfg: Recipes object from master config conditional: list of 3 focusing on what were looking for action_kw: action keyword is a string of what to look for in matching choices where ever true is used will popoulate with every item/section Returns: final_choices: """ decisions = {} conditional = ["any" for i in range(3)] final_choices = [] pos = condition_position # Gather which recipes might represent a decision point for r in recipes: choices = [] for t_name, trigger in r.triggers.items(): for condition in trigger.conditions: # if the position of interest is not a generic place holder if condition[pos] != "any": # Look at how it adjusts the config for k, v in r.adj_config.items(): # If our action word shows up then we know its a choice if action_kw in v.keys(): choices.append(condition[pos]) # Look inside the adjustment for more choices for kk,vv in v.items(): actions = [k,kk,vv] if kk == action_kw: choices.append(actions[pos]) # if the list is not empty add it if choices: final_choices.append(tuple(set(sorted(choices)))) # Because there will be redundent sets of choices, we perform a set to clean up return list(set(final_choices))
[docs]def ask_user(question, choices=None): """ Asks the user which choice to make and handles incorrect choices. """ valid = False if choices == None: choices = ["yes", "no"] if choices != None: choice_str = "(" + ", ".join(choices) + ")\n" while not valid: msg = question + choice_str s = input(msg) if s not in choices: print("Invalid Choice: Try again.") valid = False else: valid = True return s
[docs]def ask_config_setup(choices_series, section=None, item=None, num_questions=None): """ Ask repeating questions for setting up a config """ if section == None and item == None: msg = "Which of these sections do you want to use? " elif section != None and item == None: msg = ("In section {}, Which of these items do you want to use? " "".format(section)) else: msg = ("In section {} item {}, Which of these values do you want to " "use? ".format(section, item)) if num_questions == None: num_questions = len(choices_series) # Add sections that we choose selections = [] for i, c in enumerate(choices_series): count = "{}/{}: ".format(i + 1, num_questions) q = count + msg selections.append(ask_user(q, c)) return selections
[docs]def remove_comment(string_entry): """ Takes a single line and removes and .ini type comments. Also remove any spaces at the begining or end of a commented line """ result = string_entry # Comment checking for c in '#;': if c in result: result = result.split(c)[0] return result
[docs]def mk_lst(values, unlst=False): """ while iterating through several type of lists and items it is convenient to assume that we recieve a list, use this function to accomplish this. It also convenient to be able to return the original type after were done with it. """ # Not a list, were not looking to unlist it. make it a list if type(values) != list and not unlst: values = [values] # We want to unlist a list we get if unlst: # single item provided so we don't want it a list if type(values) == list: if len(values) == 1: values = values[0] return values
[docs]def remove_chars(orig_str, char_str, replace_str=None): """ Removes all character in orig_str that are in char_str Args: orig_str: Original string needing cleaning char_str: String of characters to be removed replace_str: replace values with a character if requested Returns: string: orig_str with out any characters in char_str """ if replace_str == None: clean_str = [c for c in orig_str if c not in char_str] else: clean_str = [c if c not in char_str else replace_str for c in orig_str] return "".join(clean_str)
[docs]def get_relative_to_cfg(path, user_cfg_path): """ Converts a path so that all paths are relative to the config file Args: path: relative str path to be converted user_cfg_path: path to the users config file Returns: path: absolute path of the input considering it was relative to the cfg """ if os.path.isabs(path): path = os.path.relpath(path, os.path.dirname(user_cfg_path)) return path
[docs]def is_kw_matched(single_word, kw_list, kw_count=1): """ Checks to see if there are any keywords in the single word and returns a boolean Args: single_word: String to be examined for keywords kw_list: List of strings to look for inside the single word kw_count: Minimum Number of keywords to be found for it to be True Returns: boolean: Whether a keyword match was found """ truths = [True for kw in kw_list if kw in single_word] if len(truths) >= kw_count: return True else: return False
[docs]def get_kw_match(potential_matches, kw_list, kw_count=1): """ Loops through a list of potential matches looking for keywords in the list of strings being presented. When a match is found return its value. Args: potential_matches: List of strings to check kw_list: List of strings to look for inside the single word kw_count: Minimum Number of keywords to be found for it to be True Returns: result: The first potential found with the keyword match """ result = False for potential in potential_matches: match = is_kw_matched(potential, kw_list, kw_count=kw_count) if match: result = potential break return result
[docs]def pcfg(cfg): """ prints out the config file to the prompt with a nice stagger Look for readability Args: cfg: dict of dict in a heirarcy of {section, {items, values}} """ for sec in cfg.keys(): print(repr(sec)) try: for item in cfg[sec].keys(): print('\t' + item) values = cfg[sec][item] if type(cfg[sec][item]) == list: out = ", ".join(cfg[sec][item]) else: out = cfg[sec][item] print('\t\t' + repr(out)) except: print('\t recipe')
[docs]def pmcfg(cfg): """ prints out the core config file to the prompt with a nice stagger Look for readability Args: cfg: dict of dict in a hierarchy of {section, {items, values}} """ for sec in cfg.keys(): print(repr(sec)) for item in cfg[sec].keys(): print('\t' + item) obj = cfg[sec][item] for att in ['type', 'options', 'default', 'description']: value = getattr(obj, att) print('\t\t' + att) if type(value) == list: out = ", ".join(value) else: out = value print('\t\t\t' + repr(out))
[docs]def is_valid(value, cast_fn, expected_data_type, allow_none=False): """ Checks whether a value can be converted using the cast_fn function. Args: value: Value to be considered cast_fn: Function used to determine the validity, should throw an exception if it cannot expected_data_type: string name of the expected data allow_none: Boolean determining if none is valid Returns: tuple: **valid (boolean)**: whether it could be casted **msg (string)**: Msg reporting what happen """ try: # Check for a non-none value if str(value).lower() != 'none': value = cast_fn(value) valid = True msg = None # Handle the error for none when not allowed elif not allow_none and str(value).lower() == 'none': valid = False msg = 'Value cannot be None' # Report all good for nones when they are allowed else: valid = True msg = None # Report an exception when the casting goes bad. except Exception as e: valid = False msg = "Expecting {0} received {1}".format(expected_data_type, type(value).__name__) return valid, msg
[docs]def get_inicheck_cmd(config_file, modules=None, master_files=None): """ Strings together an inicheck cli command based on modules and files """ cmd = "inicheck -f {}".format(config_file) if master_files != None: master_files = mk_lst(master_files) cmd += " -mf {}".format(" ".join(master_files)) if modules != None: modules = mk_lst(modules) cmd += " -m {}".format(" ".join(modules)) return cmd