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