convert-constants 5.83 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

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

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

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

41
42
43
44
45

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

  """
46
  name = name.replace("-", "_")
47
48
49
50
51
52
53
54
55
56
57
58
59
  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


60
61
62
63
64
65
66
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())


67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
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


87
def ConvertVariable(prefix, name, value):
88
89
  """Converts a given variable to Haskell code.

90
91
  @param prefix: a prefix for the Haskell name (useful for module
      identification)
92
93
94
95
96
97
  @param name: the Python name
  @param value: the value
  @return: a list of Haskell code lines

  """
  lines = []
98
99
100
101
102
103
104
  if prefix:
    pfx_name = prefix + "_"
    fqn = prefix + "." + name
  else:
    pfx_name = ""
    fqn = name
  hs_name = NameRules(pfx_name + name)
105
  hs_typeval = HaskellTypeVal(value)
106
107
108
109
110
111
112
113
  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)))
114
115
116
  elif hs_typeval is not None:
    # this is a simple value
    (hs_type, hs_val) = hs_typeval
117
    lines.append("-- | Converted from Python constant %s" % fqn)
118
119
    lines.append("%s :: %s" % (hs_name, hs_type))
    lines.append("%s = %s" % (hs_name, hs_val))
120
121
  elif isinstance(value, dict):
    if value:
122
      lines.append("-- Following lines come from dictionary %s" % fqn)
123
      for k in sorted(value.keys()):
124
        lines.extend(ConvertVariable(prefix, DictKeyName(name, k), value[k]))
125
126
127
128
129
  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)
      tvals = ", ".join(e[1] for e in tvs)
130
      lines.append("-- | Converted from Python tuple %s" % fqn)
131
132
133
      lines.append("%s :: (%s)" % (hs_name, ttypes))
      lines.append("%s = (%s)" % (hs_name, tvals))
    else:
134
      lines.append("-- Skipped tuple %s, cannot convert all elements" % fqn)
135
136
137
138
139
140
141
142
143
144
145
146
147
  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:
148
        lines.append("-- | Converted from Python list or set %s" % fqn)
149
150
151
        lines.append("%s :: [%s]" % (hs_name, uniq_types.pop()))
        lines.append("%s = [%s]" % (hs_name, ", ".join(tvals)))
      else:
152
        lines.append("-- | Skipped list/set %s, is not homogeneous" % fqn)
153
    else:
154
      lines.append("-- | Skipped list/set %s, cannot convert all elems" % fqn)
155
156
157
  elif isinstance(value, RE_TYPE):
    tvs = HaskellTypeVal(value.pattern)
    assert tvs is not None
158
    lines.append("-- | Converted from Python RE object %s" % fqn)
159
160
    lines.append("%s :: %s" % (hs_name, tvs[0]))
    lines.append("%s = %s" % (hs_name, tvs[1]))
161
  else:
162
    lines.append("-- Skipped %s, %s not handled" % (fqn, type(value)))
163
164
165
  return lines


166
def Convert(module, prefix):
167
168
169
170
171
  """Converts the constants to Haskell.

  """
  lines = [""]

172
  all_names = dir(module)
173
174

  for name in all_names:
175
176
177
178
179
    value = getattr(module, name)
    new_lines = ConvertVariable(prefix, name, value)
    if new_lines:
      lines.extend(new_lines)
      lines.append("")
180
181
182
183
184

  return "\n".join(lines)


def main():
185
186
  print Convert(constants, "")
  print Convert(luxi, "luxi")
187
188
189
190


if __name__ == "__main__":
  main()