~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/views.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2009-02-21 07:21:10 UTC
  • mfrom: (4029.1.1 ianc-integration)
  • Revision ID: pqm@pqm.ubuntu.com-20090221072110-uf8tjt38l6r3vo6o
Filtered views (Ian Clatworthy)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2008 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
"""View management.
 
18
 
 
19
Views are contained within a working tree and normally constructed
 
20
when first accessed.  Clients should do, for example, ...
 
21
 
 
22
  tree.views.lookup_view()
 
23
"""
 
24
 
 
25
 
 
26
import re
 
27
 
 
28
from bzrlib import (
 
29
    errors,
 
30
    )
 
31
 
 
32
 
 
33
_VIEWS_FORMAT_MARKER_RE = re.compile(r'Bazaar views format (\d+)')
 
34
_VIEWS_FORMAT1_MARKER = "Bazaar views format 1\n"
 
35
 
 
36
 
 
37
class _Views(object):
 
38
    """Base class for View managers."""
 
39
 
 
40
    def supports_views(self):
 
41
        raise NotImplementedError(self.supports_views)
 
42
 
 
43
 
 
44
class PathBasedViews(_Views):
 
45
    """View storage in an unversioned tree control file.
 
46
 
 
47
    Views are stored in terms of paths relative to the tree root.
 
48
 
 
49
    The top line of the control file is a format marker in the format:
 
50
 
 
51
      Bazaar views format X
 
52
 
 
53
    where X is an integer number. After this top line, version 1 format is
 
54
    stored as follows:
 
55
 
 
56
     * optional name-values pairs in the format 'name=value'
 
57
 
 
58
     * optional view definitions, one per line in the format
 
59
 
 
60
       views:
 
61
       name file1 file2 ...
 
62
       name file1 file2 ...
 
63
 
 
64
    where the fields are separated by a nul character (\0). The views file
 
65
    is encoded in utf-8. The only supported keyword in version 1 is
 
66
    'current' which stores the name of the current view, if any.
 
67
    """
 
68
 
 
69
    def __init__(self, tree):
 
70
        self.tree = tree
 
71
        self._loaded = False
 
72
        self._current = None
 
73
        self._views = {}
 
74
 
 
75
    def supports_views(self):
 
76
        return True
 
77
 
 
78
    def get_view_info(self):
 
79
        """Get the current view and dictionary of views.
 
80
 
 
81
        :return: current, views where
 
82
          current = the name of the current view or None if no view is enabled
 
83
          views = a map from view name to list of files/directories
 
84
        """
 
85
        self._load_view_info()
 
86
        return self._current, self._views
 
87
 
 
88
    def set_view_info(self, current, views):
 
89
        """Set the current view and dictionary of views.
 
90
 
 
91
        :param current: the name of the current view or None if no view is
 
92
          enabled
 
93
        :param views: a map from view name to list of files/directories
 
94
        """
 
95
        if current is not None and current not in views:
 
96
            raise errors.NoSuchView(current)
 
97
        self.tree.lock_write()
 
98
        try:
 
99
            self._current = current
 
100
            self._views = views
 
101
            self._save_view_info()
 
102
        finally:
 
103
            self.tree.unlock()
 
104
 
 
105
    def lookup_view(self, view_name=None):
 
106
        """Return the contents of a view.
 
107
 
 
108
        :param view_Name: name of the view or None to lookup the current view
 
109
        :return: the list of files/directories in the requested view
 
110
        """
 
111
        self._load_view_info()
 
112
        try:
 
113
            if view_name is None:
 
114
                if self._current:
 
115
                    view_name = self._current
 
116
                else:
 
117
                    return []
 
118
            return self._views[view_name]
 
119
        except KeyError:
 
120
            raise errors.NoSuchView(view_name)
 
121
 
 
122
    def set_view(self, view_name, view_files, make_current=True):
 
123
        """Add or update a view definition.
 
124
 
 
125
        :param view_name: the name of the view
 
126
        :param view_files: the list of files/directories in the view
 
127
        :param make_current: make this view the current one or not
 
128
        """
 
129
        self.tree.lock_write()
 
130
        try:
 
131
            self._load_view_info()
 
132
            self._views[view_name] = view_files
 
133
            if make_current:
 
134
                self._current = view_name
 
135
            self._save_view_info()
 
136
        finally:
 
137
            self.tree.unlock()
 
138
 
 
139
    def delete_view(self, view_name):
 
140
        """Delete a view definition.
 
141
 
 
142
        If the view deleted is the current one, the current view is reset.
 
143
        """
 
144
        self.tree.lock_write()
 
145
        try:
 
146
            self._load_view_info()
 
147
            try:
 
148
                del self._views[view_name]
 
149
            except KeyError:
 
150
                raise errors.NoSuchView(view_name)
 
151
            if view_name == self._current:
 
