File: generate_migration_docs

package info (click to toggle)
gamera 3.4.1%2Bsvn1423-4
  • links: PTS, VCS
  • area: main
  • in suites: jessie, jessie-kfreebsd
  • size: 22,292 kB
  • ctags: 25,015
  • sloc: xml: 122,324; ansic: 50,812; cpp: 50,489; python: 34,987; makefile: 119; sh: 101
file content (362 lines) | stat: -rwxr-xr-x 13,397 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
#!/usr/bin/env python

"""This script builds the documentation about migrating
from Gamera 2.x to Gamera 3.x.  End users should not
have to run this script."""

import cStringIO
import imp
import os
import re
import textwrap

# This is the main content of the document with Python
# formatting markers in it for parts that are
# automatically generated.
source = """=======================================
Migrating from Gamera 2.x to Gamera 3.x
=======================================

The migration from Gamera 2.x to Gamera 3.x is intended to be as
painless as possible.  

In future releases of Gamera, backward compatibility will need to be
broken in order to fix some low-level design flaws in Gamera.  Gamera
3.x is the first step in that process.  Certain functions signatures
that existed in 2.x have been deprecated in 3.x, but they continue to
work in order to maintain backward compatibility with existing
scripts.  As of Gamera 3.x, these deprecated calls all have easy
alternatives, and they should be replaced with the new recommended
forms as soon as possible to ensure compatibility with future versions
of Gamera.

Note, however, that some rarely-used deprecated functions do not have
direct alternatives in Gamera 2.x, so this migration process may break
your scripts' compatibility with Gamera 2.x.  However, if appropriate
care is taken, such as switching based on the Gamera version, it
should still be possible to write code that is compatible with both
Gamera 2.x and Gamera 3.x.

This document is divided into the following sections:

  - `Reasons for deprecations`_ describes the different categories of
    deprecated functions, and what changes are required in end-user
    scripts.

  - `How to migrate existing code`_ presents some tips and
    techniques for making migration easier.

  - `Migration tools`_ describes the provided tools for finding and
    replacing deprecated calls in end-user code.  This tools provide
    only a semi-automated process.

  - `C++ deprecations reference`_ and `Python deprecations reference`_
    list all deprecated functions, with their reason for deprecation
    and a suggested alternative.

Reasons for deprecations
========================

(x, y) coordinate consistency
-----------------------------

In Gamera 2.x, some functions received coordinates in the order (y, x),
(or (rows, cols)), while others took coordinates in (x, y) order.
This self-inconsistency and departure from the majority of image
processing systems often resulted in confusion and subtle errors.

The new recommended way to call these functions is to pass in Point,
FloatPoint, Size or Dim arguments as required, instead of two
integers.  This solution allows the old function signatures to be
maintained for backward compatibility, while allowing migration to a
style that consistently uses (x, y) ordering everywhere.

For example, ``image.get(r, c)`` becomes ``image.get(Point(c, r))``.

2-element sequences in place of Point type
''''''''''''''''''''''''''''''''''''''''''

For convenience in Python code, 2-element sequences can be used
wherever Point or FloatPoint is expected.  Therefore, ``image.get((x, y))``
is equivalent to ``image.get(Point(x, y))``.

Dimensions type
'''''''''''''''

Additionally, the ``Dimensions`` class, whose constructor is
``Dimensions(nrows, ncols)``, has been deprecated because it is
inconsistent with the new requirement of "(x, y) everywhere".  Since
it would be impossible to change the order of the constructor's
arguments without breaking backward imcompatibility, a new type has
been introduced, ``Dim``, which is identical to Dimensions in all
respects except its constructor is ``Dim(ncols, nrows)``.  All uses of
the ``Dimensions`` type are deprecated and should be migrated to use ``Dim``
instead.

FloatPoint type
'''''''''''''''

A new FloatPoint type has been added to hold coordinates using
floating point numbers.  The standard Gamera ``Point`` stores
coordinates as unsigned (positive) integers, and doesn't have any
arithmetic operators.  For this reason, ``FloatPoint`` is highly
recommended for any analyses that require precision and flexibility.
``Point`` is kept around for backward compatibility, and because it is
a more natural way to refer to physical pixels, as opposed to logical
coordinates.

There are, however, implicit conversions (in Python only) between the
two types, so a ``FloatPoint`` can be used in place of ``Point`` where
it makes sense.  Care should be taken to ensure that negative values
are never used to reference image pixels.  (Range checking is
performed when accessing from Python, but not when accessing from C++.)

Additionally, the standard arithmetic operators are available on
``FloatPoint`` objects (+ - * / abs).

Functions should be parameterized by arguments, not by name
-----------------------------------------------------------

There are certain groups of plugin functions that perform essentially
the same functionality.  Take for example::

  black_horizontal_run_histogram()
  black_vertical_run_histogram()
  white_horizontal_run_histogram()
  white_vertical_run_histogram()

These four functions compute a run length histogram, parameterized by
the color and direction of the runs.  Maintaining
four separate functions for a single logical task has a number
of disadvantages:

  - Even if the code is abstracted such that the core of the algorithm
    is in a single function, the documentation still needs to be
    updated in multiple places.

  - The generated documentation becomes longer and therefore harder to browse
    through, and contains a lot of redundant information or excessive
    hyperlinking.

  - Autocompletion in the Gamera shell becomes less useful.

  - Alphabetization of the functions doesn't necessarily reveal their
    membership as part of the same family of functionality.

Therefore, in Gamera 3.x, these sorts of functions have been merged
into a single function.  For example, the four functions above are now
the single function::

  run_histogram(color, direction)

How to migrate existing code
============================

There are two distinct techniques for finding deprecated functions:
one for C++ code and one for Python code.

C++ code
--------

On the C++ side, all deprecated function calls will be caught at
compile time.  Simply recompiling your C++ code will provide compiler
warnings to this effect.

The compiler warnings produced by gcc can be fairly cryptic.  The
gamera_deprecation_filter_ (described below) will filter these warning
messages and produce detailed human-readable suggestions for updating
your deprecated calls.

.. note::

  This technique only works with gcc version 3.1 and greater.

Python code
-----------

Finding the deprecated calls in Python code is somewhat more
difficult, since the exact function calls being made can not be
determined until runtime.

When a deprecated function call is made, a deprecation warning is
printed to stderr.  This message provides the name of the deprecated
function, the reason it was deprecated, and a suggested alternative.
The only way to find all deprecated calls in your code this is to
ensure that all of your code is run.  This is sometimes difficult to
do, though a code coverage tool such as Garth Rees' `Statement
coverage for Python`__ may help.

.. __: http://www.garethrees.org/2001/12/04/python-coverage/

A manual search through your code for deprecated functions may in some
cases be more efficient.  A master list of all deprecated Python
functions in Gamera is presented in the `Python deprecations reference`_.

Migration tools
===============

There are a number of scripts in the ``migration_tools`` directory
that make the process of migrating code from Gamera 2.x to 3.x easier.

  - gamera_deprecation_filter_: Helps display the deprecated calls in
    your C++ code.

  - replace_get_set_: Replaces C++ calls to get and set in the old
    form to the new form.

gamera_deprecation_filter
-------------------------

%(gamera_deprecation_filter_docs)s

replace_get_set
---------------

%(replace_get_set_docs)s

C++ deprecations reference
==========================

This is an alphabetical list of the deprecated C++ functions.

%(cpp_deprecations)s

Python deprecations reference
=============================

This is an alphabetical list of the deprecated Python functions.

%(python_deprecations)s
"""

