Statistics
| Branch: | Tag: | Revision:

root / autotools / convert-constants @ b8669a69

History | View | Annotate | Download (10.6 kB)

1
#!/usr/bin/python
2
#
3

    
4
# Copyright (C) 2011, 2012, 2013 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 _constants
29
from ganeti import compat
30
from ganeti import constants
31
from ganeti import errors
32
from ganeti import luxi
33
from ganeti import qlang
34
from ganeti import jstore
35

    
36

    
37
#: Constant name regex
38
CONSTANT_RE = re.compile("^[A-Z][A-Z0-9_-]+$")
39

    
40
#: Private name regex
41
PRIVATE_RE = re.compile("^__.+__$")
42

    
43
#: The type of regex objects
44
RE_TYPE = type(CONSTANT_RE)
45

    
46
#: Keys which do not declare a value (manually maintained). By adding
47
# values here, we can make more lists use the actual names; otherwise
48
# we'll have (e.g.) both DEFAULT_ENABLED_HYPERVISOR and HT_XEN_PVM
49
# declare the same value, and thus the list of valid hypervisors will
50
# have strings instead of easily looked-up names.
51
IGNORED_DECL_NAMES = ["DEFAULT_ENABLED_HYPERVISOR"]
52

    
53

    
54
def NameRules(name):
55
  """Converts the upper-cased Python name to Haskell camelCase.
56

    
57
  """
58
  name = name.replace("-", "_")
59
  elems = name.split("_")
60
  return elems[0].lower() + "".join(e.capitalize() for e in elems[1:])
61

    
62

    
63
def StringValueRules(value):
64
  """Converts a string value from Python to Haskell.
65

    
66
  """
67
  value = value.encode("string_escape") # escapes backslashes
68
  value = value.replace("\"", "\\\"")
69
  return value
70

    
71

    
72
def DictKeyName(dict_name, key_name):
73
  """Converts a dict plus key name to a full name.
74

    
75
  """
76
  return"%s_%s" % (dict_name, str(key_name).upper())
77

    
78

    
79
def HaskellTypeVal(value):
80
  """Returns the Haskell type and value for a Python value.
81

    
82
  Note that this only work for 'plain' Python types.
83

    
84
  @returns: (string, string) or None, if we can't determine the type.
85

    
86
  """
87
  if isinstance(value, basestring):
88
    return ("String", "\"%s\"" % StringValueRules(value))
89
  elif isinstance(value, bool):
90
    return ("Bool", "%s" % value)
91
  elif isinstance(value, int):
92
    return ("Int", "%d" % value)
93
  elif isinstance(value, long):
94
    return ("Integer", "%d" % value)
95
  elif isinstance(value, float):
96
    return ("Double", "%f" % value)
97
  else:
98
    return None
99

    
100

    
101
def IdentifyOrigin(all_items, value):
102
  """Tries to identify a constant name from a constant's value.
103

    
104
  This uses a simple algorithm: is there a constant (and only one)
105
  with the same value? If so, then it returns that constants' name.
106

    
107
  @note: it is recommended to use this only for tuples/lists/sets, and
108
      not for individual (top-level) values
109
  @param all_items: a dictionary of name/values for the current module
110
  @param value: the value for which we try to find an origin
111

    
112
  """
113
  found = [name for (name, v) in all_items.items()
114
           if v is value and name not in IGNORED_DECL_NAMES]
115
  if len(found) == 1:
116
    return found[0]
117
  else:
118
    return None
119

    
120

    
121
def FormatListElems(all_items, pfx_name, ovals, tvals):
122
  """Formats a list's elements.
123

    
124
  This formats the elements as either values or, if we find all
125
  origins, as names.
126

    
127
  @param all_items: a dictionary of name/values for the current module
128
  @param pfx_name: the prefix name currently used
129
  @param ovals: the list of actual (Python) values
130
  @param tvals: the list of values we want to format in the Haskell form
131

    
132
  """
133
  origins = [IdentifyOrigin(all_items, v) for v in ovals]
134
  if compat.all(x is not None for x in origins):
