convert-constants 7.44 KB
Newer Older
1
2
3
#!/usr/bin/python
#

4
# Copyright (C) 2011, 2012 Google Inc.
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.

"""Script for converting Python constants to Haskell code fragments.

"""

import re
26
import types
27

28
from ganeti import compat
29
30
from ganeti import constants
from ganeti import luxi
31
from ganeti import qlang
32

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

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

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

42
43
44
45
46

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

  """
47
  name = name.replace("-", "_")
48
49
50
51
52
53
54
55
56
57
58
59
60
  elems = name.split("_")
  return elems[0].lower() + "".join(e.capitalize() for e in elems[1:])


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

  """
  value = value.encode("string_escape") # escapes backslashes
  value = value.replace("\"", "\\\"")
  return value


61
62
63
64
65
66
67
def DictKeyName(dict_name, key_name):
  """Converts a dict plus key name to a full name.

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


68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
def HaskellTypeVal(value):
  """Returns the Haskell type and value for a Python value.

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

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

  """
  if isinstance(value, basestring):
    return ("String", "\"%s\"" % StringValueRules(value))
  elif isinstance(value, int):
    return ("Int", "%d" % value)
  elif isinstance(value, long):
    return ("Integer", "%d" % value)
  elif isinstance(value, float):
    return ("Double", "%f" % value)
  else:
    return None


88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
def IdentifyOrigin(all_items, value):
  """Tries to identify a constant name from a constant's value.

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

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

  """
  found = [name for (name, v) in all_items.items() if v is value]
  if len(found) == 1:
    return found[0]
  else:
    return None


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

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

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

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


def ConvertVariable(prefix, name, value, all_items):
128
129
  """Converts a given variable to Haskell code.

130
131
  @param prefix: a prefix for the Haskell name (useful for module
      identification)
132
133
  @param name: the Python name
  @param value: the value
134
135
  @param all_items: a dictionary of name/value for the module being
      processed
136
137
138
139
  @return: a list of Haskell code lines

  """
  lines = []
140
141
142
143
144
145
146
  if prefix:
    pfx_name = prefix + "_"
    fqn = prefix + "." + name
  else:
    pfx_name = ""
    fqn = name
  hs_name = NameRules(pfx_name + name)
147
  hs_typeval = HaskellTypeVal(value)
148
149
150
151
152
153
154
155
  if (isinstance(value, types.ModuleType) or callable(value) or
      PRIVATE_RE.match(name)):
    # no sense in marking these, as we don't _want_ to convert them; the
    # message in the next if block is for datatypes we don't _know_
    # (yet) how to convert
    pass
  elif not CONSTANT_RE.match(name):
    lines.append("-- Skipped %s %s, not constant" % (fqn, type(value)))
156
157
158
  elif hs_typeval is not None:
    # this is a simple value
    (hs_type, hs_val) = hs_typeval
159
    lines.append("-- | Converted from Python constant %s" % fqn)
160
161
    lines.append("%s :: %s" % (hs_name, hs_type))
    lines.append("%s = %s" % (hs_name, hs_val))
162
163
  elif isinstance(value, dict):
    if value:
164
      lines.append("-- Following lines come from dictionary %s" % fqn)
165
      for k in sorted(value.keys()):
166
167
        lines.extend(ConvertVariable(prefix, DictKeyName(name, k),
                                     value[k], all_items))
168
169
170
171
  elif isinstance(value, tuple):
    tvs = [HaskellTypeVal(elem) for elem in value]
    if compat.all(e is not None for e in tvs):
      ttypes = ", ".join(e[0] for e in tvs)
172
      tvals = FormatListElems(all_items, pfx_name, value, [e[1] for e in tvs])
173
      lines.append("-- | Converted from Python tuple %s" % fqn)
174
175
176
      lines.append("%s :: (%s)" % (hs_name, ttypes))
      lines.append("%s = (%s)" % (hs_name, tvals))
    else:
177
      lines.append("-- Skipped tuple %s, cannot convert all elements" % fqn)
178
179
180
181
182
183
184
185
186
187
188
189
190
  elif isinstance(value, (list, set, frozenset)):
    # Lists and frozensets are handled the same in Haskell: as lists,
    # since lists are immutable and we don't need for constants the
    # high-speed of an actual Set type. However, we can only convert
    # them if they have the same type for all elements (which is a
    # normal expectation for constants, our code should be well
    # behaved); note that this is different from the tuples case,
    # where we always (for some values of always) can convert
    tvs = [HaskellTypeVal(elem) for elem in value]
    if compat.all(e is not None for e in tvs):
      ttypes, tvals = zip(*tvs)
      uniq_types = set(ttypes)
      if len(uniq_types) == 1:
191
        values = FormatListElems(all_items, pfx_name, value, tvals)
192
        lines.append("-- | Converted from Python list or set %s" % fqn)
193
        lines.append("%s :: [%s]" % (hs_name, uniq_types.pop()))
194
        lines.append("%s = [%s]" % (hs_name, values))
195
      else:
196
        lines.append("-- | Skipped list/set %s, is not homogeneous" % fqn)
197
    else:
198
      lines.append("-- | Skipped list/set %s, cannot convert all elems" % fqn)
199
200
201
  elif isinstance(value, RE_TYPE):
    tvs = HaskellTypeVal(value.pattern)
    assert tvs is not None
202
    lines.append("-- | Converted from Python RE object %s" % fqn)
203
204
    lines.append("%s :: %s" % (hs_name, tvs[0]))
    lines.append("%s = %s" % (hs_name, tvs[1]))
205
  else:
206
    lines.append("-- Skipped %s, %s not handled" % (fqn, type(value)))
207
208
209
  return lines


210
def Convert(module, prefix):
211
212
213
214
215
  """Converts the constants to Haskell.

  """
  lines = [""]

216
  all_items = dict((name, getattr(module, name)) for name in dir(module))
217

218
219
220
  for name in sorted(all_items.keys()):
    value = all_items[name]
    new_lines = ConvertVariable(prefix, name, value, all_items)
221
222
223
    if new_lines:
      lines.extend(new_lines)
      lines.append("")
224
225
226
227
228

  return "\n".join(lines)


def main():
229
230
  print Convert(constants, "")
  print Convert(luxi, "luxi")
231
  print Convert(qlang, "qlang")
232
233
234
235


if __name__ == "__main__":
  main()