~bzr-pqm/bzr/bzr.dev

5752.3.8 by John Arbash Meinel
Merge bzr.dev 5764 to resolve release-notes (aka NEWS) conflicts
1
# Copyright (C) 2008, 2009, 2011 Canonical Ltd
3368.2.1 by Ian Clatworthy
first cut at working tree content filtering
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
4183.7.1 by Sabin Iacob
update FSF mailing address
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
3368.2.1 by Ian Clatworthy
first cut at working tree content filtering
16
17
3368.2.6 by Ian Clatworthy
make filter API explicit wrt chunks and add context parameter
18
"""Working tree content filtering support.
19
3368.2.22 by Ian Clatworthy
more changes from abentley's review
20
A filter consists of a read converter, write converter pair.
3368.2.25 by Ian Clatworthy
feedback from poolie's review
21
The content in the working tree is called the convenience format
22
while the content actually stored in called the canonical format.
23
The read converter produces canonical content from convenience
24
content while the writer goes the other way.
25
3368.2.22 by Ian Clatworthy
more changes from abentley's review
26
Converters have the following signatures::
3368.2.6 by Ian Clatworthy
make filter API explicit wrt chunks and add context parameter
27
3368.2.22 by Ian Clatworthy
more changes from abentley's review
28
    read_converter(chunks) -> chunks
29
    write_converter(chunks, context) -> chunks
3368.2.6 by Ian Clatworthy
make filter API explicit wrt chunks and add context parameter
30
31
where:
32
3368.2.22 by Ian Clatworthy
more changes from abentley's review
33
 * chunks is an iterator over a sequence of byte strings
34
35
 * context is an optional ContentFilterContent object (possibly None)
36
   providing converters access to interesting information, e.g. the
37
   relative path of the file.
38
39
Note that context is currently only supported for write converters.
3368.2.6 by Ian Clatworthy
make filter API explicit wrt chunks and add context parameter
40
"""
41
4183.1.1 by Vincent Ladeuil
Fix python2.6 obsoleted import.
42
from cStringIO import StringIO
3368.2.1 by Ian Clatworthy
first cut at working tree content filtering
43
from bzrlib.lazy_import import lazy_import
44
lazy_import(globals(), """
45
from bzrlib import (
3368.2.39 by Ian Clatworthy
add config & caching to ContentFilterContext
46
    config,
3368.2.15 by Ian Clatworthy
add filter stack registry code and tests
47
    errors,
48
    osutils,
3368.2.19 by Ian Clatworthy
first round of changes from abentley's review
49
    registry,
3368.2.15 by Ian Clatworthy
add filter stack registry code and tests
50
    )
3368.2.1 by Ian Clatworthy
first cut at working tree content filtering
51
""")
52
53
54
class ContentFilter(object):
55
56
    def __init__(self, reader, writer):
57
        """Create a filter that converts content while reading and writing.
3368.2.47 by Ian Clatworthy
merge bzr.dev r4042
58
3368.2.25 by Ian Clatworthy
feedback from poolie's review
59
        :param reader: function for converting convenience to canonical content
60
        :param writer: function for converting canonical to convenience content
3368.2.1 by Ian Clatworthy
first cut at working tree content filtering
61
        """
62
        self.reader = reader
63
        self.writer = writer
64
3368.2.30 by Ian Clatworthy
add -Dfilters support
65
    def __repr__(self):
66
        return "reader: %s, writer: %s" % (self.reader,self.writer)
67
3368.2.1 by Ian Clatworthy
first cut at working tree content filtering
68
3368.2.6 by Ian Clatworthy
make filter API explicit wrt chunks and add context parameter
69
class ContentFilterContext(object):
3368.2.33 by Ian Clatworthy
expand filter context to support interesting stuff
70
    """Object providing information that filters can use."""
3368.2.9 by Ian Clatworthy
default context should only support relpath
71
3368.2.33 by Ian Clatworthy
expand filter context to support interesting stuff
72
    def __init__(self, relpath=None, tree=None, entry=None):
3368.2.9 by Ian Clatworthy
default context should only support relpath
73
        """Create a context.
74
75
        :param relpath: the relative path or None if this context doesn't
76
           support that information.
3368.2.33 by Ian Clatworthy
expand filter context to support interesting stuff
77
        :param tree: the Tree providing this file or None if this context
78
           doesn't support that information.
79
        :param entry: the InventoryEntry object if it is already known or
80
           None if it should be derived if possible
3368.2.9 by Ian Clatworthy
default context should only support relpath
81
        """
