Merge pull request 'Refactoring & install with pyproject.toml' (#1) from toml into main

Reviewed-on: #1
This commit is contained in:
lysis 2024-03-30 10:16:16 +01:00
commit 015ea337f2
19 changed files with 457 additions and 495 deletions

1
.gitignore vendored
View File

@ -4,3 +4,4 @@ build/
.idea/ .idea/
.venv .venv
userconfig.egg-info/ userconfig.egg-info/
*.swp

View File

@ -0,0 +1,19 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="PyPep8Inspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="ignoredErrors">
<list>
<option value="E501" />
</list>
</option>
</inspection_tool>
<inspection_tool class="PyShadowingBuiltinsInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="ignoredNames">
<list>
<option value="format" />
</list>
</option>
</inspection_tool>
</profile>
</component>

View File

@ -1,4 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.7 (pyuserconfig)" project-jdk-type="Python SDK" /> <component name="Black">
<option name="sdkName" value="Python 3.6 (pyuserconfig)" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12 (userconfig)" project-jdk-type="Python SDK" />
</project> </project>

View File

@ -1,8 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4"> <module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager"> <component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" /> <content url="file://$MODULE_DIR$">
<orderEntry type="jdk" jdkName="Python 3.7 (pyuserconfig)" jdkType="Python SDK" /> <excludeFolder url="file://$MODULE_DIR$/venv" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
</component> </component>
<component name="TestRunnerService"> <component name="TestRunnerService">

View File

@ -0,0 +1,40 @@
Index: .idea/pyuserconfig.iml
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP
<+><?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<module type=\"PYTHON_MODULE\" version=\"4\">\n <component name=\"NewModuleRootManager\">\n <content url=\"file://$MODULE_DIR$\" />\n <orderEntry type=\"jdk\" jdkName=\"Python 3.7 (pyuserconfig)\" jdkType=\"Python SDK\" />\n <orderEntry type=\"sourceFolder\" forTests=\"false\" />\n </component>\n <component name=\"TestRunnerService\">\n <option name=\"PROJECT_TEST_RUNNER\" value=\"Unittests\" />\n </component>\n</module>
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 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
- <content url="file://$MODULE_DIR$" />
- <orderEntry type="jdk" jdkName="Python 3.7 (pyuserconfig)" jdkType="Python SDK" />
+ <content url="file://$MODULE_DIR$">
+ <excludeFolder url="file://$MODULE_DIR$/venv" />
+ </content>
+ <orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="TestRunnerService">
Index: .idea/misc.xml
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP
<+><?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n <component name=\"ProjectRootManager\" version=\"2\" project-jdk-name=\"Python 3.7 (pyuserconfig)\" project-jdk-type=\"Python SDK\" />\n</project>
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 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
- <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.7 (pyuserconfig)" project-jdk-type="Python SDK" />
+ <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.6 (pyuserconfig)" project-jdk-type="Python SDK" />
</project>
\ No newline at end of file

View File

@ -0,0 +1,4 @@
<changelist name="Uncommitted_changes_before_Checkout_at_29_03_2024_21_55_[Changes]" date="1711745731399" recycled="true" deleted="true">
<option name="PATH" value="$PROJECT_DIR$/.idea/shelf/Uncommitted_changes_before_Checkout_at_29_03_2024_21_55_[Changes]/shelved.patch" />
<option name="DESCRIPTION" value="Uncommitted changes before Checkout at 29/03/2024 21:55 [Changes]" />
</changelist>

View File

View File

@ -1,77 +0,0 @@
#!/usr/bin/env python3
# encoding: utf-8
#
"""
cfgfile.py
Created by Marcus Stoegbauer on 2013-01-12.
"""
import configparser
import os
import re
class Conf(object):
confobj = configparser.RawConfigParser()
cfgfile = ''
debug = None
def __init__(self, filename=None):
"""if filename is set, open config file and initialize the ConfigParser
"""
self.confobj = configparser.RawConfigParser()
if filename:
self.setfilename(filename)
def setdebug(self, debug):
"""docstring for setdebug"""
self.debug = debug
def setfilename(self, filename):
"""initialize the ConfigParser
"""
ret = self.confobj.read(filename)
if len(ret) == 0 or ret[0] != filename:
raise Exception('Cannot read config file ' + filename)
self.cfgfile = filename
if self.debug:
self.debug.debug("Read config file %s" % filename, 2)
self.debug.debug("Replacing environment variables in %s." % filename, 3)
for s in self.confobj.sections():
for (i, val) in self.confobj.items(s):
tempre = re.search(r"\$([A-Z]+)[^A-Z]*", val)
if tempre:
varname = tempre.group(1)
if self.debug:
self.debug.debug("Found variable %s in %s." % (varname, i), 3)
if varname in os.environ:
if self.debug:
self.debug.debug("%s exists in environment, replacing with %s." %
(varname, os.environ[varname]), 3)
self.set(s, i, val.replace("$"+varname, os.environ[varname]))
def get(self, section, option):
"""returns the value of option in section
"""
if not self.cfgfile:
raise Exception('No config file set')
try:
return self.confobj.get(section, option)
except configparser.NoOptionError:
raise ValueError('Option does not exist')
def set(self, section, option, value):
"""docstring for update"""
self.confobj.set(section, option, value)
def getitems(self, section):
"""returns all items in section
"""
if not self.cfgfile:
raise Exception('No config file set')
return self.confobj.items(section)
def check(self, section, option):
"""checks for option in section"""
return self.confobj.has_option(section, option)

View File

@ -1,66 +0,0 @@
#!/usr/bin/env python3
# encoding: utf-8
#
"""
checks.py
Created by Marcus Stoegbauer on 2013-01-10.
"""
import platform
from operator import itemgetter
class Checks(object):
def __init__(self):
pass
def get_short_hostname(self):
"""docstring for getShortHostname"""
hostname = platform.node()
if hostname.count("."):
hostname = hostname.split(".")[0]
return hostname
# def getShortHostname
def __classes_for_host__(self, reverse=False):
"""docstring for __classesForHost"""
classes = []
for c in dir(self):
if c.startswith("__"):
continue
ret = getattr(self, c)()
if type(ret) == tuple and len(ret) == 3:
classes.append(ret)
return map(lambda k: (k[1], k[2]), sorted(classes, key=itemgetter(0), reverse=reverse))
def header(self):
"""docstring for header"""
return (0, "", "header")
def footer(self):
"""docstring for footer"""
return (1000, "", "footer")
def all(self):
"""docstring for all"""
return (998, "", "all")
def arch(self):
"""docstring for arch"""
return (800, "Arch", platform.system())
def hostname(self):
"""docstring for hostname"""
hostname = self.get_short_hostname()
return (10, "Host", hostname)
def app(self):
"""docstring for app"""
hostname = self.get_short_hostname()
if hostname == "glitters":
return (500, "", "rancid_hosts")
else:
return ()
# def app
# def checks

56
cli/__init__.py Executable file
View File

@ -0,0 +1,56 @@
import os
import argparse
from userconfig.cfgfile import Conf
from userconfig.tools import Debug
from userconfig.checks import classes_for_host
from userconfig import Userconfig
def main():
parser = argparse.ArgumentParser(prog='userconfig',
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)
parser.add_argument('-f', '--file',
help='userconfig2.cfg config file',
dest='file', action='store')
cmdline = parser.parse_args()
debug = Debug()
debug.set_verbose(cmdline.verbose)
cfg = Conf(filename=cmdline.file, debug=debug)
uc = Userconfig(cfg)
cfg.debug.stdout(f"Verbose level is {cfg.debug.get_verbose()}", 1)
cfg.debug.stdout("================ main ===============", 1)
temp_classes = ""
for h in classes_for_host():
temp_classes = temp_classes + str(h) + ","
cfg.debug.stdout("+++ Current host is in classes %s" % temp_classes, 1)
configdir = cfg.get("configdir")
for d in os.listdir(configdir):
name = configdir+"/"+d
cfg.debug.stdout("+++ Working in %s" % name, 1)
if not os.path.isdir(name):
cfg.debug.stdout("--- %s is not a directory, skipping." % name, 3)
continue
elif d.startswith(".svn") or d.startswith(".git"):
cfg.debug.stdout("--- %s is .svn or .git, skipping." % name, 3)
continue
elif os.path.isfile(name+"/.ignore"):
cfg.debug.stdout("--- %s contains file .ignore, skipping." % name, 3)
continue
else:
cfg.debug.stdout("+++ Processing files in %s" % name, 2)
(destfiles, dirConfig) = uc.workdir(name)
if isinstance(destfiles, dict):
if len(destfiles.keys()) > 0:
cfg.debug.stdout("+++ Building %d files: %s" % (len(destfiles.keys()), destfiles.keys()), 3)
uc.process_all_files(destfiles, dirConfig)
else:
cfg.debug.stdout("--- No files found for %s, skipping." % name, 1)
if __name__ == '__main__':
main()

19
pyproject.toml Normal file
View File

@ -0,0 +1,19 @@
[build-system]
requires = ["setuptools", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "userconfig"
version = "2.0b-1"
authors = [ {name = "Marcus Stoegbauer", email = "marcus@grmpf.org"} ]
maintainers = [ {name = "Marcus Stoegbauer", email = "marcus@grmpf.org"} ]
description = "Generate config files for user home"
[project.scripts]
userconfig = "cli:main"
[tool.setuptools]
packages = [ "userconfig", "cli" ]
[tool.setuptools.data-files]
"etc" = [ "userconfig2.conf" ]

View File

@ -1,13 +1,5 @@
#!/usr/bin/env python3 # migrated to pyproject.toml
# as reccomended: https://packaging.python.org/en/latest/guides/modernize-setup-py-project/#what-if-something-that-can-not-be-changed-expects-a-setup-py-file
from setuptools import setup
from distutils.core import setup setup()
setup(name="userconfig",
version="0.1",
description="Generate config files for user home",
author="Marcus Stoegbauer",
author_email="marcus@grmpf.org",
packages=["Userconfig"],
scripts=["userconfig.py"],
data_files=[('etc', ['userconfig.cfg'])]
)

View File

@ -1,6 +0,0 @@
[Main]
configdir = $HOME/.userconfig
configfile = userconfig.cfg
debug = 0
stamp = %userconfig_generated 1.0%
stampreplace = $userconfig_stamp$

View File

@ -1,270 +0,0 @@
#!/usr/bin/env python3
# encoding: utf-8
#
"""
userconfig.py
Created by Marcus Stoegbauer on 2013-01-10.
"""
import sys
import os
import tempfile
import re
import getopt
import time
import subprocess
#
import Userconfig.cfgfile as cfgfile
from Userconfig.checks import Checks
import Userconfig.Tools as Tools
debug = Tools.Debug()
classchecks = Checks()
cfg = cfgfile.Conf()
help_message = """
-h help
-v verbose level (multiple v for higher level)
-c userconfig.cfg config file
"""
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.debug(" ================ workconf ===============", 1)
debug.debug(" 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.debug(" +++ Found file %s in directory %s" % (name, directory), 4)
debug.debug(" ================ workconf ===============", 1)
return ret
def workdir(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
directory
"""
debug.debug(" ================ workdir ===============", 1)
debug.debug(" Working on directory %s" % directory, 3)
# skip directory if no CONFIGFILE present
if not os.path.isfile(directory+"/"+cfg.get("Main", "configfile")):
debug.debug(" --- No %s in %s, skipping." % (cfg.get("Main", "configfile"), directory), 1)
return {},None
# get config file for directory
dir_config = Tools.get_config(directory + "/" + cfg.get("Main", "configfile"))
if not dir_config:
debug.debug(" --- Cannot read %s in %s, skipping." % (cfg.get("Main", "configfile"), directory), 1)
return {},None
destdir = dir_config.get("Main","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 = {}
# FIXME: reverse_order should really be a bool in .cfg, implement variable types in cfg file
reverse_sort = False
try:
reverse_sort = (dir_config.get("Main", "reverse") == 'True')
except ValueError:
reverse_sort = False
if os.access(directory + "/install.sh", os.X_OK):
subprocess.call([directory + "/install.sh"])
# walk through all know classes in directory and find filenames
for h in classchecks.__classes_for_host__(reverse_sort):
# build classes directory
if h[0] != "":
classdir = directory+"/"+h[0]+"_"+h[1]
else:
classdir = directory+"/"+h[1]
debug.debug(" ??? Looking for directory %s." % classdir, 4)
# if class directory exists
if os.path.isdir(classdir):
debug.debug(" +++ Found directory %s, getting files." % classdir, 4)
# get list of files within this class directory
tempfiles = workconf(classdir)
debug.debug(" +++ 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:
destfiles[destname] = []
destfiles[destname].append(f) # append each file to dict
debug.debug(" +++ Added file to %s, now %d files: %s" % (destname, len(destfiles[destname]), destfiles[destname]), 4)
debug.debug(" === workdir: %s, Files: %s" % (directory, str(destfiles)), 3)
debug.debug(" ================ workdir ===============", 1)
return destfiles, dir_config
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.debug(" ================ build_file ===============", 1)
if commentstring != "":
debug.debug(" +++ commentstring found, adding header.", 3)
content.append(commentstring + " " + cfg.get("Main","stamp") + " " + time.strftime("%+") + "\n")
for f in classfiles:
debug.debug(" +++ 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.debug(" +++ 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.debug(" +++ Writing merged files into tempfile %s." % tempfilename, 3)
for block in content:
fp.write(block)
fp.write("\n")
fp.close()
debug.debug(" ================ 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.debug(" ================ process_all_files ===============", 1)
for df in destfiles.keys():
debug.debug(" ??? Processing source files for %s." % df, 2)
if not os.path.exists(os.path.dirname(df)):
debug.debug(" +++ 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.debug(" --- 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.debug(" +++ Found commentstring %s in %s" % (commentstring, df), 3)
tempfilename = build_file(destfiles[df], df, commentstring)
if not tempfilename:
debug.debug(" --- Error while creating temp file for %s, skipping." % df, 1)
continue
debug.debug(" +++ 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.debug("File %s has changed" % df, 0)
if not Tools.user_config_generated(df, cfg):
debug.debug(" +++ %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.debug("Copy %s to %s." % (tempfilename, df), 0)
Tools.copy_file(tempfilename, df, debug)
# remove tmp
debug.debug(" +++ Removing temporary file %s." % tempfilename, 2)
os.remove(tempfilename)
debug.debug(" ================ process_all_files ===============", 1)
def main():
configfile_destinations = [os.environ['HOME'] + "/etc/",
os.environ['HOME'] + "/.local/etc"]
configfile = ''
for directory in configfile_destinations:
if os.path.isfile(directory + "/userconfig.cfg"):
configfile = directory + "/userconfig.cfg"
break
try:
try:
opts, args = getopt.getopt(sys.argv[1:], "hdc:v", ["help", "debug", "config="])
except getopt.GetoptError as msg:
raise Usage(msg)
for option, value in opts:
if option == "-v":
debug.addverbose()
if option in ("-h", "--help"):
raise Usage(help_message)
if option in ("-d", "--debug.debug"):
pass
if option in ("-c", "--config"):
configfile = value
except Usage as err:
Tools.error(sys.argv[0].split("/")[-1] + ": " + str(err.msg))
Tools.error("\t for help use --help")
return 2
debug.debug("Verbose level is %d" % debug.verbose, 1)
debug.debug("Using configfile %s." % configfile, 1)
if not os.path.isfile(configfile):
Tools.error("No config file specified.")
return 2
cfg.setdebug(debug)
cfg.setfilename(configfile)
debug.debug("================ main ===============", 1)
tempclasses = ""
for h in classchecks.__classes_for_host__():
tempclasses=tempclasses + str(h) + ","
debug.debug("+++ Current host is in classes %s" % tempclasses, 1)
configdir = cfg.get("Main", "configdir")
for d in os.listdir(configdir):
destfiles = {}
name = configdir+"/"+d
debug.debug("+++ Working in %s" % name, 1)
if not os.path.isdir(name):
debug.debug("--- %s is not a directory, skipping." % name, 3)
continue
elif d.startswith(".svn") or d.startswith(".git"):
debug.debug("--- %s is .svn or .git, skipping." % name, 3)
continue
elif os.path.isfile(name+"/.ignore"):
debug.debug("--- %s contains file .ignore, skipping." % name, 3)
continue
else:
debug.debug("+++ Processing files in %s" % name, 2)
(destfiles, dirConfig) = workdir(name)
if isinstance(destfiles, dict):
if len(destfiles.keys()) > 0:
debug.debug("+++ Building %d files: %s" % (len(destfiles.keys()), destfiles.keys()), 3)
process_all_files(destfiles, dirConfig)
else:
debug.debug("--- No files found for %s, skipping." % name, 1)
if __name__ == '__main__':
main()

175
userconfig/__init__.py Normal file
View File

@ -0,0 +1,175 @@
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:
_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(section="Main", option="commentstring"):
commentstring = dir_config.get(section="Main", option="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
directory
"""
self._cfg.debug.stdout(" ================ workdir ===============", 1)
self._cfg.debug.stdout(" Working on directory %s" % directory, 3)
# skip directory if no CONFIGFILE present
if not os.path.isfile(directory+"/"+self._cfg.get("configfile")):
self._cfg.debug.stdout(f' --- No file {self._cfg.get("configfile")} in {directory}, skipping.', 1)
return {}, None
# get config file for directory
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}, '
f'skipping.', 1)
return {}, None
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 = {}
try:
reverse_sort = dir_config.get(section="Main", option="reverse", boolean=True)
except ValueError:
reverse_sort = False
if os.access(directory + "/install.sh", os.X_OK):
subprocess.call([directory + "/install.sh"])
# walk through all know classes in directory and find filenames
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]
self._cfg.debug.stdout(" ??? Looking for directory %s." % classdir, 4)
# if class directory exists
if os.path.isdir(classdir):
self._cfg.debug.stdout(" +++ Found directory %s, getting files." % classdir, 4)
# get list of files within this class directory
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 destname not in destfiles:
destfiles[destname] = []
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)
self._cfg.debug.stdout(" === workdir: %s, Files: %s" % (directory, str(destfiles)), 3)
self._cfg.debug.stdout(" ================ workdir ===============", 1)
return destfiles, dir_config

