Statistics
| Branch: | Tag: | Revision:

root / autotools / convert-constants @ d5cc16d7

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 _autoconf
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

    
35

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

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

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

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

    
52

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

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

    
61

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

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

    
70

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

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

    
77

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

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

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

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

    
97

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

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

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

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

    
117

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

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

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

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

    
137

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

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

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

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

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

    
186

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

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

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

    
273

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

    
277
  """
278
  lines = [""]
279

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

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

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

    
291

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

    
299

    
300
if __name__ == "__main__":
301
  main()