82
        self._relpath = relpath
3368.2.33 by Ian Clatworthy
expand filter context to support interesting stuff
83
        self._tree = tree
84
        self._entry = entry
3368.2.39 by Ian Clatworthy
add config & caching to ContentFilterContext
85
        # Cached values
86
        self._revision_id = None
87
        self._revision = None
88
        self._config = None
3368.2.6 by Ian Clatworthy
make filter API explicit wrt chunks and add context parameter
89
90
    def relpath(self):
91
        """Relative path of file to tree-root."""
3368.2.19 by Ian Clatworthy
first round of changes from abentley's review
92
        return self._relpath
3368.2.6 by Ian Clatworthy
make filter API explicit wrt chunks and add context parameter
93
3368.2.33 by Ian Clatworthy
expand filter context to support interesting stuff
94
    def source_tree(self):
95
        """Source Tree object."""
96
        return self._tree
97
3368.2.35 by Ian Clatworthy
add revision_id to filter context
98
    def file_id(self):
3368.2.33 by Ian Clatworthy
expand filter context to support interesting stuff
99
        """File-id of file."""
100
        if self._entry is not None:
101
            return self._entry.file_id
102
        elif self._tree is None:
103
            return None
104
        else:
3368.2.35 by Ian Clatworthy
add revision_id to filter context
105
            return self._tree.path2id(self._relpath)
106
107
    def revision_id(self):
108
        """Id of revision that last changed this file."""
3368.2.39 by Ian Clatworthy
add config & caching to ContentFilterContext
109
        if self._revision_id is None:
110
            if self._entry is not None:
111
                self._revision_id = self._entry.revision
112
            elif self._tree is not None:
113
                file_id = self._tree.path2id(self._relpath)
114
                self._entry = self._tree.inventory[file_id]
115
                self._revision_id = self._entry.revision
116
        return self._revision_id
3368.2.33 by Ian Clatworthy
expand filter context to support interesting stuff
117
118
    def revision(self):
3368.2.35 by Ian Clatworthy
add revision_id to filter context
119
        """Revision this variation of the file was introduced in."""
3368.2.39 by Ian Clatworthy
add config & caching to ContentFilterContext
120
        if self._revision is None:
121
            rev_id = self.revision_id()
122
            if rev_id is not None:
123
                repo = getattr(self._tree, '_repository', None)
124
                if repo is None:
125
                    repo = self._tree.branch.repository
126
                self._revision = repo.get_revision(rev_id)
127
        return self._revision
128
129
    def config(self):
130
        """The Config object to search for configuration settings."""
131
        if self._config is None:
132
            branch = getattr(self._tree, 'branch', None)
133
            if branch is not None:
134
                self._config = branch.get_config()
135
            else:
136
                self._config = config.GlobalConfig()
137
        return self._config
3368.2.33 by Ian Clatworthy
expand filter context to support interesting stuff
138
3368.2.6 by Ian Clatworthy
make filter API explicit wrt chunks and add context parameter
139
3368.2.10 by Ian Clatworthy
don't support context for read filters
140
def filtered_input_file(f, filters):
3368.2.1 by Ian Clatworthy
first cut at working tree content filtering
141
    """Get an input file that converts external to internal content.
3368.2.47 by Ian Clatworthy
merge bzr.dev r4042
142
3368.2.1 by Ian Clatworthy
first cut at working tree content filtering
143
    :param f: the original input file
144
    :param filters: the stack of filters to apply
145
    :return: a file-like object
146
    """
147
    if filters:
3368.2.6 by Ian Clatworthy
make filter API explicit wrt chunks and add context parameter
148
        chunks = [f.read()]
3368.2.1 by Ian Clatworthy
first cut at working tree content filtering
149
        for filter in filters:
3368.2.10 by Ian Clatworthy
don't support context for read filters
150
            if filter.reader is not None:
151
                chunks = filter.reader(chunks)
4183.1.1 by Vincent Ladeuil
Fix python2.6 obsoleted import.
152
        return StringIO(''.join(chunks))
