Revision 74e15780

b/.gitignore
143 143
/src/AutoConf.hs
144 144
/src/Ganeti/Curl/Internal.hs
145 145
/src/Ganeti/Hs2Py/ListConstants.hs
146
/src/Ganeti/PyConstants.hs
147 146
/src/Ganeti/Version.hs
148 147
/test/hs/Test/Ganeti/TestImports.hs
b/Makefile.am
36 36
CHECK_IMPORTS = $(top_srcdir)/autotools/check-imports
37 37
DOCPP = $(top_srcdir)/autotools/docpp
38 38
REPLACE_VARS_SED = autotools/replace_vars.sed
39
CONVERT_CONSTANTS = $(top_srcdir)/autotools/convert-constants
40 39
PRINT_PY_CONSTANTS = $(top_srcdir)/autotools/print-py-constants
41 40
BUILD_RPC = $(top_srcdir)/autotools/build-rpc
42 41
SHELL_ENV_INIT = autotools/shell-env-init
......
780 779
	test/hs/Test/Ganeti/TestImports.hs \
781 780
	src/AutoConf.hs \
782 781
	src/Ganeti/Hs2Py/ListConstants.hs \
783
	src/Ganeti/PyConstants.hs \
784 782
	src/Ganeti/Curl/Internal.hs \
785 783
	src/Ganeti/Version.hs
786 784
HS_BUILT_SRCS_IN = \
......
1134 1132
	autotools/check-python-code \
1135 1133
	autotools/check-tar \
1136 1134
	autotools/check-version \
1137
	autotools/convert-constants \
1138 1135
	autotools/docpp \
1139 1136
	autotools/gen-py-coverage \
1140 1137
	autotools/print-py-constants \
......
1756 1753
		 sed -n -e "/=/ s/\(.*\) =.*/    '\1:/g p"); \
1757 1754
	m4 -DPY_CONSTANT_NAMES="$$NAMES" $(abs_top_srcdir)/$< > $@
1758 1755

  
1759
src/Ganeti/PyConstants.hs: src/Ganeti/PyConstants.hs.in \
1760
	lib/constants.py lib/luxi.py lib/errors.py \
1761
	lib/jstore.py $(RUN_IN_TEMPDIR) \
1762
	$(CONVERT_CONSTANTS) $(built_base_sources) \
1763
	| lib/_vcsversion.py
1764
	set -e; \
1765
	{ cat $< ; \
1766
	  PYTHONPATH=. $(RUN_IN_TEMPDIR) $(CURDIR)/$(CONVERT_CONSTANTS); \
1767
	} > $@
1768

  
1769 1756
src/Ganeti/Curl/Internal.hs: src/Ganeti/Curl/Internal.hsc | stamp-directories
1770 1757
	hsc2hs -o $@ $<
1771 1758

  
......
1877 1864
	  echo "VCS_VERSION = '$$VCSVER'"; \
1878 1865
	} > $@
1879 1866

  
1880
lib/opcodes.py: Makefile src/hs2py src/Ganeti/PyConstants.hs \
1867
lib/opcodes.py: Makefile src/hs2py src/Ganeti/Constants.hs \
1881 1868
		lib/opcodes.py.in_before lib/opcodes.py.in_after \
1882 1869
		| stamp-directories
1883 1870
	cat $(abs_top_srcdir)/lib/opcodes.py.in_before > $@
/dev/null
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
  pass
302

  
303

  
304
if __name__ == "__main__":
305
  main()
b/src/Ganeti/Constants.hs
26 26

  
27 27
-}
28 28

  
29
module Ganeti.Constants (module Ganeti.HsConstants,
30
                         module Ganeti.PyConstants) where
29
module Ganeti.Constants (module Ganeti.HsConstants) where
31 30

  
32 31
import Ganeti.HsConstants
33
import Ganeti.PyConstants
/dev/null
1
{-| Ganeti constants.
2

  
3
These are duplicated from the Python code. Note that this file is
4
autogenerated using @autotools/convert_constants@ script with a header
5
from @PyConstants.hs.in@.
6

  
7
-}
8

  
9
{-
10

  
11
Copyright (C) 2013 Google Inc.
12

  
13
This program is free software; you can redistribute it and/or modify
14
it under the terms of the GNU General Public License as published by
15
the Free Software Foundation; either version 2 of the License, or
16
(at your option) any later version.
17

  
18
This program is distributed in the hope that it will be useful, but
19
WITHOUT ANY WARRANTY; without even the implied warranty of
20
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
21
General Public License for more details.
22

  
23
You should have received a copy of the GNU General Public License
24
along with this program; if not, write to the Free Software
25
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
26
02110-1301, USA.
27

  
28
-}
29

  
30
module Ganeti.PyConstants where

Also available in: Unified diff