1# encoding: utf-8
2"""
3The :class:`~traitlets.config.application.Application` object for the command
4line :command:`ipython` program.
5"""
6
7# Copyright (c) IPython Development Team.
8# Distributed under the terms of the Modified BSD License.
9
10
11import logging
12import os
13import sys
14import warnings
15
16from traitlets.config.loader import Config
17from traitlets.config.application import boolean_flag, catch_config_error
18from IPython.core import release
19from IPython.core import usage
20from IPython.core.completer import IPCompleter
21from IPython.core.crashhandler import CrashHandler
22from IPython.core.formatters import PlainTextFormatter
23from IPython.core.history import HistoryManager
24from IPython.core.application import (
25 ProfileDir, BaseIPythonApplication, base_flags, base_aliases
26)
27from IPython.core.magic import MagicsManager
28from IPython.core.magics import (
29 ScriptMagics, LoggingMagics
30)
31from IPython.core.shellapp import (
32 InteractiveShellApp, shell_flags, shell_aliases
33)
34from IPython.extensions.storemagic import StoreMagics
35from .interactiveshell import TerminalInteractiveShell
36from IPython.paths import get_ipython_dir
37from traitlets import (
38 Bool, List, default, observe, Type
39)
40
41#-----------------------------------------------------------------------------
42# Globals, utilities and helpers
43#-----------------------------------------------------------------------------
44
45_examples = """
46ipython --matplotlib # enable matplotlib integration
47ipython --matplotlib=qt # enable matplotlib integration with qt4 backend
48
49ipython --log-level=DEBUG # set logging to DEBUG
50ipython --profile=foo # start with profile foo
51
52ipython profile create foo # create profile foo w/ default config files
53ipython help profile # show the help for the profile subcmd
54
55ipython locate # print the path to the IPython directory
56ipython locate profile foo # print the path to the directory for profile `foo`
57"""
58
59#-----------------------------------------------------------------------------
60# Crash handler for this application
61#-----------------------------------------------------------------------------
62
63class IPAppCrashHandler(CrashHandler):
64 """sys.excepthook for IPython itself, leaves a detailed report on disk."""
65
66 def __init__(self, app):
67 contact_name = release.author
68 contact_email = release.author_email
69 bug_tracker = 'https://github.com/ipython/ipython/issues'
70 super(IPAppCrashHandler,self).__init__(
71 app, contact_name, contact_email, bug_tracker
72 )
73
74 def make_report(self,traceback):
75 """Return a string containing a crash report."""
76
77 sec_sep = self.section_sep
78 # Start with parent report
79 report = [super(IPAppCrashHandler, self).make_report(traceback)]
80 # Add interactive-specific info we may have
81 rpt_add = report.append
82 try:
83 rpt_add(sec_sep+"History of session input:")
84 for line in self.app.shell.user_ns['_ih']:
85 rpt_add(line)
86 rpt_add('\n*** Last line of input (may not be in above history):\n')
87 rpt_add(self.app.shell._last_input_line+'\n')
88 except:
89 pass
90
91 return ''.join(report)
92
93#-----------------------------------------------------------------------------
94# Aliases and Flags
95#-----------------------------------------------------------------------------
96flags = dict(base_flags)
97flags.update(shell_flags)
98frontend_flags = {}
99addflag = lambda *args: frontend_flags.update(boolean_flag(*args))
100addflag('autoedit-syntax', 'TerminalInteractiveShell.autoedit_syntax',
101 'Turn on auto editing of files with syntax errors.',
102 'Turn off auto editing of files with syntax errors.'
103)
104addflag('simple-prompt', 'TerminalInteractiveShell.simple_prompt',
105 "Force simple minimal prompt using `raw_input`",
106 "Use a rich interactive prompt with prompt_toolkit",
107)
108
109addflag('banner', 'TerminalIPythonApp.display_banner',
110 "Display a banner upon starting IPython.",
111 "Don't display a banner upon starting IPython."
112)
113addflag('confirm-exit', 'TerminalInteractiveShell.confirm_exit',
114 """Set to confirm when you try to exit IPython with an EOF (Control-D
115 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
116 you can force a direct exit without any confirmation.""",
117 "Don't prompt the user when exiting."
118)
119addflag(
120 "tip",
121 "TerminalInteractiveShell.enable_tip",
122 """Shows a tip when IPython starts.""",
123 "Don't show tip when IPython starts.",
124)
125addflag('term-title', 'TerminalInteractiveShell.term_title',
126 "Enable auto setting the terminal title.",
127 "Disable auto setting the terminal title."
128)
129classic_config = Config()
130classic_config.InteractiveShell.cache_size = 0
131classic_config.PlainTextFormatter.pprint = False
132classic_config.TerminalInteractiveShell.prompts_class = (
133 "IPython.terminal.prompts.ClassicPrompts"
134)
135classic_config.InteractiveShell.separate_in = ""
136classic_config.InteractiveShell.separate_out = ""
137classic_config.InteractiveShell.separate_out2 = ""
138classic_config.InteractiveShell.colors = "nocolor"
139classic_config.InteractiveShell.xmode = "Plain"
140
141frontend_flags['classic']=(
142 classic_config,
143 "Gives IPython a similar feel to the classic Python prompt."
144)
145# # log doesn't make so much sense this way anymore
146# paa('--log','-l',
147# action='store_true', dest='InteractiveShell.logstart',
148# help="Start logging to the default log file (./ipython_log.py).")
149#
150# # quick is harder to implement
151frontend_flags['quick']=(
152 {'TerminalIPythonApp' : {'quick' : True}},
153 "Enable quick startup with no config files."
154)
155
156frontend_flags['i'] = (
157 {'TerminalIPythonApp' : {'force_interact' : True}},
158 """If running code from the command line, become interactive afterwards.
159 It is often useful to follow this with `--` to treat remaining flags as
160 script arguments.
161 """
162)
163flags.update(frontend_flags)
164
165aliases = dict(base_aliases)
166aliases.update(shell_aliases) # type: ignore[arg-type]
167
168#-----------------------------------------------------------------------------
169# Main classes and functions
170#-----------------------------------------------------------------------------
171
172
173class LocateIPythonApp(BaseIPythonApplication):
174 description = """print the path to the IPython dir"""
175 subcommands = dict(
176 profile=('IPython.core.profileapp.ProfileLocate',
177 "print the path to an IPython profile directory",
178 ),
179 )
180 def start(self):
181 if self.subapp is not None:
182 return self.subapp.start()
183 else:
184 print(self.ipython_dir)
185
186
187class TerminalIPythonApp(BaseIPythonApplication, InteractiveShellApp):
188 name = "ipython"
189 description = usage.cl_usage
190 crash_handler_class = IPAppCrashHandler # typing: ignore[assignment]
191 examples = _examples
192
193 flags = flags
194 aliases = aliases
195 classes = List()
196
197 interactive_shell_class = Type(
198 klass=object, # use default_value otherwise which only allow subclasses.
199 default_value=TerminalInteractiveShell,
200 help="Class to use to instantiate the TerminalInteractiveShell object. Useful for custom Frontends"
201 ).tag(config=True)
202
203 @default('classes')
204 def _classes_default(self):
205 """This has to be in a method, for TerminalIPythonApp to be available."""
206 return [
207 InteractiveShellApp, # ShellApp comes before TerminalApp, because
208 self.__class__, # it will also affect subclasses (e.g. QtConsole)
209 TerminalInteractiveShell,
210 HistoryManager,
211 MagicsManager,
212 ProfileDir,
213 PlainTextFormatter,
214 IPCompleter,
215 ScriptMagics,
216 LoggingMagics,
217 StoreMagics,
218 ]
219
220 subcommands = dict(
221 profile = ("IPython.core.profileapp.ProfileApp",
222 "Create and manage IPython profiles."
223 ),
224 kernel = ("ipykernel.kernelapp.IPKernelApp",
225 "Start a kernel without an attached frontend."
226 ),
227 locate=('IPython.terminal.ipapp.LocateIPythonApp',
228 LocateIPythonApp.description
229 ),
230 history=('IPython.core.historyapp.HistoryApp',
231 "Manage the IPython history database."
232 ),
233 )
234
235 # *do* autocreate requested profile, but don't create the config file.
236 auto_create = Bool(True).tag(config=True)
237
238 # configurables
239 quick = Bool(False,
240 help="""Start IPython quickly by skipping the loading of config files."""
241 ).tag(config=True)
242 @observe('quick')
243 def _quick_changed(self, change):
244 if change['new']:
245 self.load_config_file = lambda *a, **kw: None
246
247 display_banner = Bool(True,
248 help="Whether to display a banner upon starting IPython."
249 ).tag(config=True)
250
251 # if there is code of files to run from the cmd line, don't interact
252 # unless the --i flag (App.force_interact) is true.
253 force_interact = Bool(False,
254 help="""If a command or file is given via the command-line,
255 e.g. 'ipython foo.py', start an interactive shell after executing the
256 file or command."""
257 ).tag(config=True)
258 @observe('force_interact')
259 def _force_interact_changed(self, change):
260 if change['new']:
261 self.interact = True
262
263 @observe('file_to_run', 'code_to_run', 'module_to_run')
264 def _file_to_run_changed(self, change):
265 new = change['new']
266 if new:
267 self.something_to_run = True
268 if new and not self.force_interact:
269 self.interact = False
270
271 # internal, not-configurable
272 something_to_run=Bool(False)
273
274 @catch_config_error
275 def initialize(self, argv=None):
276 """Do actions after construct, but before starting the app."""
277 super(TerminalIPythonApp, self).initialize(argv)
278 if self.subapp is not None:
279 # don't bother initializing further, starting subapp
280 return
281 # print(self.extra_args)
282 if self.extra_args and not self.something_to_run:
283 self.file_to_run = self.extra_args[0]
284 self.init_path()
285 # create the shell
286 self.init_shell()
287 # and draw the banner
288 self.init_banner()
289 # Now a variety of things that happen after the banner is printed.
290 self.init_gui_pylab()
291 self.init_extensions()
292 self.init_code()
293
294 def init_shell(self):
295 """initialize the InteractiveShell instance"""
296 # Create an InteractiveShell instance.
297 # shell.display_banner should always be False for the terminal
298 # based app, because we call shell.show_banner() by hand below
299 # so the banner shows *before* all extension loading stuff.
300 self.shell = self.interactive_shell_class.instance(parent=self,
301 profile_dir=self.profile_dir,
302 ipython_dir=self.ipython_dir, user_ns=self.user_ns)
303 self.shell.configurables.append(self)
304
305 def init_banner(self):
306 """optionally display the banner"""
307 if self.display_banner and self.interact:
308 self.shell.show_banner()
309 # Make sure there is a space below the banner.
310 if self.log_level <= logging.INFO: print()
311
312 def _pylab_changed(self, name, old, new):
313 """Replace --pylab='inline' with --pylab='auto'"""
314 if new == 'inline':
315 warnings.warn("'inline' not available as pylab backend, "
316 "using 'auto' instead.")
317 self.pylab = 'auto'
318
319 def start(self):
320 if self.subapp is not None:
321 return self.subapp.start()
322 # perform any prexec steps:
323 if self.interact:
324 self.log.debug("Starting IPython's mainloop...")
325 self.shell.mainloop()
326 else:
327 self.log.debug("IPython not interactive...")
328 self.shell.restore_term_title()
329 if not self.shell.last_execution_succeeded:
330 sys.exit(1)
331
332def load_default_config(ipython_dir=None):
333 """Load the default config file from the default ipython_dir.
334
335 This is useful for embedded shells.
336 """
337 if ipython_dir is None:
338 ipython_dir = get_ipython_dir()
339
340 profile_dir = os.path.join(ipython_dir, 'profile_default')
341 app = TerminalIPythonApp()
342 app.config_file_paths.append(profile_dir)
343 app.load_config_file()
344 return app.config
345
346launch_new_instance = TerminalIPythonApp.launch_instance