summaryrefslogtreecommitdiffstats
path: root/src/lib/Server
diff options
context:
space:
mode:
authorChris St. Pierre <chris.a.st.pierre@gmail.com>2011-05-17 15:46:57 -0400
committerSol Jerome <sol.jerome@gmail.com>2011-06-14 12:36:45 -0500
commitb7234fe39d1f25eb55320fed8491781aef1ed9fe (patch)
treeefbe27f8d74fc2c5c6af0e00f79e615413759fbd /src/lib/Server
parenta19c87f8dc03132e7257a539c7e486b9c9298aff (diff)
downloadbcfg2-b7234fe39d1f25eb55320fed8491781aef1ed9fe.tar.gz
bcfg2-b7234fe39d1f25eb55320fed8491781aef1ed9fe.tar.bz2
bcfg2-b7234fe39d1f25eb55320fed8491781aef1ed9fe.zip
added bcfg2-lint MergeFiles plugin to suggest config files and probes
that are very similar and could be merged added text wrapping to bcfg2-lint error handling
Diffstat (limited to 'src/lib/Server')
-rw-r--r--src/lib/Server/Lint/MergeFiles.py71
-rw-r--r--src/lib/Server/Lint/__init__.py43
2 files changed, 104 insertions, 10 deletions
diff --git a/src/lib/Server/Lint/MergeFiles.py b/src/lib/Server/Lint/MergeFiles.py
new file mode 100644
index 000000000..1e177acff
--- /dev/null
+++ b/src/lib/Server/Lint/MergeFiles.py
@@ -0,0 +1,71 @@
+import os
+from copy import deepcopy
+from difflib import SequenceMatcher
+import Bcfg2.Options
+import Bcfg2.Server.Lint
+
+class MergeFiles(Bcfg2.Server.Lint.ServerPlugin):
+ """ find Probes or Cfg files with multiple similar files that
+ might be merged into one """
+
+ @Bcfg2.Server.Lint.returnErrors
+ def Run(self):
+ if 'Cfg' in self.core.plugins:
+ self.check_cfg()
+ if 'Probes' in self.core.plugins:
+ self.check_probes()
+
+ def check_cfg(self):
+ for filename, entryset in self.core.plugins['Cfg'].entries.items():
+ for mset in self.get_similar(entryset.entries):
+ self.LintError("merge-cfg",
+ "The following files are similar: %s. "
+ "Consider merging them into a single Genshi "
+ "template." %
+ ", ".join([os.path.join(filename, p)
+ for p in mset]))
+
+ def check_probes(self):
+ probes = self.core.plugins['Probes'].probes.entries
+ for mset in self.get_similar(probes):
+ self.LintError("merge-cfg",
+ "The following probes are similar: %s. "
+ "Consider merging them into a single probe." %
+ ", ".join([p for p in mset]))
+
+ def get_similar(self, entries):
+ if "threshold" in self.config:
+ # accept threshold either as a percent (e.g., "threshold=75") or
+ # as a ratio (e.g., "threshold=.75")
+ threshold = float(self.config['threshold'])
+ if threshold > 1:
+ threshold /= 100
+ else:
+ threshold = 0.75
+ rv = []
+ elist = entries.items()
+ while elist:
+ result = self._find_similar(elist.pop(0), deepcopy(elist),
+ threshold)
+ if len(result) > 1:
+ elist = [(fname, fdata)
+ for fname, fdata in elist
+ if fname not in result]
+ rv.append(result)
+ return rv
+
+ def _find_similar(self, ftuple, others, threshold):
+ fname, fdata = ftuple
+ rv = [fname]
+ while others:
+ cname, cdata = others.pop(0)
+ sm = SequenceMatcher(None, fdata.data, cdata.data)
+ # perform progressively more expensive comparisons
+ if (sm.real_quick_ratio() > threshold and
+ sm.quick_ratio() > threshold and
+ sm.ratio() > threshold):
+ rv.extend(self._find_similar((cname, cdata), deepcopy(others),
+ threshold))
+ return rv
+
+
diff --git a/src/lib/Server/Lint/__init__.py b/src/lib/Server/Lint/__init__.py
index 3b89d1f9e..013cbf2ba 100644
--- a/src/lib/Server/Lint/__init__.py
+++ b/src/lib/Server/Lint/__init__.py
@@ -4,6 +4,7 @@ __all__ = ['Bundles',
'Comments',
'Duplicates',
'InfoXML',
+ 'MergeFiles',
'Pkgmgr',
'RequiredAttrs',
'Validate']
@@ -11,6 +12,7 @@ __all__ = ['Bundles',
import logging
import os.path
from copy import copy
+import textwrap
import lxml.etree
import Bcfg2.Logger
@@ -84,7 +86,9 @@ class ErrorHandler (object):
"properties-schema-not-found":"warning",
"xml-failed-to-parse":"error",
"xml-failed-to-read":"error",
- "xml-failed-to-verify":"error",}
+ "xml-failed-to-verify":"error",
+ "merge-cfg":"warning",
+ "merge-probes":"warning",}
def __init__(self, config=None):
self.errors = 0
@@ -92,6 +96,9 @@ class ErrorHandler (object):
self.logger = logging.getLogger('bcfg2-lint')
+ self._wrapper = textwrap.TextWrapper(initial_indent = " ",
+ subsequent_indent = " ")
+
self._handlers = {}
if config is not None:
for err, action in config.items():
@@ -116,26 +123,42 @@ class ErrorHandler (object):
self._handlers[err](msg)
self.logger.debug(" (%s)" % err)
else:
- self.logger.info("Unknown error %s" % err)
+ # assume that it's an error, but complain
+ self.error(msg)
+ self.logger.warning("Unknown error %s" % err)
def error(self, msg):
""" log an error condition """
self.errors += 1
- lines = msg.splitlines()
- self.logger.error("ERROR: %s" % lines.pop())
- [self.logger.error(" %s" % l) for l in lines]
+ self._log(msg, self.logger.error, prefix="ERROR: ")
def warn(self, msg):
""" log a warning condition """
self.warnings += 1
- lines = msg.splitlines()
- self.logger.warning("WARNING: %s" % lines.pop())
- [self.logger.warning(" %s" % l) for l in lines]
+ self._log(msg, self.logger.warning, prefix="WARNING: ")
def debug(self, msg):
""" log a silent/debug condition """
- lines = msg.splitlines()
- [self.logger.debug("%s" % l) for l in lines]
+ self._log(msg, self.logger.debug)
+
+ def _log(self, msg, logfunc, prefix=""):
+ # a message may itself consist of multiple lines. wrap() will
+ # elide them all into a single paragraph, which we don't want.
+ # so we split the message into its paragraphs and wrap each
+ # paragraph individually. this means, unfortunately, that we
+ # lose textwrap's built-in initial indent functionality,
+ # because we want to only treat the very first line of the
+ # first paragraph specially. so we do some silliness.
+ rawlines = msg.splitlines()
+ firstline = True
+ for rawline in rawlines:
+ lines = self._wrapper.wrap(rawline)
+ for line in lines:
+ if firstline:
+ logfunc("%s%s" % (prefix, line.lstrip()))
+ firstline = False
+ else:
+ logfunc(line)
class ServerlessPlugin (Plugin):