1"""Implementation of magic functions related to History.
2"""
3#-----------------------------------------------------------------------------
4#  Copyright (c) 2012, IPython Development Team.
5#
6#  Distributed under the terms of the Modified BSD License.
7#
8#  The full license is in the file COPYING.txt, distributed with this software.
9#-----------------------------------------------------------------------------
10
11#-----------------------------------------------------------------------------
12# Imports
13#-----------------------------------------------------------------------------
14from __future__ import print_function
15
16# Stdlib
17import os
18import sys
19from io import open as io_open
20
21# Our own packages
22from IPython.core.error import StdinNotImplementedError
23from IPython.core.magic import Magics, magics_class, line_magic
24from IPython.core.magic_arguments import (argument, magic_arguments,
25                                          parse_argstring)
26from IPython.testing.skipdoctest import skip_doctest
27from IPython.utils import io
28from IPython.utils.py3compat import cast_unicode_py2
29
30#-----------------------------------------------------------------------------
31# Magics class implementation
32#-----------------------------------------------------------------------------
33
34
35_unspecified = object()
36
37
38@magics_class
39class HistoryMagics(Magics):
40
41    @magic_arguments()
42    @argument(
43        '-n', dest='print_nums', action='store_true', default=False,
44        help="""
45        print line numbers for each input.
46        This feature is only available if numbered prompts are in use.
47        """)
48    @argument(
49        '-o', dest='get_output', action='store_true', default=False,
50        help="also print outputs for each input.")
51    @argument(
52        '-p', dest='pyprompts', action='store_true', default=False,
53        help="""
54        print classic '>>>' python prompts before each input.
55        This is useful for making documentation, and in conjunction
56        with -o, for producing doctest-ready output.
57        """)
58    @argument(
59        '-t', dest='raw', action='store_false', default=True,
60        help="""
61        print the 'translated' history, as IPython understands it.
62        IPython filters your input and converts it all into valid Python
63        source before executing it (things like magics or aliases are turned
64        into function calls, for example). With this option, you'll see the
65        native history instead of the user-entered version: '%%cd /' will be
66        seen as 'get_ipython().magic("%%cd /")' instead of '%%cd /'.
67        """)
68    @argument(
69        '-f', dest='filename',
70        help="""
71        FILENAME: instead of printing the output to the screen, redirect
72        it to the given file.  The file is always overwritten, though *when
73        it can*, IPython asks for confirmation first. In particular, running
74        the command 'history -f FILENAME' from the IPython Notebook
75        interface will replace FILENAME even if it already exists *without*
76        confirmation.
77        """)
78    @argument(
79        '-g', dest='pattern', nargs='*', default=None,
80        help="""
81        treat the arg as a glob pattern to search for in (full) history.
82        This includes the saved history (almost all commands ever written).
83        The pattern may contain '?' to match one unknown character and '*'
84        to match any number of unknown characters. Use '%%hist -g' to show
85        full saved history (may be very long).
86        """)
87    @argument(
88        '-l', dest='limit', type=int, nargs='?', default=_unspecified,
89        help="""
90        get the last n lines from all sessions. Specify n as a single
91        arg, or the default is the last 10 lines.
92        """)
93    @argument(
94        '-u', dest='unique', action='store_true',
95        help="""
96        when searching history using `-g`, show only unique history.
97        """)
98    @argument('range', nargs='*')
99    @skip_doctest
100    @line_magic
101    def history(self, parameter_s = ''):
102        """Print input history (_i<n> variables), with most recent last.
103
104        By default, input history is printed without line numbers so it can be
105        directly pasted into an editor. Use -n to show them.
106
107        By default, all input history from the current session is displayed.
108        Ranges of history can be indicated using the syntax:
109
110        ``4``
111            Line 4, current session
112        ``4-6``
113            Lines 4-6, current session
114        ``243/1-5``
115            Lines 1-5, session 243
116        ``~2/7``
117            Line 7, session 2 before current
118        ``~8/1-~6/5``
119            From the first line of 8 sessions ago, to the fifth line of 6
120            sessions ago.
121
122        Multiple ranges can be entered, separated by spaces
123
124        The same syntax is used by %macro, %save, %edit, %rerun
125
126        Examples
127        --------
128        ::
129
130          In [6]: %history -n 4-6
131          4:a = 12
132          5:print a**2
133          6:%history -n 4-6
134
135        """
136
137        args = parse_argstring(self.history, parameter_s)
138
139        # For brevity
140        history_manager = self.shell.history_manager
141
142        def _format_lineno(session, line):
143            """Helper function to format line numbers properly."""
144            if session in (0, history_manager.session_number):
145                return str(line)
146            return "%s/%s" % (session, line)
147
148        # Check if output to specific file was requested.
149        outfname = args.filename
150        if not outfname:
151            outfile = sys.stdout  # default
152            # We don't want to close stdout at the end!
153            close_at_end = False
154        else:
155            if os.path.exists(outfname):
156                try:
157                    ans = io.ask_yes_no("File %r exists. Overwrite?" % outfname)
158                except StdinNotImplementedError:
159                    ans = True
160                if not ans:
161                    print('Aborting.')
162                    return
163                print("Overwriting file.")
164            outfile = io_open(outfname, 'w', encoding='utf-8')
165            close_at_end = True
166
167        print_nums = args.print_nums
168        get_output = args.get_output
169        pyprompts = args.pyprompts
170        raw = args.raw
171
172        pattern = None
173        limit = None if args.limit is _unspecified else args.limit
174
175        if args.pattern is not None:
176            if args.pattern:
177                pattern = "*" + " ".join(args.pattern) + "*"
178            else:
179                pattern = "*"
180            hist = history_manager.search(pattern, raw=raw, output=get_output,
181                                          n=limit, unique=args.unique)
182            print_nums = True
183        elif args.limit is not _unspecified:
184            n = 10 if limit is None else limit
185            hist = history_manager.get_tail(n, raw=raw, output=get_output)
186        else:
187            if args.range:      # Get history by ranges
188                hist = history_manager.get_range_by_str(" ".join(args.range),
189                                                        raw, get_output)
190            else:               # Just get history for the current session
191                hist = history_manager.get_range(raw=raw, output=get_output)
192
193        # We could be displaying the entire history, so let's not try to pull
194        # it into a list in memory. Anything that needs more space will just
195        # misalign.
196        width = 4
197
198        for session, lineno, inline in hist:
199            # Print user history with tabs expanded to 4 spaces.  The GUI
200            # clients use hard tabs for easier usability in auto-indented code,
201            # but we want to produce PEP-8 compliant history for safe pasting
202            # into an editor.
203            if get_output:
204                inline, output = inline
205            inline = inline.expandtabs(4).rstrip()
206
207            multiline = "\n" in inline
208            line_sep = '\n' if multiline else ' '
209            if print_nums:
210                print(u'%s:%s' % (_format_lineno(session, lineno).rjust(width),
211                        line_sep),  file=outfile, end=u'')
212            if pyprompts:
213                print(u">>> ", end=u"", file=outfile)
214                if multiline:
215                    inline = "\n... ".join(inline.splitlines()) + "\n..."
216            print(inline, file=outfile)
217            if get_output and output:
218                print(cast_unicode_py2(output), file=outfile)
219
220        if close_at_end:
221            outfile.close()
222
223    @line_magic
224    def recall(self, arg):
225        r"""Repeat a command, or get command to input line for editing.
226
227        %recall and %rep are equivalent.
228
229        - %recall (no arguments):
230
231        Place a string version of last computation result (stored in the
232        special '_' variable) to the next input prompt. Allows you to create
233        elaborate command lines without using copy-paste::
234
235             In[1]: l = ["hei", "vaan"]
236             In[2]: "".join(l)
237            Out[2]: heivaan
238             In[3]: %recall
239             In[4]: heivaan_ <== cursor blinking
240
241        %recall 45
242
243        Place history line 45 on the next input prompt. Use %hist to find
244        out the number.
245
246        %recall 1-4
247
248        Combine the specified lines into one cell, and place it on the next
249        input prompt. See %history for the slice syntax.
250
251        %recall foo+bar
252
253        If foo+bar can be evaluated in the user namespace, the result is
254        placed at the next input prompt. Otherwise, the history is searched
255        for lines which contain that substring, and the most recent one is
256        placed at the next input prompt.
257        """
258        if not arg:                 # Last output
259            self.shell.set_next_input(str(self.shell.user_ns["_"]))
260            return
261                                    # Get history range
262        histlines = self.shell.history_manager.get_range_by_str(arg)
263        cmd = "\n".join(x[2] for x in histlines)
264        if cmd:
265            self.shell.set_next_input(cmd.rstrip())
266            return
267
268        try:                        # Variable in user namespace
269            cmd = str(eval(arg, self.shell.user_ns))
270        except Exception:           # Search for term in history
271            histlines = self.shell.history_manager.search("*"+arg+"*")
272            for h in reversed([x[2] for x in histlines]):
273                if 'recall' in h or 'rep' in h:
274                    continue
275                self.shell.set_next_input(h.rstrip())
276                return
277        else:
278            self.shell.set_next_input(cmd.rstrip())
279        print("Couldn't evaluate or find in history:", arg)
280
281    @line_magic
282    def rerun(self, parameter_s=''):
283        """Re-run previous input
284
285        By default, you can specify ranges of input history to be repeated
286        (as with %history). With no arguments, it will repeat the last line.
287
288        Options:
289
290          -l <n> : Repeat the last n lines of input, not including the
291          current command.
292
293          -g foo : Repeat the most recent line which contains foo
294        """
295        opts, args = self.parse_options(parameter_s, 'l:g:', mode='string')
296        if "l" in opts:         # Last n lines
297            n = int(opts['l'])
298            hist = self.shell.history_manager.get_tail(n)
299        elif "g" in opts:       # Search
300            p = "*"+opts['g']+"*"
301            hist = list(self.shell.history_manager.search(p))
302            for l in reversed(hist):
303                if "rerun" not in l[2]:
304                    hist = [l]     # The last match which isn't a %rerun
305                    break
306            else:
307                hist = []          # No matches except %rerun
308        elif args:              # Specify history ranges
309            hist = self.shell.history_manager.get_range_by_str(args)
310        else:                   # Last line
311            hist = self.shell.history_manager.get_tail(1)
312        hist = [x[2] for x in hist]
313        if not hist:
314            print("No lines in history match specification")
315            return
316        histlines = "\n".join(hist)
317        print("=== Executing: ===")
318        print(histlines)
319        print("=== Output: ===")
320        self.shell.run_cell("\n".join(hist), store_history=False)
321