Support newstyple rbd commangd output
[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
32 #: Constant name regex
33 CONSTANT_RE = re.compile("^[A-Z][A-Z0-9_-]+$")
34
35 #: Private name regex
36 PRIVATE_RE = re.compile("^__.+__$")
37
38 #: The type of regex objects
39 RE_TYPE = type(CONSTANT_RE)
40
41
42 def NameRules(name):
43   """Converts the upper-cased Python name to Haskell camelCase.
44
45   """
46   name = name.replace("-", "_")
47   elems = name.split("_")
48   return elems[0].lower() + "".join(e.capitalize() for e in elems[1:])
49
50
51 def StringValueRules(value):
52   """Converts a string value from Python to Haskell.
53
54   """
55   value = value.encode("string_escape") # escapes backslashes
56   value = value.replace("\"", "\\\"")
57   return value
58
59
60 def DictKeyName(dict_name, key_name):
61   """Converts a dict plus key name to a full name.
62
63   """
64   return"%s_%s" % (dict_name, str(key_name).upper())
65
66
67 def HaskellTypeVal(value):
68   """Returns the Haskell type and value for a Python value.
69
70   Note that this only work for 'plain' Python types.
71
72   @returns: (string, string) or None, if we can't determine the type.
73
74   """
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)
83   else:
84     return None
85
86
87 def IdentifyOrigin(all_items, value):
88   """Tries to identify a constant name from a constant's value.
89
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.
92
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
97
98   """
99   found = [name for (name, v) in all_items.items() if v is value]
100   if len(found) == 1:
101     return found[0]
102   else:
103     return None
104
105
106 def FormatListElems(all_items, pfx_name, ovals, tvals):
107   """Formats a list's elements.
108
109   This formats the elements as either values or, if we find all
110   origins, as names.
111
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
116
117   """
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]
121   else:
122     values = tvals
123   return ", ".join(values)
124
125
126 def ConvertVariable(prefix, name, value, all_items):
127   """Converts a given variable to Haskell code.
128
129   @param prefix: a prefix for the Haskell name (useful for module
130       identification)
131   @param name: the Python name
132   @param value: the value
133   @param all_items: a dictionary of name/value for the module being
134       processed
135   @return: a list of Haskell code lines
136
137   """
138   lines = []
139   if prefix:
140     pfx_name = prefix + "_"
141     fqn = prefix + "." + name
142   else:
143     pfx_name = ""
144     fqn = 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
152     pass
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):
162     if value:
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))
175     else:
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))
194       else:
195         lines.append("-- | Skipped list/set %s, is not homogeneous" % fqn)
196     else:
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]))
204   else:
205     lines.append("-- Skipped %s, %s not handled" % (fqn, type(value)))
206   return lines
207
208
209 def Convert(module, prefix):
210   """Converts the constants to Haskell.
211
212   """
213   lines = [""]
214
215   all_items = dict((name, getattr(module, name)) for name in dir(module))
216
217   for name in sorted(all_items.keys()):
218     value = all_items[name]
219     new_lines = ConvertVariable(prefix, name, value, all_items)
220     if new_lines:
221       lines.extend(new_lines)
222       lines.append("")
223
224   return "\n".join(lines)
225
226
227 def main():
228   print Convert(constants, "")
229   print Convert(luxi, "luxi")
230
231
232 if __name__ == "__main__":
233   main()