2
This module contains code for file conversion, including implicit conversion
6
import re, imp, os.path
7
from ConfigParser import *
9
from rubber import _, msg
10
import rubber.converters
11
from rubber.util import Variables
13
re_variable = re.compile('[a-zA-Z]+')
15
def expand_cases (string, vars):
17
Expand variables and cases in a template string. Variables must occur as
18
$VAR (with only letters in the name) or ${VAR}, their values are taken
19
from the dictionary-like object 'vars'. The "$" character can be written
20
litterally by saying "$$". Cases are written in braces, separated with
21
commas, as in {foo,bar,quux}. Commas at top-level also count as choice
22
separators, as if there was a pair of braces around the whole string.
23
Returns a pair (cases,pos) where 'cases' is the list of expansions of the
24
string and 'pos' is the position of the first unbalanced "}" or the end of
27
pos = 0 # current position
28
start = 0 # starting point of the current chunk
29
cases = [] # possible expansions from previous cases
30
current = [''] # possible values for the current case, up to 'start'
32
while pos < len(string):
36
if string[pos] == ',':
37
suffix = string[start:pos]
38
cases.extend([s + suffix for s in current])
42
elif string[pos] == '{':
43
mid = string[start:pos]
44
next, shift = expand_cases(string[pos + 1:], vars)
45
current = [left + mid + right for left in current for right in next]
46
start = pos = pos + shift + 2
48
elif string[pos] == '}':
49
suffix = string[start:pos]
50
return cases + [s + suffix for s in current], pos
54
elif string[pos] == '$' and pos < len(string):
55
if string[pos + 1] == '{':
56
end = string.find('}', pos + 2)
59
name = string[pos+2:end]
60
suffix = string[start:pos]
63
current = [s + suffix for s in current]
65
elif string[pos + 1] == '$':
66
suffix = string[start:pos+1]
67
current = [s + suffix for s in current]
70
m = re_variable.match(string, pos + 1)
73
suffix = string[start:pos]
76
current = [s + suffix for s in current]
84
suffix = string[start:]
85
return cases + [s + suffix for s in current], pos
87
class Rule (Variables):
89
This class represents a single rule, as described in rules.ini. It is
90
essentially a dictionary, but also includes a compiled form of the regular
91
expression for the target.
93
def __init__ (self, context, dict):
94
Variables.__init__(self, context, dict)
95
self.cost = dict['cost']
96
self.re_target = re.compile(dict['target'] + '$')
98
class Converter (object):
100
This class represents a set of translation rules that may be used to
101
produce input files. Objects contain a table of rewriting rules to deduce
102
potential source names from the target's name, and each rule has a given
103
cost that indicates how expensive the translation is.
105
Each rule contains a module name. The module is searched for in the
106
package rubber.converters and it is supposed to contain two functions:
108
- check(source, target, context):
109
Returns True if conversion from 'source' to 'target' is possible (i.e.
110
the source file is suitable, all required tools are available, etc.).
111
The 'context' object is a dictionary-like object that contains values
112
from the rule and possibly additional user settings. If the function
113
is absent, conversion is always assumed to be possible.
115
- convert(source, target, context, set):
116
Produce a dependency node in the given 'set' to produce 'target' from
117
'source', using settings from the 'context'.
119
def __init__ (self, set):
121
Initialize the converter, associated with a given dependency set, with
122
an empty set of rules.
128
def read_ini (self, filename):
130
Read a set of rules from a file. The file has the form of an INI file,
131
each section describes a rule.
137
msg.error(_("parse error, ignoring this file"), file=filename)
139
for name in cp.sections():
140
dict = { 'name': name }
141
for key in cp.options(name):
142
dict[key] = cp.get(name, key)
144
dict['cost'] = cp.getint(name, 'cost')
145
except NoOptionError:
146
msg.warn(_("ignoring rule `%s' (no cost found)") % name,
150
msg.warn(_("ignoring rule `%s' (invalid cost)") % name,
153
if 'target' not in dict:
154
msg.warn(_("ignoring rule `%s' (no target found)") % name,
157
if 'rule' not in dict:
158
msg.warn(_("ignoring rule `%s' (no module found)") % name,
160
if not self.load_module(dict['rule']):
161
msg.warn(_("ignoring rule `%s' (module `%s' not found)") %
162
(name, dict['rule']), file=filename)
163
self.rules[name] = Rule(None, dict)
165
def load_module (self, name):
167
Check if the module of the given name exists and load it. Returns True
168
if the module was loaded and False otherwise.
170
if name in self.modules:
171
return self.modules[name] is not None
173
answer = imp.find_module(name, rubber.converters.__path__)
175
self.modules[name] = None
177
self.modules[name] = imp.load_module(name, *answer)
180
def may_produce (self, name):
182
Return true if the given filename may be that of a file generated by
183
this converter, i.e. if it matches one of the target regular
186
for rule in self.rules.values():
187
if rule.re_target.match(name):
191
def best_rule (self, target, check=None, context=None):
193
Search for an applicable rule for the given target with the least
194
cost. The returned value is a dictionary that describes the best rule
195
found, or None if no rule is applicable. The optional argument 'check'
196
is a function that takes the rule parameters as arguments (as a
197
dictionary that contains at least 'source' and 'target') and can
198
return false if the rule is refused. The optional argument 'context'
199
is a dictionary that contains additional parameters passed to the
204
for rule in self.rules.values():
205
match = rule.re_target.match(target)
208
templates, _ = expand_cases(rule['source'], {})
209
for template in templates:
210
source = match.expand(template)
213
if not os.path.exists(source):
215
candidates.append((rule['cost'], source, target, rule))
218
for cost, source, target, rule in candidates:
219
instance = Variables(context, rule)
220
instance['source'] = source
221
instance['target'] = target
222
if check is not None and not check(instance):
224
module = self.modules[rule['rule']]
225
if hasattr(module, 'check'):
226
if not module.check(source, target, instance):
232
def apply (self, instance):
234
Apply a rule with the variables given in the dictionary passed as
235
argument (as returned from the 'best_rule' method), and return a
236
dependency node for the result.
238
module = self.modules[instance['rule']]
239
return module.convert(
240
instance['source'], instance['target'], instance, self.set)