From 4a714d48ad727f6ad708ba2d3695f1dea4479fb6 Mon Sep 17 00:00:00 2001 From: Larry Hastings Date: Tue, 14 Jan 2014 22:22:41 -0800 Subject: Issue #20268: Argument Clinic now supports cloning the parameters and return converter from existing functions. --- Doc/howto/clinic.rst | 43 +++++++++++++++++++++++++++++++++++++++++++ Misc/NEWS | 3 +++ Tools/clinic/clinic.py | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+) diff --git a/Doc/howto/clinic.rst b/Doc/howto/clinic.rst index 9c558bc5..0df8d44 100644 --- a/Doc/howto/clinic.rst +++ b/Doc/howto/clinic.rst @@ -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 ------------------- diff --git a/Misc/NEWS b/Misc/NEWS index 73a660a..ea31879 100644 --- 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. diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 56e4911..ed59f05 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -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: -- cgit v0.12