1"""
2Simple utility for splitting user input. This is used by both inputsplitter and
3prefilter.
4"""
5
6# -----------------------------------------------------------------------------
7# Imports
8# -----------------------------------------------------------------------------
9
10import re
11import warnings
12from typing import TYPE_CHECKING
13
14if TYPE_CHECKING:
15 from IPython.core.oinspect import OInfo
16
17# -----------------------------------------------------------------------------
18# Main function
19# -----------------------------------------------------------------------------
20
21# RegExp for splitting line contents into pre-char//first word-method//rest.
22# For clarity, each group in on one line.
23
24# WARNING: update the regexp if the escapes in interactiveshell are changed, as
25# they are hardwired in.
26
27# Although it's not solely driven by the regex, note that:
28# ,;/% only trigger if they are the first character on the line
29# ! and !! trigger if they are first char(s) *or* follow an indent
30# ? triggers as first or last char.
31
32line_split = re.compile(
33 r"""
34 ^(\s*) # any leading space
35 ([,;/%]|!!?|\?\??)? # escape character or characters
36 \s*(%{0,2}[\w\.\*]*) # function/method, possibly with leading %
37 # to correctly treat things like '?%magic'
38 (.*?$|$) # rest of line
39 """,
40 re.VERBOSE,
41)
42
43
44def split_user_input(
45 line: str, pattern: re.Pattern[str] | None = None
46) -> tuple[str, str, str, str]:
47 """Split user input into initial whitespace, escape character, function part
48 and the rest.
49 """
50 assert isinstance(line, str)
51
52 if pattern is None:
53 pattern = line_split
54 match = pattern.match(line)
55 if not match:
56 # print("match failed for line '%s'" % line)
57 try:
58 ifun, the_rest = line.split(None, 1)
59 except ValueError:
60 # print("split failed for line '%s'" % line)
61 ifun, the_rest = line, ""
62 pre = re.match(r"^(\s*)(.*)", line).groups()[0]
63 esc = ""
64 else:
65 pre, esc, ifun, the_rest = match.groups()
66
67 # print('line:<%s>' % line) # dbg
68 # print('pre <%s> ifun <%s> rest <%s>' % (pre,ifun.strip(),the_rest)) # dbg
69 return pre, esc or "", ifun.strip(), the_rest
70
71
72class LineInfo:
73 """A single line of input and associated info.
74
75 Includes the following as properties:
76
77 line
78 The original, raw line
79
80 continue_prompt
81 Is this line a continuation in a sequence of multiline input?
82
83 pre
84 Any leading whitespace.
85
86 esc
87 The escape character(s) in pre or the empty string if there isn't one.
88 Note that '!!' and '??' are possible values for esc. Otherwise it will
89 always be a single character.
90
91 ifun
92 The 'function part', which is basically the maximal initial sequence
93 of valid python identifiers and the '.' character. This is what is
94 checked for alias and magic transformations, used for auto-calling,
95 etc. In contrast to Python identifiers, it may start with "%" and contain
96 "*".
97
98 the_rest
99 Everything else on the line.
100
101 raw_the_rest
102 the_rest without whitespace stripped.
103 """
104
105 line: str
106 continue_prompt: bool
107 pre: str
108 esc: str
109 ifun: str
110 raw_the_rest: str
111 the_rest: str
112 pre_char: str
113 pre_whitespace: str
114
115 def __init__(self, line: str, continue_prompt: bool = False) -> None:
116 assert isinstance(line, str)
117 self.line = line
118 self.continue_prompt = continue_prompt
119 self.pre, self.esc, self.ifun, self.raw_the_rest = split_user_input(line)
120 self.the_rest = self.raw_the_rest.lstrip()
121
122 self.pre_char = self.pre.strip()
123 if self.pre_char:
124 self.pre_whitespace = "" # No whitespace allowed before esc chars
125 else:
126 self.pre_whitespace = self.pre
127
128 def ofind(self, ip) -> "OInfo":
129 """Do a full, attribute-walking lookup of the ifun in the various
130 namespaces for the given IPython InteractiveShell instance.
131
132 Return a dict with keys: {found, obj, ospace, ismagic}
133
134 Note: can cause state changes because of calling getattr, but should
135 only be run if autocall is on and if the line hasn't matched any
136 other, less dangerous handlers.
137
138 Does cache the results of the call, so can be called multiple times
139 without worrying about *further* damaging state.
140
141 .. deprecated:: 9.9
142 Use ``shell._ofind(line_info.ifun)`` directly instead.
143 """
144 warnings.warn(
145 "LineInfo.ofind() is deprecated since IPython 9.9. "
146 "Use shell._ofind(line_info.ifun) directly instead.",
147 DeprecationWarning,
148 stacklevel=2,
149 )
150 return ip._ofind(self.ifun)
151
152 def __str__(self) -> str:
153 return "LineInfo [{}|{}|{}|{}]".format(self.pre, self.esc, self.ifun, self.the_rest)
154
155 def __repr__(self) -> str:
156 return "<" + str(self) + ">"