# This is a list of reasons for deprecating functions and
# their 
reasons = [
   ("(x, y) coordinate consistency",
    "#x-y-coordinate-consistency"),
   ("Functions parameterized by arguments, not by name",
    "#functions-should-be-parameterized-by-arguments-not-by-name")]

def get_tool_docs(tools, chunks):
   for tool in tools:
      mod = imp.load_module(tool, open(tool, "r"), tool, ("", "", imp.PY_SOURCE))
      chunks[tool + "_docs"] = mod.__doc__

def get_files_recursively(root, extensions):
   for dirpath, dirnames, filenames in os.walk(root):
      if not "build" in dirpath and not "dist" in dirpath:
         for filename in filenames:
            root, ext = os.path.splitext(filename)
            if ext in extensions:
               yield open(os.path.join(dirpath, filename), "r")

def find_all_matches(regex, file):
   for match in regex.findall(file.read()):
      yield match

def find_comments(file):
   lines = file.readlines()
   for lineno, line in enumerate(lines):
      if "GAMERA_CPP_DEPRECATED" in line and not line.startswith("#"):
         comment = []
         for i in xrange(lineno, -1, -1):
            if i < 0 or i >= len(lines):
               raise RuntimeError("Couldn't find comment.")
            find = lines[i].find(r"*/")
            if find != -1:
               comment.insert(0, lines[i][:find].strip())
               break
         for j in xrange(i - 1, -1, -1):
            if j < 0 or j >= len(lines):
               raise RuntimeError("Couldn't find comment.")
            find = lines[j].find(r"/*")
            if find != -1:
               comment.insert(0, lines[j][find+2:].strip())
               break
            comment.insert(0, lines[j].strip())
         comment = "\n".join(comment)
         yield comment

def format_deprecations(deprecations):
   table_line = "+" + ("-" * 80) + "+" + ("-" * 80) + "+\n"
   table_header = "+" + ("=" * 80) + "+" + ("=" * 80) + "+\n"
   output = cStringIO.StringIO()
   output.write(table_line)
   output.write("|%-80s|%-80s|\n" % ("Deprecated function", "Notes"))
   output.write(table_header)
   for deprecation in deprecations:
      parts = deprecation.split("\n\n")
      if len(parts) != 3:
         continue
      function = parts[0].strip()
      reason = parts[1][len("Reason: "):].strip()
      for i, r in enumerate(reasons):
         if reason.startswith(r[0]):
            function = function + " [%d_]" % (i + 1)
            break
      suggestion = "\n\n".join(parts[2:]).strip()
      function = textwrap.wrap(function)
      suggestion = textwrap.wrap(suggestion)
      lines = max(len(function), len(suggestion))
      for x in function, suggestion:
         if len(x) < lines:
            for i in range(lines - len(x)):
               x.append("")
      for a, b in zip(function, suggestion):
         output.write("|%-80s|%-80s|\n" % (a, b))
      output.write(table_line)
   output.write("\n")
   for i, r in enumerate(reasons):
      output.write(".. _%d: %s\n" % (i + 1, r[1]))
   output.write("\n")
   return output.getvalue()

def get_cpp_deprecations():
   deprecations = []
   for fd in get_files_recursively("..", [".hpp", ".cpp"]):
      for match in find_comments(fd):
         deprecations.append(match)
   deprecations.sort(lambda x, y: cmp(x.lower(), y.lower()))
   print "%d C++ deprecations" % len(deprecations)
   return deprecations

cpp_python_deprecation_regex = re.compile(
   "send_deprecation_warning\((?P<x>.*?),\s*\"[^\"]+\",\s*__LINE__\)", re.DOTALL)
python_deprecation_regex = re.compile(
   "warn_deprecated\((?P<x>(?:(?:(?:\"\"\".*?\"\"\")|(?:'''.*?''')|(?:\".*?\")|(?:\'.*?\'))\s*)+)\)", re.DOTALL)
python_deprecation_regex2 = re.compile("\.\.\s+warning::\s+(?P<x>.*?)\"\"\"", re.DOTALL)
def get_python_deprecations():
   deprecations = []
   for data in get_files_recursively("..", [".hpp", ".cpp"]):
      for match in find_all_matches(cpp_python_deprecation_regex, data):
         try:
            deprecations.append(eval("(%s)" % match))
         except SyntaxError:
            pass
   for data in get_files_recursively("..", [".py"]):
      for match in find_all_matches(python_deprecation_regex, data):
         try:
            deprecations.append(eval(match))
         except SyntaxError:
            pass
   for data in get_files_recursively("..", [".py"]):
      for match in find_all_matches(python_deprecation_regex2, data):
         deprecations.append(match)
   deprecations.sort(lambda x, y: cmp(x.lower(), y.lower()))
   print "%d Python deprecations" % len(deprecations)
   return deprecations

def main():
   chunks = {}
   get_tool_docs("gamera_deprecation_filter replace_get_set".split(),
                 chunks)

   chunks["cpp_deprecations"] = format_deprecations(get_cpp_deprecations())
   chunks["python_deprecations"] = format_deprecations(get_python_deprecations())
   
   output = open("../doc/src/migration_guide.txt", "w")
   output.write(source % chunks)
   output.close()

if __name__ == "__main__":
   main()