/stdhome

To get this branch, use:
bzr branch http://bzr.ed.am/stdhome

« back to all changes in this revision

Viewing changes to lib/stdhome/walker/walker.py

  • Committer: Tim Marston
  • Date: 2014-02-26 19:10:31 UTC
  • Revision ID: tim@ed.am-20140226191031-elcqy5j09h2syn2j
moved copy-in, copy-out and deployment conflict checking to a set of "walkers";
bzr vcs back-end now parses affected files during update; deployment state now
includes affected files

Show diffs side-by-side

added added

removed removed

 
1
# walker.py
 
2
#
 
3
# Copyright (C) 2013 to 2014 Tim Marston <tim@edm.am>
 
4
#
 
5
# This file is part of stdhome (hereafter referred to as "this program").
 
6
# See http://ed.am/dev/stdhome for more information.
 
7
#
 
8
# This program is free software: you can redistribute it and/or modify
 
9
# it under the terms of the GNU General Public License as published by
 
10
# the Free Software Foundation, either version 3 of the License, or
 
11
# (at your option) any later version.
 
12
#
 
13
# This program is distributed in the hope that it will be useful,
 
14
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
15
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
16
# GNU General Public License for more details.
 
17
#
 
18
# You should have received a copy of the GNU General Public License
 
19
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
20
 
 
21
 
 
22
import os
 
23
import stdhome.the
 
24
 
 
25
 
 
26
class Walker:
 
27
        """Classes which derive from the Walker class are intended to walk through
 
28
        (traverse) a single list of filenames in two locations.  They must provide
 
29
        the two locations (src_dir and dst_dir) and a list of relative filenames
 
30
        (walk_list).  For each files in the walk list, the process() method is
 
31
        called, allowing the deriving class to perform some kind of processing.  See
 
32
        process() for more information.
 
33
        """
 
34
 
 
35
        def walk( self ):
 
36
                """Iterates over self.walk_list, calling process() for each entry in turn.  For
 
37
                directory entries, where process() returns False, subsequent entries in
 
38
                walk_list that fall under the directory are skipped.
 
39
                """
 
40
 
 
41
                skip = ''
 
42
 
 
43
                for rel_file in self.walk_list:
 
44
 
 
45
                        # if we're skipping, skip entries in subdirectories, or turn off
 
46
                        # skipping if it no longer matches
 
47
                        if skip:
 
48
                                if skip == rel_file[ : len( skip ) ]:
 
49
                                        continue
 
50
                                else:
 
51
                                        skip = ''
 
52
 
 
53
                        src_file = os.path.join( self.src_dir, rel_file )
 
54
                        dst_file = os.path.join( self.dst_dir, rel_file )
 
55
 
 
56
                        src_type = Walker.get_file_type( src_file )
 
57
                        dst_type = Walker.get_file_type( dst_file )
 
58
 
 
59
                        recurse = self.process(
 
60
                                rel_file, src_file, src_type, dst_file, dst_type )
 
61
 
 
62
                        # Set up skipping, as required.  Note that we don't check to see if
 
63
                        # we're dealing with a directory here.  We can't, because we've no
 
64
                        # way of knowing what to check.  It could be src_type or dst_type
 
65
                        # (if src_dir or dst_dir was used to generate the walk list) or it
 
66
                        # could be neither (if the walk list came from somewhere else).  But
 
67
                        # it shouldn't matter.  We adding an os.pathset to the end of the
 
68
                        # filename, so it wuill only match files that are descendents of a
 
69
                        # directory with the name of this file.
 
70
                        if not recurse: skip = rel_file + os.pathsep
 
71
 
 
72
 
 
73
        @staticmethod
 
74
        def get_file_type( full_file ):
 
75
                """Returns the type of a given file, at the time of calling.  Types are 'd' for
 
76
                directory, 'f' for file, 'l' for symlink, '_' for missing and '?' for
 
77
                anything else.
 
78
                """
 
79
 
 
80
                if not os.path.lexists( full_file ):
 
81
                        return '_'
 
82
                elif os.path.islink( full_file ):
 
83
                        return 'l'
 
84
                elif os.path.isfile( full_file ):
 
85
                        return 'f'
 
86
                elif os.path.isdir( full_file ):
 
87
                        return 'd'
 
88
                else:
 
89
                        return '?'
 
90
 
 
91
 
 
92
        @staticmethod
 
93
        def generate_walk_list( full_dir, rel_dir = '' ):
 
94
                """Returns a list of files and directories in full_dir, specified as relative
 
95
                files (relative to full_dir), breadth first.
 
96
                """
 
97
 
 
98
                ret = list()
 
99
 
 
100
                for file in os.listdir( os.path.join( full_dir, rel_dir ) ):
 
101
 
 
102
                        rel_file = os.path.join( rel_dir, file )
 
103
                        if rel_file == '.bzr': continue
 
104
 
 
105
                        full_file = os.path.join( full_dir, rel_file )
 
106
 
 
107
                        if os.path.isfile( full_file ) or os.path.islink( full_file ):
 
108
                                ret.append( rel_file )
 
109
                        elif os.path.isdir( full_file ):
 
110
                                ret.append( rel_file )
 
111
                                ret.extend( generate_file_list( full_dir, rel_file ) )
 
112
                        else:
 
113
                                raise RuntimeError(
 
114
                                        'unknown/exotic file: %s' % full_file )
 
115
 
 
116
                return ret
 
117
 
 
118
 
 
119
        @staticmethod
 
120
        def name_of_type( type ):
 
121
                if type == 'd': return 'a directory'
 
122
                elif type == 'f': return 'a file'
 
123
                elif type == 'l': return 'a symlink'
 
124
                elif type == '_': return 'missing'
 
125
                else: return 'something exotic'