summaryrefslogtreecommitdiffstats
path: root/src/lib/Bcfg2/Server/Lint/MergeFiles.py
blob: 68d010316518189d1addc89a07105e705b1067d2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
import os
import copy
from difflib import SequenceMatcher
import Bcfg2.Server.Lint
from Bcfg2.Server.Plugins.Cfg import CfgGenerator

class MergeFiles(Bcfg2.Server.Lint.ServerPlugin):
    """ find Probes or Cfg files with multiple similar files that
    might be merged into one """
    def Run(self):
        if 'Cfg' in self.core.plugins:
            self.check_cfg()
        if 'Probes' in self.core.plugins:
            self.check_probes()

    @classmethod
    def Errors(cls):
        return {"merge-cfg":"warning",
                "merge-probes":"warning"}


    def check_cfg(self):
        for filename, entryset in self.core.plugins['Cfg'].entries.items():
            candidates = dict([(f, e) for f, e in entryset.entries.items()
                               if isinstance(e, CfgGenerator)])
            for mset in self.get_similar(candidates):
                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-probes",
                           "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), copy.copy(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), copy.copy(others),
                                             threshold))
        return rv