135
    values = [NameRules(pfx_name + origin) for origin in origins]
136
  else:
137
    values = tvals
138
  return ", ".join(values)
139

    
140

    
141
def FormatDict(all_items, pfx_name, py_name, hs_name, mydict):
142
  """Converts a dictionary to a Haskell association list ([(k, v)]),
143
  if possible.
144

    
145
  @param all_items: a dictionary of name/values for the current module
146
  @param pfx_name: the prefix name currently used
147
  @param py_name: the Python name
148
  @param hs_name: the Haskell name
149
  @param mydict: a dictonary, unknown yet if homogenous or not
150

    
151
  """
152
  # need this for ordering
153
  orig_list = mydict.items()
154
  list_form = [(HaskellTypeVal(k), HaskellTypeVal(v)) for k, v in orig_list]
155
  if compat.any(v is None or k is None for k, v in list_form):
156
    # type not known
157
    return []
158
  all_keys = [k for k, _ in list_form]
159
  all_vals = [v for _, v in list_form]
160
  key_types = set(k[0] for k in all_keys)
161
  val_types = set(v[0] for v in all_vals)
162
  if not(len(key_types) == 1 and len(val_types) == 1):
163
    # multiple types
164
    return []
165
  # record the key and value Haskell types
166
  key_type = key_types.pop()
167
  val_type = val_types.pop()
168

    
169
  # now try to find names for the keys, instead of raw values
170
  key_origins = [IdentifyOrigin(all_items, k) for k, _ in orig_list]
171
  if compat.all(x is not None for x in key_origins):
172
    key_v = [NameRules(pfx_name + origin) for origin in key_origins]
173
  else:
174
    key_v = [k[1] for k in all_keys]
175
  # ... and for values
176
  val_origins = [IdentifyOrigin(all_items, v) for _, v in orig_list]
177
  if compat.all(x is not None for x in val_origins):
178
    val_v = [NameRules(pfx_name + origin) for origin in val_origins]
179
  else:
180
    val_v = [v[1] for v in all_vals]
181

    
182
  # finally generate the output
183
  kv_pairs = ["(%s, %s)" % (k, v) for k, v in zip(key_v, val_v)]
184
  return ["-- | Converted from Python dictionary @%s@" % py_name,
185
          "%s :: [(%s, %s)]" % (hs_name, key_type, val_type),
186
          "%s = [%s]" % (hs_name, ", ".join(kv_pairs)),
187
          ]
188

    
189

    
190
def ConvertVariable(prefix, name, value, all_items):
191
  """Converts a given variable to Haskell code.
192

    
193
  @param prefix: a prefix for the Haskell name (useful for module
194
      identification)
195
  @param name: the Python name
196
  @param value: the value
197
  @param all_items: a dictionary of name/value for the module being
198
      processed
199
  @return: a list of Haskell code lines
200

    
201
  """
202
  lines = []
203
  if prefix:
204
    pfx_name = prefix + "_"
205
    fqn = prefix + "." + name
206
  else:
207
    pfx_name = ""
208
    fqn = name
209
  hs_name = NameRules(pfx_name + name)
210
  hs_typeval = HaskellTypeVal(value)
211
  if (isinstance(value, types.ModuleType) or callable(value) or
212
      PRIVATE_RE.match(name)):
213
    # no sense in marking these, as we don't _want_ to convert them; the
214
    # message in the next if block is for datatypes we don't _know_
215
    # (yet) how to convert
216
    pass
217
  elif not CONSTANT_RE.match(name):
218
    lines.append("-- Skipped %s %s, not constant" % (fqn, type(value)))
219
  elif hs_typeval is not None:
220
    # this is a simple value
221
    (hs_type, hs_val) = hs_typeval
222
    lines.append("-- | Converted from Python constant @%s@" % fqn)
223
    lines.append("%s :: %s" % (hs_name, hs_type))
224
    lines.append("%s = %s" % (hs_name, hs_val))
225
  elif isinstance(value, dict):
226
    if value:
227
      lines.append("-- Following lines come from dictionary %s" % fqn)
228
      # try to build a real map here, if all keys have same type, and
