4 # Copyright (C) 2011, 2012 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 compat
29 from ganeti import constants
30 from ganeti import luxi
31 from ganeti import qlang
33 #: Constant name regex
34 CONSTANT_RE = re.compile("^[A-Z][A-Z0-9_-]+$")
37 PRIVATE_RE = re.compile("^__.+__$")
39 #: The type of regex objects
40 RE_TYPE = type(CONSTANT_RE)
44 """Converts the upper-cased Python name to Haskell camelCase.
47 name = name.replace("-", "_")
48 elems = name.split("_")
49 return elems[0].lower() + "".join(e.capitalize() for e in elems[1:])
52 def StringValueRules(value):
53 """Converts a string value from Python to Haskell.
56 value = value.encode("string_escape") # escapes backslashes
57 value = value.replace("\"", "\\\"")
61 def DictKeyName(dict_name, key_name):
62 """Converts a dict plus key name to a full name.
65 return"%s_%s" % (dict_name, str(key_name).upper())
68 def HaskellTypeVal(value):
69 """Returns the Haskell type and value for a Python value.
71 Note that this only work for 'plain' Python types.
73 @returns: (string, string) or None, if we can't determine the type.
76 if isinstance(value, basestring):
77 return ("String", "\"%s\"" % StringValueRules(value))
78 elif isinstance(value, int):
79 return ("Int", "%d" % value)
80 elif isinstance(value, long):
81 return ("Integer", "%d" % value)
82 elif isinstance(value, float):
83 return ("Double", "%f" % value)
88 def IdentifyOrigin(all_items, value):
89 """Tries to identify a constant name from a constant's value.
91 This uses a simple algorithm: is there a constant (and only one)
92 with the same value? If so, then it returns that constants' name.
94 @note: it is recommended to use this only for tuples/lists/sets, and
95 not for individual (top-level) values
96 @param all_items: a dictionary of name/values for the current module
97 @param value: the value for which we try to find an origin
100 found = [name for (name, v) in all_items.items() if v is value]
107 def FormatListElems(all_items, pfx_name, ovals, tvals):
108 """Formats a list's elements.
110 This formats the elements as either values or, if we find all
113 @param all_items: a dictionary of name/values for the current module
114 @param pfx_name: the prefix name currently used
115 @param ovals: the list of actual (Python) values
116 @param tvals: the list of values we want to format in the Haskell form
119 origins = [IdentifyOrigin(all_items, v) for v in ovals]
120 if compat.all(x is not None for x in origins):
121 values = [NameRules(pfx_name + origin) for origin in origins]
124 return ", ".join(values)
127 def ConvertVariable(prefix, name, value, all_items):
128 """Converts a given variable to Haskell code.
130 @param prefix: a prefix for the Haskell name (useful for module
132 @param name: the Python name
133 @param value: the value
134 @param all_items: a dictionary of name/value for the module being
136 @return: a list of Haskell code lines
141 pfx_name = prefix + "_"
142 fqn = prefix + "." + name
146 hs_name = NameRules(pfx_name + name)
147 hs_typeval = HaskellTypeVal(value)
148 if (isinstance(value, types.ModuleType) or callable(value) or
149 PRIVATE_RE.match(name)):
150 # no sense in marking these, as we don't _want_ to convert them; the
151 # message in the next if block is for datatypes we don't _know_
152 # (yet) how to convert
154 elif not CONSTANT_RE.match(name):
155 lines.append("-- Skipped %s %s, not constant" % (fqn, type(value)))
156 elif hs_typeval is not None:
157 # this is a simple value
158 (hs_type, hs_val) = hs_typeval
159 lines.append("-- | Converted from Python constant %s" % fqn)
160 lines.append("%s :: %s" % (hs_name, hs_type))
161 lines.append("%s = %s" % (hs_name, hs_val))
162 elif isinstance(value, dict):
164 lines.append("-- Following lines come from dictionary %s" % fqn)
165 for k in sorted(value.keys()):
166 lines.extend(ConvertVariable(prefix, DictKeyName(name, k),
167 value[k], all_items))
168 elif isinstance(value, tuple):
169 tvs = [HaskellTypeVal(elem) for elem in value]
170 if compat.all(e is not None for e in tvs):
171 ttypes = ", ".join(e[0] for e in tvs)
172 tvals = FormatListElems(all_items, pfx_name, value, [e[1] for e in tvs])
173 lines.append("-- | Converted from Python tuple %s" % fqn)
174 lines.append("%s :: (%s)" % (hs_name, ttypes))
175 lines.append("%s = (%s)" % (hs_name, tvals))
177 lines.append("-- Skipped tuple %s, cannot convert all elements" % fqn)
178 elif isinstance(value, (list, set, frozenset)):
179 # Lists and frozensets are handled the same in Haskell: as lists,
180 # since lists are immutable and we don't need for constants the
181 # high-speed of an actual Set type. However, we can only convert
182 # them if they have the same type for all elements (which is a
183 # normal expectation for constants, our code should be well
184 # behaved); note that this is different from the tuples case,
185 # where we always (for some values of always) can convert
186 tvs = [HaskellTypeVal(elem) for elem in value]
187 if compat.all(e is not None for e in tvs):
188 ttypes, tvals = zip(*tvs)
189 uniq_types = set(ttypes)
190 if len(uniq_types) == 1:
191 values = FormatListElems(all_items, pfx_name, value, tvals)
192 lines.append("-- | Converted from Python list or set %s" % fqn)
193 lines.append("%s :: [%s]" % (hs_name, uniq_types.pop()))
194 lines.append("%s = [%s]" % (hs_name, values))
196 lines.append("-- | Skipped list/set %s, is not homogeneous" % fqn)
198 lines.append("-- | Skipped list/set %s, cannot convert all elems" % fqn)
199 elif isinstance(value, RE_TYPE):
200 tvs = HaskellTypeVal(value.pattern)
201 assert tvs is not None
202 lines.append("-- | Converted from Python RE object %s" % fqn)
203 lines.append("%s :: %s" % (hs_name, tvs[0]))
204 lines.append("%s = %s" % (hs_name, tvs[1]))
206 lines.append("-- Skipped %s, %s not handled" % (fqn, type(value)))
210 def Convert(module, prefix):
211 """Converts the constants to Haskell.
216 all_items = dict((name, getattr(module, name)) for name in dir(module))
218 for name in sorted(all_items.keys()):
219 value = all_items[name]
220 new_lines = ConvertVariable(prefix, name, value, all_items)
222 lines.extend(new_lines)
225 return "\n".join(lines)
229 print Convert(constants, "")
230 print Convert(luxi, "luxi")
231 print Convert(qlang, "qlang")
234 if __name__ == "__main__":