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
32 #: Constant name regex
33 CONSTANT_RE = re.compile("^[A-Z][A-Z0-9_-]+$")
36 PRIVATE_RE = re.compile("^__.+__$")
38 #: The type of regex objects
39 RE_TYPE = type(CONSTANT_RE)
43 """Converts the upper-cased Python name to Haskell camelCase.
46 name = name.replace("-", "_")
47 elems = name.split("_")
48 return elems[0].lower() + "".join(e.capitalize() for e in elems[1:])
51 def StringValueRules(value):
52 """Converts a string value from Python to Haskell.
55 value = value.encode("string_escape") # escapes backslashes
56 value = value.replace("\"", "\\\"")
60 def DictKeyName(dict_name, key_name):
61 """Converts a dict plus key name to a full name.
64 return"%s_%s" % (dict_name, str(key_name).upper())
67 def HaskellTypeVal(value):
68 """Returns the Haskell type and value for a Python value.
70 Note that this only work for 'plain' Python types.
72 @returns: (string, string) or None, if we can't determine the type.
75 if isinstance(value, basestring):
76 return ("String", "\"%s\"" % StringValueRules(value))
77 elif isinstance(value, int):
78 return ("Int", "%d" % value)
79 elif isinstance(value, long):
80 return ("Integer", "%d" % value)
81 elif isinstance(value, float):
82 return ("Double", "%f" % value)
87 def IdentifyOrigin(all_items, value):
88 """Tries to identify a constant name from a constant's value.
90 This uses a simple algorithm: is there a constant (and only one)
91 with the same value? If so, then it returns that constants' name.
93 @note: it is recommended to use this only for tuples/lists/sets, and
94 not for individual (top-level) values
95 @param all_items: a dictionary of name/values for the current module
96 @param value: the value for which we try to find an origin
99 found = [name for (name, v) in all_items.items() if v is value]
106 def FormatListElems(all_items, pfx_name, ovals, tvals):
107 """Formats a list's elements.
109 This formats the elements as either values or, if we find all
112 @param all_items: a dictionary of name/values for the current module
113 @param pfx_name: the prefix name currently used
114 @param ovals: the list of actual (Python) values
115 @param tvals: the list of values we want to format in the Haskell form
118 origins = [IdentifyOrigin(all_items, v) for v in ovals]
119 if compat.all(x is not None for x in origins):
120 values = [NameRules(pfx_name + origin) for origin in origins]
123 return ", ".join(values)
126 def ConvertVariable(prefix, name, value, all_items):
127 """Converts a given variable to Haskell code.
129 @param prefix: a prefix for the Haskell name (useful for module
131 @param name: the Python name
132 @param value: the value
133 @param all_items: a dictionary of name/value for the module being
135 @return: a list of Haskell code lines
140 pfx_name = prefix + "_"
141 fqn = prefix + "." + name
145 hs_name = NameRules(pfx_name + name)
146 hs_typeval = HaskellTypeVal(value)
147 if (isinstance(value, types.ModuleType) or callable(value) or
148 PRIVATE_RE.match(name)):
149 # no sense in marking these, as we don't _want_ to convert them; the
150 # message in the next if block is for datatypes we don't _know_
151 # (yet) how to convert
153 elif not CONSTANT_RE.match(name):
154 lines.append("-- Skipped %s %s, not constant" % (fqn, type(value)))
155 elif hs_typeval is not None:
156 # this is a simple value
157 (hs_type, hs_val) = hs_typeval
158 lines.append("-- | Converted from Python constant %s" % fqn)
159 lines.append("%s :: %s" % (hs_name, hs_type))
160 lines.append("%s = %s" % (hs_name, hs_val))
161 elif isinstance(value, dict):
163 lines.append("-- Following lines come from dictionary %s" % fqn)
164 for k in sorted(value.keys()):
165 lines.extend(ConvertVariable(prefix, DictKeyName(name, k),
166 value[k], all_items))
167 elif isinstance(value, tuple):
168 tvs = [HaskellTypeVal(elem) for elem in value]
169 if compat.all(e is not None for e in tvs):
170 ttypes = ", ".join(e[0] for e in tvs)
171 tvals = FormatListElems(all_items, pfx_name, value, [e[1] for e in tvs])
172 lines.append("-- | Converted from Python tuple %s" % fqn)
173 lines.append("%s :: (%s)" % (hs_name, ttypes))
174 lines.append("%s = (%s)" % (hs_name, tvals))
176 lines.append("-- Skipped tuple %s, cannot convert all elements" % fqn)
177 elif isinstance(value, (list, set, frozenset)):
178 # Lists and frozensets are handled the same in Haskell: as lists,
179 # since lists are immutable and we don't need for constants the
180 # high-speed of an actual Set type. However, we can only convert
181 # them if they have the same type for all elements (which is a
182 # normal expectation for constants, our code should be well
183 # behaved); note that this is different from the tuples case,
184 # where we always (for some values of always) can convert
185 tvs = [HaskellTypeVal(elem) for elem in value]
186 if compat.all(e is not None for e in tvs):
187 ttypes, tvals = zip(*tvs)
188 uniq_types = set(ttypes)
189 if len(uniq_types) == 1:
190 values = FormatListElems(all_items, pfx_name, value, tvals)
191 lines.append("-- | Converted from Python list or set %s" % fqn)
192 lines.append("%s :: [%s]" % (hs_name, uniq_types.pop()))
193 lines.append("%s = [%s]" % (hs_name, values))
195 lines.append("-- | Skipped list/set %s, is not homogeneous" % fqn)
197 lines.append("-- | Skipped list/set %s, cannot convert all elems" % fqn)
198 elif isinstance(value, RE_TYPE):
199 tvs = HaskellTypeVal(value.pattern)
200 assert tvs is not None
201 lines.append("-- | Converted from Python RE object %s" % fqn)
202 lines.append("%s :: %s" % (hs_name, tvs[0]))
203 lines.append("%s = %s" % (hs_name, tvs[1]))
205 lines.append("-- Skipped %s, %s not handled" % (fqn, type(value)))
209 def Convert(module, prefix):
210 """Converts the constants to Haskell.
215 all_items = dict((name, getattr(module, name)) for name in dir(module))
217 for name in sorted(all_items.keys()):
218 value = all_items[name]
219 new_lines = ConvertVariable(prefix, name, value, all_items)
221 lines.extend(new_lines)
224 return "\n".join(lines)
228 print Convert(constants, "")
229 print Convert(luxi, "luxi")
232 if __name__ == "__main__":