3368.2.1 by Ian Clatworthy
first cut at working tree content filtering
153
    else:
154
        return f
155
156
3368.2.19 by Ian Clatworthy
first round of changes from abentley's review
157
def filtered_output_bytes(chunks, filters, context=None):
158
    """Convert byte chunks from internal to external format.
3368.2.47 by Ian Clatworthy
merge bzr.dev r4042
159
3368.2.6 by Ian Clatworthy
make filter API explicit wrt chunks and add context parameter
160
    :param chunks: an iterator containing the original content
3368.2.1 by Ian Clatworthy
first cut at working tree content filtering
161
    :param filters: the stack of filters to apply
3368.2.6 by Ian Clatworthy
make filter API explicit wrt chunks and add context parameter
162
    :param context: a ContentFilterContext object passed to
163
        each filter
3368.2.3 by Ian Clatworthy
tests for ..input_file and ..output_lines
164
    :return: an iterator containing the content to output
3368.2.1 by Ian Clatworthy
first cut at working tree content filtering
165
    """
166
    if filters:
167
        for filter in reversed(filters):
3368.2.10 by Ian Clatworthy
don't support context for read filters
168
            if filter.writer is not None:
169
                chunks = filter.writer(chunks, context)
3368.2.6 by Ian Clatworthy
make filter API explicit wrt chunks and add context parameter
170
    return chunks
3368.2.1 by Ian Clatworthy
first cut at working tree content filtering
171
172
3368.2.19 by Ian Clatworthy
first round of changes from abentley's review
173
def internal_size_sha_file_byname(name, filters):
174
    """Get size and sha of internal content given external content.
3368.2.47 by Ian Clatworthy
merge bzr.dev r4042
175
3368.2.6 by Ian Clatworthy
make filter API explicit wrt chunks and add context parameter
176
    :param name: path to file
177
    :param filters: the stack of filters to apply
178
    """
3368.2.19 by Ian Clatworthy
first round of changes from abentley's review
179
    f = open(name, 'rb', 65000)
180
    try:
181
        if filters:
182
            f = filtered_input_file(f, filters)
3368.2.49 by Ian Clatworthy
added osutils.size_sha_file() with tests
183
        return osutils.size_sha_file(f)
3368.2.19 by Ian Clatworthy
first round of changes from abentley's review
184
    finally:
185
        f.close()
3368.2.15 by Ian Clatworthy
add filter stack registry code and tests
186
187
188
# The registry of filter stacks indexed by name.
3368.2.26 by Ian Clatworthy
more feedback tweaks
189
# See register_filter_stack_map for details on the registered values.
3368.2.19 by Ian Clatworthy
first round of changes from abentley's review
190
_filter_stacks_registry = registry.Registry()
3368.2.15 by Ian Clatworthy
add filter stack registry code and tests
191
192
193
# Cache of preferences -> stack
3368.2.25 by Ian Clatworthy
feedback from poolie's review
194
# TODO: make this per branch (say) rather than global
3368.2.15 by Ian Clatworthy
add filter stack registry code and tests
195
_stack_cache = {}
196
197
4257.1.1 by Ian Clatworthy
register content filters using a callable, not a dict+callable
198
def register_filter_stack_map(name, stack_map_lookup):
3368.2.15 by Ian Clatworthy
add filter stack registry code and tests
199
    """Register the filter stacks to use for various preference values.
200
201
    :param name: the preference/filter-stack name
4257.1.1 by Ian Clatworthy
register content filters using a callable, not a dict+callable
202
    :param stack_map_lookup: a callable where
203
      the parameter is the preference value to match and
204
      the result is the matching stack of filters to use,
205
      or None if none.
3368.2.15 by Ian Clatworthy
add filter stack registry code and tests
206
    """
3368.2.19 by Ian Clatworthy
first round of changes from abentley's review
207
    if name in _filter_stacks_registry:
3368.2.15 by Ian Clatworthy
add filter stack registry code and tests
208
        raise errors.BzrError(
209
            "filter stack for %s already installed" % name)
4257.1.1 by Ian Clatworthy
register content filters using a callable, not a dict+callable
210
    _filter_stacks_registry.register(name, stack_map_lookup)
