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