Show More
@@ -1,220 +1,219 | |||||
1 | """Logger class for IPython's logging facilities. |
|
1 | """Logger class for IPython's logging facilities. | |
2 | """ |
|
2 | """ | |
3 |
|
3 | |||
4 | #***************************************************************************** |
|
4 | #***************************************************************************** | |
5 | # Copyright (C) 2001 Janko Hauser <jhauser@zscout.de> and |
|
5 | # Copyright (C) 2001 Janko Hauser <jhauser@zscout.de> and | |
6 | # Copyright (C) 2001-2006 Fernando Perez <fperez@colorado.edu> |
|
6 | # Copyright (C) 2001-2006 Fernando Perez <fperez@colorado.edu> | |
7 | # |
|
7 | # | |
8 | # Distributed under the terms of the BSD License. The full license is in |
|
8 | # Distributed under the terms of the BSD License. The full license is in | |
9 | # the file COPYING, distributed as part of this software. |
|
9 | # the file COPYING, distributed as part of this software. | |
10 | #***************************************************************************** |
|
10 | #***************************************************************************** | |
11 |
|
11 | |||
12 | #**************************************************************************** |
|
12 | #**************************************************************************** | |
13 | # Modules and globals |
|
13 | # Modules and globals | |
14 |
|
14 | |||
15 | # Python standard modules |
|
15 | # Python standard modules | |
16 | import glob |
|
16 | import glob | |
17 | import io |
|
17 | import io | |
18 | import os |
|
18 | import os | |
19 | import time |
|
19 | import time | |
20 |
|
20 | |||
21 | from IPython.utils.py3compat import str_to_unicode |
|
|||
22 |
|
21 | |||
23 | #**************************************************************************** |
|
22 | #**************************************************************************** | |
24 | # FIXME: This class isn't a mixin anymore, but it still needs attributes from |
|
23 | # FIXME: This class isn't a mixin anymore, but it still needs attributes from | |
25 | # ipython and does input cache management. Finish cleanup later... |
|
24 | # ipython and does input cache management. Finish cleanup later... | |
26 |
|
25 | |||
27 | class Logger(object): |
|
26 | class Logger(object): | |
28 | """A Logfile class with different policies for file creation""" |
|
27 | """A Logfile class with different policies for file creation""" | |
29 |
|
28 | |||
30 | def __init__(self, home_dir, logfname='Logger.log', loghead=u'', |
|
29 | def __init__(self, home_dir, logfname='Logger.log', loghead=u'', | |
31 | logmode='over'): |
|
30 | logmode='over'): | |
32 |
|
31 | |||
33 | # this is the full ipython instance, we need some attributes from it |
|
32 | # this is the full ipython instance, we need some attributes from it | |
34 | # which won't exist until later. What a mess, clean up later... |
|
33 | # which won't exist until later. What a mess, clean up later... | |
35 | self.home_dir = home_dir |
|
34 | self.home_dir = home_dir | |
36 |
|
35 | |||
37 | self.logfname = logfname |
|
36 | self.logfname = logfname | |
38 | self.loghead = loghead |
|
37 | self.loghead = loghead | |
39 | self.logmode = logmode |
|
38 | self.logmode = logmode | |
40 | self.logfile = None |
|
39 | self.logfile = None | |
41 |
|
40 | |||
42 | # Whether to log raw or processed input |
|
41 | # Whether to log raw or processed input | |
43 | self.log_raw_input = False |
|
42 | self.log_raw_input = False | |
44 |
|
43 | |||
45 | # whether to also log output |
|
44 | # whether to also log output | |
46 | self.log_output = False |
|
45 | self.log_output = False | |
47 |
|
46 | |||
48 | # whether to put timestamps before each log entry |
|
47 | # whether to put timestamps before each log entry | |
49 | self.timestamp = False |
|
48 | self.timestamp = False | |
50 |
|
49 | |||
51 | # activity control flags |
|
50 | # activity control flags | |
52 | self.log_active = False |
|
51 | self.log_active = False | |
53 |
|
52 | |||
54 | # logmode is a validated property |
|
53 | # logmode is a validated property | |
55 | def _set_mode(self,mode): |
|
54 | def _set_mode(self,mode): | |
56 | if mode not in ['append','backup','global','over','rotate']: |
|
55 | if mode not in ['append','backup','global','over','rotate']: | |
57 | raise ValueError('invalid log mode %s given' % mode) |
|
56 | raise ValueError('invalid log mode %s given' % mode) | |
58 | self._logmode = mode |
|
57 | self._logmode = mode | |
59 |
|
58 | |||
60 | def _get_mode(self): |
|
59 | def _get_mode(self): | |
61 | return self._logmode |
|
60 | return self._logmode | |
62 |
|
61 | |||
63 | logmode = property(_get_mode,_set_mode) |
|
62 | logmode = property(_get_mode,_set_mode) | |
64 |
|
63 | |||
65 | def logstart(self, logfname=None, loghead=None, logmode=None, |
|
64 | def logstart(self, logfname=None, loghead=None, logmode=None, | |
66 | log_output=False, timestamp=False, log_raw_input=False): |
|
65 | log_output=False, timestamp=False, log_raw_input=False): | |
67 | """Generate a new log-file with a default header. |
|
66 | """Generate a new log-file with a default header. | |
68 |
|
67 | |||
69 | Raises RuntimeError if the log has already been started""" |
|
68 | Raises RuntimeError if the log has already been started""" | |
70 |
|
69 | |||
71 | if self.logfile is not None: |
|
70 | if self.logfile is not None: | |
72 | raise RuntimeError('Log file is already active: %s' % |
|
71 | raise RuntimeError('Log file is already active: %s' % | |
73 | self.logfname) |
|
72 | self.logfname) | |
74 |
|
73 | |||
75 | # The parameters can override constructor defaults |
|
74 | # The parameters can override constructor defaults | |
76 | if logfname is not None: self.logfname = logfname |
|
75 | if logfname is not None: self.logfname = logfname | |
77 | if loghead is not None: self.loghead = loghead |
|
76 | if loghead is not None: self.loghead = loghead | |
78 | if logmode is not None: self.logmode = logmode |
|
77 | if logmode is not None: self.logmode = logmode | |
79 |
|
78 | |||
80 | # Parameters not part of the constructor |
|
79 | # Parameters not part of the constructor | |
81 | self.timestamp = timestamp |
|
80 | self.timestamp = timestamp | |
82 | self.log_output = log_output |
|
81 | self.log_output = log_output | |
83 | self.log_raw_input = log_raw_input |
|
82 | self.log_raw_input = log_raw_input | |
84 |
|
83 | |||
85 | # init depending on the log mode requested |
|
84 | # init depending on the log mode requested | |
86 | isfile = os.path.isfile |
|
85 | isfile = os.path.isfile | |
87 | logmode = self.logmode |
|
86 | logmode = self.logmode | |
88 |
|
87 | |||
89 | if logmode == 'append': |
|
88 | if logmode == 'append': | |
90 | self.logfile = io.open(self.logfname, 'a', encoding='utf-8') |
|
89 | self.logfile = io.open(self.logfname, 'a', encoding='utf-8') | |
91 |
|
90 | |||
92 | elif logmode == 'backup': |
|
91 | elif logmode == 'backup': | |
93 | if isfile(self.logfname): |
|
92 | if isfile(self.logfname): | |
94 | backup_logname = self.logfname+'~' |
|
93 | backup_logname = self.logfname+'~' | |
95 | # Manually remove any old backup, since os.rename may fail |
|
94 | # Manually remove any old backup, since os.rename may fail | |
96 | # under Windows. |
|
95 | # under Windows. | |
97 | if isfile(backup_logname): |
|
96 | if isfile(backup_logname): | |
98 | os.remove(backup_logname) |
|
97 | os.remove(backup_logname) | |
99 | os.rename(self.logfname,backup_logname) |
|
98 | os.rename(self.logfname,backup_logname) | |
100 | self.logfile = io.open(self.logfname, 'w', encoding='utf-8') |
|
99 | self.logfile = io.open(self.logfname, 'w', encoding='utf-8') | |
101 |
|
100 | |||
102 | elif logmode == 'global': |
|
101 | elif logmode == 'global': | |
103 | self.logfname = os.path.join(self.home_dir,self.logfname) |
|
102 | self.logfname = os.path.join(self.home_dir,self.logfname) | |
104 | self.logfile = io.open(self.logfname, 'a', encoding='utf-8') |
|
103 | self.logfile = io.open(self.logfname, 'a', encoding='utf-8') | |
105 |
|
104 | |||
106 | elif logmode == 'over': |
|
105 | elif logmode == 'over': | |
107 | if isfile(self.logfname): |
|
106 | if isfile(self.logfname): | |
108 | os.remove(self.logfname) |
|
107 | os.remove(self.logfname) | |
109 | self.logfile = io.open(self.logfname,'w', encoding='utf-8') |
|
108 | self.logfile = io.open(self.logfname,'w', encoding='utf-8') | |
110 |
|
109 | |||
111 | elif logmode == 'rotate': |
|
110 | elif logmode == 'rotate': | |
112 | if isfile(self.logfname): |
|
111 | if isfile(self.logfname): | |
113 | if isfile(self.logfname+'.001~'): |
|
112 | if isfile(self.logfname+'.001~'): | |
114 | old = glob.glob(self.logfname+'.*~') |
|
113 | old = glob.glob(self.logfname+'.*~') | |
115 | old.sort() |
|
114 | old.sort() | |
116 | old.reverse() |
|
115 | old.reverse() | |
117 | for f in old: |
|
116 | for f in old: | |
118 | root, ext = os.path.splitext(f) |
|
117 | root, ext = os.path.splitext(f) | |
119 | num = int(ext[1:-1])+1 |
|
118 | num = int(ext[1:-1])+1 | |
120 | os.rename(f, root+'.'+repr(num).zfill(3)+'~') |
|
119 | os.rename(f, root+'.'+repr(num).zfill(3)+'~') | |
121 | os.rename(self.logfname, self.logfname+'.001~') |
|
120 | os.rename(self.logfname, self.logfname+'.001~') | |
122 | self.logfile = io.open(self.logfname, 'w', encoding='utf-8') |
|
121 | self.logfile = io.open(self.logfname, 'w', encoding='utf-8') | |
123 |
|
122 | |||
124 | if logmode != 'append': |
|
123 | if logmode != 'append': | |
125 | self.logfile.write(self.loghead) |
|
124 | self.logfile.write(self.loghead) | |
126 |
|
125 | |||
127 | self.logfile.flush() |
|
126 | self.logfile.flush() | |
128 | self.log_active = True |
|
127 | self.log_active = True | |
129 |
|
128 | |||
130 | def switch_log(self,val): |
|
129 | def switch_log(self,val): | |
131 | """Switch logging on/off. val should be ONLY a boolean.""" |
|
130 | """Switch logging on/off. val should be ONLY a boolean.""" | |
132 |
|
131 | |||
133 | if val not in [False,True,0,1]: |
|
132 | if val not in [False,True,0,1]: | |
134 | raise ValueError('Call switch_log ONLY with a boolean argument, ' |
|
133 | raise ValueError('Call switch_log ONLY with a boolean argument, ' | |
135 | 'not with: %s' % val) |
|
134 | 'not with: %s' % val) | |
136 |
|
135 | |||
137 | label = {0:'OFF',1:'ON',False:'OFF',True:'ON'} |
|
136 | label = {0:'OFF',1:'ON',False:'OFF',True:'ON'} | |
138 |
|
137 | |||
139 | if self.logfile is None: |
|
138 | if self.logfile is None: | |
140 | print(""" |
|
139 | print(""" | |
141 | Logging hasn't been started yet (use logstart for that). |
|
140 | Logging hasn't been started yet (use logstart for that). | |
142 |
|
141 | |||
143 | %logon/%logoff are for temporarily starting and stopping logging for a logfile |
|
142 | %logon/%logoff are for temporarily starting and stopping logging for a logfile | |
144 | which already exists. But you must first start the logging process with |
|
143 | which already exists. But you must first start the logging process with | |
145 | %logstart (optionally giving a logfile name).""") |
|
144 | %logstart (optionally giving a logfile name).""") | |
146 |
|
145 | |||
147 | else: |
|
146 | else: | |
148 | if self.log_active == val: |
|
147 | if self.log_active == val: | |
149 | print('Logging is already',label[val]) |
|
148 | print('Logging is already',label[val]) | |
150 | else: |
|
149 | else: | |
151 | print('Switching logging',label[val]) |
|
150 | print('Switching logging',label[val]) | |
152 | self.log_active = not self.log_active |
|
151 | self.log_active = not self.log_active | |
153 | self.log_active_out = self.log_active |
|
152 | self.log_active_out = self.log_active | |
154 |
|
153 | |||
155 | def logstate(self): |
|
154 | def logstate(self): | |
156 | """Print a status message about the logger.""" |
|
155 | """Print a status message about the logger.""" | |
157 | if self.logfile is None: |
|
156 | if self.logfile is None: | |
158 | print('Logging has not been activated.') |
|
157 | print('Logging has not been activated.') | |
159 | else: |
|
158 | else: | |
160 | state = self.log_active and 'active' or 'temporarily suspended' |
|
159 | state = self.log_active and 'active' or 'temporarily suspended' | |
161 | print('Filename :', self.logfname) |
|
160 | print('Filename :', self.logfname) | |
162 | print('Mode :', self.logmode) |
|
161 | print('Mode :', self.logmode) | |
163 | print('Output logging :', self.log_output) |
|
162 | print('Output logging :', self.log_output) | |
164 | print('Raw input log :', self.log_raw_input) |
|
163 | print('Raw input log :', self.log_raw_input) | |
165 | print('Timestamping :', self.timestamp) |
|
164 | print('Timestamping :', self.timestamp) | |
166 | print('State :', state) |
|
165 | print('State :', state) | |
167 |
|
166 | |||
168 | def log(self, line_mod, line_ori): |
|
167 | def log(self, line_mod, line_ori): | |
169 | """Write the sources to a log. |
|
168 | """Write the sources to a log. | |
170 |
|
169 | |||
171 | Inputs: |
|
170 | Inputs: | |
172 |
|
171 | |||
173 | - line_mod: possibly modified input, such as the transformations made |
|
172 | - line_mod: possibly modified input, such as the transformations made | |
174 | by input prefilters or input handlers of various kinds. This should |
|
173 | by input prefilters or input handlers of various kinds. This should | |
175 | always be valid Python. |
|
174 | always be valid Python. | |
176 |
|
175 | |||
177 | - line_ori: unmodified input line from the user. This is not |
|
176 | - line_ori: unmodified input line from the user. This is not | |
178 | necessarily valid Python. |
|
177 | necessarily valid Python. | |
179 | """ |
|
178 | """ | |
180 |
|
179 | |||
181 | # Write the log line, but decide which one according to the |
|
180 | # Write the log line, but decide which one according to the | |
182 | # log_raw_input flag, set when the log is started. |
|
181 | # log_raw_input flag, set when the log is started. | |
183 | if self.log_raw_input: |
|
182 | if self.log_raw_input: | |
184 | self.log_write(line_ori) |
|
183 | self.log_write(line_ori) | |
185 | else: |
|
184 | else: | |
186 | self.log_write(line_mod) |
|
185 | self.log_write(line_mod) | |
187 |
|
186 | |||
188 | def log_write(self, data, kind='input'): |
|
187 | def log_write(self, data, kind='input'): | |
189 | """Write data to the log file, if active""" |
|
188 | """Write data to the log file, if active""" | |
190 |
|
189 | |||
191 | #print 'data: %r' % data # dbg |
|
190 | #print 'data: %r' % data # dbg | |
192 | if self.log_active and data: |
|
191 | if self.log_active and data: | |
193 | write = self.logfile.write |
|
192 | write = self.logfile.write | |
194 | if kind=='input': |
|
193 | if kind=='input': | |
195 | if self.timestamp: |
|
194 | if self.timestamp: | |
196 |
write(str |
|
195 | write(str(time.strftime('# %a, %d %b %Y %H:%M:%S\n', | |
197 | time.localtime()))) |
|
196 | time.localtime()))) | |
198 | write(data) |
|
197 | write(data) | |
199 | elif kind=='output' and self.log_output: |
|
198 | elif kind=='output' and self.log_output: | |
200 | odata = u'\n'.join([u'#[Out]# %s' % s |
|
199 | odata = u'\n'.join([u'#[Out]# %s' % s | |
201 | for s in data.splitlines()]) |
|
200 | for s in data.splitlines()]) | |
202 | write(u'%s\n' % odata) |
|
201 | write(u'%s\n' % odata) | |
203 | self.logfile.flush() |
|
202 | self.logfile.flush() | |
204 |
|
203 | |||
205 | def logstop(self): |
|
204 | def logstop(self): | |
206 | """Fully stop logging and close log file. |
|
205 | """Fully stop logging and close log file. | |
207 |
|
206 | |||
208 | In order to start logging again, a new logstart() call needs to be |
|
207 | In order to start logging again, a new logstart() call needs to be | |
209 | made, possibly (though not necessarily) with a new filename, mode and |
|
208 | made, possibly (though not necessarily) with a new filename, mode and | |
210 | other options.""" |
|
209 | other options.""" | |
211 |
|
210 | |||
212 | if self.logfile is not None: |
|
211 | if self.logfile is not None: | |
213 | self.logfile.close() |
|
212 | self.logfile.close() | |
214 | self.logfile = None |
|
213 | self.logfile = None | |
215 | else: |
|
214 | else: | |
216 | print("Logging hadn't been started.") |
|
215 | print("Logging hadn't been started.") | |
217 | self.log_active = False |
|
216 | self.log_active = False | |
218 |
|
217 | |||
219 | # For backwards compatibility, in case anyone was using this. |
|
218 | # For backwards compatibility, in case anyone was using this. | |
220 | close_log = logstop |
|
219 | close_log = logstop |
@@ -1,184 +1,183 | |||||
1 | """Implementation of magic functions for IPython's own logging. |
|
1 | """Implementation of magic functions for IPython's own logging. | |
2 | """ |
|
2 | """ | |
3 | #----------------------------------------------------------------------------- |
|
3 | #----------------------------------------------------------------------------- | |
4 | # Copyright (c) 2012 The IPython Development Team. |
|
4 | # Copyright (c) 2012 The IPython Development Team. | |
5 | # |
|
5 | # | |
6 | # Distributed under the terms of the Modified BSD License. |
|
6 | # Distributed under the terms of the Modified BSD License. | |
7 | # |
|
7 | # | |
8 | # The full license is in the file COPYING.txt, distributed with this software. |
|
8 | # The full license is in the file COPYING.txt, distributed with this software. | |
9 | #----------------------------------------------------------------------------- |
|
9 | #----------------------------------------------------------------------------- | |
10 |
|
10 | |||
11 | #----------------------------------------------------------------------------- |
|
11 | #----------------------------------------------------------------------------- | |
12 | # Imports |
|
12 | # Imports | |
13 | #----------------------------------------------------------------------------- |
|
13 | #----------------------------------------------------------------------------- | |
14 |
|
14 | |||
15 | # Stdlib |
|
15 | # Stdlib | |
16 | import os |
|
16 | import os | |
17 | import sys |
|
17 | import sys | |
18 |
|
18 | |||
19 | # Our own packages |
|
19 | # Our own packages | |
20 | from IPython.core.magic import Magics, magics_class, line_magic |
|
20 | from IPython.core.magic import Magics, magics_class, line_magic | |
21 | from warnings import warn |
|
21 | from warnings import warn | |
22 | from IPython.utils.py3compat import str_to_unicode |
|
|||
23 |
|
22 | |||
24 | #----------------------------------------------------------------------------- |
|
23 | #----------------------------------------------------------------------------- | |
25 | # Magic implementation classes |
|
24 | # Magic implementation classes | |
26 | #----------------------------------------------------------------------------- |
|
25 | #----------------------------------------------------------------------------- | |
27 |
|
26 | |||
28 | @magics_class |
|
27 | @magics_class | |
29 | class LoggingMagics(Magics): |
|
28 | class LoggingMagics(Magics): | |
30 | """Magics related to all logging machinery.""" |
|
29 | """Magics related to all logging machinery.""" | |
31 |
|
30 | |||
32 | @line_magic |
|
31 | @line_magic | |
33 | def logstart(self, parameter_s=''): |
|
32 | def logstart(self, parameter_s=''): | |
34 | """Start logging anywhere in a session. |
|
33 | """Start logging anywhere in a session. | |
35 |
|
34 | |||
36 | %logstart [-o|-r|-t] [log_name [log_mode]] |
|
35 | %logstart [-o|-r|-t] [log_name [log_mode]] | |
37 |
|
36 | |||
38 | If no name is given, it defaults to a file named 'ipython_log.py' in your |
|
37 | If no name is given, it defaults to a file named 'ipython_log.py' in your | |
39 | current directory, in 'rotate' mode (see below). |
|
38 | current directory, in 'rotate' mode (see below). | |
40 |
|
39 | |||
41 | '%logstart name' saves to file 'name' in 'backup' mode. It saves your |
|
40 | '%logstart name' saves to file 'name' in 'backup' mode. It saves your | |
42 | history up to that point and then continues logging. |
|
41 | history up to that point and then continues logging. | |
43 |
|
42 | |||
44 | %logstart takes a second optional parameter: logging mode. This can be one |
|
43 | %logstart takes a second optional parameter: logging mode. This can be one | |
45 | of (note that the modes are given unquoted): |
|
44 | of (note that the modes are given unquoted): | |
46 |
|
45 | |||
47 | append |
|
46 | append | |
48 | Keep logging at the end of any existing file. |
|
47 | Keep logging at the end of any existing file. | |
49 |
|
48 | |||
50 | backup |
|
49 | backup | |
51 | Rename any existing file to name~ and start name. |
|
50 | Rename any existing file to name~ and start name. | |
52 |
|
51 | |||
53 | global |
|
52 | global | |
54 | Append to a single logfile in your home directory. |
|
53 | Append to a single logfile in your home directory. | |
55 |
|
54 | |||
56 | over |
|
55 | over | |
57 | Overwrite any existing log. |
|
56 | Overwrite any existing log. | |
58 |
|
57 | |||
59 | rotate |
|
58 | rotate | |
60 | Create rotating logs: name.1~, name.2~, etc. |
|
59 | Create rotating logs: name.1~, name.2~, etc. | |
61 |
|
60 | |||
62 | Options: |
|
61 | Options: | |
63 |
|
62 | |||
64 | -o |
|
63 | -o | |
65 | log also IPython's output. In this mode, all commands which |
|
64 | log also IPython's output. In this mode, all commands which | |
66 | generate an Out[NN] prompt are recorded to the logfile, right after |
|
65 | generate an Out[NN] prompt are recorded to the logfile, right after | |
67 | their corresponding input line. The output lines are always |
|
66 | their corresponding input line. The output lines are always | |
68 | prepended with a '#[Out]# ' marker, so that the log remains valid |
|
67 | prepended with a '#[Out]# ' marker, so that the log remains valid | |
69 | Python code. |
|
68 | Python code. | |
70 |
|
69 | |||
71 | Since this marker is always the same, filtering only the output from |
|
70 | Since this marker is always the same, filtering only the output from | |
72 | a log is very easy, using for example a simple awk call:: |
|
71 | a log is very easy, using for example a simple awk call:: | |
73 |
|
72 | |||
74 | awk -F'#\\[Out\\]# ' '{if($2) {print $2}}' ipython_log.py |
|
73 | awk -F'#\\[Out\\]# ' '{if($2) {print $2}}' ipython_log.py | |
75 |
|
74 | |||
76 | -r |
|
75 | -r | |
77 | log 'raw' input. Normally, IPython's logs contain the processed |
|
76 | log 'raw' input. Normally, IPython's logs contain the processed | |
78 | input, so that user lines are logged in their final form, converted |
|
77 | input, so that user lines are logged in their final form, converted | |
79 | into valid Python. For example, %Exit is logged as |
|
78 | into valid Python. For example, %Exit is logged as | |
80 | _ip.magic("Exit"). If the -r flag is given, all input is logged |
|
79 | _ip.magic("Exit"). If the -r flag is given, all input is logged | |
81 | exactly as typed, with no transformations applied. |
|
80 | exactly as typed, with no transformations applied. | |
82 |
|
81 | |||
83 | -t |
|
82 | -t | |
84 | put timestamps before each input line logged (these are put in |
|
83 | put timestamps before each input line logged (these are put in | |
85 | comments). |
|
84 | comments). | |
86 | """ |
|
85 | """ | |
87 |
|
86 | |||
88 | opts,par = self.parse_options(parameter_s,'ort') |
|
87 | opts,par = self.parse_options(parameter_s,'ort') | |
89 | log_output = 'o' in opts |
|
88 | log_output = 'o' in opts | |
90 | log_raw_input = 'r' in opts |
|
89 | log_raw_input = 'r' in opts | |
91 | timestamp = 't' in opts |
|
90 | timestamp = 't' in opts | |
92 |
|
91 | |||
93 | logger = self.shell.logger |
|
92 | logger = self.shell.logger | |
94 |
|
93 | |||
95 | # if no args are given, the defaults set in the logger constructor by |
|
94 | # if no args are given, the defaults set in the logger constructor by | |
96 | # ipython remain valid |
|
95 | # ipython remain valid | |
97 | if par: |
|
96 | if par: | |
98 | try: |
|
97 | try: | |
99 | logfname,logmode = par.split() |
|
98 | logfname,logmode = par.split() | |
100 | except: |
|
99 | except: | |
101 | logfname = par |
|
100 | logfname = par | |
102 | logmode = 'backup' |
|
101 | logmode = 'backup' | |
103 | else: |
|
102 | else: | |
104 | logfname = logger.logfname |
|
103 | logfname = logger.logfname | |
105 | logmode = logger.logmode |
|
104 | logmode = logger.logmode | |
106 | # put logfname into rc struct as if it had been called on the command |
|
105 | # put logfname into rc struct as if it had been called on the command | |
107 | # line, so it ends up saved in the log header Save it in case we need |
|
106 | # line, so it ends up saved in the log header Save it in case we need | |
108 | # to restore it... |
|
107 | # to restore it... | |
109 | old_logfile = self.shell.logfile |
|
108 | old_logfile = self.shell.logfile | |
110 | if logfname: |
|
109 | if logfname: | |
111 | logfname = os.path.expanduser(logfname) |
|
110 | logfname = os.path.expanduser(logfname) | |
112 | self.shell.logfile = logfname |
|
111 | self.shell.logfile = logfname | |
113 |
|
112 | |||
114 | loghead = u'# IPython log file\n\n' |
|
113 | loghead = u'# IPython log file\n\n' | |
115 | try: |
|
114 | try: | |
116 | logger.logstart(logfname, loghead, logmode, log_output, timestamp, |
|
115 | logger.logstart(logfname, loghead, logmode, log_output, timestamp, | |
117 | log_raw_input) |
|
116 | log_raw_input) | |
118 | except: |
|
117 | except: | |
119 | self.shell.logfile = old_logfile |
|
118 | self.shell.logfile = old_logfile | |
120 | warn("Couldn't start log: %s" % sys.exc_info()[1]) |
|
119 | warn("Couldn't start log: %s" % sys.exc_info()[1]) | |
121 | else: |
|
120 | else: | |
122 | # log input history up to this point, optionally interleaving |
|
121 | # log input history up to this point, optionally interleaving | |
123 | # output if requested |
|
122 | # output if requested | |
124 |
|
123 | |||
125 | if timestamp: |
|
124 | if timestamp: | |
126 | # disable timestamping for the previous history, since we've |
|
125 | # disable timestamping for the previous history, since we've | |
127 | # lost those already (no time machine here). |
|
126 | # lost those already (no time machine here). | |
128 | logger.timestamp = False |
|
127 | logger.timestamp = False | |
129 |
|
128 | |||
130 | if log_raw_input: |
|
129 | if log_raw_input: | |
131 | input_hist = self.shell.history_manager.input_hist_raw |
|
130 | input_hist = self.shell.history_manager.input_hist_raw | |
132 | else: |
|
131 | else: | |
133 | input_hist = self.shell.history_manager.input_hist_parsed |
|
132 | input_hist = self.shell.history_manager.input_hist_parsed | |
134 |
|
133 | |||
135 | if log_output: |
|
134 | if log_output: | |
136 | log_write = logger.log_write |
|
135 | log_write = logger.log_write | |
137 | output_hist = self.shell.history_manager.output_hist |
|
136 | output_hist = self.shell.history_manager.output_hist | |
138 | for n in range(1,len(input_hist)-1): |
|
137 | for n in range(1,len(input_hist)-1): | |
139 | log_write(input_hist[n].rstrip() + u'\n') |
|
138 | log_write(input_hist[n].rstrip() + u'\n') | |
140 | if n in output_hist: |
|
139 | if n in output_hist: | |
141 |
log_write(str |
|
140 | log_write(str(repr(output_hist[n])),'output') | |
142 | else: |
|
141 | else: | |
143 | logger.log_write(u'\n'.join(input_hist[1:])) |
|
142 | logger.log_write(u'\n'.join(input_hist[1:])) | |
144 | logger.log_write(u'\n') |
|
143 | logger.log_write(u'\n') | |
145 | if timestamp: |
|
144 | if timestamp: | |
146 | # re-enable timestamping |
|
145 | # re-enable timestamping | |
147 | logger.timestamp = True |
|
146 | logger.timestamp = True | |
148 |
|
147 | |||
149 | print ('Activating auto-logging. ' |
|
148 | print ('Activating auto-logging. ' | |
150 | 'Current session state plus future input saved.') |
|
149 | 'Current session state plus future input saved.') | |
151 | logger.logstate() |
|
150 | logger.logstate() | |
152 |
|
151 | |||
153 | @line_magic |
|
152 | @line_magic | |
154 | def logstop(self, parameter_s=''): |
|
153 | def logstop(self, parameter_s=''): | |
155 | """Fully stop logging and close log file. |
|
154 | """Fully stop logging and close log file. | |
156 |
|
155 | |||
157 | In order to start logging again, a new %logstart call needs to be made, |
|
156 | In order to start logging again, a new %logstart call needs to be made, | |
158 | possibly (though not necessarily) with a new filename, mode and other |
|
157 | possibly (though not necessarily) with a new filename, mode and other | |
159 | options.""" |
|
158 | options.""" | |
160 | self.shell.logger.logstop() |
|
159 | self.shell.logger.logstop() | |
161 |
|
160 | |||
162 | @line_magic |
|
161 | @line_magic | |
163 | def logoff(self, parameter_s=''): |
|
162 | def logoff(self, parameter_s=''): | |
164 | """Temporarily stop logging. |
|
163 | """Temporarily stop logging. | |
165 |
|
164 | |||
166 | You must have previously started logging.""" |
|
165 | You must have previously started logging.""" | |
167 | self.shell.logger.switch_log(0) |
|
166 | self.shell.logger.switch_log(0) | |
168 |
|
167 | |||
169 | @line_magic |
|
168 | @line_magic | |
170 | def logon(self, parameter_s=''): |
|
169 | def logon(self, parameter_s=''): | |
171 | """Restart logging. |
|
170 | """Restart logging. | |
172 |
|
171 | |||
173 | This function is for restarting logging which you've temporarily |
|
172 | This function is for restarting logging which you've temporarily | |
174 | stopped with %logoff. For starting logging for the first time, you |
|
173 | stopped with %logoff. For starting logging for the first time, you | |
175 | must use the %logstart function, which allows you to specify an |
|
174 | must use the %logstart function, which allows you to specify an | |
176 | optional log filename.""" |
|
175 | optional log filename.""" | |
177 |
|
176 | |||
178 | self.shell.logger.switch_log(1) |
|
177 | self.shell.logger.switch_log(1) | |
179 |
|
178 | |||
180 | @line_magic |
|
179 | @line_magic | |
181 | def logstate(self, parameter_s=''): |
|
180 | def logstate(self, parameter_s=''): | |
182 | """Print the status of the logging system.""" |
|
181 | """Print the status of the logging system.""" | |
183 |
|
182 | |||
184 | self.shell.logger.logstate() |
|
183 | self.shell.logger.logstate() |
@@ -1,164 +1,164 | |||||
1 | # coding: utf-8 |
|
1 | # coding: utf-8 | |
2 | """Tests for profile-related functions. |
|
2 | """Tests for profile-related functions. | |
3 |
|
3 | |||
4 | Currently only the startup-dir functionality is tested, but more tests should |
|
4 | Currently only the startup-dir functionality is tested, but more tests should | |
5 | be added for: |
|
5 | be added for: | |
6 |
|
6 | |||
7 | * ipython profile create |
|
7 | * ipython profile create | |
8 | * ipython profile list |
|
8 | * ipython profile list | |
9 | * ipython profile create --parallel |
|
9 | * ipython profile create --parallel | |
10 | * security dir permissions |
|
10 | * security dir permissions | |
11 |
|
11 | |||
12 | Authors |
|
12 | Authors | |
13 | ------- |
|
13 | ------- | |
14 |
|
14 | |||
15 | * MinRK |
|
15 | * MinRK | |
16 |
|
16 | |||
17 | """ |
|
17 | """ | |
18 |
|
18 | |||
19 | #----------------------------------------------------------------------------- |
|
19 | #----------------------------------------------------------------------------- | |
20 | # Imports |
|
20 | # Imports | |
21 | #----------------------------------------------------------------------------- |
|
21 | #----------------------------------------------------------------------------- | |
22 |
|
22 | |||
23 | import os |
|
23 | import os | |
24 | import shutil |
|
24 | import shutil | |
25 | import sys |
|
25 | import sys | |
26 | import tempfile |
|
26 | import tempfile | |
27 |
|
27 | |||
28 | from unittest import TestCase |
|
28 | from unittest import TestCase | |
29 |
|
29 | |||
30 | import nose.tools as nt |
|
30 | import nose.tools as nt | |
31 |
|
31 | |||
32 | from IPython.core.profileapp import list_profiles_in, list_bundled_profiles |
|
32 | from IPython.core.profileapp import list_profiles_in, list_bundled_profiles | |
33 | from IPython.core.profiledir import ProfileDir |
|
33 | from IPython.core.profiledir import ProfileDir | |
34 |
|
34 | |||
35 | from IPython.testing import decorators as dec |
|
35 | from IPython.testing import decorators as dec | |
36 | from IPython.testing import tools as tt |
|
36 | from IPython.testing import tools as tt | |
37 | from IPython.utils import py3compat |
|
37 | from IPython.utils import py3compat | |
38 | from IPython.utils.process import getoutput |
|
38 | from IPython.utils.process import getoutput | |
39 | from IPython.utils.tempdir import TemporaryDirectory |
|
39 | from IPython.utils.tempdir import TemporaryDirectory | |
40 |
|
40 | |||
41 | #----------------------------------------------------------------------------- |
|
41 | #----------------------------------------------------------------------------- | |
42 | # Globals |
|
42 | # Globals | |
43 | #----------------------------------------------------------------------------- |
|
43 | #----------------------------------------------------------------------------- | |
44 | TMP_TEST_DIR = tempfile.mkdtemp() |
|
44 | TMP_TEST_DIR = tempfile.mkdtemp() | |
45 | HOME_TEST_DIR = os.path.join(TMP_TEST_DIR, "home_test_dir") |
|
45 | HOME_TEST_DIR = os.path.join(TMP_TEST_DIR, "home_test_dir") | |
46 | IP_TEST_DIR = os.path.join(HOME_TEST_DIR,'.ipython') |
|
46 | IP_TEST_DIR = os.path.join(HOME_TEST_DIR,'.ipython') | |
47 |
|
47 | |||
48 | # |
|
48 | # | |
49 | # Setup/teardown functions/decorators |
|
49 | # Setup/teardown functions/decorators | |
50 | # |
|
50 | # | |
51 |
|
51 | |||
52 | def setup(): |
|
52 | def setup(): | |
53 | """Setup test environment for the module: |
|
53 | """Setup test environment for the module: | |
54 |
|
54 | |||
55 | - Adds dummy home dir tree |
|
55 | - Adds dummy home dir tree | |
56 | """ |
|
56 | """ | |
57 | # Do not mask exceptions here. In particular, catching WindowsError is a |
|
57 | # Do not mask exceptions here. In particular, catching WindowsError is a | |
58 | # problem because that exception is only defined on Windows... |
|
58 | # problem because that exception is only defined on Windows... | |
59 | os.makedirs(IP_TEST_DIR) |
|
59 | os.makedirs(IP_TEST_DIR) | |
60 |
|
60 | |||
61 |
|
61 | |||
62 | def teardown(): |
|
62 | def teardown(): | |
63 | """Teardown test environment for the module: |
|
63 | """Teardown test environment for the module: | |
64 |
|
64 | |||
65 | - Remove dummy home dir tree |
|
65 | - Remove dummy home dir tree | |
66 | """ |
|
66 | """ | |
67 | # Note: we remove the parent test dir, which is the root of all test |
|
67 | # Note: we remove the parent test dir, which is the root of all test | |
68 | # subdirs we may have created. Use shutil instead of os.removedirs, so |
|
68 | # subdirs we may have created. Use shutil instead of os.removedirs, so | |
69 | # that non-empty directories are all recursively removed. |
|
69 | # that non-empty directories are all recursively removed. | |
70 | shutil.rmtree(TMP_TEST_DIR) |
|
70 | shutil.rmtree(TMP_TEST_DIR) | |
71 |
|
71 | |||
72 |
|
72 | |||
73 | #----------------------------------------------------------------------------- |
|
73 | #----------------------------------------------------------------------------- | |
74 | # Test functions |
|
74 | # Test functions | |
75 | #----------------------------------------------------------------------------- |
|
75 | #----------------------------------------------------------------------------- | |
76 | def win32_without_pywin32(): |
|
76 | def win32_without_pywin32(): | |
77 | if sys.platform == 'win32': |
|
77 | if sys.platform == 'win32': | |
78 | try: |
|
78 | try: | |
79 | import pywin32 |
|
79 | import pywin32 | |
80 | except ImportError: |
|
80 | except ImportError: | |
81 | return True |
|
81 | return True | |
82 | return False |
|
82 | return False | |
83 |
|
83 | |||
84 |
|
84 | |||
85 | class ProfileStartupTest(TestCase): |
|
85 | class ProfileStartupTest(TestCase): | |
86 | def setUp(self): |
|
86 | def setUp(self): | |
87 | # create profile dir |
|
87 | # create profile dir | |
88 | self.pd = ProfileDir.create_profile_dir_by_name(IP_TEST_DIR, 'test') |
|
88 | self.pd = ProfileDir.create_profile_dir_by_name(IP_TEST_DIR, 'test') | |
89 | self.options = ['--ipython-dir', IP_TEST_DIR, '--profile', 'test'] |
|
89 | self.options = ['--ipython-dir', IP_TEST_DIR, '--profile', 'test'] | |
90 | self.fname = os.path.join(TMP_TEST_DIR, 'test.py') |
|
90 | self.fname = os.path.join(TMP_TEST_DIR, 'test.py') | |
91 |
|
91 | |||
92 | def tearDown(self): |
|
92 | def tearDown(self): | |
93 | # We must remove this profile right away so its presence doesn't |
|
93 | # We must remove this profile right away so its presence doesn't | |
94 | # confuse other tests. |
|
94 | # confuse other tests. | |
95 | shutil.rmtree(self.pd.location) |
|
95 | shutil.rmtree(self.pd.location) | |
96 |
|
96 | |||
97 | def init(self, startup_file, startup, test): |
|
97 | def init(self, startup_file, startup, test): | |
98 | # write startup python file |
|
98 | # write startup python file | |
99 | with open(os.path.join(self.pd.startup_dir, startup_file), 'w') as f: |
|
99 | with open(os.path.join(self.pd.startup_dir, startup_file), 'w') as f: | |
100 | f.write(startup) |
|
100 | f.write(startup) | |
101 | # write simple test file, to check that the startup file was run |
|
101 | # write simple test file, to check that the startup file was run | |
102 | with open(self.fname, 'w') as f: |
|
102 | with open(self.fname, 'w') as f: | |
103 | f.write(py3compat.doctest_refactor_print(test)) |
|
103 | f.write(py3compat.doctest_refactor_print(test)) | |
104 |
|
104 | |||
105 | def validate(self, output): |
|
105 | def validate(self, output): | |
106 | tt.ipexec_validate(self.fname, output, '', options=self.options) |
|
106 | tt.ipexec_validate(self.fname, output, '', options=self.options) | |
107 |
|
107 | |||
108 | @dec.skipif(win32_without_pywin32(), "Test requires pywin32 on Windows") |
|
108 | @dec.skipif(win32_without_pywin32(), "Test requires pywin32 on Windows") | |
109 | def test_startup_py(self): |
|
109 | def test_startup_py(self): | |
110 | self.init('00-start.py', 'zzz=123\n', |
|
110 | self.init('00-start.py', 'zzz=123\n', | |
111 | py3compat.doctest_refactor_print('print zzz\n')) |
|
111 | py3compat.doctest_refactor_print('print zzz\n')) | |
112 | self.validate('123') |
|
112 | self.validate('123') | |
113 |
|
113 | |||
114 | @dec.skipif(win32_without_pywin32(), "Test requires pywin32 on Windows") |
|
114 | @dec.skipif(win32_without_pywin32(), "Test requires pywin32 on Windows") | |
115 | def test_startup_ipy(self): |
|
115 | def test_startup_ipy(self): | |
116 | self.init('00-start.ipy', '%xmode plain\n', '') |
|
116 | self.init('00-start.ipy', '%xmode plain\n', '') | |
117 | self.validate('Exception reporting mode: Plain') |
|
117 | self.validate('Exception reporting mode: Plain') | |
118 |
|
118 | |||
119 |
|
119 | |||
120 | def test_list_profiles_in(): |
|
120 | def test_list_profiles_in(): | |
121 | # No need to remove these directories and files, as they will get nuked in |
|
121 | # No need to remove these directories and files, as they will get nuked in | |
122 | # the module-level teardown. |
|
122 | # the module-level teardown. | |
123 | td = tempfile.mkdtemp(dir=TMP_TEST_DIR) |
|
123 | td = tempfile.mkdtemp(dir=TMP_TEST_DIR) | |
124 |
td = |
|
124 | td = str(td) | |
125 | for name in ('profile_foo', 'profile_hello', 'not_a_profile'): |
|
125 | for name in ('profile_foo', 'profile_hello', 'not_a_profile'): | |
126 | os.mkdir(os.path.join(td, name)) |
|
126 | os.mkdir(os.path.join(td, name)) | |
127 | if dec.unicode_paths: |
|
127 | if dec.unicode_paths: | |
128 | os.mkdir(os.path.join(td, u'profile_ΓΌnicode')) |
|
128 | os.mkdir(os.path.join(td, u'profile_ΓΌnicode')) | |
129 |
|
129 | |||
130 | with open(os.path.join(td, 'profile_file'), 'w') as f: |
|
130 | with open(os.path.join(td, 'profile_file'), 'w') as f: | |
131 | f.write("I am not a profile directory") |
|
131 | f.write("I am not a profile directory") | |
132 | profiles = list_profiles_in(td) |
|
132 | profiles = list_profiles_in(td) | |
133 |
|
133 | |||
134 | # unicode normalization can turn u'ΓΌnicode' into u'u\0308nicode', |
|
134 | # unicode normalization can turn u'ΓΌnicode' into u'u\0308nicode', | |
135 | # so only check for *nicode, and that creating a ProfileDir from the |
|
135 | # so only check for *nicode, and that creating a ProfileDir from the | |
136 | # name remains valid |
|
136 | # name remains valid | |
137 | found_unicode = False |
|
137 | found_unicode = False | |
138 | for p in list(profiles): |
|
138 | for p in list(profiles): | |
139 | if p.endswith('nicode'): |
|
139 | if p.endswith('nicode'): | |
140 | pd = ProfileDir.find_profile_dir_by_name(td, p) |
|
140 | pd = ProfileDir.find_profile_dir_by_name(td, p) | |
141 | profiles.remove(p) |
|
141 | profiles.remove(p) | |
142 | found_unicode = True |
|
142 | found_unicode = True | |
143 | break |
|
143 | break | |
144 | if dec.unicode_paths: |
|
144 | if dec.unicode_paths: | |
145 | nt.assert_true(found_unicode) |
|
145 | nt.assert_true(found_unicode) | |
146 | nt.assert_equal(set(profiles), {'foo', 'hello'}) |
|
146 | nt.assert_equal(set(profiles), {'foo', 'hello'}) | |
147 |
|
147 | |||
148 |
|
148 | |||
149 | def test_list_bundled_profiles(): |
|
149 | def test_list_bundled_profiles(): | |
150 | # This variable will need to be updated when a new profile gets bundled |
|
150 | # This variable will need to be updated when a new profile gets bundled | |
151 | bundled = sorted(list_bundled_profiles()) |
|
151 | bundled = sorted(list_bundled_profiles()) | |
152 | nt.assert_equal(bundled, []) |
|
152 | nt.assert_equal(bundled, []) | |
153 |
|
153 | |||
154 |
|
154 | |||
155 | def test_profile_create_ipython_dir(): |
|
155 | def test_profile_create_ipython_dir(): | |
156 | """ipython profile create respects --ipython-dir""" |
|
156 | """ipython profile create respects --ipython-dir""" | |
157 | with TemporaryDirectory() as td: |
|
157 | with TemporaryDirectory() as td: | |
158 | getoutput([sys.executable, '-m', 'IPython', 'profile', 'create', |
|
158 | getoutput([sys.executable, '-m', 'IPython', 'profile', 'create', | |
159 | 'foo', '--ipython-dir=%s' % td]) |
|
159 | 'foo', '--ipython-dir=%s' % td]) | |
160 | profile_dir = os.path.join(td, 'profile_foo') |
|
160 | profile_dir = os.path.join(td, 'profile_foo') | |
161 | assert os.path.exists(profile_dir) |
|
161 | assert os.path.exists(profile_dir) | |
162 | ipython_config = os.path.join(profile_dir, 'ipython_config.py') |
|
162 | ipython_config = os.path.join(profile_dir, 'ipython_config.py') | |
163 | assert os.path.exists(ipython_config) |
|
163 | assert os.path.exists(ipython_config) | |
164 |
|
164 |
@@ -1,206 +1,206 | |||||
1 | """Extra magics for terminal use.""" |
|
1 | """Extra magics for terminal use.""" | |
2 |
|
2 | |||
3 | # Copyright (c) IPython Development Team. |
|
3 | # Copyright (c) IPython Development Team. | |
4 | # Distributed under the terms of the Modified BSD License. |
|
4 | # Distributed under the terms of the Modified BSD License. | |
5 |
|
5 | |||
6 |
|
6 | |||
7 | from logging import error |
|
7 | from logging import error | |
8 | import os |
|
8 | import os | |
9 | import sys |
|
9 | import sys | |
10 |
|
10 | |||
11 | from IPython.core.error import TryNext, UsageError |
|
11 | from IPython.core.error import TryNext, UsageError | |
12 | from IPython.core.inputsplitter import IPythonInputSplitter |
|
12 | from IPython.core.inputsplitter import IPythonInputSplitter | |
13 | from IPython.core.magic import Magics, magics_class, line_magic |
|
13 | from IPython.core.magic import Magics, magics_class, line_magic | |
14 | from IPython.lib.clipboard import ClipboardEmpty |
|
14 | from IPython.lib.clipboard import ClipboardEmpty | |
15 | from IPython.utils.text import SList, strip_email_quotes |
|
15 | from IPython.utils.text import SList, strip_email_quotes | |
16 | from IPython.utils import py3compat |
|
16 | from IPython.utils import py3compat | |
17 |
|
17 | |||
18 | def get_pasted_lines(sentinel, l_input=py3compat.input, quiet=False): |
|
18 | def get_pasted_lines(sentinel, l_input=py3compat.input, quiet=False): | |
19 | """ Yield pasted lines until the user enters the given sentinel value. |
|
19 | """ Yield pasted lines until the user enters the given sentinel value. | |
20 | """ |
|
20 | """ | |
21 | if not quiet: |
|
21 | if not quiet: | |
22 | print("Pasting code; enter '%s' alone on the line to stop or use Ctrl-D." \ |
|
22 | print("Pasting code; enter '%s' alone on the line to stop or use Ctrl-D." \ | |
23 | % sentinel) |
|
23 | % sentinel) | |
24 | prompt = ":" |
|
24 | prompt = ":" | |
25 | else: |
|
25 | else: | |
26 | prompt = "" |
|
26 | prompt = "" | |
27 | while True: |
|
27 | while True: | |
28 | try: |
|
28 | try: | |
29 |
l = |
|
29 | l = str(l_input(prompt)) | |
30 | if l == sentinel: |
|
30 | if l == sentinel: | |
31 | return |
|
31 | return | |
32 | else: |
|
32 | else: | |
33 | yield l |
|
33 | yield l | |
34 | except EOFError: |
|
34 | except EOFError: | |
35 | print('<EOF>') |
|
35 | print('<EOF>') | |
36 | return |
|
36 | return | |
37 |
|
37 | |||
38 |
|
38 | |||
39 | @magics_class |
|
39 | @magics_class | |
40 | class TerminalMagics(Magics): |
|
40 | class TerminalMagics(Magics): | |
41 | def __init__(self, shell): |
|
41 | def __init__(self, shell): | |
42 | super(TerminalMagics, self).__init__(shell) |
|
42 | super(TerminalMagics, self).__init__(shell) | |
43 | self.input_splitter = IPythonInputSplitter() |
|
43 | self.input_splitter = IPythonInputSplitter() | |
44 |
|
44 | |||
45 | def store_or_execute(self, block, name): |
|
45 | def store_or_execute(self, block, name): | |
46 | """ Execute a block, or store it in a variable, per the user's request. |
|
46 | """ Execute a block, or store it in a variable, per the user's request. | |
47 | """ |
|
47 | """ | |
48 | if name: |
|
48 | if name: | |
49 | # If storing it for further editing |
|
49 | # If storing it for further editing | |
50 | self.shell.user_ns[name] = SList(block.splitlines()) |
|
50 | self.shell.user_ns[name] = SList(block.splitlines()) | |
51 | print("Block assigned to '%s'" % name) |
|
51 | print("Block assigned to '%s'" % name) | |
52 | else: |
|
52 | else: | |
53 | b = self.preclean_input(block) |
|
53 | b = self.preclean_input(block) | |
54 | self.shell.user_ns['pasted_block'] = b |
|
54 | self.shell.user_ns['pasted_block'] = b | |
55 | self.shell.using_paste_magics = True |
|
55 | self.shell.using_paste_magics = True | |
56 | try: |
|
56 | try: | |
57 | self.shell.run_cell(b) |
|
57 | self.shell.run_cell(b) | |
58 | finally: |
|
58 | finally: | |
59 | self.shell.using_paste_magics = False |
|
59 | self.shell.using_paste_magics = False | |
60 |
|
60 | |||
61 | def preclean_input(self, block): |
|
61 | def preclean_input(self, block): | |
62 | lines = block.splitlines() |
|
62 | lines = block.splitlines() | |
63 | while lines and not lines[0].strip(): |
|
63 | while lines and not lines[0].strip(): | |
64 | lines = lines[1:] |
|
64 | lines = lines[1:] | |
65 | return strip_email_quotes('\n'.join(lines)) |
|
65 | return strip_email_quotes('\n'.join(lines)) | |
66 |
|
66 | |||
67 | def rerun_pasted(self, name='pasted_block'): |
|
67 | def rerun_pasted(self, name='pasted_block'): | |
68 | """ Rerun a previously pasted command. |
|
68 | """ Rerun a previously pasted command. | |
69 | """ |
|
69 | """ | |
70 | b = self.shell.user_ns.get(name) |
|
70 | b = self.shell.user_ns.get(name) | |
71 |
|
71 | |||
72 | # Sanity checks |
|
72 | # Sanity checks | |
73 | if b is None: |
|
73 | if b is None: | |
74 | raise UsageError('No previous pasted block available') |
|
74 | raise UsageError('No previous pasted block available') | |
75 | if not isinstance(b, str): |
|
75 | if not isinstance(b, str): | |
76 | raise UsageError( |
|
76 | raise UsageError( | |
77 | "Variable 'pasted_block' is not a string, can't execute") |
|
77 | "Variable 'pasted_block' is not a string, can't execute") | |
78 |
|
78 | |||
79 | print("Re-executing '%s...' (%d chars)"% (b.split('\n',1)[0], len(b))) |
|
79 | print("Re-executing '%s...' (%d chars)"% (b.split('\n',1)[0], len(b))) | |
80 | self.shell.run_cell(b) |
|
80 | self.shell.run_cell(b) | |
81 |
|
81 | |||
82 | @line_magic |
|
82 | @line_magic | |
83 | def autoindent(self, parameter_s = ''): |
|
83 | def autoindent(self, parameter_s = ''): | |
84 | """Toggle autoindent on/off (if available).""" |
|
84 | """Toggle autoindent on/off (if available).""" | |
85 |
|
85 | |||
86 | self.shell.set_autoindent() |
|
86 | self.shell.set_autoindent() | |
87 | print("Automatic indentation is:",['OFF','ON'][self.shell.autoindent]) |
|
87 | print("Automatic indentation is:",['OFF','ON'][self.shell.autoindent]) | |
88 |
|
88 | |||
89 | @line_magic |
|
89 | @line_magic | |
90 | def cpaste(self, parameter_s=''): |
|
90 | def cpaste(self, parameter_s=''): | |
91 | """Paste & execute a pre-formatted code block from clipboard. |
|
91 | """Paste & execute a pre-formatted code block from clipboard. | |
92 |
|
92 | |||
93 | You must terminate the block with '--' (two minus-signs) or Ctrl-D |
|
93 | You must terminate the block with '--' (two minus-signs) or Ctrl-D | |
94 | alone on the line. You can also provide your own sentinel with '%paste |
|
94 | alone on the line. You can also provide your own sentinel with '%paste | |
95 | -s %%' ('%%' is the new sentinel for this operation). |
|
95 | -s %%' ('%%' is the new sentinel for this operation). | |
96 |
|
96 | |||
97 | The block is dedented prior to execution to enable execution of method |
|
97 | The block is dedented prior to execution to enable execution of method | |
98 | definitions. '>' and '+' characters at the beginning of a line are |
|
98 | definitions. '>' and '+' characters at the beginning of a line are | |
99 | ignored, to allow pasting directly from e-mails, diff files and |
|
99 | ignored, to allow pasting directly from e-mails, diff files and | |
100 | doctests (the '...' continuation prompt is also stripped). The |
|
100 | doctests (the '...' continuation prompt is also stripped). The | |
101 | executed block is also assigned to variable named 'pasted_block' for |
|
101 | executed block is also assigned to variable named 'pasted_block' for | |
102 | later editing with '%edit pasted_block'. |
|
102 | later editing with '%edit pasted_block'. | |
103 |
|
103 | |||
104 | You can also pass a variable name as an argument, e.g. '%cpaste foo'. |
|
104 | You can also pass a variable name as an argument, e.g. '%cpaste foo'. | |
105 | This assigns the pasted block to variable 'foo' as string, without |
|
105 | This assigns the pasted block to variable 'foo' as string, without | |
106 | dedenting or executing it (preceding >>> and + is still stripped) |
|
106 | dedenting or executing it (preceding >>> and + is still stripped) | |
107 |
|
107 | |||
108 | '%cpaste -r' re-executes the block previously entered by cpaste. |
|
108 | '%cpaste -r' re-executes the block previously entered by cpaste. | |
109 | '%cpaste -q' suppresses any additional output messages. |
|
109 | '%cpaste -q' suppresses any additional output messages. | |
110 |
|
110 | |||
111 | Do not be alarmed by garbled output on Windows (it's a readline bug). |
|
111 | Do not be alarmed by garbled output on Windows (it's a readline bug). | |
112 | Just press enter and type -- (and press enter again) and the block |
|
112 | Just press enter and type -- (and press enter again) and the block | |
113 | will be what was just pasted. |
|
113 | will be what was just pasted. | |
114 |
|
114 | |||
115 | IPython statements (magics, shell escapes) are not supported (yet). |
|
115 | IPython statements (magics, shell escapes) are not supported (yet). | |
116 |
|
116 | |||
117 | See also |
|
117 | See also | |
118 | -------- |
|
118 | -------- | |
119 | paste: automatically pull code from clipboard. |
|
119 | paste: automatically pull code from clipboard. | |
120 |
|
120 | |||
121 | Examples |
|
121 | Examples | |
122 | -------- |
|
122 | -------- | |
123 | :: |
|
123 | :: | |
124 |
|
124 | |||
125 | In [8]: %cpaste |
|
125 | In [8]: %cpaste | |
126 | Pasting code; enter '--' alone on the line to stop. |
|
126 | Pasting code; enter '--' alone on the line to stop. | |
127 | :>>> a = ["world!", "Hello"] |
|
127 | :>>> a = ["world!", "Hello"] | |
128 | :>>> print " ".join(sorted(a)) |
|
128 | :>>> print " ".join(sorted(a)) | |
129 | :-- |
|
129 | :-- | |
130 | Hello world! |
|
130 | Hello world! | |
131 | """ |
|
131 | """ | |
132 | opts, name = self.parse_options(parameter_s, 'rqs:', mode='string') |
|
132 | opts, name = self.parse_options(parameter_s, 'rqs:', mode='string') | |
133 | if 'r' in opts: |
|
133 | if 'r' in opts: | |
134 | self.rerun_pasted() |
|
134 | self.rerun_pasted() | |
135 | return |
|
135 | return | |
136 |
|
136 | |||
137 | quiet = ('q' in opts) |
|
137 | quiet = ('q' in opts) | |
138 |
|
138 | |||
139 | sentinel = opts.get('s', u'--') |
|
139 | sentinel = opts.get('s', u'--') | |
140 | block = '\n'.join(get_pasted_lines(sentinel, quiet=quiet)) |
|
140 | block = '\n'.join(get_pasted_lines(sentinel, quiet=quiet)) | |
141 | self.store_or_execute(block, name) |
|
141 | self.store_or_execute(block, name) | |
142 |
|
142 | |||
143 | @line_magic |
|
143 | @line_magic | |
144 | def paste(self, parameter_s=''): |
|
144 | def paste(self, parameter_s=''): | |
145 | """Paste & execute a pre-formatted code block from clipboard. |
|
145 | """Paste & execute a pre-formatted code block from clipboard. | |
146 |
|
146 | |||
147 | The text is pulled directly from the clipboard without user |
|
147 | The text is pulled directly from the clipboard without user | |
148 | intervention and printed back on the screen before execution (unless |
|
148 | intervention and printed back on the screen before execution (unless | |
149 | the -q flag is given to force quiet mode). |
|
149 | the -q flag is given to force quiet mode). | |
150 |
|
150 | |||
151 | The block is dedented prior to execution to enable execution of method |
|
151 | The block is dedented prior to execution to enable execution of method | |
152 | definitions. '>' and '+' characters at the beginning of a line are |
|
152 | definitions. '>' and '+' characters at the beginning of a line are | |
153 | ignored, to allow pasting directly from e-mails, diff files and |
|
153 | ignored, to allow pasting directly from e-mails, diff files and | |
154 | doctests (the '...' continuation prompt is also stripped). The |
|
154 | doctests (the '...' continuation prompt is also stripped). The | |
155 | executed block is also assigned to variable named 'pasted_block' for |
|
155 | executed block is also assigned to variable named 'pasted_block' for | |
156 | later editing with '%edit pasted_block'. |
|
156 | later editing with '%edit pasted_block'. | |
157 |
|
157 | |||
158 | You can also pass a variable name as an argument, e.g. '%paste foo'. |
|
158 | You can also pass a variable name as an argument, e.g. '%paste foo'. | |
159 | This assigns the pasted block to variable 'foo' as string, without |
|
159 | This assigns the pasted block to variable 'foo' as string, without | |
160 | executing it (preceding >>> and + is still stripped). |
|
160 | executing it (preceding >>> and + is still stripped). | |
161 |
|
161 | |||
162 | Options: |
|
162 | Options: | |
163 |
|
163 | |||
164 | -r: re-executes the block previously entered by cpaste. |
|
164 | -r: re-executes the block previously entered by cpaste. | |
165 |
|
165 | |||
166 | -q: quiet mode: do not echo the pasted text back to the terminal. |
|
166 | -q: quiet mode: do not echo the pasted text back to the terminal. | |
167 |
|
167 | |||
168 | IPython statements (magics, shell escapes) are not supported (yet). |
|
168 | IPython statements (magics, shell escapes) are not supported (yet). | |
169 |
|
169 | |||
170 | See also |
|
170 | See also | |
171 | -------- |
|
171 | -------- | |
172 | cpaste: manually paste code into terminal until you mark its end. |
|
172 | cpaste: manually paste code into terminal until you mark its end. | |
173 | """ |
|
173 | """ | |
174 | opts, name = self.parse_options(parameter_s, 'rq', mode='string') |
|
174 | opts, name = self.parse_options(parameter_s, 'rq', mode='string') | |
175 | if 'r' in opts: |
|
175 | if 'r' in opts: | |
176 | self.rerun_pasted() |
|
176 | self.rerun_pasted() | |
177 | return |
|
177 | return | |
178 | try: |
|
178 | try: | |
179 | block = self.shell.hooks.clipboard_get() |
|
179 | block = self.shell.hooks.clipboard_get() | |
180 | except TryNext as clipboard_exc: |
|
180 | except TryNext as clipboard_exc: | |
181 | message = getattr(clipboard_exc, 'args') |
|
181 | message = getattr(clipboard_exc, 'args') | |
182 | if message: |
|
182 | if message: | |
183 | error(message[0]) |
|
183 | error(message[0]) | |
184 | else: |
|
184 | else: | |
185 | error('Could not get text from the clipboard.') |
|
185 | error('Could not get text from the clipboard.') | |
186 | return |
|
186 | return | |
187 | except ClipboardEmpty: |
|
187 | except ClipboardEmpty: | |
188 | raise UsageError("The clipboard appears to be empty") |
|
188 | raise UsageError("The clipboard appears to be empty") | |
189 |
|
189 | |||
190 | # By default, echo back to terminal unless quiet mode is requested |
|
190 | # By default, echo back to terminal unless quiet mode is requested | |
191 | if 'q' not in opts: |
|
191 | if 'q' not in opts: | |
192 | write = self.shell.write |
|
192 | write = self.shell.write | |
193 | write(self.shell.pycolorize(block)) |
|
193 | write(self.shell.pycolorize(block)) | |
194 | if not block.endswith('\n'): |
|
194 | if not block.endswith('\n'): | |
195 | write('\n') |
|
195 | write('\n') | |
196 | write("## -- End pasted text --\n") |
|
196 | write("## -- End pasted text --\n") | |
197 |
|
197 | |||
198 | self.store_or_execute(block, name) |
|
198 | self.store_or_execute(block, name) | |
199 |
|
199 | |||
200 | # Class-level: add a '%cls' magic only on Windows |
|
200 | # Class-level: add a '%cls' magic only on Windows | |
201 | if sys.platform == 'win32': |
|
201 | if sys.platform == 'win32': | |
202 | @line_magic |
|
202 | @line_magic | |
203 | def cls(self, s): |
|
203 | def cls(self, s): | |
204 | """Clear screen. |
|
204 | """Clear screen. | |
205 | """ |
|
205 | """ | |
206 | os.system("cls") |
|
206 | os.system("cls") |
General Comments 0
You need to be logged in to leave comments.
Login now