3368.2.15 by Ian Clatworthy
add filter stack registry code and tests
211
212
4182.1.1 by Jelmer Vernooij
Add wrapper function for lazily registering filter stack maps.
213
def lazy_register_filter_stack_map(name, module_name, member_name):
214
    """Lazily register the filter stacks to use for various preference values.
215
216
    :param name: the preference/filter-stack name
217
    :param module_name: The python path to the module of the filter stack map.
4257.1.1 by Ian Clatworthy
register content filters using a callable, not a dict+callable
218
    :param member_name: The name of the stack_map_lookup callable
4210.3.2 by Ian Clatworthy
tweak docstring for lazy content filter registration
219
      in the module.
4182.1.1 by Jelmer Vernooij
Add wrapper function for lazily registering filter stack maps.
220
    """
221
    if name in _filter_stacks_registry:
222
        raise errors.BzrError(
223
            "filter stack for %s already installed" % name)
224
    _filter_stacks_registry.register_lazy(name, module_name, member_name)
225
226
3368.2.15 by Ian Clatworthy
add filter stack registry code and tests
227
def _get_registered_names():
228
    """Get the list of names with filters registered."""
229
    # Note: We may want to intelligently order these later.
230
    # If so, the register_ fn will need to support an optional priority.
231
    return _filter_stacks_registry.keys()
232
233
234
def _get_filter_stack_for(preferences):
235
    """Get the filter stack given a sequence of preferences.
3368.2.47 by Ian Clatworthy
merge bzr.dev r4042
236
3368.2.15 by Ian Clatworthy
add filter stack registry code and tests
237
    :param preferences: a sequence of (name,value) tuples where
238
      name is the preference name and
3368.2.25 by Ian Clatworthy
feedback from poolie's review
239
      value is the key into the filter stack map registered
3368.2.15 by Ian Clatworthy
add filter stack registry code and tests
240
      for that preference.
241
    """
3368.2.16 by Ian Clatworthy
add real implementation of Tree.get_filter_stack
242
    if preferences is None:
243
        return []
3368.2.15 by Ian Clatworthy
add filter stack registry code and tests
244
    stack = _stack_cache.get(preferences)
245
    if stack is not None:
246
        return stack
247
    stack = []
248
    for k, v in preferences:
4423.2.1 by Ian Clatworthy
fix bug #379370
249
        if v is None:
250
            continue
3368.2.19 by Ian Clatworthy
first round of changes from abentley's review
251
        try:
4257.1.1 by Ian Clatworthy
register content filters using a callable, not a dict+callable
252
            stack_map_lookup = _filter_stacks_registry.get(k)
3368.2.19 by Ian Clatworthy
first round of changes from abentley's review
253
        except KeyError:
3368.2.26 by Ian Clatworthy
more feedback tweaks
254
            # Some preferences may not have associated filters
3368.2.19 by Ian Clatworthy
first round of changes from abentley's review
255
            continue
4257.1.1 by Ian Clatworthy
register content filters using a callable, not a dict+callable
256
        items = stack_map_lookup(v)
3368.2.19 by Ian Clatworthy
first round of changes from abentley's review
257
        if items:
258
            stack.extend(items)
3368.2.15 by Ian Clatworthy
add filter stack registry code and tests
259
    _stack_cache[preferences] = stack
260
    return stack
261
262
263
def _reset_registry(value=None):
264
    """Reset the filter stack registry.
265
266
    This function is provided to aid testing. The expected usage is::
267
268
      old = _reset_registry()
269
      # run tests
270
      _reset_registry(old)
271
272
    :param value: the value to set the registry to or None for an empty one.
273
    :return: the existing value before it reset.
274
    """
275
    global _filter_stacks_registry
3368.2.19 by Ian Clatworthy
first round of changes from abentley's review
276
    original = _filter_stacks_registry
3368.2.15 by Ian Clatworthy
add filter stack registry code and tests
277
    if value is None:
3368.2.19 by Ian Clatworthy
first round of changes from abentley's review
278
        _filter_stacks_registry = registry.Registry()
3368.2.15 by Ian Clatworthy
add filter stack registry code and tests
279
    else:
280
        _filter_stacks_registry = value
281
    _stack_cache.clear()
282
    return original
4275.1.1 by Ian Clatworthy
fix eol content filter loading
283
284
285
# Register the standard filters
286
from bzrlib.filters import eol
287
eol.register_eol_content_filter()