Statistics
| Branch: | Tag: | Revision:

root / autotools / convert-constants @ 9eeb0aa5

History | View | Annotate | Download (10 kB)

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
from ganeti import _autoconf
33

    
34

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

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

    
41
#: The type of regex objects
42
RE_TYPE = type(CONSTANT_RE)
43

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

    
51

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

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

    
60

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

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

    
69

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

    
73
  """
74
  return"%s_%s" % (dict_name, str(key_name).upper())
75

    
76

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

    
80
  Note that this only work for 'plain' Python types.
81

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

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

    
96

    
97
def IdentifyOrigin(all_items, value):
98
  """Tries to identify a constant name from a constant's value.
99

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

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

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

    
116

    
117
def FormatListElems(all_items, pfx_name, ovals, tvals):
118
  """Formats a list's elements.
119

    
120
  This formats the elements as either values or, if we find all
121
  origins, as names.
122

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

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

    
136

    
137
def FormatDict(all_items, pfx_name, py_name, hs_name, mydict):
138
  """Converts a dictionary to a Haskell association list ([(k, v)]),
139
  if possible.
140

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

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

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

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

    
185

    
186
def ConvertVariable(prefix, name, value, all_items):
187
  """Converts a given variable to Haskell code.
188

    
189
  @param prefix: a prefix for the Haskell name (useful for module
190
      identification)
191
  @param name: the Python name
192
  @param value: the value
193
  @param all_items: a dictionary of name/value for the module being
194
      processed
195
  @return: a list of Haskell code lines
196

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

    
272

    
273
def Convert(module, prefix):
274
  """Converts the constants to Haskell.
275

    
276
  """
277
  lines = [""]
278

    
279
  all_items = dict((name, getattr(module, name)) for name in dir(module))
280

    
281
  for name in sorted(all_items.keys()):
282
    value = all_items[name]
283
    new_lines = ConvertVariable(prefix, name, value, all_items)
284
    if new_lines:
285
      lines.extend(new_lines)
286
      lines.append("")
287

    
288
  return "\n".join(lines)
289

    
290

    
291
def main():
292
  print Convert(constants, "")
293
  print Convert(luxi, "luxi")
294
  print Convert(qlang, "qlang")
295
  print Convert(_autoconf, "autoconf")
296

    
297

    
298
if __name__ == "__main__":
299
  main()