]> granicus.if.org Git - python/commitdiff
Issue #20268: Argument Clinic now supports cloning the parameters
authorLarry Hastings <larry@hastings.org>
Wed, 15 Jan 2014 06:22:41 +0000 (22:22 -0800)
committerLarry Hastings <larry@hastings.org>
Wed, 15 Jan 2014 06:22:41 +0000 (22:22 -0800)
and return converter from existing functions.

Doc/howto/clinic.rst
Misc/NEWS
Tools/clinic/clinic.py

index 9c558bc5039ae9a6818b581e2e66a2fc47cbdce4..0df8d445c93cf490c25ea97dbc95f6568c86826c 100644 (file)
@@ -847,6 +847,49 @@ their parameters (if any),
 just run ``Tools/clinic/clinic.py --converters`` for the full list.
 
 
+Cloning existing functions
+--------------------------
+
+If you have a number of functions that look similar, you may be able to
+use Clinic's "clone" feature.  When you clone an existing function,
+you reuse:
+
+* its parameters, including
+
+  * their names,
+
+  * their converters, with all parameters,
+
+  * their default values,
+
+  * their per-parameter docstrings,
+
+  * their *kind* (whether they're positional only,
+    positional or keyword, or keyword only), and
+
+* its return converter.
+
+The only thing not copied from the original function is its docstring;
+the syntax allows you to specify a new docstring.
+
+Here's the syntax for cloning a function::
+
+    /*[clinic input]
+    module.class.new_function [as c_basename] = module.class.existing_function
+
+    Docstring for new_function goes here.
+    [clinic start generated code]*/
+
+(The functions can be in different modules or classes.  I wrote
+``module.class`` in the sample just to illustrate that you must
+use the full path to *both* functions.)
+
+Sorry, there's no syntax for partially-cloning a function, or cloning a function
+then modifying it.  Cloning is an all-or nothing proposition.
+
+Also, the function you are cloning from must have been previously defined
+in the current file.
+
 Calling Python code
 -------------------
 
index 73a660a4d0ca0e203c350b97554d9fcbdc532693..ea318795845490b5bb22edaa6aa01256503ae863 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -86,6 +86,9 @@ Tests
 Tools/Demos
 -----------
 
+- Issue #20268: Argument Clinic now supports cloning the parameters and
+  return converter of existing functions.
+
 - Issue #20228: Argument Clinic now has special support for class special
   methods.
 
index 56e491168e8e3e4f380dc6dc2601c6a5dbb99a35..ed59f05b1ecd6f99577a3042443701b09de39123 100755 (executable)
@@ -2301,6 +2301,12 @@ class DSLParser:
         #     modulename.fnname [as c_basename] [-> return annotation]
         # square brackets denote optional syntax.
         #
+        # alternatively:
+        #     modulename.fnname [as c_basename] = modulename.existing_fn_name
+        # clones the parameters and return converter from that
+        # function.  you can't modify them.  you must enter a
+        # new docstring.
+        #
         # (but we might find a directive first!)
         #
         # this line is permitted to start with whitespace.
@@ -2319,6 +2325,45 @@ class DSLParser:
             directive(*fields[1:])
             return
 
+        # are we cloning?
+        before, equals, existing = line.rpartition('=')
+        if equals:
+            full_name, _, c_basename = before.partition(' as ')
+            full_name = full_name.strip()
+            c_basename = c_basename.strip()
+            existing = existing.strip()
+            if (is_legal_py_identifier(full_name) and
+                (not c_basename or is_legal_c_identifier(c_basename)) and
+                is_legal_py_identifier(existing)):
+                # we're cloning!
+                fields = [x.strip() for x in existing.split('.')]
+                function_name = fields.pop()
+                module, cls = self.clinic._module_and_class(fields)
+
+                for existing_function in (cls or module).functions:
+                    if existing_function.name == function_name:
+                        break
+                else:
+                    existing_function = None
+                if not existing_function:
+                    fail("Couldn't find existing function " + repr(existing) + "!")
+
+                fields = [x.strip() for x in full_name.split('.')]
+                function_name = fields.pop()
+                module, cls = self.clinic._module_and_class(fields)
+
+                if not (existing_function.kind == self.kind and existing_function.coexist == self.coexist):
+                    fail("'kind' of function and cloned function don't match!  (@classmethod/@staticmethod/@coexist)")
+                self.function = Function(name=function_name, full_name=full_name, module=module, cls=cls, c_basename=c_basename,
+                                 return_converter=existing_function.return_converter, kind=existing_function.kind, coexist=existing_function.coexist)
+
+                self.function.parameters = existing_function.parameters.copy()
+
+                self.block.signatures.append(self.function)
+                (cls or module).functions.append(self.function)
+                self.next(self.state_function_docstring)
+                return
+
         line, _, returns = line.partition('->')
 
         full_name, _, c_basename = line.partition(' as ')
@@ -2373,6 +2418,7 @@ class DSLParser:
         self.function = Function(name=function_name, full_name=full_name, module=module, cls=cls, c_basename=c_basename,
                                  return_converter=return_converter, kind=self.kind, coexist=self.coexist)
         self.block.signatures.append(self.function)
+        (cls or module).functions.append(self.function)
         self.next(self.state_parameters_start)
 
     # Now entering the parameters section.  The rules, formally stated: