##// END OF EJS Templates
Convert str_to_unicode to str function call.
Srinivas Reddy Thatiparthy -
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_to_unicode(time.strftime('# %a, %d %b %Y %H:%M:%S\n',
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_to_unicode(repr(output_hist[n])),'output')
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 = py3compat.str_to_unicode(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 = py3compat.str_to_unicode(l_input(prompt))
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