152
                self._current = None
 
153
            self._save_view_info()
 
154
        finally:
 
155
            self.tree.unlock()
 
156
 
 
157
    def _save_view_info(self):
 
158
        """Save the current view and all view definitions.
 
159
 
 
160
        Be sure to have initialised self._current and self._views before
 
161
        calling this method.
 
162
        """
 
163
        self.tree.lock_write()
 
164
        try:
 
165
            if self._current is None:
 
166
                keywords = {}
 
167
            else:
 
168
                keywords = {'current': self._current}
 
169
            self.tree._transport.put_bytes('views',
 
170
                self._serialize_view_content(keywords, self._views))
 
171
        finally:
 
172
            self.tree.unlock()
 
173
 
 
174
    def _load_view_info(self):
 
175
        """Load the current view and dictionary of view definitions."""
 
176
        if not self._loaded:
 
177
            self.tree.lock_read()
 
178
            try:
 
179
                try:
 
180
                    view_content = self.tree._transport.get_bytes('views')
 
181
                except errors.NoSuchFile, e:
 
182
                    self._current, self._views = None, {}
 
183
                else:
 
184
                    keywords, self._views = \
 
185
                        self._deserialize_view_content(view_content)
 
186
                    self._current = keywords.get('current')
 
187
            finally:
 
188
                self.tree.unlock()
 
189
            self._loaded = True
 
190
 
 
191
    def _serialize_view_content(self, keywords, view_dict):
 
192
        """Convert view keywords and a view dictionary into a stream."""
 
193
        lines = [_VIEWS_FORMAT1_MARKER]
 
194
        for key in keywords:
 
195
            line = "%s=%s\n" % (key,keywords[key])
 
196
            lines.append(line.encode('utf-8'))
 
197
        if view_dict:
 
198
            lines.append("views:\n".encode('utf-8'))
 
199
            for view in sorted(view_dict):
 
200
                view_data = "%s\0%s\n" % (view, "\0".join(view_dict[view]))
 
201
                lines.append(view_data.encode('utf-8'))
 
202
        return "".join(lines)
 
203
 
 
204
    def _deserialize_view_content(self, view_content):
 
205
        """Convert a stream into view keywords and a dictionary of views."""
 
206
        # as a special case to make initialization easy, an empty definition
 
207
        # maps to no current view and an empty view dictionary
 
208
        if view_content == '':
 
209
            return {}, {}
 
210
        lines = view_content.splitlines()
 
211
        match = _VIEWS_FORMAT_MARKER_RE.match(lines[0])
 
212
        if not match:
 
213
            raise ValueError(
 
214
                "format marker missing from top of views file")
 
215
        elif match.group(1) != '1':
 
216
            raise ValueError(
 
217
                "cannot decode views format %s" % match.group(1))
 
218
        try:
 
219
            keywords = {}
 
220
            views = {}
 
221
            in_views = False
 
222
            for line in lines[1:]:
 
223
                text = line.decode('utf-8')
 
224
                if in_views:
 
225
                    parts = text.split('\0')
 
226
                    view = parts.pop(0)
 
227
                    views[view] = parts
 
228
                elif text == 'views:':
 
229
                    in_views = True
 
230
                    continue
 
231
                elif text.find('=') >= 0:
 
232
                    # must be a name-value pair
 
233
                    keyword, value = text.split('=', 1)
 
234
                    keywords[keyword] = value
 
235
                else:
 
236
                    raise ValueError("failed to deserialize views line %s",
 
237
                        text)
 
238
            return keywords, views
 
239
        except ValueError, e:
 
240
            raise ValueError("failed to deserialize views content %r: %s"
 
241
                % (view_content, e))
 
242
 
 
243
 
 
244
class DisabledViews(_Views):
 
245
    """View storage that refuses to store anything.
 
246
 
 
247
    This is used by older formats that can't store views.
 
248
    """
 
249
 
 
250
    def __init__(self, tree):
 
251
        self.tree = tree
 
252
 
 
253
    def supports_views(self):
 
254
        return False
 
255
 
 
256
    def _not_supported(self, *a, **k):
 
257
        raise errors.ViewsNotSupported(self.tree)
 
258
 
 
259
    get_view_info = _not_supported
 
260
    set_view_info = _not_supported
 
261
    lookup_view = _not_supported
 
262
    set_view = _not_supported
 
263
    delete_view = _not_supported
 
264
 
 
265
 
 
266
def view_display_str(view_files, encoding=None):
 
267
    """Get the display string for a list of view files.
 
268
 
 
269
    :param view_files: the list of file names
 
270
    :param encoding: the encoding to display the files in
 
271
    """
 
272
    if encoding is None:
 
273
        return ", ".join(view_files)
 
274
    else:
 
275
        return ", ".join([v.encode(encoding, 'replace') for v in view_files])