4 # Copyright (C) 2011, 2012, 2013 Google Inc.
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 # General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 """Script for converting Python constants to Haskell code fragments.
28 from ganeti import _autoconf
29 from ganeti import compat
30 from ganeti import constants
31 from ganeti import errors
32 from ganeti import luxi
33 from ganeti import opcodes
34 from ganeti import qlang
35 from ganeti import jstore
38 #: Constant name regex
39 CONSTANT_RE = re.compile("^[A-Z][A-Z0-9_-]+$")
42 PRIVATE_RE = re.compile("^__.+__$")
44 #: The type of regex objects
45 RE_TYPE = type(CONSTANT_RE)
47 #: Keys which do not declare a value (manually maintained). By adding
48 # values here, we can make more lists use the actual names; otherwise
49 # we'll have (e.g.) both DEFAULT_ENABLED_HYPERVISOR and HT_XEN_PVM
50 # declare the same value, and thus the list of valid hypervisors will
51 # have strings instead of easily looked-up names.
52 IGNORED_DECL_NAMES = ["DEFAULT_ENABLED_HYPERVISOR"]
56 """Converts the upper-cased Python name to Haskell camelCase.
59 name = name.replace("-", "_")
60 elems = name.split("_")
61 return elems[0].lower() + "".join(e.capitalize() for e in elems[1:])
64 def StringValueRules(value):
65 """Converts a string value from Python to Haskell.
68 value = value.encode("string_escape") # escapes backslashes
69 value = value.replace("\"", "\\\"")
73 def DictKeyName(dict_name, key_name):
74 """Converts a dict plus key name to a full name.
77 return"%s_%s" % (dict_name, str(key_name).upper())
80 def HaskellTypeVal(value):
81 """Returns the Haskell type and value for a Python value.
83 Note that this only work for 'plain' Python types.
85 @returns: (string, string) or None, if we can't determine the type.
88 if isinstance(value, basestring):
89 return ("String", "\"%s\"" % StringValueRules(value))
90 elif isinstance(value, bool):
91 return ("Bool", "%s" % value)
92 elif isinstance(value, int):
93 return ("Int", "%d" % value)
94 elif isinstance(value, long):
95 return ("Integer", "%d" % value)
96 elif isinstance(value, float):
97 return ("Double", "%f" % value)
102 def IdentifyOrigin(all_items, value):
103 """Tries to identify a constant name from a constant's value.
105 This uses a simple algorithm: is there a constant (and only one)
106 with the same value? If so, then it returns that constants' name.
108 @note: it is recommended to use this only for tuples/lists/sets, and
109 not for individual (top-level) values
110 @param all_items: a dictionary of name/values for the current module
111 @param value: the value for which we try to find an origin
114 found = [name for (name, v) in all_items.items()
115 if v is value and name not in IGNORED_DECL_NAMES]
122 def FormatListElems(all_items, pfx_name, ovals, tvals):
123 """Formats a list's elements.
125 This formats the elements as either values or, if we find all
128 @param all_items: a dictionary of name/values for the current module
129 @param pfx_name: the prefix name currently used
130 @param ovals: the list of actual (Python) values
131 @param tvals: the list of values we want to format in the Haskell form
134 origins = [IdentifyOrigin(all_items, v) for v in ovals]
135 if compat.all(x is not None for x in origins):
136 values = [NameRules(pfx_name + origin) for origin in origins]
139 return ", ".join(values)
142 def FormatDict(all_items, pfx_name, py_name, hs_name, mydict):
143 """Converts a dictionary to a Haskell association list ([(k, v)]),
146 @param all_items: a dictionary of name/values for the current module
147 @param pfx_name: the prefix name currently used
148 @param py_name: the Python name
149 @param hs_name: the Haskell name
150 @param mydict: a dictonary, unknown yet if homogenous or not
153 # need this for ordering
154 orig_list = mydict.items()
155 list_form = [(HaskellTypeVal(k), HaskellTypeVal(v)) for k, v in orig_list]
156 if compat.any(v is None or k is None for k, v in list_form):
159 all_keys = [k for k, _ in list_form]
160 all_vals = [v for _, v in list_form]
161 key_types = set(k[0] for k in all_keys)
162 val_types = set(v[0] for v in all_vals)
163 if not(len(key_types) == 1 and len(val_types) == 1):
166 # record the key and value Haskell types
167 key_type = key_types.pop()
168 val_type = val_types.pop()
170 # now try to find names for the keys, instead of raw values
171 key_origins = [IdentifyOrigin(all_items, k) for k, _ in orig_list]
172 if compat.all(x is not None for x in key_origins):
173 key_v = [NameRules(pfx_name + origin) for origin in key_origins]
175 key_v = [k[1] for k in all_keys]
177 val_origins = [IdentifyOrigin(all_items, v) for _, v in orig_list]
178 if compat.all(x is not None for x in val_origins):
179 val_v = [NameRules(pfx_name + origin) for origin in val_origins]
181 val_v = [v[1] for v in all_vals]
183 # finally generate the output
184 kv_pairs = ["(%s, %s)" % (k, v) for k, v in zip(key_v, val_v)]
185 return ["-- | Converted from Python dictionary @%s@" % py_name,
186 "%s :: [(%s, %s)]" % (hs_name, key_type, val_type),
187 "%s = [%s]" % (hs_name, ", ".join(kv_pairs)),
191 def ConvertVariable(prefix, name, value, all_items):
192 """Converts a given variable to Haskell code.
194 @param prefix: a prefix for the Haskell name (useful for module
196 @param name: the Python name
197 @param value: the value
198 @param all_items: a dictionary of name/value for the module being
200 @return: a list of Haskell code lines
205 pfx_name = prefix + "_"
206 fqn = prefix + "." + name
210 hs_name = NameRules(pfx_name + name)
211 hs_typeval = HaskellTypeVal(value)
212 if (isinstance(value, types.ModuleType) or callable(value) or
213 PRIVATE_RE.match(name)):
214 # no sense in marking these, as we don't _want_ to convert them; the
215 # message in the next if block is for datatypes we don't _know_
216 # (yet) how to convert
218 elif not CONSTANT_RE.match(name):
219 lines.append("-- Skipped %s %s, not constant" % (fqn, type(value)))
220 elif hs_typeval is not None:
221 # this is a simple value
222 (hs_type, hs_val) = hs_typeval
223 lines.append("-- | Converted from Python constant @%s@" % fqn)
224 lines.append("%s :: %s" % (hs_name, hs_type))
225 lines.append("%s = %s" % (hs_name, hs_val))
226 elif isinstance(value, dict):
228 lines.append("-- Following lines come from dictionary %s" % fqn)
229 # try to build a real map here, if all keys have same type, and
230 # all values too (i.e. we have a homogeneous dictionary)
231 lines.extend(FormatDict(all_items, pfx_name, fqn, hs_name, value))
232 # and now create individual names
233 for k in sorted(value.keys()):
234 lines.extend(ConvertVariable(prefix, DictKeyName(name, k),
235 value[k], all_items))
236 elif isinstance(value, tuple):
237 tvs = [HaskellTypeVal(elem) for elem in value]
238 # Custom rule for special cluster verify error tuples
239 if name.startswith("CV_E") and len(value) == 3 and tvs[1][0] is not None:
240 cv_ename = hs_name + "Code"
241 lines.append("-- | Special cluster verify code %s" % name)
242 lines.append("%s :: %s" % (cv_ename, tvs[1][0]))
243 lines.append("%s = %s" % (cv_ename, tvs[1][1]))
245 if compat.all(e is not None for e in tvs):
246 ttypes = ", ".join(e[0] for e in tvs)
247 tvals = FormatListElems(all_items, pfx_name, value, [e[1] for e in tvs])
248 lines.append("-- | Converted from Python tuple @%s@" % fqn)
249 lines.append("%s :: (%s)" % (hs_name, ttypes))
250 lines.append("%s = (%s)" % (hs_name, tvals))
252 lines.append("-- Skipped tuple %s, cannot convert all elements" % fqn)
253 elif isinstance(value, (list, set, frozenset)):
254 # Lists and frozensets are handled the same in Haskell: as lists,
255 # since lists are immutable and we don't need for constants the
256 # high-speed of an actual Set type. However, we can only convert
257 # them if they have the same type for all elements (which is a
258 # normal expectation for constants, our code should be well
259 # behaved); note that this is different from the tuples case,
260 # where we always (for some values of always) can convert
261 tvs = [HaskellTypeVal(elem) for elem in value]
262 if compat.all(e is not None for e in tvs):
263 ttypes, tvals = zip(*tvs)
264 uniq_types = set(ttypes)
265 if len(uniq_types) == 1:
266 values = FormatListElems(all_items, pfx_name, value, tvals)
267 lines.append("-- | Converted from Python list or set @%s@" % fqn)
268 lines.append("%s :: [%s]" % (hs_name, uniq_types.pop()))
269 lines.append("%s = [%s]" % (hs_name, values))
271 lines.append("-- | Skipped list/set %s, is not homogeneous" % fqn)
273 lines.append("-- | Skipped list/set %s, cannot convert all elems" % fqn)
274 elif isinstance(value, RE_TYPE):
275 tvs = HaskellTypeVal(value.pattern)
276 assert tvs is not None
277 lines.append("-- | Converted from Python RE object @%s@" % fqn)
278 lines.append("%s :: %s" % (hs_name, tvs[0]))
279 lines.append("%s = %s" % (hs_name, tvs[1]))
281 lines.append("-- Skipped %s, %s not handled" % (fqn, type(value)))
285 def Convert(module, prefix):
286 """Converts the constants to Haskell.
291 all_items = dict((name, getattr(module, name)) for name in dir(module))
293 for name in sorted(all_items.keys()):
294 value = all_items[name]
295 new_lines = ConvertVariable(prefix, name, value, all_items)
297 lines.extend(new_lines)
300 return "\n".join(lines)
304 """Convert some extra computed-values to Haskell.
308 lines.extend(ConvertVariable("opcodes", "OP_IDS",
309 opcodes.OP_MAPPING.keys(), {}))
310 return "\n".join(lines)
314 print Convert(constants, "")
315 print Convert(luxi, "luxi")
316 print Convert(qlang, "qlang")
317 print Convert(_autoconf, "autoconf")
318 print Convert(errors, "errors")
319 print Convert(jstore, "jstore")
323 if __name__ == "__main__":