diff --git a/.idea/misc.xml b/.idea/misc.xml index 36e0613..279105b 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,7 +1,7 @@ - - + \ No newline at end of file diff --git a/.idea/pyuserconfig.iml b/.idea/pyuserconfig.iml index 4becf17..85c7612 100644 --- a/.idea/pyuserconfig.iml +++ b/.idea/pyuserconfig.iml @@ -1,8 +1,10 @@ - - + + + + diff --git a/.idea/shelf/Uncommitted_changes_before_Checkout_at_29_03_2024_21_55_[Changes]/shelved.patch b/.idea/shelf/Uncommitted_changes_before_Checkout_at_29_03_2024_21_55_[Changes]/shelved.patch new file mode 100644 index 0000000..22e45fa --- /dev/null +++ b/.idea/shelf/Uncommitted_changes_before_Checkout_at_29_03_2024_21_55_[Changes]/shelved.patch @@ -0,0 +1,40 @@ +Index: .idea/pyuserconfig.iml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP +<+>\n\n \n \n \n \n \n \n \n +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +diff --git a/.idea/pyuserconfig.iml b/.idea/pyuserconfig.iml +--- a/.idea/pyuserconfig.iml (revision cba08629cbfd9427213cb8e1719d2ce1e601bdfc) ++++ b/.idea/pyuserconfig.iml (date 1711709021136) +@@ -1,8 +1,10 @@ + + + +- +- ++ ++ ++ ++ + + + +Index: .idea/misc.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP +<+>\n\n \n +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +diff --git a/.idea/misc.xml b/.idea/misc.xml +--- a/.idea/misc.xml (revision cba08629cbfd9427213cb8e1719d2ce1e601bdfc) ++++ b/.idea/misc.xml (date 1711709021140) +@@ -1,4 +1,4 @@ + + +- ++ + +\ No newline at end of file diff --git a/.idea/shelf/Uncommitted_changes_before_Checkout_at_29_03_2024_21_55__Changes_.xml b/.idea/shelf/Uncommitted_changes_before_Checkout_at_29_03_2024_21_55__Changes_.xml new file mode 100644 index 0000000..082f683 --- /dev/null +++ b/.idea/shelf/Uncommitted_changes_before_Checkout_at_29_03_2024_21_55__Changes_.xml @@ -0,0 +1,4 @@ + + \ No newline at end of file diff --git a/cli/__init__.py b/cli/__init__.py index f85c788..79b627b 100755 --- a/cli/__init__.py +++ b/cli/__init__.py @@ -1,147 +1,32 @@ -import sys import os -import tempfile -import re import argparse -import time from userconfig.cfgfile import Conf from userconfig.tools import Debug from userconfig.checks import classes_for_host -#FIXME: fill in: from userconfig.tools import ... - - -class Usage(Exception): - def __init__(self, msg): - self.msg = msg - - -def workconf(directory, depth=2): - """walks through directory, collecting all filenames, returns list of all filenames""" - dirs = os.listdir(directory) - ret = [] - debug.stdout(" ================ workconf ===============", 1) - debug.stdout(" Finding files in directory %s." % directory, 4) - for d in dirs: - name = directory+"/"+d - if os.path.isdir(name): - workconf(name, depth+1) - if name.endswith(".swp"): - continue - if d == ".svn": - continue - ret.append(name) - debug.stdout(" +++ Found file %s in directory %s" % (name, directory), 4) - debug.stdout(" ================ workconf ===============", 1) - return ret - - - - -def build_file(classfiles, destfile, commentstring): - """open all classfiles, assemble them and write the contents into a tempfile - returns the name of tempfile - :param classfiles: - :param destfile: - :param commentstring: - :return: str - """ - content = [] - debug.stdout(" ================ build_file ===============", 1) - if commentstring != "": - debug.stdout(" +++ commentstring found, adding header.", 3) - content.append(commentstring + " " + cfg.get("Main","stamp") + " " + time.strftime("%+") + "\n") - - for f in classfiles: - debug.stdout(" +++ Merging %s." % f, 4) - fp = open(f, "r") - filecontent = fp.read() - fp.close() - if commentstring == "": - # look for stamp in content, replace with real stamp - if re.search(re.escape(cfg.get("Main","stampreplace")), filecontent): - debug.stdout(" +++ commentstring empty, replacing stamp in file", 3) - filecontent = re.sub(re.escape(cfg.get("Main","stampreplace")), cfg.get("Main","stamp"), filecontent) - content.append(filecontent) - - (tempfd, tempfilename) = tempfile.mkstemp(prefix=os.path.basename(destfile), dir="/tmp") - - try: - fp = os.fdopen(tempfd, "w") - except: - Tools.error("Cannot write to temporary file %s" % tempfilename) - os.remove(tempfilename) - return False - debug.stdout(" +++ Writing merged files into tempfile %s." % tempfilename, 3) - for block in content: - fp.write(block) - fp.write("\n") - fp.close() - debug.stdout(" ================ build_file ===============", 1) - return tempfilename - - -def process_all_files(destfiles, dir_config): - """processes all files in destfiles, generate files from classes, compare and copy if necessary""" - debug.stdout(" ================ process_all_files ===============", 1) - for df in destfiles.keys(): - debug.stdout(" ??? Processing source files for %s." % df, 2) - if not os.path.exists(os.path.dirname(df)): - debug.stdout(" +++ Directory %s does not exist, creating" % os.path.dirname(df), 1) - os.mkdir(os.path.dirname(df)) - if not os.path.isdir(os.path.dirname(df)): - debug.stdout(" --- Destination directory %s does not exist, skipping." % os.path.dirname(df), 1) - return False - # assemble file to tmp - commentstring = "" - if dir_config.check("Main", "commentstring"): - commentstring = dir_config.get("Main", "commentstring") - debug.stdout(" +++ Found commentstring %s in %s" % (commentstring, df), 3) - - tempfilename = build_file(destfiles[df], df, commentstring) - if not tempfilename: - debug.stdout(" --- Error while creating temp file for %s, skipping." % df, 1) - continue - debug.stdout(" +++ Merged files %s for %s into %s" % (str(destfiles[df]), df, tempfilename), 2) - - # diff assembled file and config file - if Tools.diff(df, tempfilename, commentstring, debug): - debug.stdout("File %s has changed" % df, 0) - if not Tools.user_config_generated(df, cfg): - debug.stdout(" +++ %s not generated by userconfig, backing up." % df, 2) - # file not generated from userconfig -> back up - Tools.backup_file(df, debug) - # copy tmp file to real location - debug.stdout("Copy %s to %s." % (tempfilename, df), 0) - Tools.copy_file(tempfilename, df, debug) - # remove tmp - debug.stdout(" +++ Removing temporary file %s." % tempfilename, 2) - os.remove(tempfilename) - debug.stdout(" ================ process_all_files ===============", 1) - - -classchecks = Checks() +from userconfig import Userconfig def main(): parser = argparse.ArgumentParser(prog='userconfig', - description='Manages configuration files, usually in the user home directory') + description='Manages configuration files, usually in the user home directory') parser.add_argument('-v', - help='Verbosity level (multiple v for higher level)', - dest='verbose', action='count', default=0) + help='Verbosity level (multiple v for higher level)', + dest='verbose', action='count', default=0) parser.add_argument('-f', '--file', - help='userconfig2.cfg config file', - dest='file', action='store') + help='userconfig2.cfg config file', + dest='file', action='store') cmdline = parser.parse_args() cfg = Conf(cmdline.file) debug = Debug() cfg.set_debug(debug) + uc = Userconfig(cfg) - cfg.debug(f"Verbose level is {cfg.debug._verbose}", 1) + cfg.debug(f"Verbose level is {cfg.debug.get_verbose()}", 1) cfg.debug("================ main ===============", 1) temp_classes = "" for h in classes_for_host(): - temp_classes=temp_classes + str(h) + "," + temp_classes = temp_classes + str(h) + "," cfg.debug("+++ Current host is in classes %s" % temp_classes, 1) configdir = cfg.get("configdir") for d in os.listdir(configdir): @@ -158,11 +43,11 @@ def main(): continue else: cfg.debug("+++ Processing files in %s" % name, 2) - (destfiles, dirConfig) = workdir(name) + (destfiles, dirConfig) = uc.workdir(name) if isinstance(destfiles, dict): if len(destfiles.keys()) > 0: cfg.debug("+++ Building %d files: %s" % (len(destfiles.keys()), destfiles.keys()), 3) - process_all_files(destfiles, dirConfig) + uc.process_all_files(destfiles, dirConfig) else: cfg.debug("--- No files found for %s, skipping." % name, 1) diff --git a/userconfig/__init__.py b/userconfig/__init__.py index 2b68104..56cccc9 100644 --- a/userconfig/__init__.py +++ b/userconfig/__init__.py @@ -1,13 +1,120 @@ from userconfig.tools import get_config import subprocess from userconfig.checks import classes_for_host +import os +from userconfig.tools import diff, user_config_generated, backup_file, copy_file +import time +import re +import tempfile -class Userconfig(): + +class Userconfig: _cfg = None def __init__(self, cfg): self._cfg = cfg + def build_file(self, classfiles, destfile, commentstring): + """open all classfiles, assemble them and write the contents into a tempfile + returns the name of tempfile + :param classfiles: + :param destfile: + :param commentstring: + :return: str + """ + content = [] + self._cfg.debug.stdout(" ================ build_file ===============", 1) + if commentstring != "": + self._cfg.debug.stdout(" +++ commentstring found, adding header.", 3) + content.append(commentstring + " " + self._cfg.get("stamp") + " " + time.strftime("%+") + "\n") + + for f in classfiles: + self._cfg.debug.stdout(" +++ Merging %s." % f, 4) + fp = open(f, "r") + filecontent = fp.read() + fp.close() + if commentstring == "": + # look for stamp in content, replace with real stamp + if re.search(re.escape(self._cfg.get("stampreplace")), filecontent): + self._cfg.debug.stdout(" +++ commentstring empty, replacing stamp in file", 3) + filecontent = re.sub(re.escape(self._cfg.get("stampreplace")), self._cfg.get("stamp"), + filecontent) + content.append(filecontent) + + (tempfd, tempfilename) = tempfile.mkstemp(prefix=os.path.basename(destfile), dir="/tmp") + + try: + fp = os.fdopen(tempfd, "w") + except Exception as e: + self._cfg.debug.stderr(f"Cannot write to temporary file {tempfilename}: {e}") + os.remove(tempfilename) + return False + self._cfg.debug.stdout(" +++ Writing merged files into tempfile %s." % tempfilename, 3) + for block in content: + fp.write(block) + fp.write("\n") + fp.close() + self._cfg.debug.stdout(" ================ build_file ===============", 1) + return tempfilename + + def process_all_files(self, destfiles, dir_config): + """processes all files in destfiles, generate files from classes, compare and copy if necessary""" + self._cfg.debug.stdout(" ================ process_all_files ===============", 1) + for df in destfiles.keys(): + self._cfg.debug.stdout(" ??? Processing source files for %s." % df, 2) + if not os.path.exists(os.path.dirname(df)): + self._cfg.debug.stdout(" +++ Directory %s does not exist, creating" % os.path.dirname(df), 1) + os.mkdir(os.path.dirname(df)) + if not os.path.isdir(os.path.dirname(df)): + self._cfg.debug.stdout(f" --- Destination directory {os.path.dirname(df)} does not exist, skipping.", + 1) + return False + # assemble file to tmp + commentstring = "" + if dir_config.check("Main", "commentstring"): + commentstring = dir_config.get("Main", "commentstring") + self._cfg.debug.stdout(" +++ Found commentstring %s in %s" % (commentstring, df), 3) + + tempfilename = self.build_file(destfiles[df], df, commentstring) + if not tempfilename: + self._cfg.debug.stdout(" --- Error while creating temp file for %s, skipping." % df, 1) + continue + self._cfg.debug.stdout(" +++ Merged files %s for %s into %s" % (str(destfiles[df]), df, tempfilename), 2) + + # diff assembled file and config file + if diff(df, tempfilename, commentstring, self._cfg): + self._cfg.debug.stdout("File %s has changed" % df, 0) + if not user_config_generated(df, self._cfg): + self._cfg.debug.stdout(" +++ %s not generated by userconfig, backing up." % df, 2) + # file not generated from userconfig -> back up + backup_file(df, self._cfg) + # copy tmp file to real location + self._cfg.debug.stdout("Copy %s to %s." % (tempfilename, df), 0) + copy_file(tempfilename, df, self._cfg) + # remove tmp + self._cfg.debug.stdout(" +++ Removing temporary file %s." % tempfilename, 2) + os.remove(tempfilename) + self._cfg.debug.stdout(" ================ process_all_files ===============", 1) + + def workconf(self, directory, depth=2): + """walks through directory, collecting all filenames, returns list of all filenames""" + dirs = os.listdir(directory) + ret = [] + self._cfg.debug.stdout(" ================ workconf ===============", 1) + self._cfg.debug.stdout(" Finding files in directory %s." % directory, 4) + for d in dirs: + name = directory + "/" + d + if os.path.isdir(name): + self.workconf(name, depth + 1) + if name.endswith(".swp"): + continue + if d == ".svn": + continue + ret.append(name) + self._cfg.debug.stdout(" +++ Found file %s in directory %s" % (name, directory), 4) + self._cfg.debug.stdout(" ================ workconf ===============", 1) + return ret + def workdir(self, directory): """walks through all host classes, checking if the classes directory exists in directory then collect all filenames within this directory and return a dict of all files for this @@ -21,17 +128,17 @@ class Userconfig(): return {}, None # get config file for directory - dir_config = get_config(directory + "/" + cfg.get("configfile")) + dir_config = get_config(directory + "/" + self._cfg.get("configfile"), self._cfg) if not dir_config: - self._cfg.debug.stdout(f' --- Cannot read config file {self._cfg.get("configfile")} in {directory}, skipping.', 1) + self._cfg.debug.stdout(f' --- Cannot read config file {self._cfg.get("configfile")} in {directory}, ' + f'skipping.', 1) return {}, None - destdir = dir_config.get(section="Main",option="dest") + destdir = dir_config.get(section="Main", option="dest") # destfiles is a dict of all files that will be created from the classes config # key is the destination filename, values are all classes filenames that are used to # build the file destfiles = {} - reverse_sort = False try: reverse_sort = dir_config.get(section="Main", option="reverse", boolean=True) except ValueError: @@ -41,27 +148,28 @@ class Userconfig(): subprocess.call([directory + "/install.sh"]) # walk through all know classes in directory and find filenames - for h in classes_for_host(reverse_sort): #### continue here + for h in classes_for_host(reverse_sort): # build classes directory if h[0] != "": classdir = directory+"/"+h[0]+"_"+h[1] else: classdir = directory+"/"+h[1] - debug.stdout(" ??? Looking for directory %s." % classdir, 4) + self._cfg.debug.stdout(" ??? Looking for directory %s." % classdir, 4) # if class directory exists if os.path.isdir(classdir): - debug.stdout(" +++ Found directory %s, getting files." % classdir, 4) + self._cfg.debug.stdout(" +++ Found directory %s, getting files." % classdir, 4) # get list of files within this class directory - tempfiles = workconf(classdir) - debug.stdout(" +++ Got %d files: %s." % (len(tempfiles), str(tempfiles)), 4) + tempfiles = self.workconf(classdir) + self._cfg.debug.stdout(" +++ Got %d files: %s." % (len(tempfiles), str(tempfiles)), 4) # put files into dict for f in tempfiles: destname = destdir+os.path.basename(f) # destination filename - if not destname in destfiles: + if destname not in destfiles: destfiles[destname] = [] - destfiles[destname].append(f) # append each file to dict - debug.stdout(" +++ Added file to %s, now %d files: %s" % (destname, len(destfiles[destname]), destfiles[destname]), 4) + destfiles[destname].append(f) # append each file to dict + self._cfg.debug.stdout(f" +++ Added file to {destname}, now {len(destfiles[destname])} files: " + f"{destfiles[destname]}", 4) - debug.stdout(" === workdir: %s, Files: %s" % (directory, str(destfiles)), 3) - debug.stdout(" ================ workdir ===============", 1) + self._cfg.debug.stdout(" === workdir: %s, Files: %s" % (directory, str(destfiles)), 3) + self._cfg.debug.stdout(" ================ workdir ===============", 1) return destfiles, dir_config diff --git a/userconfig/tools.py b/userconfig/tools.py index c9b628f..aceb172 100644 --- a/userconfig/tools.py +++ b/userconfig/tools.py @@ -6,7 +6,7 @@ import time import shutil -class Debug(): +class Debug: _verbose = 0 def __init__(self, verbose=0): @@ -18,6 +18,9 @@ class Debug(): def add_verbose(self): self._verbose += 1 + def get_verbose(self): + return self._verbose + def stdout(self, out, level=0): if self._verbose >= level: print(out) @@ -29,10 +32,9 @@ class Debug(): def get_config(filename, cfg): """reads filename as config, checks for DEST parameter and returns cfgfile object""" - ret = None try: ret = Conf(filename) - except: + except ValueError: cfg.debug.stderr("Error reading config file %s" % filename) return False # check for DEST parameter @@ -50,21 +52,22 @@ def read_skip_comment(fp, commentstring): """ for line in fp: line = line[:-1] - if (commentstring != "" and not re.match("^"+re.escape(commentstring), line)) and line !="" and not re.match("^\s+$", line): + if ((commentstring != "" and not re.match("^"+re.escape(commentstring), line)) and line != "" and + not re.match(r"^\s+$", line)): yield line -def diff(destfile, tempfile, commentstring, debug): +def diff(destfile, tempfile, commentstring, cfg): """diff destfile and tempfile, returns True if files differ, False if they are the same""" - debug.stdout("Diffing %s and %s" % (destfile, tempfile), 3) + cfg.debug.stdout("Diffing %s and %s" % (destfile, tempfile), 3) if not os.path.isfile(destfile): - debug.stdout("Destfile %s does not exist, returning True." % destfile, 3) + cfg.debug.stdout("Destfile %s does not exist, returning True." % destfile, 3) # destfile does not exist -> copy tempfile over return True # if not destfile if not os.path.isfile(tempfile): # tempfile does not exist, this should never happen - error("Temporary file %s does not exist, this should not happen." % tempfile) + cfg.debug.stderr("Temporary file %s does not exist, this should not happen." % tempfile) sys.exit(1) # if not tempfile @@ -75,13 +78,13 @@ def diff(destfile, tempfile, commentstring, debug): if line1 != line2: fp1.close() fp2.close() - debug.stdout("%s differs, return true" % destfile, 3) + cfg.debug.stdout("%s differs, return true" % destfile, 3) return True # if differ # for line fp1.close() fp2.close() - debug.stdout("%s is the same, return false" % destfile, 3) + cfg.debug.stdout("%s is the same, return false" % destfile, 3) return False @@ -92,47 +95,47 @@ def user_config_generated(filename, cfg): # filename does not exist, so it was not generated by userconfig return False - if not cfg.check("Main","stamp"): + if not cfg.check("Main", "stamp"): # no STAMP in userconfig.cfg, so no way to check if file was generated by userconfig return False fp = open(filename, "r") for line in fp: - if re.search(re.escape(cfg.get("Main","stamp")), line): + if re.search(re.escape(cfg.get("Main", "stamp")), line): return True return False -def backup_file(filename, debug): +def backup_file(filename, cfg): """make backup of filename, returns True if backup is successful, False else""" if os.path.isfile(filename): - debug.stdout("%s exists, finding backup name." % filename, 3) + cfg.debug.stdout("%s exists, finding backup name." % filename, 3) backupname = filename+".userconfig."+time.strftime("%F") testbackupname = backupname counter = 0 while os.path.isfile(testbackupname): - counter+=1 - testbackupname=backupname+"."+str(counter) - debug.stdout("Renaming %s to %s" % (filename, testbackupname), 1) + counter += 1 + testbackupname = backupname+"."+str(counter) + cfg.debug.stdout("Renaming %s to %s" % (filename, testbackupname), 1) os.rename(filename, testbackupname) return True else: - debug.stdout("%s does not exist, do not need backup." % filename, 3) + cfg.debug.stdout("%s does not exist, do not need backup." % filename, 3) return False -def copy_file(sourcefile, destfile, debug): +def copy_file(sourcefile, destfile, cfg): """copy sourcefile to destfile, returns True if successful, False else""" if os.path.isfile(sourcefile): # sourcefile exists - debug.stdout("Source file %s exists, proceeding with copy." % sourcefile, 3) + cfg.debug.stdout("Source file %s exists, proceeding with copy." % sourcefile, 3) if not os.path.isfile(destfile) or os.access(destfile, os.W_OK): - debug.stdout("Copying %s to %s" % (sourcefile, destfile), 1) + cfg.debug.stdout("Copying %s to %s" % (sourcefile, destfile), 1) shutil.copy(sourcefile, destfile) return True # destfile is writable else: - debug.stdout("Destination file %s does not exist or is not writable." % destfile, 3) + cfg.debug.stdout("Destination file %s does not exist or is not writable." % destfile, 3) return False