Source code for inicheck.cli

# !/usr/bin/env python

import argparse
from . output import *
from . tools import get_user_config, check_config
from . changes import ChangeLog
from os.path import basename, abspath, join
import sys
from . config import MasterConfig, UserConfig
from . import __version__
from .utilities import *
from collections import OrderedDict
from . import __version__

[docs]def main(): parser = argparse.ArgumentParser(description="Examine and auto populate" " ini files with a master" " file comparison.") parser.add_argument('--config_file','-f', dest='config_file', type=str, help='Path to a config file that needs checking') parser.add_argument('--master', '-mf', metavar='MF', type=str, nargs='+', help='Path to a config file that used to check ' ' against') parser.add_argument('--modules', '-m', metavar='M', type=str, nargs='+', help="Modules name with an attribute __CoreConfig__ that" " is a path to a master config file for checking" " against") parser.add_argument('--write','-w', dest='write', action='store_true', help="Determines whether to write out the file with" " all the defaults") parser.add_argument('--recipes','-r', dest='recipes', action='store_true', help="Prints out the recipe summary") parser.add_argument('--non-defaults','-nd', dest='defaults', action='store_true', help="Prints out a summary of the non-defaults") parser.add_argument('--details', '-d', type=str, nargs='+', help="Provide" " section item and value for details regarding them") parser.add_argument('--change', '-c', action='store_true', help="Automatically apply changes to the config" " according to a packages changelog. Including" " recommended default changes.") parser.add_argument('--version', action='version', version=('%(prog)s {version}' '').format(version=__version__)) args = parser.parse_args() inicheck_main(config_file=args.config_file, master=args.master, modules=args.modules, write_out=args.write, show_recipes=args.recipes, show_non_defaults=args.defaults, details=args.details, apply_changelog=args.change)
[docs]def inicheck_main(config_file=None, master=None, modules=None, write_out=False, show_recipes=False, show_non_defaults=False, details=None, apply_changelog=False): """ Function used for the CLI for inicheck. This is mostly for cleaner testing. Allows users to look at master config details, check their config file against the master, look at recipes applied, examine non-default entries, write new config files, and apply changes from a changelog """ # Module not provided, or master config if modules == None and master == None: print("ERROR: Please provide either a module or a path to a master" " config, or ask for details on config entries") sys.exit() # Normal operation else: # Requesting details for a section and item if details != None: if len(details) == 1: print("Providing details for section {0}..." "".format(details[0])) elif len(details) == 2: print("Providing details for section {0} and item {1}..." "".format(details[0], details[1])) else: print("Details option can at most recieve section and item ") sys.exit() mcfg = MasterConfig(path=master, modules=modules) print_details(details, mcfg.cfg) # Requesting a check on a config file else: f = abspath(config_file) ucfg = get_user_config(f, master_files=master, modules=modules, cli=True) # Check out any change logs for issues chlog = ChangeLog(paths = ucfg.mcfg.changelogs, mcfg=ucfg.mcfg) potentials, required = chlog.get_active_changes(ucfg) # Request to apply changes if apply_changelog: print("Applying {} changes due to deprecation..." "".format(len(required) + len(potentials))) ucfg.cfg = chlog.apply_changes(ucfg, potentials, required) required = [] # report issues if there are recommended changes if len(required) > 0: cmd = get_inicheck_cmd(config_file, master_files=master, modules=modules) cmd += " --change -w" print_change_report(potentials, required, ucfg) print("Please make the above changes before continuing." " To apply the name and default changes automatically " "use:\n{}".format(cmd)) else: warnings, errors = check_config(ucfg) print_config_report(warnings, errors) # Print out the recipes summary if show_recipes: print_recipe_summary(ucfg.recipes) # Print out the summary of non-defaults values if show_non_defaults: print_non_defaults(ucfg) # Output the config file as inicheck interprets it. if write_out: out_f = './{0}_full.ini'.format( basename(f).split('.')[0]) print("Writing complete config file with all recipes and" " necessary defaults...") print('{0}'.format(out_f)) generate_config(ucfg, out_f, cli=True)
[docs]def inidiff(): """ Creates a report showing the difference in files """ parser = argparse.ArgumentParser(description="Examine and compare" " ini files with a master file" "") parser.add_argument('--config_files','-f', dest='config_files', type=str, nargs='+', required=True, help='Path to two config file that needs comparing') parser.add_argument('--master', '-mf', metavar='MF', type=str, nargs='+', help='Path to a config file that used to check against') parser.add_argument('--modules', '-m', metavar='M', type=str, nargs='+', help="Modules name with an attribute __CoreConfig__ that" " is a path to a master config file for checking" " against") parser.add_argument('--version', action='version', version=('%(prog)s {version}' '').format(version=__version__)) args = parser.parse_args() inidiff_main(args.config_files, master=args.master, modules=args.modules)
[docs]def inidiff_main(config_files, master=None, modules=None): # handle multiple files cfgs = [] # Keep track of non-default and mismatches non_default_count = 0 mismatch_count = 0 total_count = 0 # Legend print out msg = "{0:<20}{1:<20}" # header formatable msg = "{0:<20}{1:<20}" # We need to start with Section, Item, and a Default tmsg = "{:<20}{:<30}{:<30}" header = ["Section","Item"] print("\nChecking the differences...\n") # Instatiates CFGs and prints out legend for i,f in enumerate(config_files): fname = abspath(f) cfgs.append(get_user_config(fname, master_files=master, modules=modules)) cfg_rename = "CFG {}".format(i + 1) # Print out the Config rename legend at the top print(msg.format(cfg_rename, fname)) # Start building the table header header.append(cfg_rename) tmsg+="{:<30}" # Print out a nice table header header.append("Default") column_header = tmsg.format(*header) msg_len = len(column_header) banner = "=" * msg_len print("\n" + column_header) print(banner) # Use the master config to look at everything mcfg = cfgs[0].mcfg.cfg for s in mcfg.keys(): for i in mcfg[s].keys(): showit = False values = ["Not Found" for c in cfgs] total_count +=1 # Iterate through all the configs, find a value for zz, ucfg in enumerate(cfgs): cfg = ucfg.cfg # Is the section in the CFG if s in cfg.keys(): # is the item in there if i in cfg[s].keys(): values[zz] = str(cfg[s][i]) # Always grab the default m = str(mcfg[s][i].default) # Check one value against all to see if theyre mistmatched mismatched = [True for cv in values if values[0] != cv ] # Non Default check non_default = [True for v in values if v != m] # Show if any values are mismatched if len(mismatched) > 0: showit = True mismatch_count += len(mismatched) elif m.lower() != "none" and len(non_default) > 0: showit = True non_default_count += len(non_default) if showit: table = [s,i] table += values table.append(m) print(tmsg.format(*table)) # # Print out some final details regarding the differences print("\nTotal items checked: {:0.0f}".format(total_count)) print("Total Non-default values: {}".format(non_default_count)) print("Total config mismatches: {:0.0f}\n".format(mismatch_count))
[docs]def inimake(): """ Attempts to walk through making a brand new ini file based on developers master config and recipes. Currently config files can have different configurations based on recipes. This script will ask the user for a decision by looking for: * Triggers built on sections and if that section is removed in another conditional * Triggers built on any_section item trigger which is also removed in another conditional """ parser = argparse.ArgumentParser(description=" Walk through creating a " " brand new config file " " guided by the master " " config and recipes!") parser.add_argument('--master', '-mf', metavar='MF', type=str, nargs='+', help='Path to a config file that used to check ' 'against') parser.add_argument('--modules', '-m', metavar='M', type=str, nargs='+', help="Modules name with an attribute __CoreConfig__ that" " is a path to a master config file for checking" " against") parser.add_argument('--version', action='version', version=('%(prog)s {version}' '').format(version=__version__)) args = parser.parse_args() hdr = " Welcome to inimake V{}".format(__version__) print("="*(len(hdr) + 1)) print(hdr) print("="*(len(hdr) + 1)) mcfg = MasterConfig(path=args.master, modules=args.modules) ucfg = UserConfig(None, mcfg=mcfg) # Start with a blank Config ucfg.raw_cfg = {} print("Building a new config file from scratch, if I have any questions" " I will ask!") ################################# SECTIONS ################################ print("Looking through {:0.0f} config recipes that could be potential " " decisions...".format(len(mcfg.recipes))) # Look at all the sections choices = find_options_in_recipes(mcfg.recipes, mcfg.cfg.keys(), "remove_section") print("There are {} sectional choices to be made...\n" "".format(len(choices))) selections = ask_config_setup(choices) # Add sections that don't end being in a choice which assumes its default for s in mcfg.cfg.keys(): section_a_choice = [True for c in choices if s in c] if not section_a_choice or s in selections: ucfg.raw_cfg[s] = {} # Apply recipes to clean up stuff that was added and populate ucfg.cfg ucfg.apply_recipes() ################################# ITEMS ################################### # # Look at all the items in all the sections # print("Great! Let's go through items inside each section...") # decisions = {} # questions = 0 # for s in ucfg.cfg.keys(): # decisions[s] = find_options_in_recipes(mcfg.recipes, # mcfg.cfg.keys(), # "remove_items", # condition_position=1) # if decisions[s]: # questions += len(decisions[s]) # # print("There are {} items related choices to be made...\n" # "".format(questions)) # # for s, choices in decisions.items(): # selections = ask_config_setup(choices, section=s, # num_questions=questions) # for i in selections: # ucfg.raw_cfg[s][i] = mcfg.cfg[s][i].default # # # Apply recipes to clean up stuff that was added and populate ucfg.cfg # ucfg.apply_recipes() # # ################################ Values ################################# # # Look at all the values in all the items # print("Great! Let's go through the values that might need choosing...") # decisions = {} # questions = 0 # for s in ucfg.cfg.keys(): # decisions[s] = {} # # for i in mcfg.cfg[s].keys(): # opts = mcfg.cfg[s][i].options # default = mcfg.cfg[s][i].default # if opts != None and type(default) != list : # # decisions[s][i] = find_options_in_recipes(mcfg.recipes, # opts, # default, # condition_position=2) # if decisions[s][i]: # questions += len(decisions[s]) # # print("There are {} value related choices to be made...\n" # "".format(questions)) # # for s, choices in decisions.items(): # selections = ask_config_setup(choices, section=s, # num_questions=questions) # for i in selections: # ucfg.raw_cfg[s][i] = mcfg.cfg[s][i].default ################################# END #################################### # Clean up and report to the user print("Excellent! All finished with questions...") out_f = "generated_config.ini" print("Outputting to {}".format(out_f)) generate_config(ucfg, out_f, cli=True) msg = ("Your config still need some populating, use the command below to " "see:\n\n inicheck -f {}").format(out_f) if args.modules != None: msg += " -m {}".format(args.module) else: msg += " -mf {}".format(" ".join(mcfg.paths)) print(msg)
[docs]def check_for_changes(directory, modules=[], paths=[]): """ Returns files with changes found in the changelog. Using the change log this function goes through all the files in the directory and returns any filenames with a config entry in python code that will error out This script only looks at and reports changes in found for sections and items listed in the changelog. Currently ignores default changes. Args: directory: Folder with python scripts to check for instances of changes paths: list of paths to an inicheck changelog modules: list of python modules with an __config_changelog__ attribute Returns: change_instances: dictionary with filenames as keys and line numbers in a list as the value. """ changelogs = [] change_instances = {} if modules: mcfg = MasterConfig(modules=modules) changelogs += mcfg.changelogs if paths: changelogs += paths ch = ChangeLog(paths=changelogs, mcfg=mcfg) # Cycle through all the changes listed in changelogs for change in ch.changes: old = "/".join([c for c in change[0] if c != 'any']) new = "/".join([c for c in change[1] if c not in ['removed','any']]) if not new: new = 'Removed' # Format a string to show the change found full_change_str = "{} --> {}".format(old, new) # Form the item name in a config scenario item_str = "['{}']".format(change[0][1]) # Formulate a section string in a config usage scenario perfect_match = "['{}']{}".format(change[0][0], item_str) secondary_match = "config{}".format(item_str) # Assign empty list to the file name for line numbers change_instances[full_change_str] = [] # Look at every file each time for r, directories, files in os.walk(directory): pyfiles = [join(r,f) for f in files if f.split(".")[-1] == 'py'] # only at python files for f in pyfiles: # Read in file contents with open(f,'r') as fp: lines = fp.readlines() fp.close() line_numbers = [] # Check every line for i, line in enumerate(lines): # replace all quotes to single simple = line.replace('"', "'") if perfect_match in simple or secondary_match in simple.lower(): line_numbers.append(i) if line_numbers: change_instances[full_change_str].append([f, line_numbers]) return change_instances
[docs]def detect_file_changes(): """ CLI tool for examining a python repo and reporting any old python code that references a deprecated config file entry as reported in a changelog """ parser = argparse.ArgumentParser(description='Search for python code ' 'referencing old config items ' 'and reports them') parser.add_argument('directory', help='Directory containing python code to ' 'search for old config items') parser.add_argument('--modules', '-m', metavar='M', type=str, nargs='+', help="Modules name with an attribute __CoreConfig__ that" " is a path to a master config file for checking" " against") parser.add_argument('--version', action='version', version=('%(prog)s {version}' '').format(version=__version__)) args = parser.parse_args() print("\nSearching {} for any deprecated config file sections/items in " "any python files...\n".format(args.directory)) instances = check_for_changes(args.directory, modules=args.modules) found = False msg = "{0:<50}{1:<50}{2:<50}" hdr = msg.format("Suggested Change", "Affected File", "Line Numbers") print(hdr) print("=" * len(hdr)) for change, affected in instances.items(): if affected: for file, line_numbers in affected: found = True print("{0:<50}{1:<50}{2:<50}".format(change, file, ', '.join([str(s) for s in set(line_numbers)]))) if not found: print("No instances from the changelog(s) were found in the code under " "{}".format(args.directory)) print("")
if __name__ == '__main__': main()