Switch Luxi Query operation to use a proper filter
[ganeti-local] / autotools / convert-constants
1 #!/usr/bin/python
2 #
3
4 # Copyright (C) 2011, 2012 Google Inc.
5 #
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.
10 #
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.
15 #
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
19 # 02110-1301, USA.
20
21 """Script for converting Python constants to Haskell code fragments.
22
23 """
24
25 import re
26 import types
27
28 from ganeti import compat
29 from ganeti import constants
30 from ganeti import luxi
31 from ganeti import qlang
32
33 #: Constant name regex
34 CONSTANT_RE = re.compile("^[A-Z][A-Z0-9_-]+$")
35
36 #: Private name regex
37 PRIVATE_RE = re.compile("^__.+__$")
38
39 #: The type of regex objects
40 RE_TYPE = type(CONSTANT_RE)
41
42
43 def NameRules(name):
44   """Converts the upper-cased Python name to Haskell camelCase.
45
46   """
47   name = name.replace("-", "_")
48   elems = name.split("_")
49   return elems[0].lower() + "".join(e.capitalize() for e in elems[1:])
50
51
52 def StringValueRules(value):
53   """Converts a string value from Python to Haskell.
54
55   """
56   value = value.encode("string_escape") # escapes backslashes
57   value = value.replace("\"", "\\\"")
58   return value
59
60
61 def DictKeyName(dict_name, key_name):
62   """Converts a dict plus key name to a full name.
63
64   """
65   return"%s_%s" % (dict_name, str(key_name).upper())
66
67
68 def HaskellTypeVal(value):
69   """Returns the Haskell type and value for a Python value.
70
71   Note that this only work for 'plain' Python types.
72
73   @returns: (string, string) or None, if we can't determine the type.
74
75   """
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)
84   else:
85     return None
86
87
88 def IdentifyOrigin(all_items, value):
89   """Tries to identify a constant name from a constant's value.
90
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.
93
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
98
99   """
100   found = [name for (name, v) in all_items.items() if v is value]
101   if len(found) == 1:
102     return found[0]
103   else:
104     return None
105
106
107 def FormatListElems(all_items, pfx_name, ovals, tvals):
108   """Formats a list's elements.
109
110   This formats the elements as either values or, if we find all
111   origins, as names.
112
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
117
118   """
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]
122   else:
123     values = tvals
124   return ", ".join(values)
125
126
127 def ConvertVariable(prefix, name, value, all_items):
128   """Converts a given variable to Haskell code.
129
130   @param prefix: a prefix for the Haskell name (useful for module
131       identification)
132   @param name: the Python name
133   @param value: the value
134   @param all_items: a dictionary of name/value for the module being
135       processed
136   @return: a list of Haskell code lines
137
138   """
139   lines = []
140   if prefix:
141     pfx_name = prefix + "_"
142     fqn = prefix + "." + name
143   else:
144     pfx_name = ""
145     fqn = 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
153     pass
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):
163     if value:
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))
176     else:
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))
195       else:
196         lines.append("-- | Skipped list/set %s, is not homogeneous" % fqn)
197     else:
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]))
205   else:
206     lines.append("-- Skipped %s, %s not handled" % (fqn, type(value)))
207   return lines
208
209
210 def Convert(module, prefix):
211   """Converts the constants to Haskell.
212
213   """
214   lines = [""]
215
216   all_items = dict((name, getattr(module, name)) for name in dir(module))
217
218   for name in sorted(all_items.keys()):
219     value = all_items[name]
220     new_lines = ConvertVariable(prefix, name, value, all_items)
221     if new_lines:
222       lines.extend(new_lines)
223       lines.append("")
224
225   return "\n".join(lines)
226
227
228 def main():
229   print Convert(constants, "")
230   print Convert(luxi, "luxi")
231   print Convert(qlang, "qlang")
232
233
234 if __name__ == "__main__":
235   main()