62
userconfig/cfgfile.py Normal file
View File

@ -0,0 +1,62 @@
import configparser
import os
import sys
class Conf(object):
_confobj = configparser.ConfigParser(os.environ)
_cfgfiles = []
debug = None
def __init__(self, filename=None, debug=None, force_filename=False):
if debug:
self.set_debug(debug)
filenames = []
if filename:
filenames.append(filename)
if not force_filename:
if os.path.isfile(f'{os.environ.get("HOME")}/etc/userconfig2.conf'):
filenames.append(f'{os.environ.get("HOME")}/etc/userconfig2.conf')
if os.path.isfile(f'{sys.prefix}/etc/userconfig2.conf'):
filenames.append(f'{sys.prefix}/etc/userconfig2.conf')
ret = self.set_filenames(filenames)
if not ret:
raise ValueError(f'Cannot open either configuration file: {",".join(filenames)}')
def set_debug(self, debug):
self.debug = debug
def set_filenames(self, filenames):
ret = self._confobj.read(filenames)
if len(ret) == 0:
return None
if self.debug:
self.debug.stdout("Read config files: %s" % ", ".join(filenames), 2)
return ret
def get(self, option, boolean=False, section='userconfig'):
try:
if boolean:
return self._confobj.getboolean(section, option)
else:
return self._confobj.get(section, option)
except (configparser.NoOptionError, configparser.NoOptionError) as e:
raise ValueError(f'Option {option} does not exist in section {section}: {e}')
def set(self, option, value, section='userconfig'):
try:
self._confobj.set(section, option, value)
except configparser.NoSectionError as e:
raise ValueError(f'Section {section} does not exist: {e}')
def get_items(self, section='userconfig'):
try:
return self._confobj.items(section)
except configparser.NoSectionError as e:
raise ValueError(f'Section {section} does not exist: {e}')
def check(self, option, section='userconfig'):
return self._confobj.has_option(section, option)
def sections(self):
return self._confobj.sections()

