aboutsummaryrefslogtreecommitdiffstats
path: root/confman.py
blob: 41125fd11b1f84492f7f6b01e147567fb4535861 (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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
import os
import re

class ConfigAction(object):
    @classmethod
    def matches(cls, filename):
        raise Exception('Not implemented')

    def __init__(self, config, relpath, source, dest):
        self.source = source
        self.dest = dest
        self.relpath = relpath
        self.config = config

    def __repr__(self):
        return self.__class__.__name__+': '+self.source+' => '+self.dest

    def check(self):
        raise Exception('Not implemented')

    def sync(self):
        raise Exception('Not implemented')

    def dest_path(self):
        return os.path.normpath(\
            os.path.join(self.config.dest, self.relpath, self.dest))

    def source_path(self):
        return os.path.normpath(\
            os.path.join(self.config.source, self.relpath, self.source))

    def _makedirs(self):
        dir = os.path.dirname(self.dest_path())
        if not os.path.isdir(dir):
            os.makedirs(dir)


class SymlinkConfigAction(ConfigAction):
    @classmethod
    def matches(cls, filename):
        return filename

    def check(self):
        dest = self.dest_path()
        if os.path.lexists(dest):
            if not os.path.islink(dest):
                raise Exception("Destination exists and is not a link")

    def sync(self):
        source = self.source_path()
        dest = self.dest_path()
        if os.path.lexists(dest):
            if os.path.islink(dest):
                if not os.path.samefile(dest, source):
                    os.unlink(dest)
                    print "Link target altered"
                else:
                    return
        else:
            self._makedirs()

        print "Created new link: "+dest+" => "+source
        os.symlink(source, dest)

class CopyConfigAction(ConfigAction):
    pass #TODO


class IgnoreConfigAction(ConfigAction):
    ignored = re.compile("_|\.git$|\.gitignore$")

    @classmethod
    def matches(cls, filename):
        if cls.ignored.match(filename):
            return None
        return False

    def __repr__(self):
        return self.__class__.__name__+': '+self.source+' => IGNORED'


class ConfigSource(object):
    def __init__(self, source, dest, classes = None):
        # handle '~'
        self.source = os.path.expanduser(source)
        self.dest = os.path.expanduser(dest)

        if classes:
            self.classes = classes
        else:
            self.classes = [
                IgnoreConfigAction,
                SymlinkConfigAction,
            ]

    def analyze(self):
        def walker(_, path, files):
            relpath = os.path.relpath(path, self.source)
            for filename in (file for file in files \
            if not os.path.isdir(os.path.join(path, file))):
                self.add(relpath, filename)

        self.tree = {}
        os.path.walk(self.source, walker, None)

    def add(self, relpath, filename):
        def get_file_class(filename):
            for cls in self.classes:
                dest = cls.matches(filename)
                if dest is not False:
                    return (cls, dest)
            raise Exception("No class found")

        cls, dest = get_file_class(filename)

        if dest is not None:
            files = self.tree.setdefault(relpath, {})
            if files.has_key(dest):
                raise Exception('Conflict: '+filename+' with '+files[dest])
            files[dest] = cls(self, relpath, filename, dest)

    def check(self):
        for file in self:
            file.check()

    def sync(self):
        for file in self:
            file.sync()

    def __iter__(self):
        for files in self.tree.itervalues():
            for file in files.itervalues():
                yield file

    def __repr__(self):
        return "\n".join(\
            (action.relpath+': '+repr(action) for action in self))