229
      # all values too (i.e. we have a homogeneous dictionary)
230
      lines.extend(FormatDict(all_items, pfx_name, fqn, hs_name, value))
231
      # and now create individual names
232
      for k in sorted(value.keys()):
233
        lines.extend(ConvertVariable(prefix, DictKeyName(name, k),
234
                                     value[k], all_items))
235
  elif isinstance(value, tuple):
236
    tvs = [HaskellTypeVal(elem) for elem in value]
237
    # Custom rule for special cluster verify error tuples
238
    if name.startswith("CV_E") and len(value) == 3 and tvs[1][0] is not None:
239
      cv_ename = hs_name + "Code"
240
      lines.append("-- | Special cluster verify code %s" % name)
241
      lines.append("%s :: %s" % (cv_ename, tvs[1][0]))
242
      lines.append("%s = %s" % (cv_ename, tvs[1][1]))
243
      lines.append("")
244
    if compat.all(e is not None for e in tvs):
245
      ttypes = ", ".join(e[0] for e in tvs)
246
      tvals = FormatListElems(all_items, pfx_name, value, [e[1] for e in tvs])
247
      lines.append("-- | Converted from Python tuple @%s@" % fqn)
248
      lines.append("%s :: (%s)" % (hs_name, ttypes))
249
      lines.append("%s = (%s)" % (hs_name, tvals))
250
    else:
251
      lines.append("-- Skipped tuple %s, cannot convert all elements" % fqn)
252
  elif isinstance(value, (list, set, frozenset)):
253
    # Lists and frozensets are handled the same in Haskell: as lists,
254
    # since lists are immutable and we don't need for constants the
255
    # high-speed of an actual Set type. However, we can only convert
256
    # them if they have the same type for all elements (which is a
257
    # normal expectation for constants, our code should be well
258
    # behaved); note that this is different from the tuples case,
259
    # where we always (for some values of always) can convert
260
    tvs = [HaskellTypeVal(elem) for elem in value]
261
    if compat.all(e is not None for e in tvs):
262
      ttypes, tvals = zip(*tvs)
263
      uniq_types = set(ttypes)
264
      if len(uniq_types) == 1:
265
        values = FormatListElems(all_items, pfx_name, value, tvals)
266
        lines.append("-- | Converted from Python list or set @%s@" % fqn)
267
        lines.append("%s :: [%s]" % (hs_name, uniq_types.pop()))
268
        lines.append("%s = [%s]" % (hs_name, values))
269
      else:
270
        lines.append("-- | Skipped list/set %s, is not homogeneous" % fqn)
271
    else:
272
      lines.append("-- | Skipped list/set %s, cannot convert all elems" % fqn)
273
  elif isinstance(value, RE_TYPE):
274
    tvs = HaskellTypeVal(value.pattern)
275
    assert tvs is not None
276
    lines.append("-- | Converted from Python RE object @%s@" % fqn)
277
    lines.append("%s :: %s" % (hs_name, tvs[0]))
278
    lines.append("%s = %s" % (hs_name, tvs[1]))
279
  else:
280
    lines.append("-- Skipped %s, %s not handled" % (fqn, type(value)))
281
  return lines
282

    
283

    
284
def Convert(module, prefix):
285
  """Converts the constants to Haskell.
286

    
287
  """
288
  lines = [""]
289

    
290
  all_items = dict((name, getattr(module, name))
291
                   for name in dir(module)
292
                   if name not in dir(_constants))
293

    
294
  for name in sorted(all_items.keys()):
295
    value = all_items[name]
296
    new_lines = ConvertVariable(prefix, name, value, all_items)
297
    if new_lines:
298
      lines.extend(new_lines)
299
      lines.append("")
300

    
301
  return "\n".join(lines)
302

    
303

    
304
def main():
305
  print Convert(constants, "")
306
  print Convert(luxi, "luxi")
307
  print Convert(qlang, "qlang")
308
  print Convert(errors, "errors")
309
  print Convert(jstore, "jstore")
310

    
311

    
312
if __name__ == "__main__":
313
  main()