Skip to content

Commit 00af701

Browse files
committed
REPL server: provide doc(obj) to view docstrings
1 parent 77ff15d commit 00af701

File tree

1 file changed

+34
-10
lines changed

1 file changed

+34
-10
lines changed

unpythonic/net/server.py

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@
3939
command-based interface, appears to work, until you ask for a help page, at
4040
which 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
4346
change a **class** definition (whether by re-assigning the reference or by
4447
reloading the module containing the definition), only new instances will use
@@ -80,7 +83,7 @@
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

8588
try:
8689
import ctypes
@@ -95,6 +98,7 @@
9598
import time
9699
import socketserver
97100
import atexit
101+
from textwrap import dedent
98102

99103
from ..collections import ThreadLocalBox, Shim
100104
from ..misc import async_raise
@@ -128,6 +132,33 @@
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
131162
def 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

157179
class 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

Comments
 (0)