Statistics
| Branch: | Tag: | Revision:

root / autotools / convert-constants @ 88ac4075

History | View | Annotate | Download (10.3 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 compat
29
from ganeti import errors
30
from ganeti import qlang
31
from ganeti import jstore
32

    
33

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

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

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

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

    
50

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

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

    
59

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

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

    
68

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

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

    
75

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

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

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

    
83
  """
84
  if isinstance(value, basestring):
85
    return ("String", "\"%s\"" % StringValueRules(value))
86
  elif isinstance(value, bool):
87
    return ("Bool", "%s" % 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
    # Custom rule for special cluster verify error tuples
235
    if name.startswith("CV_E") and len(value) == 3 and tvs[1][0] is not None:
236
      cv_ename = hs_name + "Code"
237
      lines.append("-- | Special cluster verify code %s" % name)
238
      lines.append("%s :: %s" % (cv_ename, tvs[1][0]))
239
      lines.append("%s = %s" % (cv_ename, tvs[1][1]))
240
      lines.append("")
241
    if compat.all(e is not None for e in tvs):
242
      ttypes = ", ".join(e[0] for e in tvs)
243
      tvals = FormatListElems(all_items, pfx_name, value, [e[1] for e in tvs])
244
      lines.append("-- | Converted from Python tuple @%s@" % fqn)
245
      lines.append("%s :: (%s)" % (hs_name, ttypes))
246
      lines.append("%s = (%s)" % (hs_name, tvals))
247
    else:
248
      lines.append("-- Skipped tuple %s, cannot convert all elements" % fqn)
249
  elif isinstance(value, (list, set, frozenset)):
250
    # Lists and frozensets are handled the same in Haskell: as lists,
251
    # since lists are immutable and we don't need for constants the
252
    # high-speed of an actual Set type. However, we can only convert
253
    # them if they have the same type for all elements (which is a
254
    # normal expectation for constants, our code should be well
255
    # behaved); note that this is different from the tuples case,
256
    # where we always (for some values of always) can convert
257
    tvs = [HaskellTypeVal(elem) for elem in value]
258
    if compat.all(e is not None for e in tvs):
259
      ttypes, tvals = zip(*tvs)
260
      uniq_types = set(ttypes)
261
      if len(uniq_types) == 1:
262
        values = FormatListElems(all_items, pfx_name, value, tvals)
263
        lines.append("-- | Converted from Python list or set @%s@" % fqn)
264
        lines.append("%s :: [%s]" % (hs_name, uniq_types.pop()))
265
        lines.append("%s = [%s]" % (hs_name, values))
266
      else:
267
        lines.append("-- | Skipped list/set %s, is not homogeneous" % fqn)
268
    else:
269
      lines.append("-- | Skipped list/set %s, cannot convert all elems" % fqn)
270
  elif isinstance(value, RE_TYPE):
271
    tvs = HaskellTypeVal(value.pattern)
272
    assert tvs is not None
273
    lines.append("-- | Converted from Python RE object @%s@" % fqn)
274
    lines.append("%s :: %s" % (hs_name, tvs[0]))
275
    lines.append("%s = %s" % (hs_name, tvs[1]))
276
  else:
277
    lines.append("-- Skipped %s, %s not handled" % (fqn, type(value)))
278
  return lines
279

    
280

    
281
def Convert(module, prefix):
282
  """Converts the constants to Haskell.
283

    
284
  """
285
  lines = [""]
286

    
287
  all_items = dict((name, getattr(module, name))
288
                   for name in dir(module))
289

    
290
  for name in sorted(all_items.keys()):
291
    value = all_items[name]
292
    new_lines = ConvertVariable(prefix, name, value, all_items)
293
    if new_lines:
294
      lines.extend(new_lines)
295
      lines.append("")
296

    
297
  return "\n".join(lines)
298

    
299

    
300
def main():
301
  print Convert(jstore, "jstore")
302

    
303

    
304
if __name__ == "__main__":
305
  main()