3939command-based interface, appears to work, until you ask for a help page, at
4040which point it runs into the same problem.
4141
42+ We provide the workaround `doc(foo)`, which just prints the docstring (if any),
43+ and performs no paging.
44+
4245**CAUTION**: as Python was not designed for arbitrary hot-patching, if you
4346change a **class** definition (whether by re-assigning the reference or by
4447reloading the module containing the definition), only new instances will use
8083# TODO: history fixes (see repl_tool in socketserverREPL), syntax highlight?
8184# TODO: figure out how to make help() work, if possible?
8285
83- __all__ = ["start" , "stop" , "server_print" , "halt" ]
86+ __all__ = ["start" , "stop" , "doc" , " server_print" , "halt" ]
8487
8588try :
8689 import ctypes
9598import time
9699import socketserver
97100import atexit
101+ from textwrap import dedent
98102
99103from ..collections import ThreadLocalBox , Shim
100104from ..misc import async_raise
128132# TODO: inject this to globals of the target module
129133# - Maybe better to inject just a single "repl" container which has this and
130134# the other stuff, and print out at connection time where to find this stuff.
135+ def doc (obj ):
136+ """Print an object's docstring, non-interactively.
137+
138+ This works around the lack of a working interactive `help()`
139+ in the REPL session.
140+ """
141+ if not hasattr (obj , "__doc__" ) or not obj .__doc__ :
142+ print ("<no docstring>" )
143+ return
144+ # Emulate help()'s dedenting. Typically, the first line in a docstring
145+ # has no leading whitespace, while the rest follow the indentation of
146+ # the function body.
147+ firstline , * rest = obj .__doc__ .split ("\n " )
148+ rest = dedent ("\n " .join (rest ))
149+ doc = [firstline , * rest .split ("\n " )]
150+ for line in doc :
151+ print (line )
152+
153+ # TODO: detect stdout, stderr and redirect to the appropriate stream.
154+ def server_print (* values , ** kwargs ):
155+ """Print to the original stdout of the server process.
156+
157+ This function is available in the REPL.
158+ """
159+ print (* values , ** kwargs , file = _original_stdout )
160+
161+ # TODO: inject this to globals of the target module
131162def halt (doit = True ):
132163 """Tell the REPL server to shut down after the last client has disconnected.
133164
@@ -144,15 +175,6 @@ def halt(doit=True):
144175 print (msg )
145176 server_print (msg )
146177
147- # TODO: inject this to globals of the target module
148- # TODO: detect stdout, stderr and redirect to the appropriate stream.
149- def server_print (* values , ** kwargs ):
150- """Print to the original stdout of the server process.
151-
152- This function is available in the REPL.
153- """
154- print (* values , ** kwargs , file = _original_stdout )
155-
156178
157179class ControlSession (socketserver .BaseRequestHandler , ApplevelProtocolMixin ):
158180 """Entry point for connections to the control server.
@@ -407,6 +429,8 @@ def start(locals, addrspec=("127.0.0.1", 1337), banner=None):
407429 " quit() or EOF (Ctrl+D) at the prompt disconnects this session.\n "
408430 " halt() tells the server to close after the last session has disconnected.\n "
409431 " print() prints in the REPL session.\n "
432+ " NOTE: print() is only properly redirected in the session's main thread.\n "
433+ " doc(obj) shows obj's docstring. Use this instead of help(obj).\n "
410434 " server_print(...) prints on the stdout of the server." )
411435 _banner = default_msg .format (addr = addr , port = port , argv = " " .join (sys .argv ))
412436 else :
0 commit comments