1
# Copyright (C) 2008, 2009, 2011 Canonical Ltd
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.
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.
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
"""Working tree content filtering support.
19
A filter consists of a read converter, write converter pair.
20
The content in the working tree is called the convenience format
21
while the content actually stored is called the canonical format.
22
The read converter produces canonical content from convenience
23
content while the writer goes the other way.
25
Converters have the following signatures::
27
read_converter(chunks) -> chunks
28
write_converter(chunks, context) -> chunks
32
* chunks is an iterator over a sequence of byte strings
34
* context is an optional ContentFilterContent object (possibly None)
35
providing converters access to interesting information, e.g. the
36
relative path of the file.
38
Note that context is currently only supported for write converters.
41
from __future__ import absolute_import
43
from cStringIO import StringIO
44
from bzrlib.lazy_import import lazy_import
45
lazy_import(globals(), """
53
from bzrlib.symbol_versioning import (
59
class ContentFilter(object):
61
def __init__(self, reader, writer):
62
"""Create a filter that converts content while reading and writing.
64
:param reader: function for converting convenience to canonical content
65
:param writer: function for converting canonical to convenience content
71
return "reader: %s, writer: %s" % (self.reader,self.writer)
74
class ContentFilterContext(object):
75
"""Object providing information that filters can use."""
77
def __init__(self, relpath=None, tree=None, entry=None):
80
:param relpath: the relative path or None if this context doesn't
81
support that information.
82
:param tree: the Tree providing this file or None if this context
83
doesn't support that information.
84
:param entry: the InventoryEntry object if it is already known or
85
None if it should be derived if possible
87
self._relpath = relpath
91
self._revision_id = None
95
"""Relative path of file to tree-root."""
98
def source_tree(self):
99
"""Source Tree object."""
103
"""File-id of file."""
104
if self._entry is not None:
105
return self._entry.file_id
106
elif self._tree is None:
109
return self._tree.path2id(self._relpath)
111
def revision_id(self):
112
"""Id of revision that last changed this file."""
113
if self._revision_id is None:
114
if self._entry is not None:
115
self._revision_id = self._entry.revision
116
elif self._tree is not None:
117
file_id = self._tree.path2id(self._relpath)
118
self._entry = self._tree.inventory[file_id]
119
self._revision_id = self._entry.revision
120
return self._revision_id
123
"""Revision this variation of the file was introduced in."""
124
if self._revision is None:
125
rev_id = self.revision_id()
126
if rev_id is not None:
127
repo = getattr(self._tree, '_repository', None)
129
repo = self._tree.branch.repository
130
self._revision = repo.get_revision(rev_id)
131
return self._revision
134
def filtered_input_file(f, filters):
135
"""Get an input file that converts external to internal content.
137
:param f: the original input file
138
:param filters: the stack of filters to apply
139
:return: a file-like object
143
for filter in filters:
144
if filter.reader is not None:
145
chunks = filter.reader(chunks)
146
return StringIO(''.join(chunks))
151
def filtered_output_bytes(chunks, filters, context=None):
152
"""Convert byte chunks from internal to external format.
154
:param chunks: an iterator containing the original content
155
:param filters: the stack of filters to apply
156
:param context: a ContentFilterContext object passed to
158
:return: an iterator containing the content to output
161
for filter in reversed(filters):
162
if filter.writer is not None:
163
chunks = filter.writer(chunks, context)
167
def internal_size_sha_file_byname(name, filters):
168
"""Get size and sha of internal content given external content.
170
:param name: path to file
171
:param filters: the stack of filters to apply
173
f = open(name, 'rb', 65000)
176
f = filtered_input_file(f, filters)
177
return osutils.size_sha_file(f)
182
# The registry of filter stacks indexed by name.
183
filter_stacks_registry = registry.Registry()
186
# Cache of preferences -> stack
187
# TODO: make this per branch (say) rather than global
191
# XXX: This function doesn't have any tests. JRV 2012-03-29
192
@deprecated_function(deprecated_in((2, 6, 0)))
193
def register_filter_stack_map(name, stack_map_lookup):
194
"""Register the filter stacks to use for various preference values.
196
:param name: the preference/filter-stack name
197
:param stack_map_lookup: a callable where
198
the parameter is the preference value to match and
199
the result is the matching stack of filters to use,
202
filter_stacks_registry.register(name, stack_map_lookup)
205
# XXX: This function doesn't have any tests. JRV 2012-03-29
206
@deprecated_function(deprecated_in((2, 6, 0)))
207
def lazy_register_filter_stack_map(name, module_name, member_name):
208
"""Lazily register the filter stacks to use for various preference values.
210
:param name: the preference/filter-stack name
211
:param module_name: The python path to the module of the filter stack map.
212
:param member_name: The name of the stack_map_lookup callable
215
filter_stacks_registry.register_lazy(name, module_name, member_name)
218
def _get_registered_names():
219
"""Get the list of names with filters registered."""
220
# Note: We may want to intelligently order these later.
221
# If so, the register_ fn will need to support an optional priority.
222
return filter_stacks_registry.keys()
225
def _get_filter_stack_for(preferences):
226
"""Get the filter stack given a sequence of preferences.
228
:param preferences: a sequence of (name,value) tuples where
229
name is the preference name and
230
value is the key into the filter stack map registered
233
if preferences is None:
235
stack = _stack_cache.get(preferences)
236
if stack is not None:
239
for k, v in preferences:
243
stack_map_lookup = filter_stacks_registry.get(k)
245
# Some preferences may not have associated filters
247
items = stack_map_lookup(v)
250
_stack_cache[preferences] = stack
254
def _reset_registry(value=None):
255
"""Reset the filter stack registry.
257
This function is provided to aid testing. The expected usage is::
259
old = _reset_registry()
263
:param value: the value to set the registry to or None for an empty one.
264
:return: the existing value before it reset.
266
global filter_stacks_registry
267
original = filter_stacks_registry
269
filter_stacks_registry = registry.Registry()
271
filter_stacks_registry = value
276
filter_stacks_registry.register_lazy('eol', 'bzrlib.filters.eol', 'eol_lookup')