19
userconfig/checks.py Normal file
View File

@ -0,0 +1,19 @@
import platform
def get_short_hostname():
hostname = platform.node()
if hostname.count("."):
hostname = hostname.split(".")[0]
return hostname
def classes_for_host(reverse=False):
classes = [(0, "", "header"),
(1000, "", "footer"),
(998, "", "all"),
(800, "Arch", platform.system()),
(10, "Host", get_short_hostname())
]
classes.sort(key=lambda k: k[0], reverse=reverse)
return [(k[1], k[2]) for k in classes]

View File

@ -1,67 +1,49 @@
#!/usr/bin/env python3
# encoding: utf-8
"""
Tools.py
Created by Marcus Stoegbauer on 2013-01-12.
"""
import sys import sys
import os import os
import Userconfig.cfgfile as cfgfile from userconfig.cfgfile import Conf
import re import re
import time import time
import shutil import shutil
class Debug(object): class Debug:
verbose = 0 _verbose = 0
def __init__(self, verbose=0): def __init__(self, verbose=0):
self.setverbose(verbose) self.set_verbose(verbose)
def setverbose(self, verbose): def set_verbose(self, verbose):
"""docstring for setverbose""" self._verbose = verbose
self.verbose = verbose
def addverbose(self): def add_verbose(self):
"""docstring for setverbose""" self._verbose += 1
self.verbose += 1
def debug(self, out, level=0): def get_verbose(self):
"""docstring for debug""" return self._verbose
if self.verbose >= level:
def stdout(self, out, level=0):
if self._verbose >= level:
print(out) print(out)
def stderr(self, out, level=0):
def error(out): if self._verbose >= level:
"""Print error on stderr""" print(str(out)+"\n", file=sys.stderr)
print(str(out)+"\n", file=sys.stderr)
def get_config(filename): def get_config(filename, cfg):
"""reads filename as config, checks for DEST parameter and returns cfgfile object""" """reads filename as config, checks for DEST parameter and returns cfgfile object"""
ret = None
try: try:
ret = cfgfile.Conf(filename) ret = Conf(filename)
except: except ValueError:
error("Error reading config file %s" % filename) cfg.debug.stderr("Error reading config file %s" % filename)
return False return False
# check for DEST parameter # check for DEST parameter
if not ret.check("Main", "dest"): if not ret.check(section="Main", option="dest"):
error("No DEST in config file %s" % filename) cfg.debug.stderr("No dest in config file %s" % filename)
return False return False
# replace $HOME with real home directory
if ret.get("Main", "dest") == "$HOME":
ret.set("Main", "dest", os.environ['HOME'])
# make sure DEST ends with / # make sure DEST ends with /
if not ret.get("Main", "dest").endswith("/"): if not ret.get(section="Main", option="dest").endswith("/"):
ret.set("Main", "dest", ret.get("Main", "dest")+"/") ret.set(section="Main", option="dest", value=ret.get(section="Main", option="dest")+"/")
return ret return ret
@ -70,21 +52,22 @@ def read_skip_comment(fp, commentstring):
""" """
for line in fp: for line in fp:
line = line[:-1] 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 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""" """diff destfile and tempfile, returns True if files differ, False if they are the same"""
debug.debug("Diffing %s and %s" % (destfile, tempfile), 3) cfg.debug.stdout("Diffing %s and %s, comment: %s" % (destfile, tempfile, commentstring), 3)
if not os.path.isfile(destfile): if not os.path.isfile(destfile):
debug.debug("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 # destfile does not exist -> copy tempfile over
return True return True
# if not destfile # if not destfile
if not os.path.isfile(tempfile): if not os.path.isfile(tempfile):
# tempfile does not exist, this should never happen # 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) sys.exit(1)
# if not tempfile # if not tempfile
@ -95,13 +78,13 @@ def diff(destfile, tempfile, commentstring, debug):
if line1 != line2: if line1 != line2:
fp1.close() fp1.close()
fp2.close() fp2.close()
debug.debug("%s differs, return true" % destfile, 3) cfg.debug.stdout("%s differs, return true" % destfile, 3)
return True return True
# if differ # if differ
# for line # for line
fp1.close() fp1.close()
fp2.close() fp2.close()
debug.debug("%s is the same, return false" % destfile, 3) cfg.debug.stdout("%s is the same, return false" % destfile, 3)
return False return False
@ -112,47 +95,47 @@ def user_config_generated(filename, cfg):
# filename does not exist, so it was not generated by userconfig # filename does not exist, so it was not generated by userconfig
return False return False
if not cfg.check("Main","stamp"): if not cfg.check("stamp"):
# no STAMP in userconfig.cfg, so no way to check if file was generated by userconfig # no STAMP in userconfig.cfg, so no way to check if file was generated by userconfig
return False return False
fp = open(filename, "r") fp = open(filename, "r")
for line in fp: for line in fp:
if re.search(re.escape(cfg.get("Main","stamp")), line): if re.search(re.escape(cfg.get("stamp")), line):
return True return True
return False return False
def backup_file(filename, debug): def backup_file(filename, cfg):
"""make backup of filename, returns True if backup is successful, False else""" """make backup of filename, returns True if backup is successful, False else"""
if os.path.isfile(filename): if os.path.isfile(filename):
debug.debug("%s exists, finding backup name." % filename, 3) cfg.debug.stdout("%s exists, finding backup name." % filename, 3)
backupname = filename+".userconfig."+time.strftime("%F") backupname = filename+".userconfig."+time.strftime("%F")
testbackupname = backupname testbackupname = backupname
counter = 0 counter = 0
while os.path.isfile(testbackupname): while os.path.isfile(testbackupname):
counter+=1 counter += 1
testbackupname=backupname+"."+str(counter) testbackupname = backupname+"."+str(counter)
debug.debug("Renaming %s to %s" % (filename, testbackupname), 1) cfg.debug.stdout("Renaming %s to %s" % (filename, testbackupname), 1)
os.rename(filename, testbackupname) os.rename(filename, testbackupname)
return True return True
else: else:
debug.debug("%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 return False
def copy_file(sourcefile, destfile, debug): def copy_file(sourcefile, destfile, cfg):
"""copy sourcefile to destfile, returns True if successful, False else""" """copy sourcefile to destfile, returns True if successful, False else"""
if os.path.isfile(sourcefile): if os.path.isfile(sourcefile):
# sourcefile exists # sourcefile exists
debug.debug("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): if not os.path.isfile(destfile) or os.access(destfile, os.W_OK):
debug.debug("Copying %s to %s" % (sourcefile, destfile), 1) cfg.debug.stdout("Copying %s to %s" % (sourcefile, destfile), 1)
shutil.copy(sourcefile, destfile) shutil.copy(sourcefile, destfile)
return True return True
# destfile is writable # destfile is writable
else: else:
debug.debug("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 return False

6
userconfig2.conf Normal file
View File

@ -0,0 +1,6 @@
[userconfig]
configdir = %(HOME)s/.userconfig
configfile = userconfig2.cfg
debug = 0
stamp = %%userconfig_generated 1.0%%
stampreplace = $userconfig_stamp$