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