Skip to content

Commit 1cacefc

Browse files
author
Ralf W. Grosse-Kunstleve
committed
automatic addition of C++ signatures to doc strings
[SVN r32290]
1 parent efcd283 commit 1cacefc

File tree

5 files changed

+109
-134
lines changed

5 files changed

+109
-134
lines changed

include/boost/python/object/function.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ struct BOOST_PYTHON_DECL function : PyObject
4040
object const& name() const;
4141

4242
private: // helper functions
43+
object signature(bool show_return_type=false) const;
44+
object signatures(bool show_return_type=false) const;
4345
void argument_error(PyObject* args, PyObject* keywords) const;
4446
void add_overload(handle<function> const&);
4547

src/object/function.cpp

Lines changed: 77 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,58 @@ PyObject* function::call(PyObject* args, PyObject* keywords) const
228228
return 0;
229229
}
230230

231+
object function::signature(bool show_return_type) const
232+
{
233+
py_function const& impl = m_fn;
234+
235+
python::detail::signature_element const* return_type = impl.signature();
236+
python::detail::signature_element const* s = return_type + 1;
237+
238+
list formal_params;
239+
if (impl.max_arity() == 0)
240+
formal_params.append("void");
241+
242+
for (unsigned n = 0; n < impl.max_arity(); ++n)
243+
{
244+
if (s[n].basename == 0)
245+
{
246+
formal_params.append("...");
247+
break;
248+
}
249+
250+
str param(s[n].basename);
251+
if (s[n].lvalue)
252+
param += " {lvalue}";
253+
254+
if (m_arg_names) // None or empty tuple will test false
255+
{
256+
object kv(m_arg_names[n]);
257+
if (kv)
258+
{
259+
char const* const fmt = len(kv) > 1 ? " %s=%r" : " %s";
260+
param += fmt % kv;
261+
}
262+
}
263+
264+
formal_params.append(param);
265+
}
266+
267+
if (show_return_type)
268+
return "%s(%s) -> %s" % make_tuple(
269+
m_name, str(", ").join(formal_params), return_type->basename);
270+
return "%s(%s)" % make_tuple(
271+
m_name, str(", ").join(formal_params));
272+
}
273+
274+
object function::signatures(bool show_return_type) const
275+
{
276+
list result;
277+
for (function const* f = this; f; f = f->m_overloads.get()) {
278+
result.append(f->signature(show_return_type));
279+
}
280+
return result;
281+
}
282+
231283
void function::argument_error(PyObject* args, PyObject* /*keywords*/) const
232284
{
233285
static handle<> exception(
@@ -244,50 +296,7 @@ void function::argument_error(PyObject* args, PyObject* /*keywords*/) const
244296
}
245297
message += str(", ").join(actual_args);
246298
message += ")\ndid not match C++ signature:\n ";
247-
248-
list signatures;
249-
for (function const* f = this; f; f = f->m_overloads.get())
250-
{
251-
py_function const& impl = f->m_fn;
252-
253-
python::detail::signature_element const* s
254-
= impl.signature() + 1; // skip over return type
255-
256-
list formal_params;
257-
if (impl.max_arity() == 0)
258-
formal_params.append("void");
259-
260-
for (unsigned n = 0; n < impl.max_arity(); ++n)
261-
{
262-
if (s[n].basename == 0)
263-
{
264-
formal_params.append("...");
265-
break;
266-
}
267-
268-
str param(s[n].basename);
269-
if (s[n].lvalue)
270-
param += " {lvalue}";
271-
272-
if (f->m_arg_names) // None or empty tuple will test false
273-
{
274-
object kv(f->m_arg_names[n]);
275-
if (kv)
276-
{
277-
char const* const fmt = len(kv) > 1 ? " %s=%r" : " %s";
278-
param += fmt % kv;
279-
}
280-
}
281-
282-
formal_params.append(param);
283-
}
284-
285-
signatures.append(
286-
"%s(%s)" % make_tuple(f->m_name, str(", ").join(formal_params))
287-
);
288-
}
289-
290-
message += str("\n ").join(signatures);
299+
message += str("\n ").join(signatures());
291300

292301
#if BOOST_PYTHON_DEBUG_ERROR_MESSAGES
293302
std::printf("\n--------\n%s\n--------\n", extract<const char*>(message)());
@@ -391,6 +400,12 @@ namespace
391400

392401
void function::add_to_namespace(
393402
object const& name_space, char const* name_, object const& attribute)
403+
{
404+
add_to_namespace(name_space, name_, attribute, 0);
405+
}
406+
407+
void function::add_to_namespace(
408+
object const& name_space, char const* name_, object const& attribute, char const* doc)
394409
{
395410
str const name(name_);
396411
PyObject* const ns = name_space.ptr();
@@ -463,16 +478,11 @@ void function::add_to_namespace(
463478
PyErr_Clear();
464479
if (PyObject_SetAttr(ns, name.ptr(), attribute.ptr()) < 0)
465480
throw_error_already_set();
466-
}
467481

468-
void function::add_to_namespace(
469-
object const& name_space, char const* name_, object const& attribute, char const* doc)
470-
{
471-
add_to_namespace(name_space, name_, attribute);
482+
object mutable_attribute(attribute);
472483
if (doc != 0)
473484
{
474485
// Accumulate documentation
475-
object mutable_attribute(attribute);
476486

477487
if (
478488
PyObject_HasAttrString(mutable_attribute.ptr(), "__doc__")
@@ -481,21 +491,35 @@ void function::add_to_namespace(
481491
mutable_attribute.attr("__doc__") += "\n\n";
482492
mutable_attribute.attr("__doc__") += doc;
483493
}
484-
else
494+
else {
485495
mutable_attribute.attr("__doc__") = doc;
496+
}
497+
}
498+
499+
{
500+
if ( PyObject_HasAttrString(mutable_attribute.ptr(), "__doc__")
501+
&& mutable_attribute.attr("__doc__")) {
502+
mutable_attribute.attr("__doc__") += "\n";
503+
}
504+
else {
505+
mutable_attribute.attr("__doc__") = "";
506+
}
507+
function* f = downcast<function>(attribute.ptr());
508+
mutable_attribute.attr("__doc__") += str("\n ").join(make_tuple(
509+
"C++ signature:", f->signature(true)));
486510
}
487511
}
488512

489513
BOOST_PYTHON_DECL void add_to_namespace(
490514
object const& name_space, char const* name, object const& attribute)
491515
{
492-
function::add_to_namespace(name_space, name, attribute);
516+
function::add_to_namespace(name_space, name, attribute, 0);
493517
}
494518

495519
BOOST_PYTHON_DECL void add_to_namespace(
496520
object const& name_space, char const* name, object const& attribute, char const* doc)
497521
{
498-
function::add_to_namespace(name_space, name, attribute, doc);
522+
function::add_to_namespace(name_space, name, attribute, doc);
499523
}
500524

501525

test/args.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -85,21 +85,21 @@
8585
>>> q.f1()
8686
(1, 4.25, 'wow')
8787
88-
>>> X.f.__doc__
89-
"This is X.f's docstring"
88+
>>> X.f.__doc__.splitlines()[:2]
89+
["This is X.f's docstring", 'C++ signature:']
9090
9191
>>> xfuncs = (X.inner0, X.inner1, X.inner2, X.inner3, X.inner4, X.inner5)
9292
>>> for f in xfuncs:
9393
... print f(q,1).value(),
9494
... print f(q, n = 1).value(),
9595
... print f(q, n = 0).value(),
96-
... print f.__doc__
97-
1 1 0 docstring
98-
1 1 0 docstring
99-
1 1 0 docstring
100-
1 1 0 docstring
101-
1 1 0 docstring
102-
1 1 0 docstring
96+
... print f.__doc__.splitlines()[:2]
97+
1 1 0 ['docstring', 'C++ signature:']
98+
1 1 0 ['docstring', 'C++ signature:']
99+
1 1 0 ['docstring', 'C++ signature:']
100+
1 1 0 ['docstring', 'C++ signature:']
101+
1 1 0 ['docstring', 'C++ signature:']
102+
1 1 0 ['docstring', 'C++ signature:']
103103
104104
>>> x = X(a1 = 44, a0 = 22)
105105
>>> x.inner0(0).value()

test/defaults.py

Lines changed: 9 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -109,17 +109,18 @@
109109
>>> x.get_state()
110110
'Got exactly two arguments from constructor: string(Phoenix); bool(1); '
111111
112-
>>> def printdoc(x):
113-
... print x.__doc__
112+
>>> def selected_doc(obj, *args):
113+
... doc = obj.__doc__.splitlines()
114+
... return [doc[i] for i in args]
114115
115-
>>> printdoc(X.__init__)
116-
doc of init
116+
>>> selected_doc(X.__init__, 0, 2, 4, 6, 8, 9, 10, 12)
117+
['C++ signature:', 'C++ signature:', 'C++ signature:', 'C++ signature:', '', 'doc of init', 'C++ signature:', 'C++ signature:']
117118
118-
>>> printdoc(Y.__init__)
119-
doc of Y init
119+
>>> selected_doc(Y.__init__, 0, 1)
120+
['doc of Y init', 'C++ signature:']
120121
121-
>>> printdoc(X.bar2)
122-
doc of X::bar2
122+
>>> selected_doc(X.bar2, 0, 2, 4, 6, 8, 9, 10)
123+
['C++ signature:', 'C++ signature:', 'C++ signature:', 'C++ signature:', '', 'doc of X::bar2', 'C++ signature:']
123124
124125
"""
125126
def run(args = None):
@@ -136,48 +137,3 @@ def run(args = None):
136137
status = run()[0]
137138
if (status == 0): print "Done."
138139
sys.exit(status)
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-

test/docstring.py

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,30 +4,23 @@
44
'''
55
>>> from docstring_ext import *
66
7-
>>> def printdoc(x):
8-
... print x.__doc__
7+
>>> def selected_doc(obj, *args):
8+
... doc = obj.__doc__.splitlines()
9+
... return [doc[i] for i in args]
910
10-
>>> printdoc(X)
11-
A simple class wrapper around a C++ int
12-
includes some error-checking
11+
>>> selected_doc(X.__init__, 0, 1, 2)
12+
['this is the __init__ function', 'its documentation has two lines.', 'C++ signature:']
1313
14-
>>> printdoc(X.__init__)
15-
this is the __init__ function
16-
its documentation has two lines.
14+
>>> selected_doc(X.value, 0, 1, 3, 4, 5)
15+
['gets the value of the object', 'C++ signature:', '', 'also gets the value of the object', 'C++ signature:']
1716
18-
>>> printdoc(create)
19-
creates a new X object
17+
>>> selected_doc(create, 0, 1)
18+
['creates a new X object', 'C++ signature:']
2019
21-
>>> printdoc(fact)
22-
compute the factorial
23-
'''
20+
>>> selected_doc(fact, 0, 1)
21+
['compute the factorial', 'C++ signature:']
2422
25-
def check_double_string():
26-
"""
27-
>>> assert check_double_string() == True
28-
"""
29-
from docstring_ext import X
30-
return X.value.__doc__ == "gets the value of the object\n\nalso gets the value of the object"
23+
'''
3124

3225
def run(args = None):
3326
import sys

0 commit comments

Comments
 (0)