Source code for inicheck.iniparse

import os
from .utilities import  remove_chars, remove_comment
from collections import OrderedDict


[docs]def read_config(fname): """ Opens and reads in the config file in its most raw form. Creates a dictionary of dictionaries that contain the string result Args: fname: Real path to the config file to be opened Returns: config: dict of dicts containing the info in a config file """ with open(fname, encoding = 'utf-8') as f: lines = f.readlines() f.close() sections = parse_sections(lines) sec_and_items = parse_items(sections) config = parse_values(sec_and_items) return config
[docs]def parse_entry(info, item=None, valid_names=None): """ Parse the values found in the master config where the entries are a little more complicated. Specifically this deals with syntax issues and whether a entry option is valid or not. This should be used only after knowning the section and item. Args: info: a single line or a List of lines to parse containing master config options item: Item name were parsing. Only for reporting errors purposes valid_names: valid property names to be parsing. Returns: properties: dictionary containing the properties as keys """ properties = OrderedDict() if type(info) != list: info = [info] last_three = [] for s in info: if '=' in s: a = s.split('=') else: raise ValueError('\n\nMaster Config file missing an equals sign in' ' entry or missing a comma right before the item ' '"{0}" in the entry:\n"{1}"\nIssue generated from:' ' \n>> "{2}"'.format(item, info, s)) name = (a[0].lower()).strip() # Check for constraints on potential names of entries if valid_names != None and name not in valid_names or len(a) > 2: raise ValueError("\nInvalid property set in the master config file," " available options are: \n * {0}\n" "\nIf this is supposed to be a recipe, you may have" " forgotten to add keyword 'recipe' to the section" " name.\nIssue generated from: \n>> '{1}'" "".format("\n * ".join(valid_names), s)) value = a[1].strip() result = [] value = value.replace('\n'," ") value = value.replace('\t',"") # Is there a list of values provided? if '[' in value: if ']' not in value: raise ValueError('Missing bracket or commas used in a list' ' instead of spaces in config file under' ' {0} in the entry:\n"{1}"'.format(name, info)) else: value = (''.join(c for c in value if c not in '[]')) value = value.split(' ') properties[name] = value return properties
[docs]def parse_sections(lines): """ Returns a dictionary containing all the sections as keys with a single string of the contents after the section Args: lines: a list of string lines containing all the raw info of a cfg file Returns: sections: dictionary containing keys that are the section names and values that are strings of the contents between sections """ result = OrderedDict() section = None i = 0 for i in range(len(lines)): line = remove_chars(lines[i],'\t') # Comment checking line = remove_comment(line) # Watch out for extraneous chars at the beginning an end line = line.strip() # Check for empty line first if line and line not in os.linesep: # Look for section if line.startswith('['): # Look for open brackets if ']' in line: # Isolate the section and anything else in the same line data = line.split("]") section = data[0] # join the rest bask together data = "]".join(data[1:]) # Clean up the section name section = (remove_chars(section,'[]')).lower().strip() # If the section already exists then we have seen it twice if section in result.keys(): raise ValueError("Section name {} already used in " "config, consider renaming it to " "something unique.".format(section)) result[section] = [] # If the data is not empty append it if data: result[section].append(data) else: # This protects from funky syntax in a config file and alerts the user if section == None: raise Exception("Non-section like syntax before any " "sections were identified at line {0} in " "config file. Please use bracketed sections" " or use # or ; to write comments." "".format(i)) else: result[section].append(line) return result
[docs]def parse_items(parsed_sections_dict, mcfg=None): """ Takes the output from parse_sections and parses the items in each section Args: parsed_sections_dict: dictionary containing keys as the sections and values as a continuous string of the Returns: result: dictionary of dictionaries containing sections,items, and their values """ result = OrderedDict() for k,v in parsed_sections_dict.items(): item = None result[k] = OrderedDict() for i,val in enumerate(v): val = val.lstrip() # Look for item notation if ':' in val: # Only split on the first colon to avoid collisions with datetime parse_loc = val.index(':') parseable = [val[0:parse_loc],val[parse_loc + 1:]] item = parseable[0].lower().strip() result[k][item] = '' # Check for a value right after the item name potential_value = (parseable[-1].replace('\n', ' ')).lstrip() # Avoid stashing properties in line with the item if '=' not in potential_value: result[k][item] = potential_value # Property value provided in line with item else: result[k][item] += " " + potential_value # User added line returns likely for readability else: result[k][item] += " " + val # Perform a final cleanup if item != None: if ',' in result[k][item]: final = [e.strip() for e in result[k][item].split(',')] result[k][item] = ", ".join(final) result[k][item] = result[k][item].strip() return result
[docs]def parse_values(parsed_items): """ Takes the output from parse_items and parses any values or properties provided in each item placing the strings into a list. If no properties are defined then it will clean up values provided and put them in a list of len one. Args: parsed_sections_dict: dict of dicts containing joined strings found under each item Returns: result: dictionary of dictionaries containing sections, items, and the values provided as a list """ result = OrderedDict() for section in parsed_items.keys(): result[section] = OrderedDict() for item, val in parsed_items[section].items(): value = val.strip() # list was provided if ',' in val: final = [v.strip() for v in value.split(',') if v != ''] # For use later always make it a list else: # watch out for items that have commented out values if value != '': final = [value] else: final = [] # If there are values provided add them if final: result[section][item] = final return result
[docs]def parse_changes(change_list): """ Takes in a section and item parsed dictionary and parses on > forming a list. It works as the following: To move an item to another section or item name section/item -> new_section/new_item section/item -> Removed Args: change_list: List of modifcations that occured to the config Returns: result: a list of a list length 2 containing of list length 4 representing section item property value which are all any by default. """ results = [] for i,s in enumerate(change_list): if "->" in s: changes = [c.lower().strip() for c in s.split("->")] else: raise ValueError("Invalid changelog syntax at line " " {}. A changelog entry must follow" " <section>/<item>/<property>/<value> ->" " <section>/<item>/<property>/<value> or REMOVED" "".format(i)) # build out an any list and populate it final_changes = [] for ii,c in enumerate(changes): mods = ["any" for i in range(4)] mod_lst = [] if "/" in c: mod_lst = c.split("/") # Catch singular sections referenced else: mod_lst.append(c) mods[0:len(mod_lst)] = mod_lst final_changes.append(mods) results.append(final_changes) return results