##// END OF EJS Templates
This is a manual merge of certain things in the ipython1-dev branch, revision 46, into the main ...
Brian E Granger -
Show More
@@ -0,0 +1,14 b''
1 # encoding: utf-8
2
3 __docformat__ = "restructuredtext en"
4
5 #-------------------------------------------------------------------------------
6 # Copyright (C) 2008 The IPython Development Team
7 #
8 # Distributed under the terms of the BSD License. The full license is in
9 # the file COPYING, distributed as part of this software.
10 #-------------------------------------------------------------------------------
11
12 #-------------------------------------------------------------------------------
13 # Imports
14 #------------------------------------------------------------------------------- No newline at end of file
@@ -0,0 +1,99 b''
1 # encoding: utf-8
2
3 """This is the official entry point to IPython's configuration system. """
4
5 __docformat__ = "restructuredtext en"
6
7 #-------------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
13
14 #-------------------------------------------------------------------------------
15 # Imports
16 #-------------------------------------------------------------------------------
17
18 import os
19 from IPython.config.cutils import get_home_dir, get_ipython_dir
20 from IPython.external.configobj import ConfigObj
21
22 class ConfigObjManager(object):
23
24 def __init__(self, configObj, filename):
25 self.current = configObj
26 self.current.indent_type = ' '
27 self.filename = filename
28 # self.write_default_config_file()
29
30 def get_config_obj(self):
31 return self.current
32
33 def update_config_obj(self, newConfig):
34 self.current.merge(newConfig)
35
36 def update_config_obj_from_file(self, filename):
37 newConfig = ConfigObj(filename, file_error=False)
38 self.current.merge(newConfig)
39
40 def update_config_obj_from_default_file(self, ipythondir=None):
41 fname = self.resolve_file_path(self.filename, ipythondir)
42 self.update_config_obj_from_file(fname)
43
44 def write_config_obj_to_file(self, filename):
45 f = open(filename, 'w')
46 self.current.write(f)
47 f.close()
48
49 def write_default_config_file(self):
50 ipdir = get_ipython_dir()
51 fname = ipdir + '/' + self.filename
52 if not os.path.isfile(fname):
53 print "Writing the configuration file to: " + fname
54 self.write_config_obj_to_file(fname)
55
56 def _import(self, key):
57 package = '.'.join(key.split('.')[0:-1])
58 obj = key.split('.')[-1]
59 execString = 'from %s import %s' % (package, obj)
60 exec execString
61 exec 'temp = %s' % obj
62 return temp
63
64 def resolve_file_path(self, filename, ipythondir = None):
65 """Resolve filenames into absolute paths.
66
67 This function looks in the following directories in order:
68
69 1. In the current working directory or by absolute path with ~ expanded
70 2. In ipythondir if that is set
71 3. In the IPYTHONDIR environment variable if it exists
72 4. In the ~/.ipython directory
73
74 Note: The IPYTHONDIR is also used by the trunk version of IPython so
75 changing it will also affect it was well.
76 """
77
78 # In cwd or by absolute path with ~ expanded
79 trythis = os.path.expanduser(filename)
80 if os.path.isfile(trythis):
81 return trythis
82
83 # In ipythondir if it is set
84 if ipythondir is not None:
85 trythis = ipythondir + '/' + filename
86 if os.path.isfile(trythis):
87 return trythis
88
89 trythis = get_ipython_dir() + '/' + filename
90 if os.path.isfile(trythis):
91 return trythis
92
93 return None
94
95
96
97
98
99
@@ -0,0 +1,99 b''
1 # encoding: utf-8
2
3 """Configuration-related utilities for all IPython."""
4
5 __docformat__ = "restructuredtext en"
6
7 #-------------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
13
14 #-------------------------------------------------------------------------------
15 # Imports
16 #-------------------------------------------------------------------------------
17
18 import os
19 import sys
20
21 #---------------------------------------------------------------------------
22 # Normal code begins
23 #---------------------------------------------------------------------------
24
25 class HomeDirError(Exception):
26 pass
27
28 def get_home_dir():
29 """Return the closest possible equivalent to a 'home' directory.
30
31 We first try $HOME. Absent that, on NT it's $HOMEDRIVE\$HOMEPATH.
32
33 Currently only Posix and NT are implemented, a HomeDirError exception is
34 raised for all other OSes. """
35
36 isdir = os.path.isdir
37 env = os.environ
38 try:
39 homedir = env['HOME']
40 if not isdir(homedir):
41 # in case a user stuck some string which does NOT resolve to a
42 # valid path, it's as good as if we hadn't foud it
43 raise KeyError
44 return homedir
45 except KeyError:
46 if os.name == 'posix':
47 raise HomeDirError,'undefined $HOME, IPython can not proceed.'
48 elif os.name == 'nt':
49 # For some strange reason, win9x returns 'nt' for os.name.
50 try:
51 homedir = os.path.join(env['HOMEDRIVE'],env['HOMEPATH'])
52 if not isdir(homedir):
53 homedir = os.path.join(env['USERPROFILE'])
54 if not isdir(homedir):
55 raise HomeDirError
56 return homedir
57 except:
58 try:
59 # Use the registry to get the 'My Documents' folder.
60 import _winreg as wreg
61 key = wreg.OpenKey(wreg.HKEY_CURRENT_USER,
62 "Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders")
63 homedir = wreg.QueryValueEx(key,'Personal')[0]
64 key.Close()
65 if not isdir(homedir):
66 e = ('Invalid "Personal" folder registry key '
67 'typically "My Documents".\n'
68 'Value: %s\n'
69 'This is not a valid directory on your system.' %
70 homedir)
71 raise HomeDirError(e)
72 return homedir
73 except HomeDirError:
74 raise
75 except:
76 return 'C:\\'
77 elif os.name == 'dos':
78 # Desperate, may do absurd things in classic MacOS. May work under DOS.
79 return 'C:\\'
80 else:
81 raise HomeDirError,'support for your operating system not implemented.'
82
83 def get_ipython_dir():
84 ipdir_def = '.ipython'
85 home_dir = get_home_dir()
86 ipdir = os.path.abspath(os.environ.get('IPYTHONDIR',
87 os.path.join(home_dir,ipdir_def)))
88 return ipdir
89
90 def import_item(key):
91 """
92 Import and return bar given the string foo.bar.
93 """
94 package = '.'.join(key.split('.')[0:-1])
95 obj = key.split('.')[-1]
96 execString = 'from %s import %s' % (package, obj)
97 exec execString
98 exec 'temp = %s' % obj
99 return temp
This diff has been collapsed as it changes many lines, (622 lines changed) Show them Hide them
@@ -0,0 +1,622 b''
1 # encoding: utf-8
2
3 """Mix of ConfigObj and Struct-like access.
4
5 Provides:
6
7 - Coupling a Struct object to a ConfigObj one, so that changes to the Traited
8 instance propagate back into the ConfigObj.
9
10 - A declarative interface for describing configurations that automatically maps
11 to valid ConfigObj representations.
12
13 - From these descriptions, valid .conf files can be auto-generated, with class
14 docstrings and traits information used for initial auto-documentation.
15
16 - Hierarchical inclusion of files, so that a base config can be overridden only
17 in specific spots.
18
19
20 Notes:
21
22 The file creation policy is:
23
24 1. Creating a SConfigManager(FooConfig,'missingfile.conf') will work
25 fine, and 'missingfile.conf' will be created empty.
26
27 2. Creating SConfigManager(FooConfig,'OKfile.conf') where OKfile.conf has
28
29 include = 'missingfile.conf'
30
31 conks out with IOError.
32
33 My rationale is that creating top-level empty files is a common and
34 reasonable need, but that having invalid include statements should
35 raise an error right away, so people know immediately that their files
36 have gone stale.
37
38
39 TODO:
40
41 - Turn the currently interactive tests into proper doc/unit tests. Complete
42 docstrings.
43
44 - Write the real ipython1 config system using this. That one is more
45 complicated than either the MPL one or the fake 'ipythontest' that I wrote
46 here, and it requires solving the issue of declaring references to other
47 objects inside the config files.
48
49 - [Low priority] Write a custom TraitsUI view so that hierarchical
50 configurations provide nicer interactive editing. The automatic system is
51 remarkably good, but for very complex configurations having a nicely
52 organized view would be nice.
53 """
54
55 __docformat__ = "restructuredtext en"
56 __license__ = 'BSD'
57
58 #-------------------------------------------------------------------------------
59 # Copyright (C) 2008 The IPython Development Team
60 #
61 # Distributed under the terms of the BSD License. The full license is in
62 # the file COPYING, distributed as part of this software.
63 #-------------------------------------------------------------------------------
64
65 #-------------------------------------------------------------------------------
66 # Imports
67 #-------------------------------------------------------------------------------
68
69 ############################################################################
70 # Stdlib imports
71 ############################################################################
72 from cStringIO import StringIO
73 from inspect import isclass
74
75 import os
76 import textwrap
77
78 ############################################################################
79 # External imports
80 ############################################################################
81
82 from IPython.external import configobj
83
84 ############################################################################
85 # Utility functions
86 ############################################################################
87
88 def get_split_ind(seq, N):
89 """seq is a list of words. Return the index into seq such that
90 len(' '.join(seq[:ind])<=N
91 """
92
93 sLen = 0
94 # todo: use Alex's xrange pattern from the cbook for efficiency
95 for (word, ind) in zip(seq, range(len(seq))):
96 sLen += len(word) + 1 # +1 to account for the len(' ')
97 if sLen>=N: return ind
98 return len(seq)
99
100 def wrap(prefix, text, cols, max_lines=6):
101 """'wrap text with prefix at length cols"""
102 pad = ' '*len(prefix.expandtabs())
103 available = cols - len(pad)
104
105 seq = text.split(' ')
106 Nseq = len(seq)
107 ind = 0
108 lines = []
109 while ind<Nseq:
110 lastInd = ind
111 ind += get_split_ind(seq[ind:], available)
112 lines.append(seq[lastInd:ind])
113
114 num_lines = len(lines)
115 abbr_end = max_lines // 2
116 abbr_start = max_lines - abbr_end
117 lines_skipped = False
118 for i in range(num_lines):
119 if i == 0:
120 # add the prefix to the first line, pad with spaces otherwise
121 ret = prefix + ' '.join(lines[i]) + '\n'
122 elif i < abbr_start or i > num_lines-abbr_end-1:
123 ret += pad + ' '.join(lines[i]) + '\n'
124 else:
125 if not lines_skipped:
126 lines_skipped = True
127 ret += ' <...snipped %d lines...> \n' % (num_lines-max_lines)
128 # for line in lines[1:]:
129 # ret += pad + ' '.join(line) + '\n'
130 return ret[:-1]
131
132 def dedent(txt):
133 """A modified version of textwrap.dedent, specialized for docstrings.
134
135 This version doesn't get confused by the first line of text having
136 inconsistent indentation from the rest, which happens a lot in docstrings.
137
138 :Examples:
139
140 >>> s = '''
141 ... First line.
142 ... More...
143 ... End'''
144
145 >>> print dedent(s)
146 First line.
147 More...
148 End
149
150 >>> s = '''First line
151 ... More...
152 ... End'''
153
154 >>> print dedent(s)
155 First line
156 More...
157 End
158 """
159 out = [textwrap.dedent(t) for t in txt.split('\n',1)
160 if t and not t.isspace()]
161 return '\n'.join(out)
162
163
164 def comment(strng,indent=''):
165 """return an input string, commented out"""
166 template = indent + '# %s'
167 lines = [template % s for s in strng.splitlines(True)]
168 return ''.join(lines)
169
170
171 def configobj2str(cobj):
172 """Dump a Configobj instance to a string."""
173 outstr = StringIO()
174 cobj.write(outstr)
175 return outstr.getvalue()
176
177 def get_config_filename(conf):
178 """Find the filename attribute of a ConfigObj given a sub-section object.
179 """
180 depth = conf.depth
181 for d in range(depth):
182 conf = conf.parent
183 return conf.filename
184
185 def sconf2File(sconf,fname,force=False):
186 """Write a SConfig instance to a given filename.
187
188 :Keywords:
189
190 force : bool (False)
191 If true, force writing even if the file exists.
192 """
193
194 if os.path.isfile(fname) and not force:
195 raise IOError("File %s already exists, use force=True to overwrite" %
196 fname)
197
198 txt = repr(sconf)
199
200 fobj = open(fname,'w')
201 fobj.write(txt)
202 fobj.close()
203
204 def filter_scalars(sc):
205 """ input sc MUST be sorted!!!"""
206 scalars = []
207 maxi = len(sc)-1
208 i = 0
209 while i<len(sc):
210 t = sc[i]
211 if t.startswith('_sconf_'):
212 # Skip altogether private _sconf_ attributes, so we actually issue
213 # a 'continue' call to avoid the append(t) below
214 i += 1
215 continue
216 scalars.append(t)
217 i += 1
218
219 return scalars
220
221
222 def get_scalars(obj):
223 """Return scalars for a Sconf class object"""
224
225 skip = set(['trait_added','trait_modified'])
226 sc = [k for k in obj.__dict__ if not k.startswith('_')]
227 sc.sort()
228 return filter_scalars(sc)
229
230
231 def get_sections(obj,sectionClass):
232 """Return sections for a Sconf class object"""
233 return [(n,v) for (n,v) in obj.__dict__.iteritems()
234 if isclass(v) and issubclass(v,sectionClass)]
235
236
237 def get_instance_sections(inst):
238 """Return sections for a Sconf instance"""
239 sections = [(k,v) for k,v in inst.__dict__.iteritems()
240 if isinstance(v,SConfig) and not k=='_sconf_parent']
241 # Sort the sections by name
242 sections.sort(key=lambda x:x[0])
243 return sections
244
245
246 def partition_instance(obj):
247 """Return scalars,sections for a given Sconf instance.
248 """
249 scnames = []
250 sections = []
251 for k,v in obj.__dict__.iteritems():
252 if isinstance(v,SConfig):
253 if not k=='_sconf_parent':
254 sections.append((k,v))
255 else:
256 scnames.append(k)
257
258 # Sort the sections by name
259 sections.sort(key=lambda x:x[0])
260
261 # Sort the scalar names, filter them and then extract the actual objects
262 scnames.sort()
263 scnames = filter_scalars(scnames)
264 scalars = [(s,obj.__dict__[s]) for s in scnames]
265
266 return scalars, sections
267
268
269 def mk_ConfigObj(filename,mk_missing_file=True):
270 """Return a ConfigObj instance with our hardcoded conventions.
271
272 Use a simple factory that wraps our option choices for using ConfigObj.
273 I'm hard-wiring certain choices here, so we'll always use instances with
274 THESE choices.
275
276 :Parameters:
277
278 filename : string
279 File to read from.
280
281 :Keywords:
282 makeMissingFile : bool (True)
283 If true, the file named by `filename` may not yet exist and it will be
284 automatically created (empty). Else, if `filename` doesn't exist, an
285 IOError will be raised.
286 """
287
288 if mk_missing_file:
289 create_empty = True
290 file_error = False
291 else:
292 create_empty = False
293 file_error = True
294
295 return configobj.ConfigObj(filename,
296 create_empty=create_empty,
297 file_error=file_error,
298 indent_type=' ',
299 interpolation='Template',
300 unrepr=True)
301
302 nullConf = mk_ConfigObj(None)
303
304
305 class RecursiveConfigObj(object):
306 """Object-oriented interface for recursive ConfigObj constructions."""
307
308 def __init__(self,filename):
309 """Return a ConfigObj instance with our hardcoded conventions.
310
311 Use a simple factory that wraps our option choices for using ConfigObj.
312 I'm hard-wiring certain choices here, so we'll always use instances with
313 THESE choices.
314
315 :Parameters:
316
317 filename : string
318 File to read from.
319 """
320
321 self.comp = []
322 self.conf = self._load(filename)
323
324 def _load(self,filename,mk_missing_file=True):
325 conf = mk_ConfigObj(filename,mk_missing_file)
326
327 # Do recursive loading. We only allow (or at least honor) the include
328 # tag at the top-level. For now, we drop the inclusion information so
329 # that there are no restrictions on which levels of the SConfig
330 # hierarchy can use include statements. But this means that
331
332 # if bookkeeping of each separate component of the recursive
333 # construction was requested, make a separate object for storage
334 # there, since we don't want that to be modified by the inclusion
335 # process.
336 self.comp.append(mk_ConfigObj(filename,mk_missing_file))
337
338 incfname = conf.pop('include',None)
339 if incfname is not None:
340 # Do recursive load. We don't want user includes that point to
341 # missing files to fail silently, so in the recursion we disable
342 # auto-creation of missing files.
343 confinc = self._load(incfname,mk_missing_file=False)
344
345 # Update with self to get proper ordering (included files provide
346 # base data, current one overwrites)
347 confinc.update(conf)
348 # And do swap to return the updated structure
349 conf = confinc
350 # Set the filename to be the original file instead of the included
351 # one
352 conf.filename = filename
353 return conf
354
355 ############################################################################
356 # Main SConfig class and supporting exceptions
357 ############################################################################
358
359 class SConfigError(Exception): pass
360
361 class SConfigInvalidKeyError(SConfigError): pass
362
363 class SConfig(object):
364 """A class representing configuration objects.
365
366 Note: this class should NOT have any traits itself, since the actual traits
367 will be declared by subclasses. This class is meant to ONLY declare the
368 necessary initialization/validation methods. """
369
370 # Any traits declared here are prefixed with _sconf_ so that our special
371 # formatting/analysis utilities can distinguish them from user traits and
372 # can avoid them.
373
374 # Once created, the tree's hierarchy can NOT be modified
375 _sconf_parent = None
376
377 def __init__(self,config=None,parent=None,monitor=None):
378 """Makes an SConfig object out of a ConfigObj instance
379 """
380
381 if config is None:
382 config = mk_ConfigObj(None)
383
384 # Validate the set of scalars ...
385 my_scalars = set(get_scalars(self))
386 cf_scalars = set(config.scalars)
387 invalid_scalars = cf_scalars - my_scalars
388 if invalid_scalars:
389 config_fname = get_config_filename(config)
390 m=("In config defined in file: %r\n"
391 "Error processing section: %s\n"
392 "These keys are invalid : %s\n"
393 "Valid key names : %s\n"
394 % (config_fname,self.__class__.__name__,
395 list(invalid_scalars),list(my_scalars)))
396 raise SConfigInvalidKeyError(m)
397
398 # ... and sections
399 section_items = get_sections(self.__class__,SConfig)
400 my_sections = set([n for n,v in section_items])
401 cf_sections = set(config.sections)
402 invalid_sections = cf_sections - my_sections
403 if invalid_sections:
404 config_fname = get_config_filename(config)
405 m = ("In config defined in file: %r\n"
406 "Error processing section: %s\n"
407 "These subsections are invalid : %s\n"
408 "Valid subsection names : %s\n"
409 % (config_fname,self.__class__.__name__,
410 list(invalid_sections),list(my_sections)))
411 raise SConfigInvalidKeyError(m)
412
413 self._sconf_parent = parent
414
415 # Now set the traits based on the config
416 for k in my_scalars:
417 setattr(self,k,config[k])
418
419 # And build subsections
420 for s,v in section_items:
421 sec_config = config.setdefault(s,{})
422 section = v(sec_config,self,monitor=monitor)
423
424 # We must use add_trait instead of setattr because we inherit from
425 # HasStrictTraits, but we need to then do a 'dummy' getattr call on
426 # self so the class trait propagates to the instance.
427 self.add_trait(s,section)
428 getattr(self,s)
429
430 def __repr__(self,depth=0):
431 """Dump a section to a string."""
432
433 indent = ' '*(depth)
434
435 top_name = self.__class__.__name__
436
437 if depth == 0:
438 label = '# %s - plaintext (in .conf format)\n' % top_name
439 else:
440 # Section titles are indented one level less than their contents in
441 # the ConfigObj write methods.
442 sec_indent = ' '*(depth-1)
443 label = '\n'+sec_indent+('[' * depth) + top_name + (']'*depth)
444
445 out = [label]
446
447 doc = self.__class__.__doc__
448 if doc is not None:
449 out.append(comment(dedent(doc),indent))
450
451 scalars, sections = partition_instance(self)
452
453 for s,v in scalars:
454 try:
455 info = self.__base_traits__[s].handler.info()
456 # Get a short version of info with lines of max. 78 chars, so
457 # that after commenting them out (with '# ') they are at most
458 # 80-chars long.
459 out.append(comment(wrap('',info.replace('\n', ' '),78-len(indent)),indent))
460 except (KeyError,AttributeError):
461 pass
462 out.append(indent+('%s = %r' % (s,v)))
463
464 for sname,sec in sections:
465 out.append(sec.__repr__(depth+1))
466
467 return '\n'.join(out)
468
469 def __str__(self):
470 return self.__class__.__name__
471
472
473 ##############################################################################
474 # High-level class(es) and utilities for handling a coupled pair of SConfig and
475 # ConfigObj instances.
476 ##############################################################################
477
478 def path_to_root(obj):
479 """Find the path to the root of a nested SConfig instance."""
480 ob = obj
481 path = []
482 while ob._sconf_parent is not None:
483 path.append(ob.__class__.__name__)
484 ob = ob._sconf_parent
485 path.reverse()
486 return path
487
488
489 def set_value(fconf,path,key,value):
490 """Set a value on a ConfigObj instance, arbitrarily deep."""
491 section = fconf
492 for sname in path:
493 section = section.setdefault(sname,{})
494 section[key] = value
495
496
497 def fmonitor(fconf):
498 """Make a monitor for coupling SConfig instances to ConfigObj ones.
499
500 We must use a closure because Traits makes assumptions about the functions
501 used with on_trait_change() that prevent the use of a callable instance.
502 """
503
504 def mon(obj,name,new):
505 #print 'OBJ:',obj # dbg
506 #print 'NAM:',name # dbg
507 #print 'NEW:',new # dbg
508 set_value(fconf,path_to_root(obj),name,new)
509
510 return mon
511
512
513 class SConfigManager(object):
514 """A simple object to manage and sync a SConfig and a ConfigObj pair.
515 """
516
517 def __init__(self,configClass,configFilename,filePriority=True):
518 """Make a new SConfigManager.
519
520 :Parameters:
521
522 configClass : class
523
524 configFilename : string
525 If the filename points to a non-existent file, it will be created
526 empty. This is useful when creating a file form from an existing
527 configClass with the class defaults.
528
529
530 :Keywords:
531
532 filePriority : bool (True)
533
534 If true, at construction time the file object takes priority and
535 overwrites the contents of the config object. Else, the data flow
536 is reversed and the file object will be overwritten with the
537 configClass defaults at write() time.
538 """
539
540 rconf = RecursiveConfigObj(configFilename)
541 # In a hierarchical object, the two following fconfs are *very*
542 # different. In self.fconf, we'll keep the outer-most fconf associated
543 # directly to the original filename. self.fconf_combined, instead,
544 # contains an object which has the combined effect of having merged all
545 # the called files in the recursive chain.
546 self.fconf = rconf.comp[0]
547 self.fconf_combined = rconf.conf
548
549 # Create a monitor to track and apply trait changes to the sconf
550 # instance over into the fconf one
551 monitor = fmonitor(self.fconf)
552
553 if filePriority:
554 self.sconf = configClass(self.fconf_combined,monitor=monitor)
555 else:
556 # Push defaults onto file object
557 self.sconf = configClass(mk_ConfigObj(None),monitor=monitor)
558 self.fconfUpdate(self.fconf,self.sconf)
559
560 def fconfUpdate(self,fconf,sconf):
561 """Update the fconf object with the data from sconf"""
562
563 scalars, sections = partition_instance(sconf)
564
565 for s,v in scalars:
566 fconf[s] = v
567
568 for secname,sec in sections:
569 self.fconfUpdate(fconf.setdefault(secname,{}),sec)
570
571 def write(self,filename=None):
572 """Write out to disk.
573
574 This method writes out only to the top file in a hierarchical
575 configuration, which means that the class defaults and other values not
576 explicitly set in the top level file are NOT written out.
577
578 :Keywords:
579
580 filename : string (None)
581 If given, the output is written to this file, otherwise the
582 .filename attribute of the top-level configuration object is used.
583 """
584 if filename is not None:
585 file_obj = open(filename,'w')
586 out = self.fconf.write(file_obj)
587 file_obj.close()
588 return out
589 else:
590 return self.fconf.write()
591
592 def writeAll(self,filename=None):
593 """Write out the entire configuration to disk.
594
595 This method, in contrast with write(), updates the .fconf_combined
596 object with the *entire* .sconf instance, and then writes it out to
597 disk. This method is thus useful for generating files that have a
598 self-contained, non-hierarchical file.
599
600 :Keywords:
601
602 filename : string (None)
603 If given, the output is written to this file, otherwise the
604 .filename attribute of the top-level configuration object is used.
605 """
606 if filename is not None:
607 file_obj = open(filename,'w')
608 self.fconfUpdate(self.fconf_combined,self.sconf)
609 out = self.fconf_combined.write(file_obj)
610 file_obj.close()
611 return out
612 else:
613 self.fconfUpdate(self.fconf_combined,self.sconf)
614 return self.fconf_combined.write()
615
616 def sconf_str(self):
617 return str(self.sconf)
618
619 def fconf_str(self):
620 return configobj2str(self.fconf)
621
622 __repr__ = __str__ = fconf_str
@@ -0,0 +1,37 b''
1 """Little utilities for testing tconfig.
2
3 This module is meant to be used via
4
5 import sctst; reload(sctst)
6 from sctst import *
7
8 at the top of the actual test scripts, so that they all get the entire set of
9 common test tools with minimal fuss.
10 """
11
12 # Standard library imports
13 import os
14 import sys
15 from pprint import pprint
16
17 # Our own imports.
18
19 from IPython.config import sconfig
20 reload(sconfig)
21
22 from sconfig import mkConfigObj, RecursiveConfigObj, SConfigManager, \
23 sconf2file
24
25 # Simple utilities/classes for testing
26
27 def cat(fname):
28 print '### FILENAME:',fname
29 print open(fname).read()
30
31
32 class App(object):
33 """A trivial 'application' class to be initialized.
34 """
35 def __init__(self,config_class,config_filename):
36 self.rcman = SConfigManager(config_class,config_filename)
37 self.rc = self.rcman.sconf
@@ -0,0 +1,14 b''
1 # Toy example of a TConfig-based configuration description
2
3 # This is the class declaration for the configuration:
4
5 # SimpleConfig
6 # Configuration for my application
7
8 solver = "Iterative2"
9
10 [Protocol]
11 # Specify the Protocol
12
13 ptype = "http2"
14 max_users = 4
@@ -0,0 +1,14 b''
1 # Toy example of a TConfig-based configuration description
2
3 # This is the class declaration for the configuration:
4
5 # SimpleConfig
6 # Configuration for my application
7
8 datafile = string(default='data.txt')
9 solver = option('Direct','Iterative')
10
11 [Protocol]
12 # Specify the Protocol
13 ptype = option('http','ftp','ssh')
14 max_users = integer(1,10)
@@ -0,0 +1,59 b''
1 """Toy example of reading an SConf object."""
2
3 from IPython.external.configobj import ConfigObj
4 from IPython.external import configobj, validate
5
6
7 from IPython.config import sconfig
8 reload(sconfig)
9
10 configspecfilename = 'simple.spec.conf'
11 filename = 'simple.conf'
12
13 print '*'*80
14 configspec = ConfigObj(configspecfilename, encoding='UTF8',
15 list_values=False)
16 print sconfig.configobj2str(configspec)
17
18 print '*'*80
19 config = ConfigObj(filename, configspec=configspec,
20 interpolation='Template',
21 unrepr=True)
22 print sconfig.configobj2str(config)
23 vdt = validate.Validator()
24 test = config.validate(vdt,preserve_errors=True)
25
26 ####
27 vdt = validate.Validator()
28 class Bunch: pass
29 vf = Bunch()
30 vf.__dict__.update(vdt.functions)
31 vf.pass_ = vdt.functions['pass']
32 vf.__dict__.pop('',None)
33 vf.__dict__.pop('pass',None)
34 ###
35
36
37 if test==True:
38 print 'All OK'
39 else:
40 err = configobj.flatten_errors(config,test)
41 print 'Flat errors:'
42 for secs,key,result in err:
43 if secs == []:
44 print 'DEFAULT:','key:',key,'err:',result
45 else:
46 print 'Secs:',secs,'key:',key,'err:',result
47
48
49 ##
50 print '*'*80
51
52 sc = sconfig.SConfig(configspecfilename)
53
54
55
56 ####
57
58
59
@@ -0,0 +1,278 b''
1 # -*- coding: utf-8 -*-
2 """String interpolation for Python (by Ka-Ping Yee, 14 Feb 2000).
3
4 This module lets you quickly and conveniently interpolate values into
5 strings (in the flavour of Perl or Tcl, but with less extraneous
6 punctuation). You get a bit more power than in the other languages,
7 because this module allows subscripting, slicing, function calls,
8 attribute lookup, or arbitrary expressions. Variables and expressions
9 are evaluated in the namespace of the caller.
10
11 The itpl() function returns the result of interpolating a string, and
12 printpl() prints out an interpolated string. Here are some examples:
13
14 from Itpl import printpl
15 printpl("Here is a $string.")
16 printpl("Here is a $module.member.")
17 printpl("Here is an $object.member.")
18 printpl("Here is a $functioncall(with, arguments).")
19 printpl("Here is an ${arbitrary + expression}.")
20 printpl("Here is an $array[3] member.")
21 printpl("Here is a $dictionary['member'].")
22
23 The filter() function filters a file object so that output through it
24 is interpolated. This lets you produce the illusion that Python knows
25 how to do interpolation:
26
27 import Itpl
28 sys.stdout = Itpl.filter()
29 f = "fancy"
30 print "Isn't this $f?"
31 print "Standard output has been replaced with a $sys.stdout object."
32 sys.stdout = Itpl.unfilter()
33 print "Okay, back $to $normal."
34
35 Under the hood, the Itpl class represents a string that knows how to
36 interpolate values. An instance of the class parses the string once
37 upon initialization; the evaluation and substitution can then be done
38 each time the instance is evaluated with str(instance). For example:
39
40 from Itpl import Itpl
41 s = Itpl("Here is $foo.")
42 foo = 5
43 print str(s)
44 foo = "bar"
45 print str(s)
46
47 $Id: Itpl.py 2305 2007-05-04 05:34:42Z bgranger $
48 """ # ' -> close an open quote for stupid emacs
49
50 #*****************************************************************************
51 #
52 # Copyright (c) 2001 Ka-Ping Yee <ping@lfw.org>
53 #
54 #
55 # Published under the terms of the MIT license, hereby reproduced:
56 #
57 # Permission is hereby granted, free of charge, to any person obtaining a copy
58 # of this software and associated documentation files (the "Software"), to
59 # deal in the Software without restriction, including without limitation the
60 # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
61 # sell copies of the Software, and to permit persons to whom the Software is
62 # furnished to do so, subject to the following conditions:
63 #
64 # The above copyright notice and this permission notice shall be included in
65 # all copies or substantial portions of the Software.
66 #
67 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
68 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
69 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
70 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
71 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
72 # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
73 # IN THE SOFTWARE.
74 #
75 #*****************************************************************************
76
77 __author__ = 'Ka-Ping Yee <ping@lfw.org>'
78 __license__ = 'MIT'
79
80 import string
81 import sys
82 from tokenize import tokenprog
83 from types import StringType
84
85 class ItplError(ValueError):
86 def __init__(self, text, pos):
87 self.text = text
88 self.pos = pos
89 def __str__(self):
90 return "unfinished expression in %s at char %d" % (
91 repr(self.text), self.pos)
92
93 def matchorfail(text, pos):
94 match = tokenprog.match(text, pos)
95 if match is None:
96 raise ItplError(text, pos)
97 return match, match.end()
98
99 class Itpl:
100 """Class representing a string with interpolation abilities.
101
102 Upon creation, an instance works out what parts of the format
103 string are literal and what parts need to be evaluated. The
104 evaluation and substitution happens in the namespace of the
105 caller when str(instance) is called."""
106
107 def __init__(self, format,codec='utf_8',encoding_errors='backslashreplace'):
108 """The single mandatory argument to this constructor is a format
109 string.
110
111 The format string is parsed according to the following rules:
112
113 1. A dollar sign and a name, possibly followed by any of:
114 - an open-paren, and anything up to the matching paren
115 - an open-bracket, and anything up to the matching bracket
116 - a period and a name
117 any number of times, is evaluated as a Python expression.
118
119 2. A dollar sign immediately followed by an open-brace, and
120 anything up to the matching close-brace, is evaluated as
121 a Python expression.
122
123 3. Outside of the expressions described in the above two rules,
124 two dollar signs in a row give you one literal dollar sign.
125
126 Optional arguments:
127
128 - codec('utf_8'): a string containing the name of a valid Python
129 codec.
130
131 - encoding_errors('backslashreplace'): a string with a valid error handling
132 policy. See the codecs module documentation for details.
133
134 These are used to encode the format string if a call to str() fails on
135 the expanded result."""
136
137 if not isinstance(format,basestring):
138 raise TypeError, "needs string initializer"
139 self.format = format
140 self.codec = codec
141 self.encoding_errors = encoding_errors
142
143 namechars = "abcdefghijklmnopqrstuvwxyz" \
144 "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
145 chunks = []
146 pos = 0
147
148 while 1:
149 dollar = string.find(format, "$", pos)
150 if dollar < 0: break
151 nextchar = format[dollar+1]
152
153 if nextchar == "{":
154 chunks.append((0, format[pos:dollar]))
155 pos, level = dollar+2, 1
156 while level:
157 match, pos = matchorfail(format, pos)
158 tstart, tend = match.regs[3]
159 token = format[tstart:tend]
160 if token == "{": level = level+1
161 elif token == "}": level = level-1
162 chunks.append((1, format[dollar+2:pos-1]))
163
164 elif nextchar in namechars:
165 chunks.append((0, format[pos:dollar]))
166 match, pos = matchorfail(format, dollar+1)
167 while pos < len(format):
168 if format[pos] == "." and \
169 pos+1 < len(format) and format[pos+1] in namechars:
170 match, pos = matchorfail(format, pos+1)
171 elif format[pos] in "([":
172 pos, level = pos+1, 1
173 while level:
174 match, pos = matchorfail(format, pos)
175 tstart, tend = match.regs[3]
176 token = format[tstart:tend]
177 if token[0] in "([": level = level+1
178 elif token[0] in ")]": level = level-1
179 else: break
180 chunks.append((1, format[dollar+1:pos]))
181
182 else:
183 chunks.append((0, format[pos:dollar+1]))
184 pos = dollar + 1 + (nextchar == "$")
185
186 if pos < len(format): chunks.append((0, format[pos:]))
187 self.chunks = chunks
188
189 def __repr__(self):
190 return "<Itpl %s >" % repr(self.format)
191
192 def _str(self,glob,loc):
193 """Evaluate to a string in the given globals/locals.
194
195 The final output is built by calling str(), but if this fails, the
196 result is encoded with the instance's codec and error handling policy,
197 via a call to out.encode(self.codec,self.encoding_errors)"""
198 result = []
199 app = result.append
200 for live, chunk in self.chunks:
201 if live: app(str(eval(chunk,glob,loc)))
202 else: app(chunk)
203 out = ''.join(result)
204 try:
205 return str(out)
206 except UnicodeError:
207 return out.encode(self.codec,self.encoding_errors)
208
209 def __str__(self):
210 """Evaluate and substitute the appropriate parts of the string."""
211
212 # We need to skip enough frames to get to the actual caller outside of
213 # Itpl.
214 frame = sys._getframe(1)
215 while frame.f_globals["__name__"] == __name__: frame = frame.f_back
216 loc, glob = frame.f_locals, frame.f_globals
217
218 return self._str(glob,loc)
219
220 class ItplNS(Itpl):
221 """Class representing a string with interpolation abilities.
222
223 This inherits from Itpl, but at creation time a namespace is provided
224 where the evaluation will occur. The interpolation becomes a bit more
225 efficient, as no traceback needs to be extracte. It also allows the
226 caller to supply a different namespace for the interpolation to occur than
227 its own."""
228
229 def __init__(self, format,globals,locals=None,
230 codec='utf_8',encoding_errors='backslashreplace'):
231 """ItplNS(format,globals[,locals]) -> interpolating string instance.
232
233 This constructor, besides a format string, takes a globals dictionary
234 and optionally a locals (which defaults to globals if not provided).
235
236 For further details, see the Itpl constructor."""
237
238 if locals is None:
239 locals = globals
240 self.globals = globals
241 self.locals = locals
242 Itpl.__init__(self,format,codec,encoding_errors)
243
244 def __str__(self):
245 """Evaluate and substitute the appropriate parts of the string."""
246 return self._str(self.globals,self.locals)
247
248 def __repr__(self):
249 return "<ItplNS %s >" % repr(self.format)
250
251 # utilities for fast printing
252 def itpl(text): return str(Itpl(text))
253 def printpl(text): print itpl(text)
254 # versions with namespace
255 def itplns(text,globals,locals=None): return str(ItplNS(text,globals,locals))
256 def printplns(text,globals,locals=None): print itplns(text,globals,locals)
257
258 class ItplFile:
259 """A file object that filters each write() through an interpolator."""
260 def __init__(self, file): self.file = file
261 def __repr__(self): return "<interpolated " + repr(self.file) + ">"
262 def __getattr__(self, attr): return getattr(self.file, attr)
263 def write(self, text): self.file.write(str(Itpl(text)))
264
265 def filter(file=sys.stdout):
266 """Return an ItplFile that filters writes to the given file object.
267
268 'file = filter(file)' replaces 'file' with a filtered object that
269 has a write() method. When called with no argument, this creates
270 a filter to sys.stdout."""
271 return ItplFile(file)
272
273 def unfilter(ifile=None):
274 """Return the original file that corresponds to the given ItplFile.
275
276 'file = unfilter(file)' undoes the effect of 'file = filter(file)'.
277 'sys.stdout = unfilter()' undoes the effect of 'sys.stdout = filter()'."""
278 return ifile and ifile.file or sys.stdout.file
This diff has been collapsed as it changes many lines, (700 lines changed) Show them Hide them
@@ -0,0 +1,700 b''
1 /***
2
3 MochiKit.Async 1.4
4
5 See <http://mochikit.com/> for documentation, downloads, license, etc.
6
7 (c) 2005 Bob Ippolito. All rights Reserved.
8
9 ***/
10
11 if (typeof(dojo) != 'undefined') {
12 dojo.provide("MochiKit.Async");
13 dojo.require("MochiKit.Base");
14 }
15 if (typeof(JSAN) != 'undefined') {
16 JSAN.use("MochiKit.Base", []);
17 }
18
19 try {
20 if (typeof(MochiKit.Base) == 'undefined') {
21 throw "";
22 }
23 } catch (e) {
24 throw "MochiKit.Async depends on MochiKit.Base!";
25 }
26
27 if (typeof(MochiKit.Async) == 'undefined') {
28 MochiKit.Async = {};
29 }
30
31 MochiKit.Async.NAME = "MochiKit.Async";
32 MochiKit.Async.VERSION = "1.4";
33 MochiKit.Async.__repr__ = function () {
34 return "[" + this.NAME + " " + this.VERSION + "]";
35 };
36 MochiKit.Async.toString = function () {
37 return this.__repr__();
38 };
39
40 /** @id MochiKit.Async.Deferred */
41 MochiKit.Async.Deferred = function (/* optional */ canceller) {
42 this.chain = [];
43 this.id = this._nextId();
44 this.fired = -1;
45 this.paused = 0;
46 this.results = [null, null];
47 this.canceller = canceller;
48 this.silentlyCancelled = false;
49 this.chained = false;
50 };
51
52 MochiKit.Async.Deferred.prototype = {
53 /** @id MochiKit.Async.Deferred.prototype.repr */
54 repr: function () {
55 var state;
56 if (this.fired == -1) {
57 state = 'unfired';
58 } else if (this.fired === 0) {
59 state = 'success';
60 } else {
61 state = 'error';
62 }
63 return 'Deferred(' + this.id + ', ' + state + ')';
64 },
65
66 toString: MochiKit.Base.forwardCall("repr"),
67
68 _nextId: MochiKit.Base.counter(),
69
70 /** @id MochiKit.Async.Deferred.prototype.cancel */
71 cancel: function () {
72 var self = MochiKit.Async;
73 if (this.fired == -1) {
74 if (this.canceller) {
75 this.canceller(this);
76 } else {
77 this.silentlyCancelled = true;
78 }
79 if (this.fired == -1) {
80 this.errback(new self.CancelledError(this));
81 }
82 } else if ((this.fired === 0) && (this.results[0] instanceof self.Deferred)) {
83 this.results[0].cancel();
84 }
85 },
86
87 _resback: function (res) {
88 /***
89
90 The primitive that means either callback or errback
91
92 ***/
93 this.fired = ((res instanceof Error) ? 1 : 0);
94 this.results[this.fired] = res;
95 this._fire();
96 },
97
98 _check: function () {
99 if (this.fired != -1) {
100 if (!this.silentlyCancelled) {
101 throw new MochiKit.Async.AlreadyCalledError(this);
102 }
103 this.silentlyCancelled = false;
104 return;
105 }
106 },
107
108 /** @id MochiKit.Async.Deferred.prototype.callback */
109 callback: function (res) {
110 this._check();
111 if (res instanceof MochiKit.Async.Deferred) {
112 throw new Error("Deferred instances can only be chained if they are the result of a callback");
113 }
114 this._resback(res);
115 },
116
117 /** @id MochiKit.Async.Deferred.prototype.errback */
118 errback: function (res) {
119 this._check();
120 var self = MochiKit.Async;
121 if (res instanceof self.Deferred) {
122 throw new Error("Deferred instances can only be chained if they are the result of a callback");
123 }
124 if (!(res instanceof Error)) {
125 res = new self.GenericError(res);
126 }
127 this._resback(res);
128 },
129
130 /** @id MochiKit.Async.Deferred.prototype.addBoth */
131 addBoth: function (fn) {
132 if (arguments.length > 1) {
133 fn = MochiKit.Base.partial.apply(null, arguments);
134 }
135 return this.addCallbacks(fn, fn);
136 },
137
138 /** @id MochiKit.Async.Deferred.prototype.addCallback */
139 addCallback: function (fn) {
140 if (arguments.length > 1) {
141 fn = MochiKit.Base.partial.apply(null, arguments);
142 }
143 return this.addCallbacks(fn, null);
144 },
145
146 /** @id MochiKit.Async.Deferred.prototype.addErrback */
147 addErrback: function (fn) {
148 if (arguments.length > 1) {
149 fn = MochiKit.Base.partial.apply(null, arguments);
150 }
151 return this.addCallbacks(null, fn);
152 },
153
154 /** @id MochiKit.Async.Deferred.prototype.addCallbacks */
155 addCallbacks: function (cb, eb) {
156 if (this.chained) {
157 throw new Error("Chained Deferreds can not be re-used");
158 }
159 this.chain.push([cb, eb]);
160 if (this.fired >= 0) {
161 this._fire();
162 }
163 return this;
164 },
165
166 _fire: function () {
167 /***
168
169 Used internally to exhaust the callback sequence when a result
170 is available.
171
172 ***/
173 var chain = this.chain;
174 var fired = this.fired;
175 var res = this.results[fired];
176 var self = this;
177 var cb = null;
178 while (chain.length > 0 && this.paused === 0) {
179 // Array
180 var pair = chain.shift();
181 var f = pair[fired];
182 if (f === null) {
183 continue;
184 }
185 try {
186 res = f(res);
187 fired = ((res instanceof Error) ? 1 : 0);
188 if (res instanceof MochiKit.Async.Deferred) {
189 cb = function (res) {
190 self._resback(res);
191 self.paused--;
192 if ((self.paused === 0) && (self.fired >= 0)) {
193 self._fire();
194 }
195 };
196 this.paused++;
197 }
198 } catch (err) {
199 fired = 1;
200 if (!(err instanceof Error)) {
201 err = new MochiKit.Async.GenericError(err);
202 }
203 res = err;
204 }
205 }
206 this.fired = fired;
207 this.results[fired] = res;
208 if (cb && this.paused) {
209 // this is for "tail recursion" in case the dependent deferred
210 // is already fired
211 res.addBoth(cb);
212 res.chained = true;
213 }
214 }
215 };
216
217 MochiKit.Base.update(MochiKit.Async, {
218 /** @id MochiKit.Async.evalJSONRequest */
219 evalJSONRequest: function (req) {
220 return MochiKit.Base.evalJSON(req.responseText);
221 },
222
223 /** @id MochiKit.Async.succeed */
224 succeed: function (/* optional */result) {
225 var d = new MochiKit.Async.Deferred();
226 d.callback.apply(d, arguments);
227 return d;
228 },
229
230 /** @id MochiKit.Async.fail */
231 fail: function (/* optional */result) {
232 var d = new MochiKit.Async.Deferred();
233 d.errback.apply(d, arguments);
234 return d;
235 },
236
237 /** @id MochiKit.Async.getXMLHttpRequest */
238 getXMLHttpRequest: function () {
239 var self = arguments.callee;
240 if (!self.XMLHttpRequest) {
241 var tryThese = [
242 function () { return new XMLHttpRequest(); },
243 function () { return new ActiveXObject('Msxml2.XMLHTTP'); },
244 function () { return new ActiveXObject('Microsoft.XMLHTTP'); },
245 function () { return new ActiveXObject('Msxml2.XMLHTTP.4.0'); },
246 function () {
247 throw new MochiKit.Async.BrowserComplianceError("Browser does not support XMLHttpRequest");
248 }
249 ];
250 for (var i = 0; i < tryThese.length; i++) {
251 var func = tryThese[i];
252 try {
253 self.XMLHttpRequest = func;
254 return func();
255 } catch (e) {
256 // pass
257 }
258 }
259 }
260 return self.XMLHttpRequest();
261 },
262
263 _xhr_onreadystatechange: function (d) {
264 // MochiKit.Logging.logDebug('this.readyState', this.readyState);
265 var m = MochiKit.Base;
266 if (this.readyState == 4) {
267 // IE SUCKS
268 try {
269 this.onreadystatechange = null;
270 } catch (e) {
271 try {
272 this.onreadystatechange = m.noop;
273 } catch (e) {
274 }
275 }
276 var status = null;
277 try {
278 status = this.status;
279 if (!status && m.isNotEmpty(this.responseText)) {
280 // 0 or undefined seems to mean cached or local
281 status = 304;
282 }
283 } catch (e) {
284 // pass
285 // MochiKit.Logging.logDebug('error getting status?', repr(items(e)));
286 }
287 // 200 is OK, 201 is CREATED, 204 is NO CONTENT
288 // 304 is NOT MODIFIED, 1223 is apparently a bug in IE
289 if (status == 200 || status == 201 || status == 204 ||
290 status == 304 || status == 1223) {
291 d.callback(this);
292 } else {
293 var err = new MochiKit.Async.XMLHttpRequestError(this, "Request failed");
294 if (err.number) {
295 // XXX: This seems to happen on page change
296 d.errback(err);
297 } else {
298 // XXX: this seems to happen when the server is unreachable
299 d.errback(err);
300 }
301 }
302 }
303 },
304
305 _xhr_canceller: function (req) {
306 // IE SUCKS
307 try {
308 req.onreadystatechange = null;
309 } catch (e) {
310 try {
311 req.onreadystatechange = MochiKit.Base.noop;
312 } catch (e) {
313 }
314 }
315 req.abort();
316 },
317
318
319 /** @id MochiKit.Async.sendXMLHttpRequest */
320 sendXMLHttpRequest: function (req, /* optional */ sendContent) {
321 if (typeof(sendContent) == "undefined" || sendContent === null) {
322 sendContent = "";
323 }
324
325 var m = MochiKit.Base;
326 var self = MochiKit.Async;
327 var d = new self.Deferred(m.partial(self._xhr_canceller, req));
328
329 try {
330 req.onreadystatechange = m.bind(self._xhr_onreadystatechange,
331 req, d);
332 req.send(sendContent);
333 } catch (e) {
334 try {
335 req.onreadystatechange = null;
336 } catch (ignore) {
337 // pass
338 }
339 d.errback(e);
340 }
341
342 return d;
343
344 },
345
346 /** @id MochiKit.Async.doXHR */
347 doXHR: function (url, opts) {
348 /*
349 Work around a Firefox bug by dealing with XHR during
350 the next event loop iteration. Maybe it's this one:
351 https://bugzilla.mozilla.org/show_bug.cgi?id=249843
352 */
353 var self = MochiKit.Async;
354 return self.callLater(0, self._doXHR, url, opts);
355 },
356
357 _doXHR: function (url, opts) {
358 var m = MochiKit.Base;
359 opts = m.update({
360 method: 'GET',
361 sendContent: ''
362 /*
363 queryString: undefined,
364 username: undefined,
365 password: undefined,
366 headers: undefined,
367 mimeType: undefined
368 */
369 }, opts);
370 var self = MochiKit.Async;
371 var req = self.getXMLHttpRequest();
372 if (opts.queryString) {
373 var qs = m.queryString(opts.queryString);
374 if (qs) {
375 url += "?" + qs;
376 }
377 }
378 // Safari will send undefined:undefined, so we have to check.
379 // We can't use apply, since the function is native.
380 if ('username' in opts) {
381 req.open(opts.method, url, true, opts.username, opts.password);
382 } else {
383 req.open(opts.method, url, true);
384 }
385 if (req.overrideMimeType && opts.mimeType) {
386 req.overrideMimeType(opts.mimeType);
387 }
388 req.setRequestHeader("X-Requested-With", "XMLHttpRequest");
389 if (opts.headers) {
390 var headers = opts.headers;
391 if (!m.isArrayLike(headers)) {
392 headers = m.items(headers);
393 }
394 for (var i = 0; i < headers.length; i++) {
395 var header = headers[i];
396 var name = header[0];
397 var value = header[1];
398 req.setRequestHeader(name, value);
399 }
400 }
401 return self.sendXMLHttpRequest(req, opts.sendContent);
402 },
403
404 _buildURL: function (url/*, ...*/) {
405 if (arguments.length > 1) {
406 var m = MochiKit.Base;
407 var qs = m.queryString.apply(null, m.extend(null, arguments, 1));
408 if (qs) {
409 return url + "?" + qs;
410 }
411 }
412 return url;
413 },
414
415 /** @id MochiKit.Async.doSimpleXMLHttpRequest */
416 doSimpleXMLHttpRequest: function (url/*, ...*/) {
417 var self = MochiKit.Async;
418 url = self._buildURL.apply(self, arguments);
419 return self.doXHR(url);
420 },
421
422 /** @id MochiKit.Async.loadJSONDoc */
423 loadJSONDoc: function (url/*, ...*/) {
424 var self = MochiKit.Async;
425 url = self._buildURL.apply(self, arguments);
426 var d = self.doXHR(url, {
427 'mimeType': 'text/plain',
428 'headers': [['Accept', 'application/json']]
429 });
430 d = d.addCallback(self.evalJSONRequest);
431 return d;
432 },
433
434 /** @id MochiKit.Async.wait */
435 wait: function (seconds, /* optional */value) {
436 var d = new MochiKit.Async.Deferred();
437 var m = MochiKit.Base;
438 if (typeof(value) != 'undefined') {
439 d.addCallback(function () { return value; });
440 }
441 var timeout = setTimeout(
442 m.bind("callback", d),
443 Math.floor(seconds * 1000));
444 d.canceller = function () {
445 try {
446 clearTimeout(timeout);
447 } catch (e) {
448 // pass
449 }
450 };
451 return d;
452 },
453
454 /** @id MochiKit.Async.callLater */
455 callLater: function (seconds, func) {
456 var m = MochiKit.Base;
457 var pfunc = m.partial.apply(m, m.extend(null, arguments, 1));
458 return MochiKit.Async.wait(seconds).addCallback(
459 function (res) { return pfunc(); }
460 );
461 }
462 });
463
464
465 /** @id MochiKit.Async.DeferredLock */
466 MochiKit.Async.DeferredLock = function () {
467 this.waiting = [];
468 this.locked = false;
469 this.id = this._nextId();
470 };
471
472 MochiKit.Async.DeferredLock.prototype = {
473 __class__: MochiKit.Async.DeferredLock,
474 /** @id MochiKit.Async.DeferredLock.prototype.acquire */
475 acquire: function () {
476 var d = new MochiKit.Async.Deferred();
477 if (this.locked) {
478 this.waiting.push(d);
479 } else {
480 this.locked = true;
481 d.callback(this);
482 }
483 return d;
484 },
485 /** @id MochiKit.Async.DeferredLock.prototype.release */
486 release: function () {
487 if (!this.locked) {
488 throw TypeError("Tried to release an unlocked DeferredLock");
489 }
490 this.locked = false;
491 if (this.waiting.length > 0) {
492 this.locked = true;
493 this.waiting.shift().callback(this);
494 }
495 },
496 _nextId: MochiKit.Base.counter(),
497 repr: function () {
498 var state;
499 if (this.locked) {
500 state = 'locked, ' + this.waiting.length + ' waiting';
501 } else {
502 state = 'unlocked';
503 }
504 return 'DeferredLock(' + this.id + ', ' + state + ')';
505 },
506 toString: MochiKit.Base.forwardCall("repr")
507
508 };
509
510 /** @id MochiKit.Async.DeferredList */
511 MochiKit.Async.DeferredList = function (list, /* optional */fireOnOneCallback, fireOnOneErrback, consumeErrors, canceller) {
512
513 // call parent constructor
514 MochiKit.Async.Deferred.apply(this, [canceller]);
515
516 this.list = list;
517 var resultList = [];
518 this.resultList = resultList;
519
520 this.finishedCount = 0;
521 this.fireOnOneCallback = fireOnOneCallback;
522 this.fireOnOneErrback = fireOnOneErrback;
523 this.consumeErrors = consumeErrors;
524
525 var cb = MochiKit.Base.bind(this._cbDeferred, this);
526 for (var i = 0; i < list.length; i++) {
527 var d = list[i];
528 resultList.push(undefined);
529 d.addCallback(cb, i, true);
530 d.addErrback(cb, i, false);
531 }
532
533 if (list.length === 0 && !fireOnOneCallback) {
534 this.callback(this.resultList);
535 }
536
537 };
538
539 MochiKit.Async.DeferredList.prototype = new MochiKit.Async.Deferred();
540
541 MochiKit.Async.DeferredList.prototype._cbDeferred = function (index, succeeded, result) {
542 this.resultList[index] = [succeeded, result];
543 this.finishedCount += 1;
544 if (this.fired == -1) {
545 if (succeeded && this.fireOnOneCallback) {
546 this.callback([index, result]);
547 } else if (!succeeded && this.fireOnOneErrback) {
548 this.errback(result);
549 } else if (this.finishedCount == this.list.length) {
550 this.callback(this.resultList);
551 }
552 }
553 if (!succeeded && this.consumeErrors) {
554 result = null;
555 }
556 return result;
557 };
558
559 /** @id MochiKit.Async.gatherResults */
560 MochiKit.Async.gatherResults = function (deferredList) {
561 var d = new MochiKit.Async.DeferredList(deferredList, false, true, false);
562 d.addCallback(function (results) {
563 var ret = [];
564 for (var i = 0; i < results.length; i++) {
565 ret.push(results[i][1]);
566 }
567 return ret;
568 });
569 return d;
570 };
571
572 /** @id MochiKit.Async.maybeDeferred */
573 MochiKit.Async.maybeDeferred = function (func) {
574 var self = MochiKit.Async;
575 var result;
576 try {
577 var r = func.apply(null, MochiKit.Base.extend([], arguments, 1));
578 if (r instanceof self.Deferred) {
579 result = r;
580 } else if (r instanceof Error) {
581 result = self.fail(r);
582 } else {
583 result = self.succeed(r);
584 }
585 } catch (e) {
586 result = self.fail(e);
587 }
588 return result;
589 };
590
591
592 MochiKit.Async.EXPORT = [
593 "AlreadyCalledError",
594 "CancelledError",
595 "BrowserComplianceError",
596 "GenericError",
597 "XMLHttpRequestError",
598 "Deferred",
599 "succeed",
600 "fail",
601 "getXMLHttpRequest",
602 "doSimpleXMLHttpRequest",
603 "loadJSONDoc",
604 "wait",
605 "callLater",
606 "sendXMLHttpRequest",
607 "DeferredLock",
608 "DeferredList",
609 "gatherResults",
610 "maybeDeferred",
611 "doXHR"
612 ];
613
614 MochiKit.Async.EXPORT_OK = [
615 "evalJSONRequest"
616 ];
617
618 MochiKit.Async.__new__ = function () {
619 var m = MochiKit.Base;
620 var ne = m.partial(m._newNamedError, this);
621
622 ne("AlreadyCalledError",
623 /** @id MochiKit.Async.AlreadyCalledError */
624 function (deferred) {
625 /***
626
627 Raised by the Deferred if callback or errback happens
628 after it was already fired.
629
630 ***/
631 this.deferred = deferred;
632 }
633 );
634
635 ne("CancelledError",
636 /** @id MochiKit.Async.CancelledError */
637 function (deferred) {
638 /***
639
640 Raised by the Deferred cancellation mechanism.
641
642 ***/
643 this.deferred = deferred;
644 }
645 );
646
647 ne("BrowserComplianceError",
648 /** @id MochiKit.Async.BrowserComplianceError */
649 function (msg) {
650 /***
651
652 Raised when the JavaScript runtime is not capable of performing
653 the given function. Technically, this should really never be
654 raised because a non-conforming JavaScript runtime probably
655 isn't going to support exceptions in the first place.
656
657 ***/
658 this.message = msg;
659 }
660 );
661
662 ne("GenericError",
663 /** @id MochiKit.Async.GenericError */
664 function (msg) {
665 this.message = msg;
666 }
667 );
668
669 ne("XMLHttpRequestError",
670 /** @id MochiKit.Async.XMLHttpRequestError */
671 function (req, msg) {
672 /***
673
674 Raised when an XMLHttpRequest does not complete for any reason.
675
676 ***/
677 this.req = req;
678 this.message = msg;
679 try {
680 // Strange but true that this can raise in some cases.
681 this.number = req.status;
682 } catch (e) {
683 // pass
684 }
685 }
686 );
687
688
689 this.EXPORT_TAGS = {
690 ":common": this.EXPORT,
691 ":all": m.concat(this.EXPORT, this.EXPORT_OK)
692 };
693
694 m.nameFunctions(this);
695
696 };
697
698 MochiKit.Async.__new__();
699
700 MochiKit.Base._exportSymbols(this, MochiKit.Async);
This diff has been collapsed as it changes many lines, (1413 lines changed) Show them Hide them
@@ -0,0 +1,1413 b''
1 /***
2
3 MochiKit.Base 1.4
4
5 See <http://mochikit.com/> for documentation, downloads, license, etc.
6
7 (c) 2005 Bob Ippolito. All rights Reserved.
8
9 ***/
10
11 if (typeof(dojo) != 'undefined') {
12 dojo.provide("MochiKit.Base");
13 }
14 if (typeof(MochiKit) == 'undefined') {
15 MochiKit = {};
16 }
17 if (typeof(MochiKit.Base) == 'undefined') {
18 MochiKit.Base = {};
19 }
20 if (typeof(MochiKit.__export__) == "undefined") {
21 MochiKit.__export__ = (MochiKit.__compat__ ||
22 (typeof(JSAN) == 'undefined' && typeof(dojo) == 'undefined')
23 );
24 }
25
26 MochiKit.Base.VERSION = "1.4";
27 MochiKit.Base.NAME = "MochiKit.Base";
28 /** @id MochiKit.Base.update */
29 MochiKit.Base.update = function (self, obj/*, ... */) {
30 if (self === null || self === undefined) {
31 self = {};
32 }
33 for (var i = 1; i < arguments.length; i++) {
34 var o = arguments[i];
35 if (typeof(o) != 'undefined' && o !== null) {
36 for (var k in o) {
37 self[k] = o[k];
38 }
39 }
40 }
41 return self;
42 };
43
44 MochiKit.Base.update(MochiKit.Base, {
45 __repr__: function () {
46 return "[" + this.NAME + " " + this.VERSION + "]";
47 },
48
49 toString: function () {
50 return this.__repr__();
51 },
52
53 /** @id MochiKit.Base.camelize */
54 camelize: function (selector) {
55 /* from dojo.style.toCamelCase */
56 var arr = selector.split('-');
57 var cc = arr[0];
58 for (var i = 1; i < arr.length; i++) {
59 cc += arr[i].charAt(0).toUpperCase() + arr[i].substring(1);
60 }
61 return cc;
62 },
63
64 /** @id MochiKit.Base.counter */
65 counter: function (n/* = 1 */) {
66 if (arguments.length === 0) {
67 n = 1;
68 }
69 return function () {
70 return n++;
71 };
72 },
73
74 /** @id MochiKit.Base.clone */
75 clone: function (obj) {
76 var me = arguments.callee;
77 if (arguments.length == 1) {
78 me.prototype = obj;
79 return new me();
80 }
81 },
82
83 _flattenArray: function (res, lst) {
84 for (var i = 0; i < lst.length; i++) {
85 var o = lst[i];
86 if (o instanceof Array) {
87 arguments.callee(res, o);
88 } else {
89 res.push(o);
90 }
91 }
92 return res;
93 },
94
95 /** @id MochiKit.Base.flattenArray */
96 flattenArray: function (lst) {
97 return MochiKit.Base._flattenArray([], lst);
98 },
99
100 /** @id MochiKit.Base.flattenArguments */
101 flattenArguments: function (lst/* ...*/) {
102 var res = [];
103 var m = MochiKit.Base;
104 var args = m.extend(null, arguments);
105 while (args.length) {
106 var o = args.shift();
107 if (o && typeof(o) == "object" && typeof(o.length) == "number") {
108 for (var i = o.length - 1; i >= 0; i--) {
109 args.unshift(o[i]);
110 }
111 } else {
112 res.push(o);
113 }
114 }
115 return res;
116 },
117
118 /** @id MochiKit.Base.extend */
119 extend: function (self, obj, /* optional */skip) {
120 // Extend an array with an array-like object starting
121 // from the skip index
122 if (!skip) {
123 skip = 0;
124 }
125 if (obj) {
126 // allow iterable fall-through, but skip the full isArrayLike
127 // check for speed, this is called often.
128 var l = obj.length;
129 if (typeof(l) != 'number' /* !isArrayLike(obj) */) {
130 if (typeof(MochiKit.Iter) != "undefined") {
131 obj = MochiKit.Iter.list(obj);
132 l = obj.length;
133 } else {
134 throw new TypeError("Argument not an array-like and MochiKit.Iter not present");
135 }
136 }
137 if (!self) {
138 self = [];
139 }
140 for (var i = skip; i < l; i++) {
141 self.push(obj[i]);
142 }
143 }
144 // This mutates, but it's convenient to return because
145 // it's often used like a constructor when turning some
146 // ghetto array-like to a real array
147 return self;
148 },
149
150
151 /** @id MochiKit.Base.updatetree */
152 updatetree: function (self, obj/*, ...*/) {
153 if (self === null || self === undefined) {
154 self = {};
155 }
156 for (var i = 1; i < arguments.length; i++) {
157 var o = arguments[i];
158 if (typeof(o) != 'undefined' && o !== null) {
159 for (var k in o) {
160 var v = o[k];
161 if (typeof(self[k]) == 'object' && typeof(v) == 'object') {
162 arguments.callee(self[k], v);
163 } else {
164 self[k] = v;
165 }
166 }
167 }
168 }
169 return self;
170 },
171
172 /** @id MochiKit.Base.setdefault */
173 setdefault: function (self, obj/*, ...*/) {
174 if (self === null || self === undefined) {
175 self = {};
176 }
177 for (var i = 1; i < arguments.length; i++) {
178 var o = arguments[i];
179 for (var k in o) {
180 if (!(k in self)) {
181 self[k] = o[k];
182 }
183 }
184 }
185 return self;
186 },
187
188 /** @id MochiKit.Base.keys */
189 keys: function (obj) {
190 var rval = [];
191 for (var prop in obj) {
192 rval.push(prop);
193 }
194 return rval;
195 },
196
197 /** @id MochiKit.Base.values */
198 values: function (obj) {
199 var rval = [];
200 for (var prop in obj) {
201 rval.push(obj[prop]);
202 }
203 return rval;
204 },
205
206 /** @id MochiKit.Base.items */
207 items: function (obj) {
208 var rval = [];
209 var e;
210 for (var prop in obj) {
211 var v;
212 try {
213 v = obj[prop];
214 } catch (e) {
215 continue;
216 }
217 rval.push([prop, v]);
218 }
219 return rval;
220 },
221
222
223 _newNamedError: function (module, name, func) {
224 func.prototype = new MochiKit.Base.NamedError(module.NAME + "." + name);
225 module[name] = func;
226 },
227
228
229 /** @id MochiKit.Base.operator */
230 operator: {
231 // unary logic operators
232 /** @id MochiKit.Base.truth */
233 truth: function (a) { return !!a; },
234 /** @id MochiKit.Base.lognot */
235 lognot: function (a) { return !a; },
236 /** @id MochiKit.Base.identity */
237 identity: function (a) { return a; },
238
239 // bitwise unary operators
240 /** @id MochiKit.Base.not */
241 not: function (a) { return ~a; },
242 /** @id MochiKit.Base.neg */
243 neg: function (a) { return -a; },
244
245 // binary operators
246 /** @id MochiKit.Base.add */
247 add: function (a, b) { return a + b; },
248 /** @id MochiKit.Base.sub */
249 sub: function (a, b) { return a - b; },
250 /** @id MochiKit.Base.div */
251 div: function (a, b) { return a / b; },
252 /** @id MochiKit.Base.mod */
253 mod: function (a, b) { return a % b; },
254 /** @id MochiKit.Base.mul */
255 mul: function (a, b) { return a * b; },
256
257 // bitwise binary operators
258 /** @id MochiKit.Base.and */
259 and: function (a, b) { return a & b; },
260 /** @id MochiKit.Base.or */
261 or: function (a, b) { return a | b; },
262 /** @id MochiKit.Base.xor */
263 xor: function (a, b) { return a ^ b; },
264 /** @id MochiKit.Base.lshift */
265 lshift: function (a, b) { return a << b; },
266 /** @id MochiKit.Base.rshift */
267 rshift: function (a, b) { return a >> b; },
268 /** @id MochiKit.Base.zrshift */
269 zrshift: function (a, b) { return a >>> b; },
270
271 // near-worthless built-in comparators
272 /** @id MochiKit.Base.eq */
273 eq: function (a, b) { return a == b; },
274 /** @id MochiKit.Base.ne */
275 ne: function (a, b) { return a != b; },
276 /** @id MochiKit.Base.gt */
277 gt: function (a, b) { return a > b; },
278 /** @id MochiKit.Base.ge */
279 ge: function (a, b) { return a >= b; },
280 /** @id MochiKit.Base.lt */
281 lt: function (a, b) { return a < b; },
282 /** @id MochiKit.Base.le */
283 le: function (a, b) { return a <= b; },
284
285 // strict built-in comparators
286 seq: function (a, b) { return a === b; },
287 sne: function (a, b) { return a !== b; },
288
289 // compare comparators
290 /** @id MochiKit.Base.ceq */
291 ceq: function (a, b) { return MochiKit.Base.compare(a, b) === 0; },
292 /** @id MochiKit.Base.cne */
293 cne: function (a, b) { return MochiKit.Base.compare(a, b) !== 0; },
294 /** @id MochiKit.Base.cgt */
295 cgt: function (a, b) { return MochiKit.Base.compare(a, b) == 1; },
296 /** @id MochiKit.Base.cge */
297 cge: function (a, b) { return MochiKit.Base.compare(a, b) != -1; },
298 /** @id MochiKit.Base.clt */
299 clt: function (a, b) { return MochiKit.Base.compare(a, b) == -1; },
300 /** @id MochiKit.Base.cle */
301 cle: function (a, b) { return MochiKit.Base.compare(a, b) != 1; },
302
303 // binary logical operators
304 /** @id MochiKit.Base.logand */
305 logand: function (a, b) { return a && b; },
306 /** @id MochiKit.Base.logor */
307 logor: function (a, b) { return a || b; },
308 /** @id MochiKit.Base.contains */
309 contains: function (a, b) { return b in a; }
310 },
311
312 /** @id MochiKit.Base.forwardCall */
313 forwardCall: function (func) {
314 return function () {
315 return this[func].apply(this, arguments);
316 };
317 },
318
319 /** @id MochiKit.Base.itemgetter */
320 itemgetter: function (func) {
321 return function (arg) {
322 return arg[func];
323 };
324 },
325
326 /** @id MochiKit.Base.typeMatcher */
327 typeMatcher: function (/* typ */) {
328 var types = {};
329 for (var i = 0; i < arguments.length; i++) {
330 var typ = arguments[i];
331 types[typ] = typ;
332 }
333 return function () {
334 for (var i = 0; i < arguments.length; i++) {
335 if (!(typeof(arguments[i]) in types)) {
336 return false;
337 }
338 }
339 return true;
340 };
341 },
342
343 /** @id MochiKit.Base.isNull */
344 isNull: function (/* ... */) {
345 for (var i = 0; i < arguments.length; i++) {
346 if (arguments[i] !== null) {
347 return false;
348 }
349 }
350 return true;
351 },
352
353 /** @id MochiKit.Base.isUndefinedOrNull */
354 isUndefinedOrNull: function (/* ... */) {
355 for (var i = 0; i < arguments.length; i++) {
356 var o = arguments[i];
357 if (!(typeof(o) == 'undefined' || o === null)) {
358 return false;
359 }
360 }
361 return true;
362 },
363
364 /** @id MochiKit.Base.isEmpty */
365 isEmpty: function (obj) {
366 return !MochiKit.Base.isNotEmpty.apply(this, arguments);
367 },
368
369 /** @id MochiKit.Base.isNotEmpty */
370 isNotEmpty: function (obj) {
371 for (var i = 0; i < arguments.length; i++) {
372 var o = arguments[i];
373 if (!(o && o.length)) {
374 return false;
375 }
376 }
377 return true;
378 },
379
380 /** @id MochiKit.Base.isArrayLike */
381 isArrayLike: function () {
382 for (var i = 0; i < arguments.length; i++) {
383 var o = arguments[i];
384 var typ = typeof(o);
385 if (
386 (typ != 'object' && !(typ == 'function' && typeof(o.item) == 'function')) ||
387 o === null ||
388 typeof(o.length) != 'number' ||
389 o.nodeType === 3
390 ) {
391 return false;
392 }
393 }
394 return true;
395 },
396
397 /** @id MochiKit.Base.isDateLike */
398 isDateLike: function () {
399 for (var i = 0; i < arguments.length; i++) {
400 var o = arguments[i];
401 if (typeof(o) != "object" || o === null
402 || typeof(o.getTime) != 'function') {
403 return false;
404 }
405 }
406 return true;
407 },
408
409
410 /** @id MochiKit.Base.xmap */
411 xmap: function (fn/*, obj... */) {
412 if (fn === null) {
413 return MochiKit.Base.extend(null, arguments, 1);
414 }
415 var rval = [];
416 for (var i = 1; i < arguments.length; i++) {
417 rval.push(fn(arguments[i]));
418 }
419 return rval;
420 },
421
422 /** @id MochiKit.Base.map */
423 map: function (fn, lst/*, lst... */) {
424 var m = MochiKit.Base;
425 var itr = MochiKit.Iter;
426 var isArrayLike = m.isArrayLike;
427 if (arguments.length <= 2) {
428 // allow an iterable to be passed
429 if (!isArrayLike(lst)) {
430 if (itr) {
431 // fast path for map(null, iterable)
432 lst = itr.list(lst);
433 if (fn === null) {
434 return lst;
435 }
436 } else {
437 throw new TypeError("Argument not an array-like and MochiKit.Iter not present");
438 }
439 }
440 // fast path for map(null, lst)
441 if (fn === null) {
442 return m.extend(null, lst);
443 }
444 // disabled fast path for map(fn, lst)
445 /*
446 if (false && typeof(Array.prototype.map) == 'function') {
447 // Mozilla fast-path
448 return Array.prototype.map.call(lst, fn);
449 }
450 */
451 var rval = [];
452 for (var i = 0; i < lst.length; i++) {
453 rval.push(fn(lst[i]));
454 }
455 return rval;
456 } else {
457 // default for map(null, ...) is zip(...)
458 if (fn === null) {
459 fn = Array;
460 }
461 var length = null;
462 for (i = 1; i < arguments.length; i++) {
463 // allow iterables to be passed
464 if (!isArrayLike(arguments[i])) {
465 if (itr) {
466 return itr.list(itr.imap.apply(null, arguments));
467 } else {
468 throw new TypeError("Argument not an array-like and MochiKit.Iter not present");
469 }
470 }
471 // find the minimum length
472 var l = arguments[i].length;
473 if (length === null || length > l) {
474 length = l;
475 }
476 }
477 rval = [];
478 for (i = 0; i < length; i++) {
479 var args = [];
480 for (var j = 1; j < arguments.length; j++) {
481 args.push(arguments[j][i]);
482 }
483 rval.push(fn.apply(this, args));
484 }
485 return rval;
486 }
487 },
488
489 /** @id MochiKit.Base.xfilter */
490 xfilter: function (fn/*, obj... */) {
491 var rval = [];
492 if (fn === null) {
493 fn = MochiKit.Base.operator.truth;
494 }
495 for (var i = 1; i < arguments.length; i++) {
496 var o = arguments[i];
497 if (fn(o)) {
498 rval.push(o);
499 }
500 }
501 return rval;
502 },
503
504 /** @id MochiKit.Base.filter */
505 filter: function (fn, lst, self) {
506 var rval = [];
507 // allow an iterable to be passed
508 var m = MochiKit.Base;
509 if (!m.isArrayLike(lst)) {
510 if (MochiKit.Iter) {
511 lst = MochiKit.Iter.list(lst);
512 } else {
513 throw new TypeError("Argument not an array-like and MochiKit.Iter not present");
514 }
515 }
516 if (fn === null) {
517 fn = m.operator.truth;
518 }
519 if (typeof(Array.prototype.filter) == 'function') {
520 // Mozilla fast-path
521 return Array.prototype.filter.call(lst, fn, self);
522 } else if (typeof(self) == 'undefined' || self === null) {
523 for (var i = 0; i < lst.length; i++) {
524 var o = lst[i];
525 if (fn(o)) {
526 rval.push(o);
527 }
528 }
529 } else {
530 for (i = 0; i < lst.length; i++) {
531 o = lst[i];
532 if (fn.call(self, o)) {
533 rval.push(o);
534 }
535 }
536 }
537 return rval;
538 },
539
540
541 _wrapDumbFunction: function (func) {
542 return function () {
543 // fast path!
544 switch (arguments.length) {
545 case 0: return func();
546 case 1: return func(arguments[0]);
547 case 2: return func(arguments[0], arguments[1]);
548 case 3: return func(arguments[0], arguments[1], arguments[2]);
549 }
550 var args = [];
551 for (var i = 0; i < arguments.length; i++) {
552 args.push("arguments[" + i + "]");
553 }
554 return eval("(func(" + args.join(",") + "))");
555 };
556 },
557
558 /** @id MochiKit.Base.methodcaller */
559 methodcaller: function (func/*, args... */) {
560 var args = MochiKit.Base.extend(null, arguments, 1);
561 if (typeof(func) == "function") {
562 return function (obj) {
563 return func.apply(obj, args);
564 };
565 } else {
566 return function (obj) {
567 return obj[func].apply(obj, args);
568 };
569 }
570 },
571
572 /** @id MochiKit.Base.method */
573 method: function (self, func) {
574 var m = MochiKit.Base;
575 return m.bind.apply(this, m.extend([func, self], arguments, 2));
576 },
577
578 /** @id MochiKit.Base.compose */
579 compose: function (f1, f2/*, f3, ... fN */) {
580 var fnlist = [];
581 var m = MochiKit.Base;
582 if (arguments.length === 0) {
583 throw new TypeError("compose() requires at least one argument");
584 }
585 for (var i = 0; i < arguments.length; i++) {
586 var fn = arguments[i];
587 if (typeof(fn) != "function") {
588 throw new TypeError(m.repr(fn) + " is not a function");
589 }
590 fnlist.push(fn);
591 }
592 return function () {
593 var args = arguments;
594 for (var i = fnlist.length - 1; i >= 0; i--) {
595 args = [fnlist[i].apply(this, args)];
596 }
597 return args[0];
598 };
599 },
600
601 /** @id MochiKit.Base.bind */
602 bind: function (func, self/* args... */) {
603 if (typeof(func) == "string") {
604 func = self[func];
605 }
606 var im_func = func.im_func;
607 var im_preargs = func.im_preargs;
608 var im_self = func.im_self;
609 var m = MochiKit.Base;
610 if (typeof(func) == "function" && typeof(func.apply) == "undefined") {
611 // this is for cases where JavaScript sucks ass and gives you a
612 // really dumb built-in function like alert() that doesn't have
613 // an apply
614 func = m._wrapDumbFunction(func);
615 }
616 if (typeof(im_func) != 'function') {
617 im_func = func;
618 }
619 if (typeof(self) != 'undefined') {
620 im_self = self;
621 }
622 if (typeof(im_preargs) == 'undefined') {
623 im_preargs = [];
624 } else {
625 im_preargs = im_preargs.slice();
626 }
627 m.extend(im_preargs, arguments, 2);
628 var newfunc = function () {
629 var args = arguments;
630 var me = arguments.callee;
631 if (me.im_preargs.length > 0) {
632 args = m.concat(me.im_preargs, args);
633 }
634 var self = me.im_self;
635 if (!self) {
636 self = this;
637 }
638 return me.im_func.apply(self, args);
639 };
640 newfunc.im_self = im_self;
641 newfunc.im_func = im_func;
642 newfunc.im_preargs = im_preargs;
643 return newfunc;
644 },
645
646 /** @id MochiKit.Base.bindMethods */
647 bindMethods: function (self) {
648 var bind = MochiKit.Base.bind;
649 for (var k in self) {
650 var func = self[k];
651 if (typeof(func) == 'function') {
652 self[k] = bind(func, self);
653 }
654 }
655 },
656
657 /** @id MochiKit.Base.registerComparator */
658 registerComparator: function (name, check, comparator, /* optional */ override) {
659 MochiKit.Base.comparatorRegistry.register(name, check, comparator, override);
660 },
661
662 _primitives: {'boolean': true, 'string': true, 'number': true},
663
664 /** @id MochiKit.Base.compare */
665 compare: function (a, b) {
666 if (a == b) {
667 return 0;
668 }
669 var aIsNull = (typeof(a) == 'undefined' || a === null);
670 var bIsNull = (typeof(b) == 'undefined' || b === null);
671 if (aIsNull && bIsNull) {
672 return 0;
673 } else if (aIsNull) {
674 return -1;
675 } else if (bIsNull) {
676 return 1;
677 }
678 var m = MochiKit.Base;
679 // bool, number, string have meaningful comparisons
680 var prim = m._primitives;
681 if (!(typeof(a) in prim && typeof(b) in prim)) {
682 try {
683 return m.comparatorRegistry.match(a, b);
684 } catch (e) {
685 if (e != m.NotFound) {
686 throw e;
687 }
688 }
689 }
690 if (a < b) {
691 return -1;
692 } else if (a > b) {
693 return 1;
694 }
695 // These types can't be compared
696 var repr = m.repr;
697 throw new TypeError(repr(a) + " and " + repr(b) + " can not be compared");
698 },
699
700 /** @id MochiKit.Base.compareDateLike */
701 compareDateLike: function (a, b) {
702 return MochiKit.Base.compare(a.getTime(), b.getTime());
703 },
704
705 /** @id MochiKit.Base.compareArrayLike */
706 compareArrayLike: function (a, b) {
707 var compare = MochiKit.Base.compare;
708 var count = a.length;
709 var rval = 0;
710 if (count > b.length) {
711 rval = 1;
712 count = b.length;
713 } else if (count < b.length) {
714 rval = -1;
715 }
716 for (var i = 0; i < count; i++) {
717 var cmp = compare(a[i], b[i]);
718 if (cmp) {
719 return cmp;
720 }
721 }
722 return rval;
723 },
724
725 /** @id MochiKit.Base.registerRepr */
726 registerRepr: function (name, check, wrap, /* optional */override) {
727 MochiKit.Base.reprRegistry.register(name, check, wrap, override);
728 },
729
730 /** @id MochiKit.Base.repr */
731 repr: function (o) {
732 if (typeof(o) == "undefined") {
733 return "undefined";
734 } else if (o === null) {
735 return "null";
736 }
737 try {
738 if (typeof(o.__repr__) == 'function') {
739 return o.__repr__();
740 } else if (typeof(o.repr) == 'function' && o.repr != arguments.callee) {
741 return o.repr();
742 }
743 return MochiKit.Base.reprRegistry.match(o);
744 } catch (e) {
745 if (typeof(o.NAME) == 'string' && (
746 o.toString == Function.prototype.toString ||
747 o.toString == Object.prototype.toString
748 )) {
749 return o.NAME;
750 }
751 }
752 try {
753 var ostring = (o + "");
754 } catch (e) {
755 return "[" + typeof(o) + "]";
756 }
757 if (typeof(o) == "function") {
758 ostring = ostring.replace(/^\s+/, "").replace(/\s+/g, " ");
759 var idx = ostring.indexOf("{");
760 if (idx != -1) {
761 ostring = ostring.substr(0, idx) + "{...}";
762 }
763 }
764 return ostring;
765 },
766
767 /** @id MochiKit.Base.reprArrayLike */
768 reprArrayLike: function (o) {
769 var m = MochiKit.Base;
770 return "[" + m.map(m.repr, o).join(", ") + "]";
771 },
772
773 /** @id MochiKit.Base.reprString */
774 reprString: function (o) {
775 return ('"' + o.replace(/(["\\])/g, '\\$1') + '"'
776 ).replace(/[\f]/g, "\\f"
777 ).replace(/[\b]/g, "\\b"
778 ).replace(/[\n]/g, "\\n"
779 ).replace(/[\t]/g, "\\t"
780 ).replace(/[\r]/g, "\\r");
781 },
782
783 /** @id MochiKit.Base.reprNumber */
784 reprNumber: function (o) {
785 return o + "";
786 },
787
788 /** @id MochiKit.Base.registerJSON */
789 registerJSON: function (name, check, wrap, /* optional */override) {
790 MochiKit.Base.jsonRegistry.register(name, check, wrap, override);
791 },
792
793
794 /** @id MochiKit.Base.evalJSON */
795 evalJSON: function () {
796 return eval("(" + MochiKit.Base._filterJSON(arguments[0]) + ")");
797 },
798
799 _filterJSON: function (s) {
800 var m = s.match(/^\s*\/\*(.*)\*\/\s*$/);
801 if (m) {
802 return m[1];
803 }
804 return s;
805 },
806
807 /** @id MochiKit.Base.serializeJSON */
808 serializeJSON: function (o) {
809 var objtype = typeof(o);
810 if (objtype == "number" || objtype == "boolean") {
811 return o + "";
812 } else if (o === null) {
813 return "null";
814 }
815 var m = MochiKit.Base;
816 var reprString = m.reprString;
817 if (objtype == "string") {
818 return reprString(o);
819 }
820 // recurse
821 var me = arguments.callee;
822 // short-circuit for objects that support "json" serialization
823 // if they return "self" then just pass-through...
824 var newObj;
825 if (typeof(o.__json__) == "function") {
826 newObj = o.__json__();
827 if (o !== newObj) {
828 return me(newObj);
829 }
830 }
831 if (typeof(o.json) == "function") {
832 newObj = o.json();
833 if (o !== newObj) {
834 return me(newObj);
835 }
836 }
837 // array
838 if (objtype != "function" && typeof(o.length) == "number") {
839 var res = [];
840 for (var i = 0; i < o.length; i++) {
841 var val = me(o[i]);
842 if (typeof(val) != "string") {
843 val = "undefined";
844 }
845 res.push(val);
846 }
847 return "[" + res.join(", ") + "]";
848 }
849 // look in the registry
850 try {
851 newObj = m.jsonRegistry.match(o);
852 if (o !== newObj) {
853 return me(newObj);
854 }
855 } catch (e) {
856 if (e != m.NotFound) {
857 // something really bad happened
858 throw e;
859 }
860 }
861 // undefined is outside of the spec
862 if (objtype == "undefined") {
863 throw new TypeError("undefined can not be serialized as JSON");
864 }
865 // it's a function with no adapter, bad
866 if (objtype == "function") {
867 return null;
868 }
869 // generic object code path
870 res = [];
871 for (var k in o) {
872 var useKey;
873 if (typeof(k) == "number") {
874 useKey = '"' + k + '"';
875 } else if (typeof(k) == "string") {
876 useKey = reprString(k);
877 } else {
878 // skip non-string or number keys
879 continue;
880 }
881 val = me(o[k]);
882 if (typeof(val) != "string") {
883 // skip non-serializable values
884 continue;
885 }
886 res.push(useKey + ":" + val);
887 }
888 return "{" + res.join(", ") + "}";
889 },
890
891
892 /** @id MochiKit.Base.objEqual */
893 objEqual: function (a, b) {
894 return (MochiKit.Base.compare(a, b) === 0);
895 },
896
897 /** @id MochiKit.Base.arrayEqual */
898 arrayEqual: function (self, arr) {
899 if (self.length != arr.length) {
900 return false;
901 }
902 return (MochiKit.Base.compare(self, arr) === 0);
903 },
904
905 /** @id MochiKit.Base.concat */
906 concat: function (/* lst... */) {
907 var rval = [];
908 var extend = MochiKit.Base.extend;
909 for (var i = 0; i < arguments.length; i++) {
910 extend(rval, arguments[i]);
911 }
912 return rval;
913 },
914
915 /** @id MochiKit.Base.keyComparator */
916 keyComparator: function (key/* ... */) {
917 // fast-path for single key comparisons
918 var m = MochiKit.Base;
919 var compare = m.compare;
920 if (arguments.length == 1) {
921 return function (a, b) {
922 return compare(a[key], b[key]);
923 };
924 }
925 var compareKeys = m.extend(null, arguments);
926 return function (a, b) {
927 var rval = 0;
928 // keep comparing until something is inequal or we run out of
929 // keys to compare
930 for (var i = 0; (rval === 0) && (i < compareKeys.length); i++) {
931 var key = compareKeys[i];
932 rval = compare(a[key], b[key]);
933 }
934 return rval;
935 };
936 },
937
938 /** @id MochiKit.Base.reverseKeyComparator */
939 reverseKeyComparator: function (key) {
940 var comparator = MochiKit.Base.keyComparator.apply(this, arguments);
941 return function (a, b) {
942 return comparator(b, a);
943 };
944 },
945
946 /** @id MochiKit.Base.partial */
947 partial: function (func) {
948 var m = MochiKit.Base;
949 return m.bind.apply(this, m.extend([func, undefined], arguments, 1));
950 },
951
952 /** @id MochiKit.Base.listMinMax */
953 listMinMax: function (which, lst) {
954 if (lst.length === 0) {
955 return null;
956 }
957 var cur = lst[0];
958 var compare = MochiKit.Base.compare;
959 for (var i = 1; i < lst.length; i++) {
960 var o = lst[i];
961 if (compare(o, cur) == which) {
962 cur = o;
963 }
964 }
965 return cur;
966 },
967
968 /** @id MochiKit.Base.objMax */
969 objMax: function (/* obj... */) {
970 return MochiKit.Base.listMinMax(1, arguments);
971 },
972
973 /** @id MochiKit.Base.objMin */
974 objMin: function (/* obj... */) {
975 return MochiKit.Base.listMinMax(-1, arguments);
976 },
977
978 /** @id MochiKit.Base.findIdentical */
979 findIdentical: function (lst, value, start/* = 0 */, /* optional */end) {
980 if (typeof(end) == "undefined" || end === null) {
981 end = lst.length;
982 }
983 if (typeof(start) == "undefined" || start === null) {
984 start = 0;
985 }
986 for (var i = start; i < end; i++) {
987 if (lst[i] === value) {
988 return i;
989 }
990 }
991 return -1;
992 },
993
994 /** @id MochiKit.Base.mean */
995 mean: function(/* lst... */) {
996 /* http://www.nist.gov/dads/HTML/mean.html */
997 var sum = 0;
998
999 var m = MochiKit.Base;
1000 var args = m.extend(null, arguments);
1001 var count = args.length;
1002
1003 while (args.length) {
1004 var o = args.shift();
1005 if (o && typeof(o) == "object" && typeof(o.length) == "number") {
1006 count += o.length - 1;
1007 for (var i = o.length - 1; i >= 0; i--) {
1008 sum += o[i];
1009 }
1010 } else {
1011 sum += o;
1012 }
1013 }
1014
1015 if (count <= 0) {
1016 throw new TypeError('mean() requires at least one argument');
1017 }
1018
1019 return sum/count;
1020 },
1021
1022 /** @id MochiKit.Base.median */
1023 median: function(/* lst... */) {
1024 /* http://www.nist.gov/dads/HTML/median.html */
1025 var data = MochiKit.Base.flattenArguments(arguments);
1026 if (data.length === 0) {
1027 throw new TypeError('median() requires at least one argument');
1028 }
1029 data.sort(compare);
1030 if (data.length % 2 == 0) {
1031 var upper = data.length / 2;
1032 return (data[upper] + data[upper - 1]) / 2;
1033 } else {
1034 return data[(data.length - 1) / 2];
1035 }
1036 },
1037
1038 /** @id MochiKit.Base.findValue */
1039 findValue: function (lst, value, start/* = 0 */, /* optional */end) {
1040 if (typeof(end) == "undefined" || end === null) {
1041 end = lst.length;
1042 }
1043 if (typeof(start) == "undefined" || start === null) {
1044 start = 0;
1045 }
1046 var cmp = MochiKit.Base.compare;
1047 for (var i = start; i < end; i++) {
1048 if (cmp(lst[i], value) === 0) {
1049 return i;
1050 }
1051 }
1052 return -1;
1053 },
1054
1055 /** @id MochiKit.Base.nodeWalk */
1056 nodeWalk: function (node, visitor) {
1057 var nodes = [node];
1058 var extend = MochiKit.Base.extend;
1059 while (nodes.length) {
1060 var res = visitor(nodes.shift());
1061 if (res) {
1062 extend(nodes, res);
1063 }
1064 }
1065 },
1066
1067
1068 /** @id MochiKit.Base.nameFunctions */
1069 nameFunctions: function (namespace) {
1070 var base = namespace.NAME;
1071 if (typeof(base) == 'undefined') {
1072 base = '';
1073 } else {
1074 base = base + '.';
1075 }
1076 for (var name in namespace) {
1077 var o = namespace[name];
1078 if (typeof(o) == 'function' && typeof(o.NAME) == 'undefined') {
1079 try {
1080 o.NAME = base + name;
1081 } catch (e) {
1082 // pass
1083 }
1084 }
1085 }
1086 },
1087
1088
1089 /** @id MochiKit.Base.queryString */
1090 queryString: function (names, values) {
1091 // check to see if names is a string or a DOM element, and if
1092 // MochiKit.DOM is available. If so, drop it like it's a form
1093 // Ugliest conditional in MochiKit? Probably!
1094 if (typeof(MochiKit.DOM) != "undefined" && arguments.length == 1
1095 && (typeof(names) == "string" || (
1096 typeof(names.nodeType) != "undefined" && names.nodeType > 0
1097 ))
1098 ) {
1099 var kv = MochiKit.DOM.formContents(names);
1100 names = kv[0];
1101 values = kv[1];
1102 } else if (arguments.length == 1) {
1103 // Allow the return value of formContents to be passed directly
1104 if (typeof(names.length) == "number" && names.length == 2) {
1105 return arguments.callee(names[0], names[1]);
1106 }
1107 var o = names;
1108 names = [];
1109 values = [];
1110 for (var k in o) {
1111 var v = o[k];
1112 if (typeof(v) == "function") {
1113 continue;
1114 } else if (MochiKit.Base.isArrayLike(v)){
1115 for (var i = 0; i < v.length; i++) {
1116 names.push(k);
1117 values.push(v[i]);
1118 }
1119 } else {
1120 names.push(k);
1121 values.push(v);
1122 }
1123 }
1124 }
1125 var rval = [];
1126 var len = Math.min(names.length, values.length);
1127 var urlEncode = MochiKit.Base.urlEncode;
1128 for (var i = 0; i < len; i++) {
1129 v = values[i];
1130 if (typeof(v) != 'undefined' && v !== null) {
1131 rval.push(urlEncode(names[i]) + "=" + urlEncode(v));
1132 }
1133 }
1134 return rval.join("&");
1135 },
1136
1137
1138 /** @id MochiKit.Base.parseQueryString */
1139 parseQueryString: function (encodedString, useArrays) {
1140 // strip a leading '?' from the encoded string
1141 var qstr = (encodedString.charAt(0) == "?")
1142 ? encodedString.substring(1)
1143 : encodedString;
1144 var pairs = qstr.replace(/\+/g, "%20").split(/(\&amp\;|\&\#38\;|\&#x26;|\&)/);
1145 var o = {};
1146 var decode;
1147 if (typeof(decodeURIComponent) != "undefined") {
1148 decode = decodeURIComponent;
1149 } else {
1150 decode = unescape;
1151 }
1152 if (useArrays) {
1153 for (var i = 0; i < pairs.length; i++) {
1154 var pair = pairs[i].split("=");
1155 var name = decode(pair.shift());
1156 if (!name) {
1157 continue;
1158 }
1159 var arr = o[name];
1160 if (!(arr instanceof Array)) {
1161 arr = [];
1162 o[name] = arr;
1163 }
1164 arr.push(decode(pair.join("=")));
1165 }
1166 } else {
1167 for (i = 0; i < pairs.length; i++) {
1168 pair = pairs[i].split("=");
1169 var name = pair.shift();
1170 if (!name) {
1171 continue;
1172 }
1173 o[decode(name)] = decode(pair.join("="));
1174 }
1175 }
1176 return o;
1177 }
1178 });
1179
1180 /** @id MochiKit.Base.AdapterRegistry */
1181 MochiKit.Base.AdapterRegistry = function () {
1182 this.pairs = [];
1183 };
1184
1185 MochiKit.Base.AdapterRegistry.prototype = {
1186 /** @id MochiKit.Base.AdapterRegistry.prototype.register */
1187 register: function (name, check, wrap, /* optional */ override) {
1188 if (override) {
1189 this.pairs.unshift([name, check, wrap]);
1190 } else {
1191 this.pairs.push([name, check, wrap]);
1192 }
1193 },
1194
1195 /** @id MochiKit.Base.AdapterRegistry.prototype.match */
1196 match: function (/* ... */) {
1197 for (var i = 0; i < this.pairs.length; i++) {
1198 var pair = this.pairs[i];
1199 if (pair[1].apply(this, arguments)) {
1200 return pair[2].apply(this, arguments);
1201 }
1202 }
1203 throw MochiKit.Base.NotFound;
1204 },
1205
1206 /** @id MochiKit.Base.AdapterRegistry.prototype.unregister */
1207 unregister: function (name) {
1208 for (var i = 0; i < this.pairs.length; i++) {
1209 var pair = this.pairs[i];
1210 if (pair[0] == name) {
1211 this.pairs.splice(i, 1);
1212 return true;
1213 }
1214 }
1215 return false;
1216 }
1217 };
1218
1219
1220 MochiKit.Base.EXPORT = [
1221 "flattenArray",
1222 "noop",
1223 "camelize",
1224 "counter",
1225 "clone",
1226 "extend",
1227 "update",
1228 "updatetree",
1229 "setdefault",
1230 "keys",
1231 "values",
1232 "items",
1233 "NamedError",
1234 "operator",
1235 "forwardCall",
1236 "itemgetter",
1237 "typeMatcher",
1238 "isCallable",
1239 "isUndefined",
1240 "isUndefinedOrNull",
1241 "isNull",
1242 "isEmpty",
1243 "isNotEmpty",
1244 "isArrayLike",
1245 "isDateLike",
1246 "xmap",
1247 "map",
1248 "xfilter",
1249 "filter",
1250 "methodcaller",
1251 "compose",
1252 "bind",
1253 "bindMethods",
1254 "NotFound",
1255 "AdapterRegistry",
1256 "registerComparator",
1257 "compare",
1258 "registerRepr",
1259 "repr",
1260 "objEqual",
1261 "arrayEqual",
1262 "concat",
1263 "keyComparator",
1264 "reverseKeyComparator",
1265 "partial",
1266 "merge",
1267 "listMinMax",
1268 "listMax",
1269 "listMin",
1270 "objMax",
1271 "objMin",
1272 "nodeWalk",
1273 "zip",
1274 "urlEncode",
1275 "queryString",
1276 "serializeJSON",
1277 "registerJSON",
1278 "evalJSON",
1279 "parseQueryString",
1280 "findValue",
1281 "findIdentical",
1282 "flattenArguments",
1283 "method",
1284 "average",
1285 "mean",
1286 "median"
1287 ];
1288
1289 MochiKit.Base.EXPORT_OK = [
1290 "nameFunctions",
1291 "comparatorRegistry",
1292 "reprRegistry",
1293 "jsonRegistry",
1294 "compareDateLike",
1295 "compareArrayLike",
1296 "reprArrayLike",
1297 "reprString",
1298 "reprNumber"
1299 ];
1300
1301 MochiKit.Base._exportSymbols = function (globals, module) {
1302 if (!MochiKit.__export__) {
1303 return;
1304 }
1305 var all = module.EXPORT_TAGS[":all"];
1306 for (var i = 0; i < all.length; i++) {
1307 globals[all[i]] = module[all[i]];
1308 }
1309 };
1310
1311 MochiKit.Base.__new__ = function () {
1312 // A singleton raised when no suitable adapter is found
1313 var m = this;
1314
1315 // convenience
1316 /** @id MochiKit.Base.noop */
1317 m.noop = m.operator.identity;
1318
1319 // Backwards compat
1320 m.forward = m.forwardCall;
1321 m.find = m.findValue;
1322
1323 if (typeof(encodeURIComponent) != "undefined") {
1324 /** @id MochiKit.Base.urlEncode */
1325 m.urlEncode = function (unencoded) {
1326 return encodeURIComponent(unencoded).replace(/\'/g, '%27');
1327 };
1328 } else {
1329 m.urlEncode = function (unencoded) {
1330 return escape(unencoded
1331 ).replace(/\+/g, '%2B'
1332 ).replace(/\"/g,'%22'
1333 ).rval.replace(/\'/g, '%27');
1334 };
1335 }
1336
1337 /** @id MochiKit.Base.NamedError */
1338 m.NamedError = function (name) {
1339 this.message = name;
1340 this.name = name;
1341 };
1342 m.NamedError.prototype = new Error();
1343 m.update(m.NamedError.prototype, {
1344 repr: function () {
1345 if (this.message && this.message != this.name) {
1346 return this.name + "(" + m.repr(this.message) + ")";
1347 } else {
1348 return this.name + "()";
1349 }
1350 },
1351 toString: m.forwardCall("repr")
1352 });
1353
1354 /** @id MochiKit.Base.NotFound */
1355 m.NotFound = new m.NamedError("MochiKit.Base.NotFound");
1356
1357
1358 /** @id MochiKit.Base.listMax */
1359 m.listMax = m.partial(m.listMinMax, 1);
1360 /** @id MochiKit.Base.listMin */
1361 m.listMin = m.partial(m.listMinMax, -1);
1362
1363 /** @id MochiKit.Base.isCallable */
1364 m.isCallable = m.typeMatcher('function');
1365 /** @id MochiKit.Base.isUndefined */
1366 m.isUndefined = m.typeMatcher('undefined');
1367
1368 /** @id MochiKit.Base.merge */
1369 m.merge = m.partial(m.update, null);
1370 /** @id MochiKit.Base.zip */
1371 m.zip = m.partial(m.map, null);
1372
1373 /** @id MochiKit.Base.average */
1374 m.average = m.mean;
1375
1376 /** @id MochiKit.Base.comparatorRegistry */
1377 m.comparatorRegistry = new m.AdapterRegistry();
1378 m.registerComparator("dateLike", m.isDateLike, m.compareDateLike);
1379 m.registerComparator("arrayLike", m.isArrayLike, m.compareArrayLike);
1380
1381 /** @id MochiKit.Base.reprRegistry */
1382 m.reprRegistry = new m.AdapterRegistry();
1383 m.registerRepr("arrayLike", m.isArrayLike, m.reprArrayLike);
1384 m.registerRepr("string", m.typeMatcher("string"), m.reprString);
1385 m.registerRepr("numbers", m.typeMatcher("number", "boolean"), m.reprNumber);
1386
1387 /** @id MochiKit.Base.jsonRegistry */
1388 m.jsonRegistry = new m.AdapterRegistry();
1389
1390 var all = m.concat(m.EXPORT, m.EXPORT_OK);
1391 m.EXPORT_TAGS = {
1392 ":common": m.concat(m.EXPORT_OK),
1393 ":all": all
1394 };
1395
1396 m.nameFunctions(this);
1397
1398 };
1399
1400 MochiKit.Base.__new__();
1401
1402 //
1403 // XXX: Internet Explorer blows
1404 //
1405 if (MochiKit.__export__) {
1406 compare = MochiKit.Base.compare;
1407 compose = MochiKit.Base.compose;
1408 serializeJSON = MochiKit.Base.serializeJSON;
1409 mean = MochiKit.Base.mean;
1410 median = MochiKit.Base.median;
1411 }
1412
1413 MochiKit.Base._exportSymbols(this, MochiKit.Base);
This diff has been collapsed as it changes many lines, (902 lines changed) Show them Hide them
@@ -0,0 +1,902 b''
1 /***
2
3 MochiKit.Color 1.4
4
5 See <http://mochikit.com/> for documentation, downloads, license, etc.
6
7 (c) 2005 Bob Ippolito and others. All rights Reserved.
8
9 ***/
10
11 if (typeof(dojo) != 'undefined') {
12 dojo.provide('MochiKit.Color');
13 dojo.require('MochiKit.Base');
14 dojo.require('MochiKit.DOM');
15 dojo.require('MochiKit.Style');
16 }
17
18 if (typeof(JSAN) != 'undefined') {
19 JSAN.use("MochiKit.Base", []);
20 JSAN.use("MochiKit.DOM", []);
21 JSAN.use("MochiKit.Style", []);
22 }
23
24 try {
25 if (typeof(MochiKit.Base) == 'undefined') {
26 throw "";
27 }
28 } catch (e) {
29 throw "MochiKit.Color depends on MochiKit.Base";
30 }
31
32 try {
33 if (typeof(MochiKit.DOM) == 'undefined') {
34 throw "";
35 }
36 } catch (e) {
37 throw "MochiKit.Color depends on MochiKit.DOM";
38 }
39
40 try {
41 if (typeof(MochiKit.Style) == 'undefined') {
42 throw "";
43 }
44 } catch (e) {
45 throw "MochiKit.Color depends on MochiKit.Style";
46 }
47
48 if (typeof(MochiKit.Color) == "undefined") {
49 MochiKit.Color = {};
50 }
51
52 MochiKit.Color.NAME = "MochiKit.Color";
53 MochiKit.Color.VERSION = "1.4";
54
55 MochiKit.Color.__repr__ = function () {
56 return "[" + this.NAME + " " + this.VERSION + "]";
57 };
58
59 MochiKit.Color.toString = function () {
60 return this.__repr__();
61 };
62
63
64 /** @id MochiKit.Color.Color */
65 MochiKit.Color.Color = function (red, green, blue, alpha) {
66 if (typeof(alpha) == 'undefined' || alpha === null) {
67 alpha = 1.0;
68 }
69 this.rgb = {
70 r: red,
71 g: green,
72 b: blue,
73 a: alpha
74 };
75 };
76
77
78 // Prototype methods
79
80 MochiKit.Color.Color.prototype = {
81
82 __class__: MochiKit.Color.Color,
83
84 /** @id MochiKit.Color.Color.prototype.colorWithAlpha */
85 colorWithAlpha: function (alpha) {
86 var rgb = this.rgb;
87 var m = MochiKit.Color;
88 return m.Color.fromRGB(rgb.r, rgb.g, rgb.b, alpha);
89 },
90
91 /** @id MochiKit.Color.Color.prototype.colorWithHue */
92 colorWithHue: function (hue) {
93 // get an HSL model, and set the new hue...
94 var hsl = this.asHSL();
95 hsl.h = hue;
96 var m = MochiKit.Color;
97 // convert back to RGB...
98 return m.Color.fromHSL(hsl);
99 },
100
101 /** @id MochiKit.Color.Color.prototype.colorWithSaturation */
102 colorWithSaturation: function (saturation) {
103 // get an HSL model, and set the new hue...
104 var hsl = this.asHSL();
105 hsl.s = saturation;
106 var m = MochiKit.Color;
107 // convert back to RGB...
108 return m.Color.fromHSL(hsl);
109 },
110
111 /** @id MochiKit.Color.Color.prototype.colorWithLightness */
112 colorWithLightness: function (lightness) {
113 // get an HSL model, and set the new hue...
114 var hsl = this.asHSL();
115 hsl.l = lightness;
116 var m = MochiKit.Color;
117 // convert back to RGB...
118 return m.Color.fromHSL(hsl);
119 },
120
121 /** @id MochiKit.Color.Color.prototype.darkerColorWithLevel */
122 darkerColorWithLevel: function (level) {
123 var hsl = this.asHSL();
124 hsl.l = Math.max(hsl.l - level, 0);
125 var m = MochiKit.Color;
126 return m.Color.fromHSL(hsl);
127 },
128
129 /** @id MochiKit.Color.Color.prototype.lighterColorWithLevel */
130 lighterColorWithLevel: function (level) {
131 var hsl = this.asHSL();
132 hsl.l = Math.min(hsl.l + level, 1);
133 var m = MochiKit.Color;
134 return m.Color.fromHSL(hsl);
135 },
136
137 /** @id MochiKit.Color.Color.prototype.blendedColor */
138 blendedColor: function (other, /* optional */ fraction) {
139 if (typeof(fraction) == 'undefined' || fraction === null) {
140 fraction = 0.5;
141 }
142 var sf = 1.0 - fraction;
143 var s = this.rgb;
144 var d = other.rgb;
145 var df = fraction;
146 return MochiKit.Color.Color.fromRGB(
147 (s.r * sf) + (d.r * df),
148 (s.g * sf) + (d.g * df),
149 (s.b * sf) + (d.b * df),
150 (s.a * sf) + (d.a * df)
151 );
152 },
153
154 /** @id MochiKit.Color.Color.prototype.compareRGB */
155 compareRGB: function (other) {
156 var a = this.asRGB();
157 var b = other.asRGB();
158 return MochiKit.Base.compare(
159 [a.r, a.g, a.b, a.a],
160 [b.r, b.g, b.b, b.a]
161 );
162 },
163
164 /** @id MochiKit.Color.Color.prototype.isLight */
165 isLight: function () {
166 return this.asHSL().b > 0.5;
167 },
168
169 /** @id MochiKit.Color.Color.prototype.isDark */
170 isDark: function () {
171 return (!this.isLight());
172 },
173
174 /** @id MochiKit.Color.Color.prototype.toHSLString */
175 toHSLString: function () {
176 var c = this.asHSL();
177 var ccc = MochiKit.Color.clampColorComponent;
178 var rval = this._hslString;
179 if (!rval) {
180 var mid = (
181 ccc(c.h, 360).toFixed(0)
182 + "," + ccc(c.s, 100).toPrecision(4) + "%"
183 + "," + ccc(c.l, 100).toPrecision(4) + "%"
184 );
185 var a = c.a;
186 if (a >= 1) {
187 a = 1;
188 rval = "hsl(" + mid + ")";
189 } else {
190 if (a <= 0) {
191 a = 0;
192 }
193 rval = "hsla(" + mid + "," + a + ")";
194 }
195 this._hslString = rval;
196 }
197 return rval;
198 },
199
200 /** @id MochiKit.Color.Color.prototype.toRGBString */
201 toRGBString: function () {
202 var c = this.rgb;
203 var ccc = MochiKit.Color.clampColorComponent;
204 var rval = this._rgbString;
205 if (!rval) {
206 var mid = (
207 ccc(c.r, 255).toFixed(0)
208 + "," + ccc(c.g, 255).toFixed(0)
209 + "," + ccc(c.b, 255).toFixed(0)
210 );
211 if (c.a != 1) {
212 rval = "rgba(" + mid + "," + c.a + ")";
213 } else {
214 rval = "rgb(" + mid + ")";
215 }
216 this._rgbString = rval;
217 }
218 return rval;
219 },
220
221 /** @id MochiKit.Color.Color.prototype.asRGB */
222 asRGB: function () {
223 return MochiKit.Base.clone(this.rgb);
224 },
225
226 /** @id MochiKit.Color.Color.prototype.toHexString */
227 toHexString: function () {
228 var m = MochiKit.Color;
229 var c = this.rgb;
230 var ccc = MochiKit.Color.clampColorComponent;
231 var rval = this._hexString;
232 if (!rval) {
233 rval = ("#" +
234 m.toColorPart(ccc(c.r, 255)) +
235 m.toColorPart(ccc(c.g, 255)) +
236 m.toColorPart(ccc(c.b, 255))
237 );
238 this._hexString = rval;
239 }
240 return rval;
241 },
242
243 /** @id MochiKit.Color.Color.prototype.asHSV */
244 asHSV: function () {
245 var hsv = this.hsv;
246 var c = this.rgb;
247 if (typeof(hsv) == 'undefined' || hsv === null) {
248 hsv = MochiKit.Color.rgbToHSV(this.rgb);
249 this.hsv = hsv;
250 }
251 return MochiKit.Base.clone(hsv);
252 },
253
254 /** @id MochiKit.Color.Color.prototype.asHSL */
255 asHSL: function () {
256 var hsl = this.hsl;
257 var c = this.rgb;
258 if (typeof(hsl) == 'undefined' || hsl === null) {
259 hsl = MochiKit.Color.rgbToHSL(this.rgb);
260 this.hsl = hsl;
261 }
262 return MochiKit.Base.clone(hsl);
263 },
264
265 /** @id MochiKit.Color.Color.prototype.toString */
266 toString: function () {
267 return this.toRGBString();
268 },
269
270 /** @id MochiKit.Color.Color.prototype.repr */
271 repr: function () {
272 var c = this.rgb;
273 var col = [c.r, c.g, c.b, c.a];
274 return this.__class__.NAME + "(" + col.join(", ") + ")";
275 }
276
277 };
278
279 // Constructor methods
280
281 MochiKit.Base.update(MochiKit.Color.Color, {
282 /** @id MochiKit.Color.Color.fromRGB */
283 fromRGB: function (red, green, blue, alpha) {
284 // designated initializer
285 var Color = MochiKit.Color.Color;
286 if (arguments.length == 1) {
287 var rgb = red;
288 red = rgb.r;
289 green = rgb.g;
290 blue = rgb.b;
291 if (typeof(rgb.a) == 'undefined') {
292 alpha = undefined;
293 } else {
294 alpha = rgb.a;
295 }
296 }
297 return new Color(red, green, blue, alpha);
298 },
299
300 /** @id MochiKit.Color.Color.fromHSL */
301 fromHSL: function (hue, saturation, lightness, alpha) {
302 var m = MochiKit.Color;
303 return m.Color.fromRGB(m.hslToRGB.apply(m, arguments));
304 },
305
306 /** @id MochiKit.Color.Color.fromHSV */
307 fromHSV: function (hue, saturation, value, alpha) {
308 var m = MochiKit.Color;
309 return m.Color.fromRGB(m.hsvToRGB.apply(m, arguments));
310 },
311
312 /** @id MochiKit.Color.Color.fromName */
313 fromName: function (name) {
314 var Color = MochiKit.Color.Color;
315 // Opera 9 seems to "quote" named colors(?!)
316 if (name.charAt(0) == '"') {
317 name = name.substr(1, name.length - 2);
318 }
319 var htmlColor = Color._namedColors[name.toLowerCase()];
320 if (typeof(htmlColor) == 'string') {
321 return Color.fromHexString(htmlColor);
322 } else if (name == "transparent") {
323 return Color.transparentColor();
324 }
325 return null;
326 },
327
328 /** @id MochiKit.Color.Color.fromString */
329 fromString: function (colorString) {
330 var self = MochiKit.Color.Color;
331 var three = colorString.substr(0, 3);
332 if (three == "rgb") {
333 return self.fromRGBString(colorString);
334 } else if (three == "hsl") {
335 return self.fromHSLString(colorString);
336 } else if (colorString.charAt(0) == "#") {
337 return self.fromHexString(colorString);
338 }
339 return self.fromName(colorString);
340 },
341
342
343 /** @id MochiKit.Color.Color.fromHexString */
344 fromHexString: function (hexCode) {
345 if (hexCode.charAt(0) == '#') {
346 hexCode = hexCode.substring(1);
347 }
348 var components = [];
349 var i, hex;
350 if (hexCode.length == 3) {
351 for (i = 0; i < 3; i++) {
352 hex = hexCode.substr(i, 1);
353 components.push(parseInt(hex + hex, 16) / 255.0);
354 }
355 } else {
356 for (i = 0; i < 6; i += 2) {
357 hex = hexCode.substr(i, 2);
358 components.push(parseInt(hex, 16) / 255.0);
359 }
360 }
361 var Color = MochiKit.Color.Color;
362 return Color.fromRGB.apply(Color, components);
363 },
364
365
366 _fromColorString: function (pre, method, scales, colorCode) {
367 // parses either HSL or RGB
368 if (colorCode.indexOf(pre) === 0) {
369 colorCode = colorCode.substring(colorCode.indexOf("(", 3) + 1, colorCode.length - 1);
370 }
371 var colorChunks = colorCode.split(/\s*,\s*/);
372 var colorFloats = [];
373 for (var i = 0; i < colorChunks.length; i++) {
374 var c = colorChunks[i];
375 var val;
376 var three = c.substring(c.length - 3);
377 if (c.charAt(c.length - 1) == '%') {
378 val = 0.01 * parseFloat(c.substring(0, c.length - 1));
379 } else if (three == "deg") {
380 val = parseFloat(c) / 360.0;
381 } else if (three == "rad") {
382 val = parseFloat(c) / (Math.PI * 2);
383 } else {
384 val = scales[i] * parseFloat(c);
385 }
386 colorFloats.push(val);
387 }
388 return this[method].apply(this, colorFloats);
389 },
390
391 /** @id MochiKit.Color.Color.fromComputedStyle */
392 fromComputedStyle: function (elem, style) {
393 var d = MochiKit.DOM;
394 var cls = MochiKit.Color.Color;
395 for (elem = d.getElement(elem); elem; elem = elem.parentNode) {
396 var actualColor = MochiKit.Style.getStyle.apply(d, arguments);
397 if (!actualColor) {
398 continue;
399 }
400 var color = cls.fromString(actualColor);
401 if (!color) {
402 break;
403 }
404 if (color.asRGB().a > 0) {
405 return color;
406 }
407 }
408 return null;
409 },
410
411 /** @id MochiKit.Color.Color.fromBackground */
412 fromBackground: function (elem) {
413 var cls = MochiKit.Color.Color;
414 return cls.fromComputedStyle(
415 elem, "backgroundColor", "background-color") || cls.whiteColor();
416 },
417
418 /** @id MochiKit.Color.Color.fromText */
419 fromText: function (elem) {
420 var cls = MochiKit.Color.Color;
421 return cls.fromComputedStyle(
422 elem, "color", "color") || cls.blackColor();
423 },
424
425 /** @id MochiKit.Color.Color.namedColors */
426 namedColors: function () {
427 return MochiKit.Base.clone(MochiKit.Color.Color._namedColors);
428 }
429 });
430
431
432 // Module level functions
433
434 MochiKit.Base.update(MochiKit.Color, {
435 /** @id MochiKit.Color.clampColorComponent */
436 clampColorComponent: function (v, scale) {
437 v *= scale;
438 if (v < 0) {
439 return 0;
440 } else if (v > scale) {
441 return scale;
442 } else {
443 return v;
444 }
445 },
446
447 _hslValue: function (n1, n2, hue) {
448 if (hue > 6.0) {
449 hue -= 6.0;
450 } else if (hue < 0.0) {
451 hue += 6.0;
452 }
453 var val;
454 if (hue < 1.0) {
455 val = n1 + (n2 - n1) * hue;
456 } else if (hue < 3.0) {
457 val = n2;
458 } else if (hue < 4.0) {
459 val = n1 + (n2 - n1) * (4.0 - hue);
460 } else {
461 val = n1;
462 }
463 return val;
464 },
465
466 /** @id MochiKit.Color.hsvToRGB */
467 hsvToRGB: function (hue, saturation, value, alpha) {
468 if (arguments.length == 1) {
469 var hsv = hue;
470 hue = hsv.h;
471 saturation = hsv.s;
472 value = hsv.v;
473 alpha = hsv.a;
474 }
475 var red;
476 var green;
477 var blue;
478 if (saturation === 0) {
479 red = value;
480 green = value;
481 blue = value;
482 } else {
483 var i = Math.floor(hue * 6);
484 var f = (hue * 6) - i;
485 var p = value * (1 - saturation);
486 var q = value * (1 - (saturation * f));
487 var t = value * (1 - (saturation * (1 - f)));
488 switch (i) {
489 case 1: red = q; green = value; blue = p; break;
490 case 2: red = p; green = value; blue = t; break;
491 case 3: red = p; green = q; blue = value; break;
492 case 4: red = t; green = p; blue = value; break;
493 case 5: red = value; green = p; blue = q; break;
494 case 6: // fall through
495 case 0: red = value; green = t; blue = p; break;
496 }
497 }
498 return {
499 r: red,
500 g: green,
501 b: blue,
502 a: alpha
503 };
504 },
505
506 /** @id MochiKit.Color.hslToRGB */
507 hslToRGB: function (hue, saturation, lightness, alpha) {
508 if (arguments.length == 1) {
509 var hsl = hue;
510 hue = hsl.h;
511 saturation = hsl.s;
512 lightness = hsl.l;
513 alpha = hsl.a;
514 }
515 var red;
516 var green;
517 var blue;
518 if (saturation === 0) {
519 red = lightness;
520 green = lightness;
521 blue = lightness;
522 } else {
523 var m2;
524 if (lightness <= 0.5) {
525 m2 = lightness * (1.0 + saturation);
526 } else {
527 m2 = lightness + saturation - (lightness * saturation);
528 }
529 var m1 = (2.0 * lightness) - m2;
530 var f = MochiKit.Color._hslValue;
531 var h6 = hue * 6.0;
532 red = f(m1, m2, h6 + 2);
533 green = f(m1, m2, h6);
534 blue = f(m1, m2, h6 - 2);
535 }
536 return {
537 r: red,
538 g: green,
539 b: blue,
540 a: alpha
541 };
542 },
543
544 /** @id MochiKit.Color.rgbToHSV */
545 rgbToHSV: function (red, green, blue, alpha) {
546 if (arguments.length == 1) {
547 var rgb = red;
548 red = rgb.r;
549 green = rgb.g;
550 blue = rgb.b;
551 alpha = rgb.a;
552 }
553 var max = Math.max(Math.max(red, green), blue);
554 var min = Math.min(Math.min(red, green), blue);
555 var hue;
556 var saturation;
557 var value = max;
558 if (min == max) {
559 hue = 0;
560 saturation = 0;
561 } else {
562 var delta = (max - min);
563 saturation = delta / max;
564
565 if (red == max) {
566 hue = (green - blue) / delta;
567 } else if (green == max) {
568 hue = 2 + ((blue - red) / delta);
569 } else {
570 hue = 4 + ((red - green) / delta);
571 }
572 hue /= 6;
573 if (hue < 0) {
574 hue += 1;
575 }
576 if (hue > 1) {
577 hue -= 1;
578 }
579 }
580 return {
581 h: hue,
582 s: saturation,
583 v: value,
584 a: alpha
585 };
586 },
587
588 /** @id MochiKit.Color.rgbToHSL */
589 rgbToHSL: function (red, green, blue, alpha) {
590 if (arguments.length == 1) {
591 var rgb = red;
592 red = rgb.r;
593 green = rgb.g;
594 blue = rgb.b;
595 alpha = rgb.a;
596 }
597 var max = Math.max(red, Math.max(green, blue));
598 var min = Math.min(red, Math.min(green, blue));
599 var hue;
600 var saturation;
601 var lightness = (max + min) / 2.0;
602 var delta = max - min;
603 if (delta === 0) {
604 hue = 0;
605 saturation = 0;
606 } else {
607 if (lightness <= 0.5) {
608 saturation = delta / (max + min);
609 } else {
610 saturation = delta / (2 - max - min);
611 }
612 if (red == max) {
613 hue = (green - blue) / delta;
614 } else if (green == max) {
615 hue = 2 + ((blue - red) / delta);
616 } else {
617 hue = 4 + ((red - green) / delta);
618 }
619 hue /= 6;
620 if (hue < 0) {
621 hue += 1;
622 }
623 if (hue > 1) {
624 hue -= 1;
625 }
626
627 }
628 return {
629 h: hue,
630 s: saturation,
631 l: lightness,
632 a: alpha
633 };
634 },
635
636 /** @id MochiKit.Color.toColorPart */
637 toColorPart: function (num) {
638 num = Math.round(num);
639 var digits = num.toString(16);
640 if (num < 16) {
641 return '0' + digits;
642 }
643 return digits;
644 },
645
646 __new__: function () {
647 var m = MochiKit.Base;
648 /** @id MochiKit.Color.fromRGBString */
649 this.Color.fromRGBString = m.bind(
650 this.Color._fromColorString, this.Color, "rgb", "fromRGB",
651 [1.0/255.0, 1.0/255.0, 1.0/255.0, 1]
652 );
653 /** @id MochiKit.Color.fromHSLString */
654 this.Color.fromHSLString = m.bind(
655 this.Color._fromColorString, this.Color, "hsl", "fromHSL",
656 [1.0/360.0, 0.01, 0.01, 1]
657 );
658
659 var third = 1.0 / 3.0;
660 /** @id MochiKit.Color.colors */
661 var colors = {
662 // NSColor colors plus transparent
663 /** @id MochiKit.Color.blackColor */
664 black: [0, 0, 0],
665 /** @id MochiKit.Color.blueColor */
666 blue: [0, 0, 1],
667 /** @id MochiKit.Color.brownColor */
668 brown: [0.6, 0.4, 0.2],
669 /** @id MochiKit.Color.cyanColor */
670 cyan: [0, 1, 1],
671 /** @id MochiKit.Color.darkGrayColor */
672 darkGray: [third, third, third],
673 /** @id MochiKit.Color.grayColor */
674 gray: [0.5, 0.5, 0.5],
675 /** @id MochiKit.Color.greenColor */
676 green: [0, 1, 0],
677 /** @id MochiKit.Color.lightGrayColor */
678 lightGray: [2 * third, 2 * third, 2 * third],
679 /** @id MochiKit.Color.magentaColor */
680 magenta: [1, 0, 1],
681 /** @id MochiKit.Color.orangeColor */
682 orange: [1, 0.5, 0],
683 /** @id MochiKit.Color.purpleColor */
684 purple: [0.5, 0, 0.5],
685 /** @id MochiKit.Color.redColor */
686 red: [1, 0, 0],
687 /** @id MochiKit.Color.transparentColor */
688 transparent: [0, 0, 0, 0],
689 /** @id MochiKit.Color.whiteColor */
690 white: [1, 1, 1],
691 /** @id MochiKit.Color.yellowColor */
692 yellow: [1, 1, 0]
693 };
694
695 var makeColor = function (name, r, g, b, a) {
696 var rval = this.fromRGB(r, g, b, a);
697 this[name] = function () { return rval; };
698 return rval;
699 };
700
701 for (var k in colors) {
702 var name = k + "Color";
703 var bindArgs = m.concat(
704 [makeColor, this.Color, name],
705 colors[k]
706 );
707 this.Color[name] = m.bind.apply(null, bindArgs);
708 }
709
710 var isColor = function () {
711 for (var i = 0; i < arguments.length; i++) {
712 if (!(arguments[i] instanceof Color)) {
713 return false;
714 }
715 }
716 return true;
717 };
718
719 var compareColor = function (a, b) {
720 return a.compareRGB(b);
721 };
722
723 m.nameFunctions(this);
724
725 m.registerComparator(this.Color.NAME, isColor, compareColor);
726
727 this.EXPORT_TAGS = {
728 ":common": this.EXPORT,
729 ":all": m.concat(this.EXPORT, this.EXPORT_OK)
730 };
731
732 }
733 });
734
735 MochiKit.Color.EXPORT = [
736 "Color"
737 ];
738
739 MochiKit.Color.EXPORT_OK = [
740 "clampColorComponent",
741 "rgbToHSL",
742 "hslToRGB",
743 "rgbToHSV",
744 "hsvToRGB",
745 "toColorPart"
746 ];
747
748 MochiKit.Color.__new__();
749
750 MochiKit.Base._exportSymbols(this, MochiKit.Color);
751
752 // Full table of css3 X11 colors <http://www.w3.org/TR/css3-color/#X11COLORS>
753
754 MochiKit.Color.Color._namedColors = {
755 aliceblue: "#f0f8ff",
756 antiquewhite: "#faebd7",
757 aqua: "#00ffff",
758 aquamarine: "#7fffd4",
759 azure: "#f0ffff",
760 beige: "#f5f5dc",
761 bisque: "#ffe4c4",
762 black: "#000000",
763 blanchedalmond: "#ffebcd",
764 blue: "#0000ff",
765 blueviolet: "#8a2be2",
766 brown: "#a52a2a",
767 burlywood: "#deb887",
768 cadetblue: "#5f9ea0",
769 chartreuse: "#7fff00",
770 chocolate: "#d2691e",
771 coral: "#ff7f50",
772 cornflowerblue: "#6495ed",
773 cornsilk: "#fff8dc",
774 crimson: "#dc143c",
775 cyan: "#00ffff",
776 darkblue: "#00008b",
777 darkcyan: "#008b8b",
778 darkgoldenrod: "#b8860b",
779 darkgray: "#a9a9a9",
780 darkgreen: "#006400",
781 darkgrey: "#a9a9a9",
782 darkkhaki: "#bdb76b",
783 darkmagenta: "#8b008b",
784 darkolivegreen: "#556b2f",
785 darkorange: "#ff8c00",
786 darkorchid: "#9932cc",
787 darkred: "#8b0000",
788 darksalmon: "#e9967a",
789 darkseagreen: "#8fbc8f",
790 darkslateblue: "#483d8b",
791 darkslategray: "#2f4f4f",
792 darkslategrey: "#2f4f4f",
793 darkturquoise: "#00ced1",
794 darkviolet: "#9400d3",
795 deeppink: "#ff1493",
796 deepskyblue: "#00bfff",
797 dimgray: "#696969",
798 dimgrey: "#696969",
799 dodgerblue: "#1e90ff",
800 firebrick: "#b22222",
801 floralwhite: "#fffaf0",
802 forestgreen: "#228b22",
803 fuchsia: "#ff00ff",
804 gainsboro: "#dcdcdc",
805 ghostwhite: "#f8f8ff",
806 gold: "#ffd700",
807 goldenrod: "#daa520",
808 gray: "#808080",
809 green: "#008000",
810 greenyellow: "#adff2f",
811 grey: "#808080",
812 honeydew: "#f0fff0",
813 hotpink: "#ff69b4",
814 indianred: "#cd5c5c",
815 indigo: "#4b0082",
816 ivory: "#fffff0",
817 khaki: "#f0e68c",
818 lavender: "#e6e6fa",
819 lavenderblush: "#fff0f5",
820 lawngreen: "#7cfc00",
821 lemonchiffon: "#fffacd",
822 lightblue: "#add8e6",
823 lightcoral: "#f08080",
824 lightcyan: "#e0ffff",
825 lightgoldenrodyellow: "#fafad2",
826 lightgray: "#d3d3d3",
827 lightgreen: "#90ee90",
828 lightgrey: "#d3d3d3",
829 lightpink: "#ffb6c1",
830 lightsalmon: "#ffa07a",
831 lightseagreen: "#20b2aa",
832 lightskyblue: "#87cefa",
833 lightslategray: "#778899",
834 lightslategrey: "#778899",
835 lightsteelblue: "#b0c4de",
836 lightyellow: "#ffffe0",
837 lime: "#00ff00",
838 limegreen: "#32cd32",
839 linen: "#faf0e6",
840 magenta: "#ff00ff",
841 maroon: "#800000",
842 mediumaquamarine: "#66cdaa",
843 mediumblue: "#0000cd",
844 mediumorchid: "#ba55d3",
845 mediumpurple: "#9370db",
846 mediumseagreen: "#3cb371",
847 mediumslateblue: "#7b68ee",
848 mediumspringgreen: "#00fa9a",
849 mediumturquoise: "#48d1cc",
850 mediumvioletred: "#c71585",
851 midnightblue: "#191970",
852 mintcream: "#f5fffa",
853 mistyrose: "#ffe4e1",
854 moccasin: "#ffe4b5",
855 navajowhite: "#ffdead",
856 navy: "#000080",
857 oldlace: "#fdf5e6",
858 olive: "#808000",
859 olivedrab: "#6b8e23",
860 orange: "#ffa500",
861 orangered: "#ff4500",
862 orchid: "#da70d6",
863 palegoldenrod: "#eee8aa",
864 palegreen: "#98fb98",
865 paleturquoise: "#afeeee",
866 palevioletred: "#db7093",
867 papayawhip: "#ffefd5",
868 peachpuff: "#ffdab9",
869 peru: "#cd853f",
870 pink: "#ffc0cb",
871 plum: "#dda0dd",
872 powderblue: "#b0e0e6",
873 purple: "#800080",
874 red: "#ff0000",
875 rosybrown: "#bc8f8f",
876 royalblue: "#4169e1",
877 saddlebrown: "#8b4513",
878 salmon: "#fa8072",
879 sandybrown: "#f4a460",
880 seagreen: "#2e8b57",
881 seashell: "#fff5ee",
882 sienna: "#a0522d",
883 silver: "#c0c0c0",
884 skyblue: "#87ceeb",
885 slateblue: "#6a5acd",
886 slategray: "#708090",
887 slategrey: "#708090",
888 snow: "#fffafa",
889 springgreen: "#00ff7f",
890 steelblue: "#4682b4",
891 tan: "#d2b48c",
892 teal: "#008080",
893 thistle: "#d8bfd8",
894 tomato: "#ff6347",
895 turquoise: "#40e0d0",
896 violet: "#ee82ee",
897 wheat: "#f5deb3",
898 white: "#ffffff",
899 whitesmoke: "#f5f5f5",
900 yellow: "#ffff00",
901 yellowgreen: "#9acd32"
902 };
This diff has been collapsed as it changes many lines, (1281 lines changed) Show them Hide them
@@ -0,0 +1,1281 b''
1 /***
2
3 MochiKit.DOM 1.4
4
5 See <http://mochikit.com/> for documentation, downloads, license, etc.
6
7 (c) 2005 Bob Ippolito. All rights Reserved.
8
9 ***/
10
11 if (typeof(dojo) != 'undefined') {
12 dojo.provide("MochiKit.DOM");
13 dojo.require("MochiKit.Base");
14 }
15 if (typeof(JSAN) != 'undefined') {
16 JSAN.use("MochiKit.Base", []);
17 }
18
19 try {
20 if (typeof(MochiKit.Base) == 'undefined') {
21 throw "";
22 }
23 } catch (e) {
24 throw "MochiKit.DOM depends on MochiKit.Base!";
25 }
26
27 if (typeof(MochiKit.DOM) == 'undefined') {
28 MochiKit.DOM = {};
29 }
30
31 MochiKit.DOM.NAME = "MochiKit.DOM";
32 MochiKit.DOM.VERSION = "1.4";
33 MochiKit.DOM.__repr__ = function () {
34 return "[" + this.NAME + " " + this.VERSION + "]";
35 };
36 MochiKit.DOM.toString = function () {
37 return this.__repr__();
38 };
39
40 MochiKit.DOM.EXPORT = [
41 "removeEmptyTextNodes",
42 "formContents",
43 "currentWindow",
44 "currentDocument",
45 "withWindow",
46 "withDocument",
47 "registerDOMConverter",
48 "coerceToDOM",
49 "createDOM",
50 "createDOMFunc",
51 "isChildNode",
52 "getNodeAttribute",
53 "removeNodeAttribute",
54 "setNodeAttribute",
55 "updateNodeAttributes",
56 "appendChildNodes",
57 "insertSiblingNodesAfter",
58 "insertSiblingNodesBefore",
59 "replaceChildNodes",
60 "removeElement",
61 "swapDOM",
62 "BUTTON",
63 "TT",
64 "PRE",
65 "H1",
66 "H2",
67 "H3",
68 "BR",
69 "CANVAS",
70 "HR",
71 "LABEL",
72 "TEXTAREA",
73 "FORM",
74 "STRONG",
75 "SELECT",
76 "OPTION",
77 "OPTGROUP",
78 "LEGEND",
79 "FIELDSET",
80 "P",
81 "UL",
82 "OL",
83 "LI",
84 "TD",
85 "TR",
86 "THEAD",
87 "TBODY",
88 "TFOOT",
89 "TABLE",
90 "TH",
91 "INPUT",
92 "SPAN",
93 "A",
94 "DIV",
95 "IMG",
96 "getElement",
97 "$",
98 "getElementsByTagAndClassName",
99 "addToCallStack",
100 "addLoadEvent",
101 "focusOnLoad",
102 "setElementClass",
103 "toggleElementClass",
104 "addElementClass",
105 "removeElementClass",
106 "swapElementClass",
107 "hasElementClass",
108 "escapeHTML",
109 "toHTML",
110 "emitHTML",
111 "scrapeText",
112 "isParent",
113 "getFirstParentByTagAndClassName",
114 "makeClipping",
115 "undoClipping",
116 "makePositioned",
117 "undoPositioned",
118 "getFirstElementByTagAndClassName"
119 ];
120
121 MochiKit.DOM.EXPORT_OK = [
122 "domConverters"
123 ];
124
125 MochiKit.DOM.DEPRECATED = [
126 ['computedStyle', 'MochiKit.Style.getStyle', '1.4'],
127 /** @id MochiKit.DOM.elementDimensions */
128 ['elementDimensions', 'MochiKit.Style.getElementDimensions', '1.4'],
129 /** @id MochiKit.DOM.elementPosition */
130 ['elementPosition', 'MochiKit.Style.getElementPosition', '1.4'],
131 ['hideElement', 'MochiKit.Style.hideElement', '1.4'],
132 /** @id MochiKit.DOM.setElementDimensions */
133 ['setElementDimensions', 'MochiKit.Style.setElementDimensions', '1.4'],
134 /** @id MochiKit.DOM.setElementPosition */
135 ['setElementPosition', 'MochiKit.Style.setElementPosition', '1.4'],
136 ['setDisplayForElement', 'MochiKit.Style.setDisplayForElement', '1.4'],
137 /** @id MochiKit.DOM.setOpacity */
138 ['setOpacity', 'MochiKit.Style.setOpacity', '1.4'],
139 ['showElement', 'MochiKit.Style.showElement', '1.4'],
140 /** @id MochiKit.DOM.Coordinates */
141 ['Coordinates', 'MochiKit.Style.Coordinates', '1.4'], // FIXME: broken
142 /** @id MochiKit.DOM.Dimensions */
143 ['Dimensions', 'MochiKit.Style.Dimensions', '1.4'] // FIXME: broken
144 ];
145
146 /** @id MochiKit.DOM.getViewportDimensions */
147 MochiKit.DOM.getViewportDimensions = new Function('' +
148 'if (!MochiKit["Style"]) {' +
149 ' throw new Error("This function has been deprecated and depends on MochiKit.Style.");' +
150 '}' +
151 'return MochiKit.Style.getViewportDimensions.apply(this, arguments);');
152
153 MochiKit.Base.update(MochiKit.DOM, {
154
155 /** @id MochiKit.DOM.currentWindow */
156 currentWindow: function () {
157 return MochiKit.DOM._window;
158 },
159
160 /** @id MochiKit.DOM.currentDocument */
161 currentDocument: function () {
162 return MochiKit.DOM._document;
163 },
164
165 /** @id MochiKit.DOM.withWindow */
166 withWindow: function (win, func) {
167 var self = MochiKit.DOM;
168 var oldDoc = self._document;
169 var oldWin = self._window;
170 var rval;
171 try {
172 self._window = win;
173 self._document = win.document;
174 rval = func();
175 } catch (e) {
176 self._window = oldWin;
177 self._document = oldDoc;
178 throw e;
179 }
180 self._window = oldWin;
181 self._document = oldDoc;
182 return rval;
183 },
184
185 /** @id MochiKit.DOM.formContents */
186 formContents: function (elem/* = document.body */) {
187 var names = [];
188 var values = [];
189 var m = MochiKit.Base;
190 var self = MochiKit.DOM;
191 if (typeof(elem) == "undefined" || elem === null) {
192 elem = self._document.body;
193 } else {
194 elem = self.getElement(elem);
195 }
196 m.nodeWalk(elem, function (elem) {
197 var name = elem.name;
198 if (m.isNotEmpty(name)) {
199 var tagName = elem.tagName.toUpperCase();
200 if (tagName === "INPUT"
201 && (elem.type == "radio" || elem.type == "checkbox")
202 && !elem.checked
203 ) {
204 return null;
205 }
206 if (tagName === "SELECT") {
207 if (elem.type == "select-one") {
208 if (elem.selectedIndex >= 0) {
209 var opt = elem.options[elem.selectedIndex];
210 var v = opt.value;
211 if (!v) {
212 var h = opt.outerHTML;
213 // internet explorer sure does suck.
214 if (h && !h.match(/^[^>]+\svalue\s*=/i)) {
215 v = opt.text;
216 }
217 }
218 names.push(name);
219 values.push(v);
220 return null;
221 }
222 // no form elements?
223 names.push(name);
224 values.push("");
225 return null;
226 } else {
227 var opts = elem.options;
228 if (!opts.length) {
229 names.push(name);
230 values.push("");
231 return null;
232 }
233 for (var i = 0; i < opts.length; i++) {
234 var opt = opts[i];
235 if (!opt.selected) {
236 continue;
237 }
238 var v = opt.value;
239 if (!v) {
240 var h = opt.outerHTML;
241 // internet explorer sure does suck.
242 if (h && !h.match(/^[^>]+\svalue\s*=/i)) {
243 v = opt.text;
244 }
245 }
246 names.push(name);
247 values.push(v);
248 }
249 return null;
250 }
251 }
252 if (tagName === "FORM" || tagName === "P" || tagName === "SPAN"
253 || tagName === "DIV"
254 ) {
255 return elem.childNodes;
256 }
257 names.push(name);
258 values.push(elem.value || '');
259 return null;
260 }
261 return elem.childNodes;
262 });
263 return [names, values];
264 },
265
266 /** @id MochiKit.DOM.withDocument */
267 withDocument: function (doc, func) {
268 var self = MochiKit.DOM;
269 var oldDoc = self._document;
270 var rval;
271 try {
272 self._document = doc;
273 rval = func();
274 } catch (e) {
275 self._document = oldDoc;
276 throw e;
277 }
278 self._document = oldDoc;
279 return rval;
280 },
281
282 /** @id MochiKit.DOM.registerDOMConverter */
283 registerDOMConverter: function (name, check, wrap, /* optional */override) {
284 MochiKit.DOM.domConverters.register(name, check, wrap, override);
285 },
286
287 /** @id MochiKit.DOM.coerceToDOM */
288 coerceToDOM: function (node, ctx) {
289 var m = MochiKit.Base;
290 var im = MochiKit.Iter;
291 var self = MochiKit.DOM;
292 if (im) {
293 var iter = im.iter;
294 var repeat = im.repeat;
295 var map = m.map;
296 }
297 var domConverters = self.domConverters;
298 var coerceToDOM = arguments.callee;
299 var NotFound = m.NotFound;
300 while (true) {
301 if (typeof(node) == 'undefined' || node === null) {
302 return null;
303 }
304 // this is a safari childNodes object, avoiding crashes w/ attr
305 // lookup
306 if (typeof(node) == "function" &&
307 typeof(node.length) == "number" &&
308 !(node instanceof Function)) {
309 node = im.list(node);
310 }
311 if (typeof(node.nodeType) != 'undefined' && node.nodeType > 0) {
312 return node;
313 }
314 if (typeof(node) == 'number' || typeof(node) == 'boolean') {
315 node = node.toString();
316 // FALL THROUGH
317 }
318 if (typeof(node) == 'string') {
319 return self._document.createTextNode(node);
320 }
321 if (typeof(node.__dom__) == 'function') {
322 node = node.__dom__(ctx);
323 continue;
324 }
325 if (typeof(node.dom) == 'function') {
326 node = node.dom(ctx);
327 continue;
328 }
329 if (typeof(node) == 'function') {
330 node = node.apply(ctx, [ctx]);
331 continue;
332 }
333
334 if (im) {
335 // iterable
336 var iterNodes = null;
337 try {
338 iterNodes = iter(node);
339 } catch (e) {
340 // pass
341 }
342 if (iterNodes) {
343 return map(coerceToDOM, iterNodes, repeat(ctx));
344 }
345 }
346
347 // adapter
348 try {
349 node = domConverters.match(node, ctx);
350 continue;
351 } catch (e) {
352 if (e != NotFound) {
353 throw e;
354 }
355 }
356
357 // fallback
358 return self._document.createTextNode(node.toString());
359 }
360 // mozilla warnings aren't too bright
361 return undefined;
362 },
363
364 /** @id MochiKit.DOM.isChildNode */
365 isChildNode: function (node, maybeparent) {
366 var self = MochiKit.DOM;
367 if (typeof(node) == "string") {
368 node = self.getElement(node);
369 }
370 if (typeof(maybeparent) == "string") {
371 maybeparent = self.getElement(maybeparent);
372 }
373 if (typeof(node) == 'undefined' || node === null || node === self._document) {
374 return false;
375 }
376 do {
377 if (node === maybeparent) {
378 return true;
379 }
380 var tagName = node.tagName;
381 node = node.parentNode;
382 if (!tagName) {
383 break;
384 }
385 tagName = tagName.toUpperCase();
386 } while (tagName != "BODY" && tagName != "HTML");
387 return false;
388 },
389
390 /** @id MochiKit.DOM.setNodeAttribute */
391 setNodeAttribute: function (node, attr, value) {
392 var o = {};
393 o[attr] = value;
394 try {
395 return MochiKit.DOM.updateNodeAttributes(node, o);
396 } catch (e) {
397 // pass
398 }
399 return null;
400 },
401
402 /** @id MochiKit.DOM.getNodeAttribute */
403 getNodeAttribute: function (node, attr) {
404 var self = MochiKit.DOM;
405 var rename = self.attributeArray.renames[attr];
406 node = self.getElement(node);
407 try {
408 if (rename) {
409 return node[rename];
410 }
411 return node.getAttribute(attr);
412 } catch (e) {
413 // pass
414 }
415 return null;
416 },
417
418 /** @id MochiKit.DOM.removeNodeAttribute */
419 removeNodeAttribute: function (node, attr) {
420 var self = MochiKit.DOM;
421 var rename = self.attributeArray.renames[attr];
422 node = self.getElement(node);
423 try {
424 if (rename) {
425 return node[rename];
426 }
427 return node.removeAttribute(attr);
428 } catch (e) {
429 // pass
430 }
431 return null;
432 },
433
434 /** @id MochiKit.DOM.updateNodeAttributes */
435 updateNodeAttributes: function (node, attrs) {
436 var elem = node;
437 var self = MochiKit.DOM;
438 if (typeof(node) == 'string') {
439 elem = self.getElement(node);
440 }
441 if (attrs) {
442 var updatetree = MochiKit.Base.updatetree;
443 if (self.attributeArray.compliant) {
444 // not IE, good.
445 for (var k in attrs) {
446 var v = attrs[k];
447 if (typeof(v) == 'object' && typeof(elem[k]) == 'object') {
448 if (k == "style" && MochiKit.Style) {
449 MochiKit.Style.setStyle(elem, v);
450 } else {
451 updatetree(elem[k], v);
452 }
453 } else if (k.substring(0, 2) == "on") {
454 if (typeof(v) == "string") {
455 v = new Function(v);
456 }
457 elem[k] = v;
458 } else {
459 elem.setAttribute(k, v);
460 }
461 }
462 } else {
463 // IE is insane in the membrane
464 var renames = self.attributeArray.renames;
465 for (var k in attrs) {
466 v = attrs[k];
467 var renamed = renames[k];
468 if (k == "style" && typeof(v) == "string") {
469 elem.style.cssText = v;
470 } else if (typeof(renamed) == "string") {
471 elem[renamed] = v;
472 } else if (typeof(elem[k]) == 'object'
473 && typeof(v) == 'object') {
474 if (k == "style" && MochiKit.Style) {
475 MochiKit.Style.setStyle(elem, v);
476 } else {
477 updatetree(elem[k], v);
478 }
479 } else if (k.substring(0, 2) == "on") {
480 if (typeof(v) == "string") {
481 v = new Function(v);
482 }
483 elem[k] = v;
484 } else {
485 elem.setAttribute(k, v);
486 }
487 }
488 }
489 }
490 return elem;
491 },
492
493 /** @id MochiKit.DOM.appendChildNodes */
494 appendChildNodes: function (node/*, nodes...*/) {
495 var elem = node;
496 var self = MochiKit.DOM;
497 if (typeof(node) == 'string') {
498 elem = self.getElement(node);
499 }
500 var nodeStack = [
501 self.coerceToDOM(
502 MochiKit.Base.extend(null, arguments, 1),
503 elem
504 )
505 ];
506 var concat = MochiKit.Base.concat;
507 while (nodeStack.length) {
508 var n = nodeStack.shift();
509 if (typeof(n) == 'undefined' || n === null) {
510 // pass
511 } else if (typeof(n.nodeType) == 'number') {
512 elem.appendChild(n);
513 } else {
514 nodeStack = concat(n, nodeStack);
515 }
516 }
517 return elem;
518 },
519
520
521 /** @id MochiKit.DOM.insertSiblingNodesBefore */
522 insertSiblingNodesBefore: function (node/*, nodes...*/) {
523 var elem = node;
524 var self = MochiKit.DOM;
525 if (typeof(node) == 'string') {
526 elem = self.getElement(node);
527 }
528 var nodeStack = [
529 self.coerceToDOM(
530 MochiKit.Base.extend(null, arguments, 1),
531 elem
532 )
533 ];
534 var parentnode = elem.parentNode;
535 var concat = MochiKit.Base.concat;
536 while (nodeStack.length) {
537 var n = nodeStack.shift();
538 if (typeof(n) == 'undefined' || n === null) {
539 // pass
540 } else if (typeof(n.nodeType) == 'number') {
541 parentnode.insertBefore(n, elem);
542 } else {
543 nodeStack = concat(n, nodeStack);
544 }
545 }
546 return parentnode;
547 },
548
549 /** @id MochiKit.DOM.insertSiblingNodesAfter */
550 insertSiblingNodesAfter: function (node/*, nodes...*/) {
551 var elem = node;
552 var self = MochiKit.DOM;
553
554 if (typeof(node) == 'string') {
555 elem = self.getElement(node);
556 }
557 var nodeStack = [
558 self.coerceToDOM(
559 MochiKit.Base.extend(null, arguments, 1),
560 elem
561 )
562 ];
563
564 if (elem.nextSibling) {
565 return self.insertSiblingNodesBefore(elem.nextSibling, nodeStack);
566 }
567 else {
568 return self.appendChildNodes(elem.parentNode, nodeStack);
569 }
570 },
571
572 /** @id MochiKit.DOM.replaceChildNodes */
573 replaceChildNodes: function (node/*, nodes...*/) {
574 var elem = node;
575 var self = MochiKit.DOM;
576 if (typeof(node) == 'string') {
577 elem = self.getElement(node);
578 arguments[0] = elem;
579 }
580 var child;
581 while ((child = elem.firstChild)) {
582 elem.removeChild(child);
583 }
584 if (arguments.length < 2) {
585 return elem;
586 } else {
587 return self.appendChildNodes.apply(this, arguments);
588 }
589 },
590
591 /** @id MochiKit.DOM.createDOM */
592 createDOM: function (name, attrs/*, nodes... */) {
593 var elem;
594 var self = MochiKit.DOM;
595 var m = MochiKit.Base;
596 if (typeof(attrs) == "string" || typeof(attrs) == "number") {
597 var args = m.extend([name, null], arguments, 1);
598 return arguments.callee.apply(this, args);
599 }
600 if (typeof(name) == 'string') {
601 // Internet Explorer is dumb
602 var xhtml = self._xhtml;
603 if (attrs && !self.attributeArray.compliant) {
604 // http://msdn.microsoft.com/workshop/author/dhtml/reference/properties/name_2.asp
605 var contents = "";
606 if ('name' in attrs) {
607 contents += ' name="' + self.escapeHTML(attrs.name) + '"';
608 }
609 if (name == 'input' && 'type' in attrs) {
610 contents += ' type="' + self.escapeHTML(attrs.type) + '"';
611 }
612 if (contents) {
613 name = "<" + name + contents + ">";
614 xhtml = false;
615 }
616 }
617 var d = self._document;
618 if (xhtml && d === document) {
619 elem = d.createElementNS("http://www.w3.org/1999/xhtml", name);
620 } else {
621 elem = d.createElement(name);
622 }
623 } else {
624 elem = name;
625 }
626 if (attrs) {
627 self.updateNodeAttributes(elem, attrs);
628 }
629 if (arguments.length <= 2) {
630 return elem;
631 } else {
632 var args = m.extend([elem], arguments, 2);
633 return self.appendChildNodes.apply(this, args);
634 }
635 },
636
637 /** @id MochiKit.DOM.createDOMFunc */
638 createDOMFunc: function (/* tag, attrs, *nodes */) {
639 var m = MochiKit.Base;
640 return m.partial.apply(
641 this,
642 m.extend([MochiKit.DOM.createDOM], arguments)
643 );
644 },
645
646 /** @id MochiKit.DOM.removeElement */
647 removeElement: function (elem) {
648 var e = MochiKit.DOM.getElement(elem);
649 e.parentNode.removeChild(e);
650 return e;
651 },
652
653 /** @id MochiKit.DOM.swapDOM */
654 swapDOM: function (dest, src) {
655 var self = MochiKit.DOM;
656 dest = self.getElement(dest);
657 var parent = dest.parentNode;
658 if (src) {
659 src = self.getElement(src);
660 parent.replaceChild(src, dest);
661 } else {
662 parent.removeChild(dest);
663 }
664 return src;
665 },
666
667 /** @id MochiKit.DOM.getElement */
668 getElement: function (id) {
669 var self = MochiKit.DOM;
670 if (arguments.length == 1) {
671 return ((typeof(id) == "string") ?
672 self._document.getElementById(id) : id);
673 } else {
674 return MochiKit.Base.map(self.getElement, arguments);
675 }
676 },
677
678 /** @id MochiKit.DOM.getElementsByTagAndClassName */
679 getElementsByTagAndClassName: function (tagName, className,
680 /* optional */parent) {
681 var self = MochiKit.DOM;
682 if (typeof(tagName) == 'undefined' || tagName === null) {
683 tagName = '*';
684 }
685 if (typeof(parent) == 'undefined' || parent === null) {
686 parent = self._document;
687 }
688 parent = self.getElement(parent);
689 var children = (parent.getElementsByTagName(tagName)
690 || self._document.all);
691 if (typeof(className) == 'undefined' || className === null) {
692 return MochiKit.Base.extend(null, children);
693 }
694
695 var elements = [];
696 for (var i = 0; i < children.length; i++) {
697 var child = children[i];
698 var cls = child.className;
699 if (!cls) {
700 continue;
701 }
702 var classNames = cls.split(' ');
703 for (var j = 0; j < classNames.length; j++) {
704 if (classNames[j] == className) {
705 elements.push(child);
706 break;
707 }
708 }
709 }
710
711 return elements;
712 },
713
714 _newCallStack: function (path, once) {
715 var rval = function () {
716 var callStack = arguments.callee.callStack;
717 for (var i = 0; i < callStack.length; i++) {
718 if (callStack[i].apply(this, arguments) === false) {
719 break;
720 }
721 }
722 if (once) {
723 try {
724 this[path] = null;
725 } catch (e) {
726 // pass
727 }
728 }
729 };
730 rval.callStack = [];
731 return rval;
732 },
733
734 /** @id MochiKit.DOM.addToCallStack */
735 addToCallStack: function (target, path, func, once) {
736 var self = MochiKit.DOM;
737 var existing = target[path];
738 var regfunc = existing;
739 if (!(typeof(existing) == 'function'
740 && typeof(existing.callStack) == "object"
741 && existing.callStack !== null)) {
742 regfunc = self._newCallStack(path, once);
743 if (typeof(existing) == 'function') {
744 regfunc.callStack.push(existing);
745 }
746 target[path] = regfunc;
747 }
748 regfunc.callStack.push(func);
749 },
750
751 /** @id MochiKit.DOM.addLoadEvent */
752 addLoadEvent: function (func) {
753 var self = MochiKit.DOM;
754 self.addToCallStack(self._window, "onload", func, true);
755
756 },
757
758 /** @id MochiKit.DOM.focusOnLoad */
759 focusOnLoad: function (element) {
760 var self = MochiKit.DOM;
761 self.addLoadEvent(function () {
762 element = self.getElement(element);
763 if (element) {
764 element.focus();
765 }
766 });
767 },
768
769 /** @id MochiKit.DOM.setElementClass */
770 setElementClass: function (element, className) {
771 var self = MochiKit.DOM;
772 var obj = self.getElement(element);
773 if (self.attributeArray.compliant) {
774 obj.setAttribute("class", className);
775 } else {
776 obj.setAttribute("className", className);
777 }
778 },
779
780 /** @id MochiKit.DOM.toggleElementClass */
781 toggleElementClass: function (className/*, element... */) {
782 var self = MochiKit.DOM;
783 for (var i = 1; i < arguments.length; i++) {
784 var obj = self.getElement(arguments[i]);
785 if (!self.addElementClass(obj, className)) {
786 self.removeElementClass(obj, className);
787 }
788 }
789 },
790
791 /** @id MochiKit.DOM.addElementClass */
792 addElementClass: function (element, className) {
793 var self = MochiKit.DOM;
794 var obj = self.getElement(element);
795 var cls = obj.className;
796 // trivial case, no className yet
797 if (cls == undefined || cls.length === 0) {
798 self.setElementClass(obj, className);
799 return true;
800 }
801 // the other trivial case, already set as the only class
802 if (cls == className) {
803 return false;
804 }
805 var classes = cls.split(" ");
806 for (var i = 0; i < classes.length; i++) {
807 // already present
808 if (classes[i] == className) {
809 return false;
810 }
811 }
812 // append class
813 self.setElementClass(obj, cls + " " + className);
814 return true;
815 },
816
817 /** @id MochiKit.DOM.removeElementClass */
818 removeElementClass: function (element, className) {
819 var self = MochiKit.DOM;
820 var obj = self.getElement(element);
821 var cls = obj.className;
822 // trivial case, no className yet
823 if (cls == undefined || cls.length === 0) {
824 return false;
825 }
826 // other trivial case, set only to className
827 if (cls == className) {
828 self.setElementClass(obj, "");
829 return true;
830 }
831 var classes = cls.split(" ");
832 for (var i = 0; i < classes.length; i++) {
833 // already present
834 if (classes[i] == className) {
835 // only check sane case where the class is used once
836 classes.splice(i, 1);
837 self.setElementClass(obj, classes.join(" "));
838 return true;
839 }
840 }
841 // not found
842 return false;
843 },
844
845 /** @id MochiKit.DOM.swapElementClass */
846 swapElementClass: function (element, fromClass, toClass) {
847 var obj = MochiKit.DOM.getElement(element);
848 var res = MochiKit.DOM.removeElementClass(obj, fromClass);
849 if (res) {
850 MochiKit.DOM.addElementClass(obj, toClass);
851 }
852 return res;
853 },
854
855 /** @id MochiKit.DOM.hasElementClass */
856 hasElementClass: function (element, className/*...*/) {
857 var obj = MochiKit.DOM.getElement(element);
858 var cls = obj.className;
859 if (!cls) {
860 return false;
861 }
862 var classes = cls.split(" ");
863 for (var i = 1; i < arguments.length; i++) {
864 var good = false;
865 for (var j = 0; j < classes.length; j++) {
866 if (classes[j] == arguments[i]) {
867 good = true;
868 break;
869 }
870 }
871 if (!good) {
872 return false;
873 }
874 }
875 return true;
876 },
877
878 /** @id MochiKit.DOM.escapeHTML */
879 escapeHTML: function (s) {
880 return s.replace(/&/g, "&amp;"
881 ).replace(/"/g, "&quot;"
882 ).replace(/</g, "&lt;"
883 ).replace(/>/g, "&gt;");
884 },
885
886 /** @id MochiKit.DOM.toHTML */
887 toHTML: function (dom) {
888 return MochiKit.DOM.emitHTML(dom).join("");
889 },
890
891 /** @id MochiKit.DOM.emitHTML */
892 emitHTML: function (dom, /* optional */lst) {
893 if (typeof(lst) == 'undefined' || lst === null) {
894 lst = [];
895 }
896 // queue is the call stack, we're doing this non-recursively
897 var queue = [dom];
898 var self = MochiKit.DOM;
899 var escapeHTML = self.escapeHTML;
900 var attributeArray = self.attributeArray;
901 while (queue.length) {
902 dom = queue.pop();
903 if (typeof(dom) == 'string') {
904 lst.push(dom);
905 } else if (dom.nodeType == 1) {
906 // we're not using higher order stuff here
907 // because safari has heisenbugs.. argh.
908 //
909 // I think it might have something to do with
910 // garbage collection and function calls.
911 lst.push('<' + dom.tagName.toLowerCase());
912 var attributes = [];
913 var domAttr = attributeArray(dom);
914 for (var i = 0; i < domAttr.length; i++) {
915 var a = domAttr[i];
916 attributes.push([
917 " ",
918 a.name,
919 '="',
920 escapeHTML(a.value),
921 '"'
922 ]);
923 }
924 attributes.sort();
925 for (i = 0; i < attributes.length; i++) {
926 var attrs = attributes[i];
927 for (var j = 0; j < attrs.length; j++) {
928 lst.push(attrs[j]);
929 }
930 }
931 if (dom.hasChildNodes()) {
932 lst.push(">");
933 // queue is the FILO call stack, so we put the close tag
934 // on first
935 queue.push("</" + dom.tagName.toLowerCase() + ">");
936 var cnodes = dom.childNodes;
937 for (i = cnodes.length - 1; i >= 0; i--) {
938 queue.push(cnodes[i]);
939 }
940 } else {
941 lst.push('/>');
942 }
943 } else if (dom.nodeType == 3) {
944 lst.push(escapeHTML(dom.nodeValue));
945 }
946 }
947 return lst;
948 },
949
950 /** @id MochiKit.DOM.scrapeText */
951 scrapeText: function (node, /* optional */asArray) {
952 var rval = [];
953 (function (node) {
954 var cn = node.childNodes;
955 if (cn) {
956 for (var i = 0; i < cn.length; i++) {
957 arguments.callee.call(this, cn[i]);
958 }
959 }
960 var nodeValue = node.nodeValue;
961 if (typeof(nodeValue) == 'string') {
962 rval.push(nodeValue);
963 }
964 })(MochiKit.DOM.getElement(node));
965 if (asArray) {
966 return rval;
967 } else {
968 return rval.join("");
969 }
970 },
971
972 /** @id MochiKit.DOM.removeEmptyTextNodes */
973 removeEmptyTextNodes: function (element) {
974 element = MochiKit.DOM.getElement(element);
975 for (var i = 0; i < element.childNodes.length; i++) {
976 var node = element.childNodes[i];
977 if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) {
978 node.parentNode.removeChild(node);
979 }
980 }
981 },
982
983 /** @id MochiKit.DOM.makeClipping */
984 makeClipping: function (element) {
985 element = MochiKit.DOM.getElement(element);
986 var oldOverflow = element.style.overflow;
987 if ((MochiKit.Style.getStyle(element, 'overflow') || 'visible') != 'hidden') {
988 element.style.overflow = 'hidden';
989 }
990 return oldOverflow;
991 },
992
993 /** @id MochiKit.DOM.undoClipping */
994 undoClipping: function (element, overflow) {
995 element = MochiKit.DOM.getElement(element);
996 if (!overflow) {
997 return;
998 }
999 element.style.overflow = overflow;
1000 },
1001
1002 /** @id MochiKit.DOM.makePositioned */
1003 makePositioned: function (element) {
1004 element = MochiKit.DOM.getElement(element);
1005 var pos = MochiKit.Style.getStyle(element, 'position');
1006 if (pos == 'static' || !pos) {
1007 element.style.position = 'relative';
1008 // Opera returns the offset relative to the positioning context,
1009 // when an element is position relative but top and left have
1010 // not been defined
1011 if (/Opera/.test(navigator.userAgent)) {
1012 element.style.top = 0;
1013 element.style.left = 0;
1014 }
1015 }
1016 },
1017
1018 /** @id MochiKit.DOM.undoPositioned */
1019 undoPositioned: function (element) {
1020 element = MochiKit.DOM.getElement(element);
1021 if (element.style.position == 'relative') {
1022 element.style.position = element.style.top = element.style.left = element.style.bottom = element.style.right = '';
1023 }
1024 },
1025
1026 /** @id MochiKit.DOM.getFirstElementByTagAndClassName */
1027 getFirstElementByTagAndClassName: function (tagName, className,
1028 /* optional */parent) {
1029 var self = MochiKit.DOM;
1030 if (typeof(tagName) == 'undefined' || tagName === null) {
1031 tagName = '*';
1032 }
1033 if (typeof(parent) == 'undefined' || parent === null) {
1034 parent = self._document;
1035 }
1036 parent = self.getElement(parent);
1037 var children = (parent.getElementsByTagName(tagName)
1038 || self._document.all);
1039 if (typeof(className) == 'undefined' || className === null) {
1040 return children[0];
1041 }
1042
1043 for (var i = 0; i < children.length; i++) {
1044 var child = children[i];
1045 var classNames = child.className.split(' ');
1046 for (var j = 0; j < classNames.length; j++) {
1047 if (classNames[j] == className) {
1048 return child;
1049 }
1050 }
1051 }
1052 },
1053
1054 /** @id MochiKit.DOM.getFirstParentByTagAndClassName */
1055 getFirstParentByTagAndClassName: function (elem, tagName, className) {
1056 var self = MochiKit.DOM;
1057 elem = self.getElement(elem);
1058 if (typeof(tagName) == 'undefined' || tagName === null) {
1059 tagName = '*';
1060 } else {
1061 tagName = tagName.toUpperCase();
1062 }
1063 if (typeof(className) == 'undefined' || className === null) {
1064 className = null;
1065 }
1066
1067 var classList = '';
1068 var curTagName = '';
1069 while (elem && elem.tagName) {
1070 elem = elem.parentNode;
1071 if (tagName == '*' && className === null) {
1072 return elem;
1073 }
1074 classList = elem.className.split(' ');
1075 curTagName = elem.tagName.toUpperCase();
1076 if (className === null && tagName == curTagName) {
1077 return elem;
1078 } else if (className !== null) {
1079 for (var i = 0; i < classList.length; i++) {
1080 if (tagName == '*' && classList[i] == className) {
1081 return elem;
1082 } else if (tagName == curTagName && classList[i] == className) {
1083 return elem;
1084 }
1085 }
1086 }
1087 }
1088 return elem;
1089 },
1090
1091 /** @id MochiKit.DOM.isParent */
1092 isParent: function (child, element) {
1093 if (!child.parentNode || child == element) {
1094 return false;
1095 }
1096
1097 if (child.parentNode == element) {
1098 return true;
1099 }
1100
1101 return MochiKit.DOM.isParent(child.parentNode, element);
1102 },
1103
1104 __new__: function (win) {
1105
1106 var m = MochiKit.Base;
1107 if (typeof(document) != "undefined") {
1108 this._document = document;
1109 var kXULNSURI = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
1110 this._xhtml = (document.documentElement &&
1111 document.createElementNS &&
1112 document.documentElement.namespaceURI === kXULNSURI);
1113 } else if (MochiKit.MockDOM) {
1114 this._document = MochiKit.MockDOM.document;
1115 }
1116 this._window = win;
1117
1118 this.domConverters = new m.AdapterRegistry();
1119
1120 var __tmpElement = this._document.createElement("span");
1121 var attributeArray;
1122 if (__tmpElement && __tmpElement.attributes &&
1123 __tmpElement.attributes.length > 0) {
1124 // for braindead browsers (IE) that insert extra junk
1125 var filter = m.filter;
1126 attributeArray = function (node) {
1127 return filter(attributeArray.ignoreAttrFilter, node.attributes);
1128 };
1129 attributeArray.ignoreAttr = {};
1130 var attrs = __tmpElement.attributes;
1131 var ignoreAttr = attributeArray.ignoreAttr;
1132 for (var i = 0; i < attrs.length; i++) {
1133 var a = attrs[i];
1134 ignoreAttr[a.name] = a.value;
1135 }
1136 attributeArray.ignoreAttrFilter = function (a) {
1137 return (attributeArray.ignoreAttr[a.name] != a.value);
1138 };
1139 attributeArray.compliant = false;
1140 attributeArray.renames = {
1141 "class": "className",
1142 "checked": "defaultChecked",
1143 "usemap": "useMap",
1144 "for": "htmlFor",
1145 "readonly": "readOnly",
1146 "colspan": "colSpan",
1147 "bgcolor": "bgColor",
1148 "cellspacing": "cellSpacing",
1149 "cellpadding": "cellPadding"
1150 };
1151 } else {
1152 attributeArray = function (node) {
1153 /***
1154
1155 Return an array of attributes for a given node,
1156 filtering out attributes that don't belong for
1157 that are inserted by "Certain Browsers".
1158
1159 ***/
1160 return node.attributes;
1161 };
1162 attributeArray.compliant = true;
1163 attributeArray.renames = {};
1164 }
1165 this.attributeArray = attributeArray;
1166
1167 // FIXME: this really belongs in Base, and could probably be cleaner
1168 var _deprecated = function(fromModule, arr) {
1169 var modules = arr[1].split('.');
1170 var str = '';
1171 var obj = {};
1172
1173 str += 'if (!MochiKit.' + modules[1] + ') { throw new Error("';
1174 str += 'This function has been deprecated and depends on MochiKit.';
1175 str += modules[1] + '.");}';
1176 str += 'return MochiKit.' + modules[1] + '.' + arr[0];
1177 str += '.apply(this, arguments);';
1178
1179 obj[modules[2]] = new Function(str);
1180 MochiKit.Base.update(MochiKit[fromModule], obj);
1181 }
1182 for (var i; i < MochiKit.DOM.DEPRECATED.length; i++) {
1183 _deprecated('DOM', MochiKit.DOM.DEPRECATED[i]);
1184 }
1185
1186 // shorthand for createDOM syntax
1187 var createDOMFunc = this.createDOMFunc;
1188 /** @id MochiKit.DOM.UL */
1189 this.UL = createDOMFunc("ul");
1190 /** @id MochiKit.DOM.OL */
1191 this.OL = createDOMFunc("ol");
1192 /** @id MochiKit.DOM.LI */
1193 this.LI = createDOMFunc("li");
1194 /** @id MochiKit.DOM.TD */
1195 this.TD = createDOMFunc("td");
1196 /** @id MochiKit.DOM.TR */
1197 this.TR = createDOMFunc("tr");
1198 /** @id MochiKit.DOM.TBODY */
1199 this.TBODY = createDOMFunc("tbody");
1200 /** @id MochiKit.DOM.THEAD */
1201 this.THEAD = createDOMFunc("thead");
1202 /** @id MochiKit.DOM.TFOOT */
1203 this.TFOOT = createDOMFunc("tfoot");
1204 /** @id MochiKit.DOM.TABLE */
1205 this.TABLE = createDOMFunc("table");
1206 /** @id MochiKit.DOM.TH */
1207 this.TH = createDOMFunc("th");
1208 /** @id MochiKit.DOM.INPUT */
1209 this.INPUT = createDOMFunc("input");
1210 /** @id MochiKit.DOM.SPAN */
1211 this.SPAN = createDOMFunc("span");
1212 /** @id MochiKit.DOM.A */
1213 this.A = createDOMFunc("a");
1214 /** @id MochiKit.DOM.DIV */
1215 this.DIV = createDOMFunc("div");
1216 /** @id MochiKit.DOM.IMG */
1217 this.IMG = createDOMFunc("img");
1218 /** @id MochiKit.DOM.BUTTON */
1219 this.BUTTON = createDOMFunc("button");
1220 /** @id MochiKit.DOM.TT */
1221 this.TT = createDOMFunc("tt");
1222 /** @id MochiKit.DOM.PRE */
1223 this.PRE = createDOMFunc("pre");
1224 /** @id MochiKit.DOM.H1 */
1225 this.H1 = createDOMFunc("h1");
1226 /** @id MochiKit.DOM.H2 */
1227 this.H2 = createDOMFunc("h2");
1228 /** @id MochiKit.DOM.H3 */
1229 this.H3 = createDOMFunc("h3");
1230 /** @id MochiKit.DOM.BR */
1231 this.BR = createDOMFunc("br");
1232 /** @id MochiKit.DOM.HR */
1233 this.HR = createDOMFunc("hr");
1234 /** @id MochiKit.DOM.LABEL */
1235 this.LABEL = createDOMFunc("label");
1236 /** @id MochiKit.DOM.TEXTAREA */
1237 this.TEXTAREA = createDOMFunc("textarea");
1238 /** @id MochiKit.DOM.FORM */
1239 this.FORM = createDOMFunc("form");
1240 /** @id MochiKit.DOM.P */
1241 this.P = createDOMFunc("p");
1242 /** @id MochiKit.DOM.SELECT */
1243 this.SELECT = createDOMFunc("select");
1244 /** @id MochiKit.DOM.OPTION */
1245 this.OPTION = createDOMFunc("option");
1246 /** @id MochiKit.DOM.OPTGROUP */
1247 this.OPTGROUP = createDOMFunc("optgroup");
1248 /** @id MochiKit.DOM.LEGEND */
1249 this.LEGEND = createDOMFunc("legend");
1250 /** @id MochiKit.DOM.FIELDSET */
1251 this.FIELDSET = createDOMFunc("fieldset");
1252 /** @id MochiKit.DOM.STRONG */
1253 this.STRONG = createDOMFunc("strong");
1254 /** @id MochiKit.DOM.CANVAS */
1255 this.CANVAS = createDOMFunc("canvas");
1256
1257 /** @id MochiKit.DOM.$ */
1258 this.$ = this.getElement;
1259
1260 this.EXPORT_TAGS = {
1261 ":common": this.EXPORT,
1262 ":all": m.concat(this.EXPORT, this.EXPORT_OK)
1263 };
1264
1265 m.nameFunctions(this);
1266
1267 }
1268 });
1269
1270
1271 MochiKit.DOM.__new__(((typeof(window) == "undefined") ? this : window));
1272
1273 //
1274 // XXX: Internet Explorer blows
1275 //
1276 if (MochiKit.__export__) {
1277 withWindow = MochiKit.DOM.withWindow;
1278 withDocument = MochiKit.DOM.withDocument;
1279 }
1280
1281 MochiKit.Base._exportSymbols(this, MochiKit.DOM);
@@ -0,0 +1,216 b''
1 /***
2
3 MochiKit.DateTime 1.4
4
5 See <http://mochikit.com/> for documentation, downloads, license, etc.
6
7 (c) 2005 Bob Ippolito. All rights Reserved.
8
9 ***/
10
11 if (typeof(dojo) != 'undefined') {
12 dojo.provide('MochiKit.DateTime');
13 }
14
15 if (typeof(MochiKit) == 'undefined') {
16 MochiKit = {};
17 }
18
19 if (typeof(MochiKit.DateTime) == 'undefined') {
20 MochiKit.DateTime = {};
21 }
22
23 MochiKit.DateTime.NAME = "MochiKit.DateTime";
24 MochiKit.DateTime.VERSION = "1.4";
25 MochiKit.DateTime.__repr__ = function () {
26 return "[" + this.NAME + " " + this.VERSION + "]";
27 };
28 MochiKit.DateTime.toString = function () {
29 return this.__repr__();
30 };
31
32 /** @id MochiKit.DateTime.isoDate */
33 MochiKit.DateTime.isoDate = function (str) {
34 str = str + "";
35 if (typeof(str) != "string" || str.length === 0) {
36 return null;
37 }
38 var iso = str.split('-');
39 if (iso.length === 0) {
40 return null;
41 }
42 return new Date(iso[0], iso[1] - 1, iso[2]);
43 };
44
45 MochiKit.DateTime._isoRegexp = /(\d{4,})(?:-(\d{1,2})(?:-(\d{1,2})(?:[T ](\d{1,2}):(\d{1,2})(?::(\d{1,2})(?:\.(\d+))?)?(?:(Z)|([+-])(\d{1,2})(?::(\d{1,2}))?)?)?)?)?/;
46
47 /** @id MochiKit.DateTime.isoTimestamp */
48 MochiKit.DateTime.isoTimestamp = function (str) {
49 str = str + "";
50 if (typeof(str) != "string" || str.length === 0) {
51 return null;
52 }
53 var res = str.match(MochiKit.DateTime._isoRegexp);
54 if (typeof(res) == "undefined" || res === null) {
55 return null;
56 }
57 var year, month, day, hour, min, sec, msec;
58 year = parseInt(res[1], 10);
59 if (typeof(res[2]) == "undefined" || res[2] === '') {
60 return new Date(year);
61 }
62 month = parseInt(res[2], 10) - 1;
63 day = parseInt(res[3], 10);
64 if (typeof(res[4]) == "undefined" || res[4] === '') {
65 return new Date(year, month, day);
66 }
67 hour = parseInt(res[4], 10);
68 min = parseInt(res[5], 10);
69 sec = (typeof(res[6]) != "undefined" && res[6] !== '') ? parseInt(res[6], 10) : 0;
70 if (typeof(res[7]) != "undefined" && res[7] !== '') {
71 msec = Math.round(1000.0 * parseFloat("0." + res[7]));
72 } else {
73 msec = 0;
74 }
75 if ((typeof(res[8]) == "undefined" || res[8] === '') && (typeof(res[9]) == "undefined" || res[9] === '')) {
76 return new Date(year, month, day, hour, min, sec, msec);
77 }
78 var ofs;
79 if (typeof(res[9]) != "undefined" && res[9] !== '') {
80 ofs = parseInt(res[10], 10) * 3600000;
81 if (typeof(res[11]) != "undefined" && res[11] !== '') {
82 ofs += parseInt(res[11], 10) * 60000;
83 }
84 if (res[9] == "-") {
85 ofs = -ofs;
86 }
87 } else {
88 ofs = 0;
89 }
90 return new Date(Date.UTC(year, month, day, hour, min, sec, msec) - ofs);
91 };
92
93 /** @id MochiKit.DateTime.toISOTime */
94 MochiKit.DateTime.toISOTime = function (date, realISO/* = false */) {
95 if (typeof(date) == "undefined" || date === null) {
96 return null;
97 }
98 var hh = date.getHours();
99 var mm = date.getMinutes();
100 var ss = date.getSeconds();
101 var lst = [
102 ((realISO && (hh < 10)) ? "0" + hh : hh),
103 ((mm < 10) ? "0" + mm : mm),
104 ((ss < 10) ? "0" + ss : ss)
105 ];
106 return lst.join(":");
107 };
108
109 /** @id MochiKit.DateTime.toISOTimeStamp */
110 MochiKit.DateTime.toISOTimestamp = function (date, realISO/* = false*/) {
111 if (typeof(date) == "undefined" || date === null) {
112 return null;
113 }
114 var sep = realISO ? "T" : " ";
115 var foot = realISO ? "Z" : "";
116 if (realISO) {
117 date = new Date(date.getTime() + (date.getTimezoneOffset() * 60000));
118 }
119 return MochiKit.DateTime.toISODate(date) + sep + MochiKit.DateTime.toISOTime(date, realISO) + foot;
120 };
121
122 /** @id MochiKit.DateTime.toISODate */
123 MochiKit.DateTime.toISODate = function (date) {
124 if (typeof(date) == "undefined" || date === null) {
125 return null;
126 }
127 var _padTwo = MochiKit.DateTime._padTwo;
128 return [
129 date.getFullYear(),
130 _padTwo(date.getMonth() + 1),
131 _padTwo(date.getDate())
132 ].join("-");
133 };
134
135 /** @id MochiKit.DateTime.americanDate */
136 MochiKit.DateTime.americanDate = function (d) {
137 d = d + "";
138 if (typeof(d) != "string" || d.length === 0) {
139 return null;
140 }
141 var a = d.split('/');
142 return new Date(a[2], a[0] - 1, a[1]);
143 };
144
145 MochiKit.DateTime._padTwo = function (n) {
146 return (n > 9) ? n : "0" + n;
147 };
148
149 /** @id MochiKit.DateTime.toPaddedAmericanDate */
150 MochiKit.DateTime.toPaddedAmericanDate = function (d) {
151 if (typeof(d) == "undefined" || d === null) {
152 return null;
153 }
154 var _padTwo = MochiKit.DateTime._padTwo;
155 return [
156 _padTwo(d.getMonth() + 1),
157 _padTwo(d.getDate()),
158 d.getFullYear()
159 ].join('/');
160 };
161
162 /** @id MochiKit.DateTime.toAmericanDate */
163 MochiKit.DateTime.toAmericanDate = function (d) {
164 if (typeof(d) == "undefined" || d === null) {
165 return null;
166 }
167 return [d.getMonth() + 1, d.getDate(), d.getFullYear()].join('/');
168 };
169
170 MochiKit.DateTime.EXPORT = [
171 "isoDate",
172 "isoTimestamp",
173 "toISOTime",
174 "toISOTimestamp",
175 "toISODate",
176 "americanDate",
177 "toPaddedAmericanDate",
178 "toAmericanDate"
179 ];
180
181 MochiKit.DateTime.EXPORT_OK = [];
182 MochiKit.DateTime.EXPORT_TAGS = {
183 ":common": MochiKit.DateTime.EXPORT,
184 ":all": MochiKit.DateTime.EXPORT
185 };
186
187 MochiKit.DateTime.__new__ = function () {
188 // MochiKit.Base.nameFunctions(this);
189 var base = this.NAME + ".";
190 for (var k in this) {
191 var o = this[k];
192 if (typeof(o) == 'function' && typeof(o.NAME) == 'undefined') {
193 try {
194 o.NAME = base + k;
195 } catch (e) {
196 // pass
197 }
198 }
199 }
200 };
201
202 MochiKit.DateTime.__new__();
203
204 if (typeof(MochiKit.Base) != "undefined") {
205 MochiKit.Base._exportSymbols(this, MochiKit.DateTime);
206 } else {
207 (function (globals, module) {
208 if ((typeof(JSAN) == 'undefined' && typeof(dojo) == 'undefined')
209 || (MochiKit.__export__ === false)) {
210 var all = module.EXPORT_TAGS[":all"];
211 for (var i = 0; i < all.length; i++) {
212 globals[all[i]] = module[all[i]];
213 }
214 }
215 })(this, MochiKit.DateTime);
216 }
This diff has been collapsed as it changes many lines, (824 lines changed) Show them Hide them
@@ -0,0 +1,824 b''
1 /***
2 MochiKit.DragAndDrop 1.4
3
4 See <http://mochikit.com/> for documentation, downloads, license, etc.
5
6 Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
7 Mochi-ized By Thomas Herve (_firstname_@nimail.org)
8
9 ***/
10
11 if (typeof(dojo) != 'undefined') {
12 dojo.provide('MochiKit.DragAndDrop');
13 dojo.require('MochiKit.Base');
14 dojo.require('MochiKit.DOM');
15 dojo.require('MochiKit.Iter');
16 dojo.require('MochiKit.Visual');
17 dojo.require('MochiKit.Signal');
18 }
19
20 if (typeof(JSAN) != 'undefined') {
21 JSAN.use("MochiKit.Base", []);
22 JSAN.use("MochiKit.DOM", []);
23 JSAN.use("MochiKit.Visual", []);
24 JSAN.use("MochiKit.Iter", []);
25 JSAN.use("MochiKit.Signal", []);
26 }
27
28 try {
29 if (typeof(MochiKit.Base) == 'undefined' ||
30 typeof(MochiKit.DOM) == 'undefined' ||
31 typeof(MochiKit.Visual) == 'undefined' ||
32 typeof(MochiKit.Signal) == 'undefined' ||
33 typeof(MochiKit.Iter) == 'undefined') {
34 throw "";
35 }
36 } catch (e) {
37 throw "MochiKit.DragAndDrop depends on MochiKit.Base, MochiKit.DOM, MochiKit.Visual, MochiKit.Signal and MochiKit.Iter!";
38 }
39
40 if (typeof(MochiKit.DragAndDrop) == 'undefined') {
41 MochiKit.DragAndDrop = {};
42 }
43
44 MochiKit.DragAndDrop.NAME = 'MochiKit.DragAndDrop';
45 MochiKit.DragAndDrop.VERSION = '1.4';
46
47 MochiKit.DragAndDrop.__repr__ = function () {
48 return '[' + this.NAME + ' ' + this.VERSION + ']';
49 };
50
51 MochiKit.DragAndDrop.toString = function () {
52 return this.__repr__();
53 };
54
55 MochiKit.DragAndDrop.EXPORT = [
56 "Droppable",
57 "Draggable"
58 ];
59
60 MochiKit.DragAndDrop.EXPORT_OK = [
61 "Droppables",
62 "Draggables"
63 ];
64
65 MochiKit.DragAndDrop.Droppables = {
66 /***
67
68 Manage all droppables. Shouldn't be used, use the Droppable object instead.
69
70 ***/
71 drops: [],
72
73 remove: function (element) {
74 this.drops = MochiKit.Base.filter(function (d) {
75 return d.element != MochiKit.DOM.getElement(element);
76 }, this.drops);
77 },
78
79 register: function (drop) {
80 this.drops.push(drop);
81 },
82
83 unregister: function (drop) {
84 this.drops = MochiKit.Base.filter(function (d) {
85 return d != drop;
86 }, this.drops);
87 },
88
89 prepare: function (element) {
90 MochiKit.Base.map(function (drop) {
91 if (drop.isAccepted(element)) {
92 if (drop.options.activeclass) {
93 MochiKit.DOM.addElementClass(drop.element,
94 drop.options.activeclass);
95 }
96 drop.options.onactive(drop.element, element);
97 }
98 }, this.drops);
99 },
100
101 findDeepestChild: function (drops) {
102 deepest = drops[0];
103
104 for (i = 1; i < drops.length; ++i) {
105 if (MochiKit.DOM.isParent(drops[i].element, deepest.element)) {
106 deepest = drops[i];
107 }
108 }
109 return deepest;
110 },
111
112 show: function (point, element) {
113 if (!this.drops.length) {
114 return;
115 }
116 var affected = [];
117
118 if (this.last_active) {
119 this.last_active.deactivate();
120 }
121 MochiKit.Iter.forEach(this.drops, function (drop) {
122 if (drop.isAffected(point, element)) {
123 affected.push(drop);
124 }
125 });
126 if (affected.length > 0) {
127 drop = this.findDeepestChild(affected);
128 MochiKit.Position.within(drop.element, point.page.x, point.page.y);
129 drop.options.onhover(element, drop.element,
130 MochiKit.Position.overlap(drop.options.overlap, drop.element));
131 drop.activate();
132 }
133 },
134
135 fire: function (event, element) {
136 if (!this.last_active) {
137 return;
138 }
139 MochiKit.Position.prepare();
140
141 if (this.last_active.isAffected(event.mouse(), element)) {
142 this.last_active.options.ondrop(element,
143 this.last_active.element, event);
144 }
145 },
146
147 reset: function (element) {
148 MochiKit.Base.map(function (drop) {
149 if (drop.options.activeclass) {
150 MochiKit.DOM.removeElementClass(drop.element,
151 drop.options.activeclass);
152 }
153 drop.options.ondesactive(drop.element, element);
154 }, this.drops);
155 if (this.last_active) {
156 this.last_active.deactivate();
157 }
158 }
159 };
160
161 /** @id MochiKit.DragAndDrop.Droppable */
162 MochiKit.DragAndDrop.Droppable = function (element, options) {
163 var cls = arguments.callee;
164 if (!(this instanceof cls)) {
165 return new cls(element, options);
166 }
167 this.__init__(element, options);
168 };
169
170 MochiKit.DragAndDrop.Droppable.prototype = {
171 /***
172
173 A droppable object. Simple use is to create giving an element:
174
175 new MochiKit.DragAndDrop.Droppable('myelement');
176
177 Generally you'll want to define the 'ondrop' function and maybe the
178 'accept' option to filter draggables.
179
180 ***/
181 __class__: MochiKit.DragAndDrop.Droppable,
182
183 __init__: function (element, /* optional */options) {
184 var d = MochiKit.DOM;
185 var b = MochiKit.Base;
186 this.element = d.getElement(element);
187 this.options = b.update({
188
189 /** @id MochiKit.DragAndDrop.greedy */
190 greedy: true,
191
192 /** @id MochiKit.DragAndDrop.hoverclass */
193 hoverclass: null,
194
195 /** @id MochiKit.DragAndDrop.activeclass */
196 activeclass: null,
197
198 /** @id MochiKit.DragAndDrop.hoverfunc */
199 hoverfunc: b.noop,
200
201 /** @id MochiKit.DragAndDrop.accept */
202 accept: null,
203
204 /** @id MochiKit.DragAndDrop.onactive */
205 onactive: b.noop,
206
207 /** @id MochiKit.DragAndDrop.ondesactive */
208 ondesactive: b.noop,
209
210 /** @id MochiKit.DragAndDrop.onhover */
211 onhover: b.noop,
212
213 /** @id MochiKit.DragAndDrop.ondrop */
214 ondrop: b.noop,
215
216 /** @id MochiKit.DragAndDrop.containment */
217 containment: [],
218 tree: false
219 }, options);
220
221 // cache containers
222 this.options._containers = [];
223 b.map(MochiKit.Base.bind(function (c) {
224 this.options._containers.push(d.getElement(c));
225 }, this), this.options.containment);
226
227 d.makePositioned(this.element); // fix IE
228
229 MochiKit.DragAndDrop.Droppables.register(this);
230 },
231
232 /** @id MochiKit.DragAndDrop.isContained */
233 isContained: function (element) {
234 if (this.options._containers.length) {
235 var containmentNode;
236 if (this.options.tree) {
237 containmentNode = element.treeNode;
238 } else {
239 containmentNode = element.parentNode;
240 }
241 return MochiKit.Iter.some(this.options._containers, function (c) {
242 return containmentNode == c;
243 });
244 } else {
245 return true;
246 }
247 },
248
249 /** @id MochiKit.DragAndDrop.isAccepted */
250 isAccepted: function (element) {
251 return ((!this.options.accept) || MochiKit.Iter.some(
252 this.options.accept, function (c) {
253 return MochiKit.DOM.hasElementClass(element, c);
254 }));
255 },
256
257 /** @id MochiKit.DragAndDrop.isAffected */
258 isAffected: function (point, element) {
259 return ((this.element != element) &&
260 this.isContained(element) &&
261 this.isAccepted(element) &&
262 MochiKit.Position.within(this.element, point.page.x,
263 point.page.y));
264 },
265
266 /** @id MochiKit.DragAndDrop.deactivate */
267 deactivate: function () {
268 /***
269
270 A droppable is deactivate when a draggable has been over it and left.
271
272 ***/
273 if (this.options.hoverclass) {
274 MochiKit.DOM.removeElementClass(this.element,
275 this.options.hoverclass);
276 }
277 this.options.hoverfunc(this.element, false);
278 MochiKit.DragAndDrop.Droppables.last_active = null;
279 },
280
281 /** @id MochiKit.DragAndDrop.activate */
282 activate: function () {
283 /***
284
285 A droppable is active when a draggable is over it.
286
287 ***/
288 if (this.options.hoverclass) {
289 MochiKit.DOM.addElementClass(this.element, this.options.hoverclass);
290 }
291 this.options.hoverfunc(this.element, true);
292 MochiKit.DragAndDrop.Droppables.last_active = this;
293 },
294
295 /** @id MochiKit.DragAndDrop.destroy */
296 destroy: function () {
297 /***
298
299 Delete this droppable.
300
301 ***/
302 MochiKit.DragAndDrop.Droppables.unregister(this);
303 },
304
305 /** @id MochiKit.DragAndDrop.repr */
306 repr: function () {
307 return '[' + this.__class__.NAME + ", options:" + MochiKit.Base.repr(this.options) + "]";
308 }
309 };
310
311 MochiKit.DragAndDrop.Draggables = {
312 /***
313
314 Manage draggables elements. Not intended to direct use.
315
316 ***/
317 drags: [],
318
319 register: function (draggable) {
320 if (this.drags.length === 0) {
321 var conn = MochiKit.Signal.connect;
322 this.eventMouseUp = conn(document, 'onmouseup', this, this.endDrag);
323 this.eventMouseMove = conn(document, 'onmousemove', this,
324 this.updateDrag);
325 this.eventKeypress = conn(document, 'onkeypress', this,
326 this.keyPress);
327 }
328 this.drags.push(draggable);
329 },
330
331 unregister: function (draggable) {
332 this.drags = MochiKit.Base.filter(function (d) {
333 return d != draggable;
334 }, this.drags);
335 if (this.drags.length === 0) {
336 var disc = MochiKit.Signal.disconnect;
337 disc(this.eventMouseUp);
338 disc(this.eventMouseMove);
339 disc(this.eventKeypress);
340 }
341 },
342
343 activate: function (draggable) {
344 // allows keypress events if window is not currently focused
345 // fails for Safari
346 window.focus();
347 this.activeDraggable = draggable;
348 },
349
350 deactivate: function () {
351 this.activeDraggable = null;
352 },
353
354 updateDrag: function (event) {
355 if (!this.activeDraggable) {
356 return;
357 }
358 var pointer = event.mouse();
359 // Mozilla-based browsers fire successive mousemove events with
360 // the same coordinates, prevent needless redrawing (moz bug?)
361 if (this._lastPointer && (MochiKit.Base.repr(this._lastPointer.page) ==
362 MochiKit.Base.repr(pointer.page))) {
363 return;
364 }
365 this._lastPointer = pointer;
366 this.activeDraggable.updateDrag(event, pointer);
367 },
368
369 endDrag: function (event) {
370 if (!this.activeDraggable) {
371 return;
372 }
373 this._lastPointer = null;
374 this.activeDraggable.endDrag(event);
375 this.activeDraggable = null;
376 },
377
378 keyPress: function (event) {
379 if (this.activeDraggable) {
380 this.activeDraggable.keyPress(event);
381 }
382 },
383
384 notify: function (eventName, draggable, event) {
385 MochiKit.Signal.signal(this, eventName, draggable, event);
386 }
387 };
388
389 /** @id MochiKit.DragAndDrop.Draggable */
390 MochiKit.DragAndDrop.Draggable = function (element, options) {
391 var cls = arguments.callee;
392 if (!(this instanceof cls)) {
393 return new cls(element, options);
394 }
395 this.__init__(element, options);
396 };
397
398 MochiKit.DragAndDrop.Draggable.prototype = {
399 /***
400
401 A draggable object. Simple instantiate :
402
403 new MochiKit.DragAndDrop.Draggable('myelement');
404
405 ***/
406 __class__ : MochiKit.DragAndDrop.Draggable,
407
408 __init__: function (element, /* optional */options) {
409 var v = MochiKit.Visual;
410 var b = MochiKit.Base;
411 options = b.update({
412
413 /** @id MochiKit.DragAndDrop.handle */
414 handle: false,
415
416 /** @id MochiKit.DragAndDrop.starteffect */
417 starteffect: function (innerelement) {
418 this._savedOpacity = MochiKit.Style.getStyle(innerelement, 'opacity') || 1.0;
419 new v.Opacity(innerelement, {duration:0.2, from:this._savedOpacity, to:0.7});
420 },
421 /** @id MochiKit.DragAndDrop.reverteffect */
422 reverteffect: function (innerelement, top_offset, left_offset) {
423 var dur = Math.sqrt(Math.abs(top_offset^2) +
424 Math.abs(left_offset^2))*0.02;
425 return new v.Move(innerelement,
426 {x: -left_offset, y: -top_offset, duration: dur});
427 },
428
429 /** @id MochiKit.DragAndDrop.endeffect */
430 endeffect: function (innerelement) {
431 new v.Opacity(innerelement, {duration:0.2, from:0.7, to:this._savedOpacity});
432 },
433
434 /** @id MochiKit.DragAndDrop.onchange */
435 onchange: b.noop,
436
437 /** @id MochiKit.DragAndDrop.zindex */
438 zindex: 1000,
439
440 /** @id MochiKit.DragAndDrop.revert */
441 revert: false,
442
443 /** @id MochiKit.DragAndDrop.scroll */
444 scroll: false,
445
446 /** @id MochiKit.DragAndDrop.scrollSensitivity */
447 scrollSensitivity: 20,
448
449 /** @id MochiKit.DragAndDrop.scrollSpeed */
450 scrollSpeed: 15,
451 // false, or xy or [x, y] or function (x, y){return [x, y];}
452
453 /** @id MochiKit.DragAndDrop.snap */
454 snap: false
455 }, options);
456
457 var d = MochiKit.DOM;
458 this.element = d.getElement(element);
459
460 if (options.handle && (typeof(options.handle) == 'string')) {
461 this.handle = d.getFirstElementByTagAndClassName(null,
462 options.handle, this.element);
463 }
464 if (!this.handle) {
465 this.handle = d.getElement(options.handle);
466 }
467 if (!this.handle) {
468 this.handle = this.element;
469 }
470
471 if (options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) {
472 options.scroll = d.getElement(options.scroll);
473 this._isScrollChild = MochiKit.DOM.isChildNode(this.element, options.scroll);
474 }
475
476 d.makePositioned(this.element); // fix IE
477
478 this.delta = this.currentDelta();
479 this.options = options;
480 this.dragging = false;
481
482 this.eventMouseDown = MochiKit.Signal.connect(this.handle,
483 'onmousedown', this, this.initDrag);
484 MochiKit.DragAndDrop.Draggables.register(this);
485 },
486
487 /** @id MochiKit.DragAndDrop.destroy */
488 destroy: function () {
489 MochiKit.Signal.disconnect(this.eventMouseDown);
490 MochiKit.DragAndDrop.Draggables.unregister(this);
491 },
492
493 /** @id MochiKit.DragAndDrop.currentDelta */
494 currentDelta: function () {
495 var s = MochiKit.Style.getStyle;
496 return [
497 parseInt(s(this.element, 'left') || '0'),
498 parseInt(s(this.element, 'top') || '0')];
499 },
500
501 /** @id MochiKit.DragAndDrop.initDrag */
502 initDrag: function (event) {
503 if (!event.mouse().button.left) {
504 return;
505 }
506 // abort on form elements, fixes a Firefox issue
507 var src = event.target();
508 var tagName = (src.tagName || '').toUpperCase();
509 if (tagName === 'INPUT' || tagName === 'SELECT' ||
510 tagName === 'OPTION' || tagName === 'BUTTON' ||
511 tagName === 'TEXTAREA') {
512 return;
513 }
514
515 if (this._revert) {
516 this._revert.cancel();
517 this._revert = null;
518 }
519
520 var pointer = event.mouse();
521 var pos = MochiKit.Position.cumulativeOffset(this.element);
522 this.offset = [pointer.page.x - pos.x, pointer.page.y - pos.y];
523
524 MochiKit.DragAndDrop.Draggables.activate(this);
525 event.stop();
526 },
527
528 /** @id MochiKit.DragAndDrop.startDrag */
529 startDrag: function (event) {
530 this.dragging = true;
531 if (this.options.selectclass) {
532 MochiKit.DOM.addElementClass(this.element,
533 this.options.selectclass);
534 }
535 if (this.options.zindex) {
536 this.originalZ = parseInt(MochiKit.Style.getStyle(this.element,
537 'z-index') || '0');
538 this.element.style.zIndex = this.options.zindex;
539 }
540
541 if (this.options.ghosting) {
542 this._clone = this.element.cloneNode(true);
543 this.ghostPosition = MochiKit.Position.absolutize(this.element);
544 this.element.parentNode.insertBefore(this._clone, this.element);
545 }
546
547 if (this.options.scroll) {
548 if (this.options.scroll == window) {
549 var where = this._getWindowScroll(this.options.scroll);
550 this.originalScrollLeft = where.left;
551 this.originalScrollTop = where.top;
552 } else {
553 this.originalScrollLeft = this.options.scroll.scrollLeft;
554 this.originalScrollTop = this.options.scroll.scrollTop;
555 }
556 }
557
558 MochiKit.DragAndDrop.Droppables.prepare(this.element);
559 MochiKit.DragAndDrop.Draggables.notify('start', this, event);
560 if (this.options.starteffect) {
561 this.options.starteffect(this.element);
562 }
563 },
564
565 /** @id MochiKit.DragAndDrop.updateDrag */
566 updateDrag: function (event, pointer) {
567 if (!this.dragging) {
568 this.startDrag(event);
569 }
570 MochiKit.Position.prepare();
571 MochiKit.DragAndDrop.Droppables.show(pointer, this.element);
572 MochiKit.DragAndDrop.Draggables.notify('drag', this, event);
573 this.draw(pointer);
574 this.options.onchange(this);
575
576 if (this.options.scroll) {
577 this.stopScrolling();
578 var p, q;
579 if (this.options.scroll == window) {
580 var s = this._getWindowScroll(this.options.scroll);
581 p = new MochiKit.Style.Coordinates(s.left, s.top);
582 q = new MochiKit.Style.Coordinates(s.left + s.width,
583 s.top + s.height);
584 } else {
585 p = MochiKit.Position.page(this.options.scroll);
586 p.x += this.options.scroll.scrollLeft;
587 p.y += this.options.scroll.scrollTop;
588 p.x += (window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0);
589 p.y += (window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0);
590 q = new MochiKit.Style.Coordinates(p.x + this.options.scroll.offsetWidth,
591 p.y + this.options.scroll.offsetHeight);
592 }
593 var speed = [0, 0];
594 if (pointer.page.x > (q.x - this.options.scrollSensitivity)) {
595 speed[0] = pointer.page.x - (q.x - this.options.scrollSensitivity);
596 } else if (pointer.page.x < (p.x + this.options.scrollSensitivity)) {
597 speed[0] = pointer.page.x - (p.x + this.options.scrollSensitivity);
598 }
599 if (pointer.page.y > (q.y - this.options.scrollSensitivity)) {
600 speed[1] = pointer.page.y - (q.y - this.options.scrollSensitivity);
601 } else if (pointer.page.y < (p.y + this.options.scrollSensitivity)) {
602 speed[1] = pointer.page.y - (p.y + this.options.scrollSensitivity);
603 }
604 this.startScrolling(speed);
605 }
606
607 // fix AppleWebKit rendering
608 if (/AppleWebKit'/.test(navigator.appVersion)) {
609 window.scrollBy(0, 0);
610 }
611 event.stop();
612 },
613
614 /** @id MochiKit.DragAndDrop.finishDrag */
615 finishDrag: function (event, success) {
616 var dr = MochiKit.DragAndDrop;
617 this.dragging = false;
618 if (this.options.selectclass) {
619 MochiKit.DOM.removeElementClass(this.element,
620 this.options.selectclass);
621 }
622
623 if (this.options.ghosting) {
624 // XXX: from a user point of view, it would be better to remove
625 // the node only *after* the MochiKit.Visual.Move end when used
626 // with revert.
627 MochiKit.Position.relativize(this.element, this.ghostPosition);
628 MochiKit.DOM.removeElement(this._clone);
629 this._clone = null;
630 }
631
632 if (success) {
633 dr.Droppables.fire(event, this.element);
634 }
635 dr.Draggables.notify('end', this, event);
636
637 var revert = this.options.revert;
638 if (revert && typeof(revert) == 'function') {
639 revert = revert(this.element);
640 }
641
642 var d = this.currentDelta();
643 if (revert && this.options.reverteffect) {
644 this._revert = this.options.reverteffect(this.element,
645 d[1] - this.delta[1], d[0] - this.delta[0]);
646 } else {
647 this.delta = d;
648 }
649
650 if (this.options.zindex) {
651 this.element.style.zIndex = this.originalZ;
652 }
653
654 if (this.options.endeffect) {
655 this.options.endeffect(this.element);
656 }
657
658 dr.Draggables.deactivate();
659 dr.Droppables.reset(this.element);
660 },
661
662 /** @id MochiKit.DragAndDrop.keyPress */
663 keyPress: function (event) {
664 if (event.key().string != "KEY_ESCAPE") {
665 return;
666 }
667 this.finishDrag(event, false);
668 event.stop();
669 },
670
671 /** @id MochiKit.DragAndDrop.endDrag */
672 endDrag: function (event) {
673 if (!this.dragging) {
674 return;
675 }
676 this.stopScrolling();
677 this.finishDrag(event, true);
678 event.stop();
679 },
680
681 /** @id MochiKit.DragAndDrop.draw */
682 draw: function (point) {
683 var pos = MochiKit.Position.cumulativeOffset(this.element);
684 var d = this.currentDelta();
685 pos.x -= d[0];
686 pos.y -= d[1];
687
688 if (this.options.scroll && (this.options.scroll != window && this._isScrollChild)) {
689 pos.x -= this.options.scroll.scrollLeft - this.originalScrollLeft;
690 pos.y -= this.options.scroll.scrollTop - this.originalScrollTop;
691 }
692
693 var p = [point.page.x - pos.x - this.offset[0],
694 point.page.y - pos.y - this.offset[1]];
695
696 if (this.options.snap) {
697 if (typeof(this.options.snap) == 'function') {
698 p = this.options.snap(p[0], p[1]);
699 } else {
700 if (this.options.snap instanceof Array) {
701 var i = -1;
702 p = MochiKit.Base.map(MochiKit.Base.bind(function (v) {
703 i += 1;
704 return Math.round(v/this.options.snap[i]) *
705 this.options.snap[i];
706 }, this), p);
707 } else {
708 p = MochiKit.Base.map(MochiKit.Base.bind(function (v) {
709 return Math.round(v/this.options.snap) *
710 this.options.snap;
711 }, this), p);
712 }
713 }
714 }
715 var style = this.element.style;
716 if ((!this.options.constraint) ||
717 (this.options.constraint == 'horizontal')) {
718 style.left = p[0] + 'px';
719 }
720 if ((!this.options.constraint) ||
721 (this.options.constraint == 'vertical')) {
722 style.top = p[1] + 'px';
723 }
724 if (style.visibility == 'hidden') {
725 style.visibility = ''; // fix gecko rendering
726 }
727 },
728
729 /** @id MochiKit.DragAndDrop.stopScrolling */
730 stopScrolling: function () {
731 if (this.scrollInterval) {
732 clearInterval(this.scrollInterval);
733 this.scrollInterval = null;
734 MochiKit.DragAndDrop.Draggables._lastScrollPointer = null;
735 }
736 },
737
738 /** @id MochiKit.DragAndDrop.startScrolling */
739 startScrolling: function (speed) {
740 if (!speed[0] && !speed[1]) {
741 return;
742 }
743 this.scrollSpeed = [speed[0] * this.options.scrollSpeed,
744 speed[1] * this.options.scrollSpeed];
745 this.lastScrolled = new Date();
746 this.scrollInterval = setInterval(MochiKit.Base.bind(this.scroll, this), 10);
747 },
748
749 /** @id MochiKit.DragAndDrop.scroll */
750 scroll: function () {
751 var current = new Date();
752 var delta = current - this.lastScrolled;
753 this.lastScrolled = current;
754
755 if (this.options.scroll == window) {
756 var s = this._getWindowScroll(this.options.scroll);
757 if (this.scrollSpeed[0] || this.scrollSpeed[1]) {
758 var dm = delta / 1000;
759 this.options.scroll.scrollTo(s.left + dm * this.scrollSpeed[0],
760 s.top + dm * this.scrollSpeed[1]);
761 }
762 } else {
763 this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000;
764 this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000;
765 }
766
767 var d = MochiKit.DragAndDrop;
768
769 MochiKit.Position.prepare();
770 d.Droppables.show(d.Draggables._lastPointer, this.element);
771 d.Draggables.notify('drag', this);
772 if (this._isScrollChild) {
773 d.Draggables._lastScrollPointer = d.Draggables._lastScrollPointer || d.Draggables._lastPointer;
774 d.Draggables._lastScrollPointer.x += this.scrollSpeed[0] * delta / 1000;
775 d.Draggables._lastScrollPointer.y += this.scrollSpeed[1] * delta / 1000;
776 if (d.Draggables._lastScrollPointer.x < 0) {
777 d.Draggables._lastScrollPointer.x = 0;
778 }
779 if (d.Draggables._lastScrollPointer.y < 0) {
780 d.Draggables._lastScrollPointer.y = 0;
781 }
782 this.draw(d.Draggables._lastScrollPointer);
783 }
784
785 this.options.onchange(this);
786 },
787
788 _getWindowScroll: function (win) {
789 var vp, w, h;
790 MochiKit.DOM.withWindow(win, function () {
791 vp = MochiKit.Style.getViewportPosition(win.document);
792 });
793 if (win.innerWidth) {
794 w = win.innerWidth;
795 h = win.innerHeight;
796 } else if (win.document.documentElement && win.document.documentElement.clientWidth) {
797 w = win.document.documentElement.clientWidth;
798 h = win.document.documentElement.clientHeight;
799 } else {
800 w = win.document.body.offsetWidth;
801 h = win.document.body.offsetHeight;
802 }
803 return {top: vp.x, left: vp.y, width: w, height: h};
804 },
805
806 /** @id MochiKit.DragAndDrop.repr */
807 repr: function () {
808 return '[' + this.__class__.NAME + ", options:" + MochiKit.Base.repr(this.options) + "]";
809 }
810 };
811
812 MochiKit.DragAndDrop.__new__ = function () {
813 MochiKit.Base.nameFunctions(this);
814
815 this.EXPORT_TAGS = {
816 ":common": this.EXPORT,
817 ":all": MochiKit.Base.concat(this.EXPORT, this.EXPORT_OK)
818 };
819 };
820
821 MochiKit.DragAndDrop.__new__();
822
823 MochiKit.Base._exportSymbols(this, MochiKit.DragAndDrop);
824
@@ -0,0 +1,305 b''
1 /***
2
3 MochiKit.Format 1.4
4
5 See <http://mochikit.com/> for documentation, downloads, license, etc.
6
7 (c) 2005 Bob Ippolito. All rights Reserved.
8
9 ***/
10
11 if (typeof(dojo) != 'undefined') {
12 dojo.provide('MochiKit.Format');
13 }
14
15 if (typeof(MochiKit) == 'undefined') {
16 MochiKit = {};
17 }
18
19 if (typeof(MochiKit.Format) == 'undefined') {
20 MochiKit.Format = {};
21 }
22
23 MochiKit.Format.NAME = "MochiKit.Format";
24 MochiKit.Format.VERSION = "1.4";
25 MochiKit.Format.__repr__ = function () {
26 return "[" + this.NAME + " " + this.VERSION + "]";
27 };
28 MochiKit.Format.toString = function () {
29 return this.__repr__();
30 };
31
32 MochiKit.Format._numberFormatter = function (placeholder, header, footer, locale, isPercent, precision, leadingZeros, separatorAt, trailingZeros) {
33 return function (num) {
34 num = parseFloat(num);
35 if (typeof(num) == "undefined" || num === null || isNaN(num)) {
36 return placeholder;
37 }
38 var curheader = header;
39 var curfooter = footer;
40 if (num < 0) {
41 num = -num;
42 } else {
43 curheader = curheader.replace(/-/, "");
44 }
45 var me = arguments.callee;
46 var fmt = MochiKit.Format.formatLocale(locale);
47 if (isPercent) {
48 num = num * 100.0;
49 curfooter = fmt.percent + curfooter;
50 }
51 num = MochiKit.Format.roundToFixed(num, precision);
52 var parts = num.split(/\./);
53 var whole = parts[0];
54 var frac = (parts.length == 1) ? "" : parts[1];
55 var res = "";
56 while (whole.length < leadingZeros) {
57 whole = "0" + whole;
58 }
59 if (separatorAt) {
60 while (whole.length > separatorAt) {
61 var i = whole.length - separatorAt;
62 //res = res + fmt.separator + whole.substring(i, whole.length);
63 res = fmt.separator + whole.substring(i, whole.length) + res;
64 whole = whole.substring(0, i);
65 }
66 }
67 res = whole + res;
68 if (precision > 0) {
69 while (frac.length < trailingZeros) {
70 frac = frac + "0";
71 }
72 res = res + fmt.decimal + frac;
73 }
74 return curheader + res + curfooter;
75 };
76 };
77
78 /** @id MochiKit.Format.numberFormatter */
79 MochiKit.Format.numberFormatter = function (pattern, placeholder/* = "" */, locale/* = "default" */) {
80 // http://java.sun.com/docs/books/tutorial/i18n/format/numberpattern.html
81 // | 0 | leading or trailing zeros
82 // | # | just the number
83 // | , | separator
84 // | . | decimal separator
85 // | % | Multiply by 100 and format as percent
86 if (typeof(placeholder) == "undefined") {
87 placeholder = "";
88 }
89 var match = pattern.match(/((?:[0#]+,)?[0#]+)(?:\.([0#]+))?(%)?/);
90 if (!match) {
91 throw TypeError("Invalid pattern");
92 }
93 var header = pattern.substr(0, match.index);
94 var footer = pattern.substr(match.index + match[0].length);
95 if (header.search(/-/) == -1) {
96 header = header + "-";
97 }
98 var whole = match[1];
99 var frac = (typeof(match[2]) == "string" && match[2] != "") ? match[2] : "";
100 var isPercent = (typeof(match[3]) == "string" && match[3] != "");
101 var tmp = whole.split(/,/);
102 var separatorAt;
103 if (typeof(locale) == "undefined") {
104 locale = "default";
105 }
106 if (tmp.length == 1) {
107 separatorAt = null;
108 } else {
109 separatorAt = tmp[1].length;
110 }
111 var leadingZeros = whole.length - whole.replace(/0/g, "").length;
112 var trailingZeros = frac.length - frac.replace(/0/g, "").length;
113 var precision = frac.length;
114 var rval = MochiKit.Format._numberFormatter(
115 placeholder, header, footer, locale, isPercent, precision,
116 leadingZeros, separatorAt, trailingZeros
117 );
118 var m = MochiKit.Base;
119 if (m) {
120 var fn = arguments.callee;
121 var args = m.concat(arguments);
122 rval.repr = function () {
123 return [
124 self.NAME,
125 "(",
126 map(m.repr, args).join(", "),
127 ")"
128 ].join("");
129 };
130 }
131 return rval;
132 };
133
134 /** @id MochiKit.Format.formatLocale */
135 MochiKit.Format.formatLocale = function (locale) {
136 if (typeof(locale) == "undefined" || locale === null) {
137 locale = "default";
138 }
139 if (typeof(locale) == "string") {
140 var rval = MochiKit.Format.LOCALE[locale];
141 if (typeof(rval) == "string") {
142 rval = arguments.callee(rval);
143 MochiKit.Format.LOCALE[locale] = rval;
144 }
145 return rval;
146 } else {
147 return locale;
148 }
149 };
150
151 /** @id MochiKit.Format.twoDigitAverage */
152 MochiKit.Format.twoDigitAverage = function (numerator, denominator) {
153 if (denominator) {
154 var res = numerator / denominator;
155 if (!isNaN(res)) {
156 return MochiKit.Format.twoDigitFloat(numerator / denominator);
157 }
158 }
159 return "0";
160 };
161
162 /** @id MochiKit.Format.twoDigitFloat */
163 MochiKit.Format.twoDigitFloat = function (someFloat) {
164 var sign = (someFloat < 0 ? '-' : '');
165 var s = Math.floor(Math.abs(someFloat) * 100).toString();
166 if (s == '0') {
167 return s;
168 }
169 if (s.length < 3) {
170 while (s.charAt(s.length - 1) == '0') {
171 s = s.substring(0, s.length - 1);
172 }
173 return sign + '0.' + s;
174 }
175 var head = sign + s.substring(0, s.length - 2);
176 var tail = s.substring(s.length - 2, s.length);
177 if (tail == '00') {
178 return head;
179 } else if (tail.charAt(1) == '0') {
180 return head + '.' + tail.charAt(0);
181 } else {
182 return head + '.' + tail;
183 }
184 };
185
186 /** @id MochiKit.Format.lstrip */
187 MochiKit.Format.lstrip = function (str, /* optional */chars) {
188 str = str + "";
189 if (typeof(str) != "string") {
190 return null;
191 }
192 if (!chars) {
193 return str.replace(/^\s+/, "");
194 } else {
195 return str.replace(new RegExp("^[" + chars + "]+"), "");
196 }
197 };
198
199 /** @id MochiKit.Format.rstrip */
200 MochiKit.Format.rstrip = function (str, /* optional */chars) {
201 str = str + "";
202 if (typeof(str) != "string") {
203 return null;
204 }
205 if (!chars) {
206 return str.replace(/\s+$/, "");
207 } else {
208 return str.replace(new RegExp("[" + chars + "]+$"), "");
209 }
210 };
211
212 /** @id MochiKit.Format.strip */
213 MochiKit.Format.strip = function (str, /* optional */chars) {
214 var self = MochiKit.Format;
215 return self.rstrip(self.lstrip(str, chars), chars);
216 };
217
218 /** @id MochiKit.Format.truncToFixed */
219 MochiKit.Format.truncToFixed = function (aNumber, precision) {
220 aNumber = Math.floor(aNumber * Math.pow(10, precision));
221 var res = (aNumber * Math.pow(10, -precision)).toFixed(precision);
222 if (res.charAt(0) == ".") {
223 res = "0" + res;
224 }
225 return res;
226 };
227
228 /** @id MochiKit.Format.roundToFixed */
229 MochiKit.Format.roundToFixed = function (aNumber, precision) {
230 return MochiKit.Format.truncToFixed(
231 aNumber + 0.5 * Math.pow(10, -precision),
232 precision
233 );
234 };
235
236 /** @id MochiKit.Format.percentFormat */
237 MochiKit.Format.percentFormat = function (someFloat) {
238 return MochiKit.Format.twoDigitFloat(100 * someFloat) + '%';
239 };
240
241 MochiKit.Format.EXPORT = [
242 "truncToFixed",
243 "roundToFixed",
244 "numberFormatter",
245 "formatLocale",
246 "twoDigitAverage",
247 "twoDigitFloat",
248 "percentFormat",
249 "lstrip",
250 "rstrip",
251 "strip"
252 ];
253
254 MochiKit.Format.LOCALE = {
255 en_US: {separator: ",", decimal: ".", percent: "%"},
256 de_DE: {separator: ".", decimal: ",", percent: "%"},
257 pt_BR: {separator: ".", decimal: ",", percent: "%"},
258 fr_FR: {separator: " ", decimal: ",", percent: "%"},
259 "default": "en_US"
260 };
261
262 MochiKit.Format.EXPORT_OK = [];
263 MochiKit.Format.EXPORT_TAGS = {
264 ':all': MochiKit.Format.EXPORT,
265 ':common': MochiKit.Format.EXPORT
266 };
267
268 MochiKit.Format.__new__ = function () {
269 // MochiKit.Base.nameFunctions(this);
270 var base = this.NAME + ".";
271 var k, v, o;
272 for (k in this.LOCALE) {
273 o = this.LOCALE[k];
274 if (typeof(o) == "object") {
275 o.repr = function () { return this.NAME; };
276 o.NAME = base + "LOCALE." + k;
277 }
278 }
279 for (k in this) {
280 o = this[k];
281 if (typeof(o) == 'function' && typeof(o.NAME) == 'undefined') {
282 try {
283 o.NAME = base + k;
284 } catch (e) {
285 // pass
286 }
287 }
288 }
289 };
290
291 MochiKit.Format.__new__();
292
293 if (typeof(MochiKit.Base) != "undefined") {
294 MochiKit.Base._exportSymbols(this, MochiKit.Format);
295 } else {
296 (function (globals, module) {
297 if ((typeof(JSAN) == 'undefined' && typeof(dojo) == 'undefined')
298 || (MochiKit.__export__ === false)) {
299 var all = module.EXPORT_TAGS[":all"];
300 for (var i = 0; i < all.length; i++) {
301 globals[all[i]] = module[all[i]];
302 }
303 }
304 })(this, MochiKit.Format);
305 }
This diff has been collapsed as it changes many lines, (851 lines changed) Show them Hide them
@@ -0,0 +1,851 b''
1 /***
2
3 MochiKit.Iter 1.4
4
5 See <http://mochikit.com/> for documentation, downloads, license, etc.
6
7 (c) 2005 Bob Ippolito. All rights Reserved.
8
9 ***/
10
11 if (typeof(dojo) != 'undefined') {
12 dojo.provide('MochiKit.Iter');
13 dojo.require('MochiKit.Base');
14 }
15
16 if (typeof(JSAN) != 'undefined') {
17 JSAN.use("MochiKit.Base", []);
18 }
19
20 try {
21 if (typeof(MochiKit.Base) == 'undefined') {
22 throw "";
23 }
24 } catch (e) {
25 throw "MochiKit.Iter depends on MochiKit.Base!";
26 }
27
28 if (typeof(MochiKit.Iter) == 'undefined') {
29 MochiKit.Iter = {};
30 }
31
32 MochiKit.Iter.NAME = "MochiKit.Iter";
33 MochiKit.Iter.VERSION = "1.4";
34 MochiKit.Base.update(MochiKit.Iter, {
35 __repr__: function () {
36 return "[" + this.NAME + " " + this.VERSION + "]";
37 },
38 toString: function () {
39 return this.__repr__();
40 },
41
42 /** @id MochiKit.Iter.registerIteratorFactory */
43 registerIteratorFactory: function (name, check, iterfactory, /* optional */ override) {
44 MochiKit.Iter.iteratorRegistry.register(name, check, iterfactory, override);
45 },
46
47 /** @id MochiKit.Iter.iter */
48 iter: function (iterable, /* optional */ sentinel) {
49 var self = MochiKit.Iter;
50 if (arguments.length == 2) {
51 return self.takewhile(
52 function (a) { return a != sentinel; },
53 iterable
54 );
55 }
56 if (typeof(iterable.next) == 'function') {
57 return iterable;
58 } else if (typeof(iterable.iter) == 'function') {
59 return iterable.iter();
60 /*
61 } else if (typeof(iterable.__iterator__) == 'function') {
62 //
63 // XXX: We can't support JavaScript 1.7 __iterator__ directly
64 // because of Object.prototype.__iterator__
65 //
66 return iterable.__iterator__();
67 */
68 }
69
70 try {
71 return self.iteratorRegistry.match(iterable);
72 } catch (e) {
73 var m = MochiKit.Base;
74 if (e == m.NotFound) {
75 e = new TypeError(typeof(iterable) + ": " + m.repr(iterable) + " is not iterable");
76 }
77 throw e;
78 }
79 },
80
81 /** @id MochiKit.Iter.count */
82 count: function (n) {
83 if (!n) {
84 n = 0;
85 }
86 var m = MochiKit.Base;
87 return {
88 repr: function () { return "count(" + n + ")"; },
89 toString: m.forwardCall("repr"),
90 next: m.counter(n)
91 };
92 },
93
94 /** @id MochiKit.Iter.cycle */
95 cycle: function (p) {
96 var self = MochiKit.Iter;
97 var m = MochiKit.Base;
98 var lst = [];
99 var iterator = self.iter(p);
100 return {
101 repr: function () { return "cycle(...)"; },
102 toString: m.forwardCall("repr"),
103 next: function () {
104 try {
105 var rval = iterator.next();
106 lst.push(rval);
107 return rval;
108 } catch (e) {
109 if (e != self.StopIteration) {
110 throw e;
111 }
112 if (lst.length === 0) {
113 this.next = function () {
114 throw self.StopIteration;
115 };
116 } else {
117 var i = -1;
118 this.next = function () {
119 i = (i + 1) % lst.length;
120 return lst[i];
121 };
122 }
123 return this.next();
124 }
125 }
126 };
127 },
128
129 /** @id MochiKit.Iter.repeat */
130 repeat: function (elem, /* optional */n) {
131 var m = MochiKit.Base;
132 if (typeof(n) == 'undefined') {
133 return {
134 repr: function () {
135 return "repeat(" + m.repr(elem) + ")";
136 },
137 toString: m.forwardCall("repr"),
138 next: function () {
139 return elem;
140 }
141 };
142 }
143 return {
144 repr: function () {
145 return "repeat(" + m.repr(elem) + ", " + n + ")";
146 },
147 toString: m.forwardCall("repr"),
148 next: function () {
149 if (n <= 0) {
150 throw MochiKit.Iter.StopIteration;
151 }
152 n -= 1;
153 return elem;
154 }
155 };
156 },
157
158 /** @id MochiKit.Iter.next */
159 next: function (iterator) {
160 return iterator.next();
161 },
162
163 /** @id MochiKit.Iter.izip */
164 izip: function (p, q/*, ...*/) {
165 var m = MochiKit.Base;
166 var self = MochiKit.Iter;
167 var next = self.next;
168 var iterables = m.map(self.iter, arguments);
169 return {
170 repr: function () { return "izip(...)"; },
171 toString: m.forwardCall("repr"),
172 next: function () { return m.map(next, iterables); }
173 };
174 },
175
176 /** @id MochiKit.Iter.ifilter */
177 ifilter: function (pred, seq) {
178 var m = MochiKit.Base;
179 seq = MochiKit.Iter.iter(seq);
180 if (pred === null) {
181 pred = m.operator.truth;
182 }
183 return {
184 repr: function () { return "ifilter(...)"; },
185 toString: m.forwardCall("repr"),
186 next: function () {
187 while (true) {
188 var rval = seq.next();
189 if (pred(rval)) {
190 return rval;
191 }
192 }
193 // mozilla warnings aren't too bright
194 return undefined;
195 }
196 };
197 },
198
199 /** @id MochiKit.Iter.ifilterfalse */
200 ifilterfalse: function (pred, seq) {
201 var m = MochiKit.Base;
202 seq = MochiKit.Iter.iter(seq);
203 if (pred === null) {
204 pred = m.operator.truth;
205 }
206 return {
207 repr: function () { return "ifilterfalse(...)"; },
208 toString: m.forwardCall("repr"),
209 next: function () {
210 while (true) {
211 var rval = seq.next();
212 if (!pred(rval)) {
213 return rval;
214 }
215 }
216 // mozilla warnings aren't too bright
217 return undefined;
218 }
219 };
220 },
221
222 /** @id MochiKit.Iter.islice */
223 islice: function (seq/*, [start,] stop[, step] */) {
224 var self = MochiKit.Iter;
225 var m = MochiKit.Base;
226 seq = self.iter(seq);
227 var start = 0;
228 var stop = 0;
229 var step = 1;
230 var i = -1;
231 if (arguments.length == 2) {
232 stop = arguments[1];
233 } else if (arguments.length == 3) {
234 start = arguments[1];
235 stop = arguments[2];
236 } else {
237 start = arguments[1];
238 stop = arguments[2];
239 step = arguments[3];
240 }
241 return {
242 repr: function () {
243 return "islice(" + ["...", start, stop, step].join(", ") + ")";
244 },
245 toString: m.forwardCall("repr"),
246 next: function () {
247 var rval;
248 while (i < start) {
249 rval = seq.next();
250 i++;
251 }
252 if (start >= stop) {
253 throw self.StopIteration;
254 }
255 start += step;
256 return rval;
257 }
258 };
259 },
260
261 /** @id MochiKit.Iter.imap */
262 imap: function (fun, p, q/*, ...*/) {
263 var m = MochiKit.Base;
264 var self = MochiKit.Iter;
265 var iterables = m.map(self.iter, m.extend(null, arguments, 1));
266 var map = m.map;
267 var next = self.next;
268 return {
269 repr: function () { return "imap(...)"; },
270 toString: m.forwardCall("repr"),
271 next: function () {
272 return fun.apply(this, map(next, iterables));
273 }
274 };
275 },
276
277 /** @id MochiKit.Iter.applymap */
278 applymap: function (fun, seq, self) {
279 seq = MochiKit.Iter.iter(seq);
280 var m = MochiKit.Base;
281 return {
282 repr: function () { return "applymap(...)"; },
283 toString: m.forwardCall("repr"),
284 next: function () {
285 return fun.apply(self, seq.next());
286 }
287 };
288 },
289
290 /** @id MochiKit.Iter.chain */
291 chain: function (p, q/*, ...*/) {
292 // dumb fast path
293 var self = MochiKit.Iter;
294 var m = MochiKit.Base;
295 if (arguments.length == 1) {
296 return self.iter(arguments[0]);
297 }
298 var argiter = m.map(self.iter, arguments);
299 return {
300 repr: function () { return "chain(...)"; },
301 toString: m.forwardCall("repr"),
302 next: function () {
303 while (argiter.length > 1) {
304 try {
305 return argiter[0].next();
306 } catch (e) {
307 if (e != self.StopIteration) {
308 throw e;
309 }
310 argiter.shift();
311 }
312 }
313 if (argiter.length == 1) {
314 // optimize last element
315 var arg = argiter.shift();
316 this.next = m.bind("next", arg);
317 return this.next();
318 }
319 throw self.StopIteration;
320 }
321 };
322 },
323
324 /** @id MochiKit.Iter.takewhile */
325 takewhile: function (pred, seq) {
326 var self = MochiKit.Iter;
327 seq = self.iter(seq);
328 return {
329 repr: function () { return "takewhile(...)"; },
330 toString: MochiKit.Base.forwardCall("repr"),
331 next: function () {
332 var rval = seq.next();
333 if (!pred(rval)) {
334 this.next = function () {
335 throw self.StopIteration;
336 };
337 this.next();
338 }
339 return rval;
340 }
341 };
342 },
343
344 /** @id MochiKit.Iter.dropwhile */
345 dropwhile: function (pred, seq) {
346 seq = MochiKit.Iter.iter(seq);
347 var m = MochiKit.Base;
348 var bind = m.bind;
349 return {
350 "repr": function () { return "dropwhile(...)"; },
351 "toString": m.forwardCall("repr"),
352 "next": function () {
353 while (true) {
354 var rval = seq.next();
355 if (!pred(rval)) {
356 break;
357 }
358 }
359 this.next = bind("next", seq);
360 return rval;
361 }
362 };
363 },
364
365 _tee: function (ident, sync, iterable) {
366 sync.pos[ident] = -1;
367 var m = MochiKit.Base;
368 var listMin = m.listMin;
369 return {
370 repr: function () { return "tee(" + ident + ", ...)"; },
371 toString: m.forwardCall("repr"),
372 next: function () {
373 var rval;
374 var i = sync.pos[ident];
375
376 if (i == sync.max) {
377 rval = iterable.next();
378 sync.deque.push(rval);
379 sync.max += 1;
380 sync.pos[ident] += 1;
381 } else {
382 rval = sync.deque[i - sync.min];
383 sync.pos[ident] += 1;
384 if (i == sync.min && listMin(sync.pos) != sync.min) {
385 sync.min += 1;
386 sync.deque.shift();
387 }
388 }
389 return rval;
390 }
391 };
392 },
393
394 /** @id MochiKit.Iter.tee */
395 tee: function (iterable, n/* = 2 */) {
396 var rval = [];
397 var sync = {
398 "pos": [],
399 "deque": [],
400 "max": -1,
401 "min": -1
402 };
403 if (arguments.length == 1 || typeof(n) == "undefined" || n === null) {
404 n = 2;
405 }
406 var self = MochiKit.Iter;
407 iterable = self.iter(iterable);
408 var _tee = self._tee;
409 for (var i = 0; i < n; i++) {
410 rval.push(_tee(i, sync, iterable));
411 }
412 return rval;
413 },
414
415 /** @id MochiKit.Iter.list */
416 list: function (iterable) {
417 // Fast-path for Array and Array-like
418 var rval;
419 if (iterable instanceof Array) {
420 return iterable.slice();
421 }
422 // this is necessary to avoid a Safari crash
423 if (typeof(iterable) == "function" &&
424 !(iterable instanceof Function) &&
425 typeof(iterable.length) == 'number') {
426 rval = [];
427 for (var i = 0; i < iterable.length; i++) {
428 rval.push(iterable[i]);
429 }
430 return rval;
431 }
432
433 var self = MochiKit.Iter;
434 iterable = self.iter(iterable);
435 var rval = [];
436 try {
437 while (true) {
438 rval.push(iterable.next());
439 }
440 } catch (e) {
441 if (e != self.StopIteration) {
442 throw e;
443 }
444 return rval;
445 }
446 // mozilla warnings aren't too bright
447 return undefined;
448 },
449
450
451 /** @id MochiKit.Iter.reduce */
452 reduce: function (fn, iterable, /* optional */initial) {
453 var i = 0;
454 var x = initial;
455 var self = MochiKit.Iter;
456 iterable = self.iter(iterable);
457 if (arguments.length < 3) {
458 try {
459 x = iterable.next();
460 } catch (e) {
461 if (e == self.StopIteration) {
462 e = new TypeError("reduce() of empty sequence with no initial value");
463 }
464 throw e;
465 }
466 i++;
467 }
468 try {
469 while (true) {
470 x = fn(x, iterable.next());
471 }
472 } catch (e) {
473 if (e != self.StopIteration) {
474 throw e;
475 }
476 }
477 return x;
478 },
479
480 /** @id MochiKit.Iter.range */
481 range: function (/* [start,] stop[, step] */) {
482 var start = 0;
483 var stop = 0;
484 var step = 1;
485 if (arguments.length == 1) {
486 stop = arguments[0];
487 } else if (arguments.length == 2) {
488 start = arguments[0];
489 stop = arguments[1];
490 } else if (arguments.length == 3) {
491 start = arguments[0];
492 stop = arguments[1];
493 step = arguments[2];
494 } else {
495 throw new TypeError("range() takes 1, 2, or 3 arguments!");
496 }
497 if (step === 0) {
498 throw new TypeError("range() step must not be 0");
499 }
500 return {
501 next: function () {
502 if ((step > 0 && start >= stop) || (step < 0 && start <= stop)) {
503 throw MochiKit.Iter.StopIteration;
504 }
505 var rval = start;
506 start += step;
507 return rval;
508 },
509 repr: function () {
510 return "range(" + [start, stop, step].join(", ") + ")";
511 },
512 toString: MochiKit.Base.forwardCall("repr")
513 };
514 },
515
516 /** @id MochiKit.Iter.sum */
517 sum: function (iterable, start/* = 0 */) {
518 if (typeof(start) == "undefined" || start === null) {
519 start = 0;
520 }
521 var x = start;
522 var self = MochiKit.Iter;
523 iterable = self.iter(iterable);
524 try {
525 while (true) {
526 x += iterable.next();
527 }
528 } catch (e) {
529 if (e != self.StopIteration) {
530 throw e;
531 }
532 }
533 return x;
534 },
535
536 /** @id MochiKit.Iter.exhaust */
537 exhaust: function (iterable) {
538 var self = MochiKit.Iter;
539 iterable = self.iter(iterable);
540 try {
541 while (true) {
542 iterable.next();
543 }
544 } catch (e) {
545 if (e != self.StopIteration) {
546 throw e;
547 }
548 }
549 },
550
551 /** @id MochiKit.Iter.forEach */
552 forEach: function (iterable, func, /* optional */self) {
553 var m = MochiKit.Base;
554 if (arguments.length > 2) {
555 func = m.bind(func, self);
556 }
557 // fast path for array
558 if (m.isArrayLike(iterable)) {
559 try {
560 for (var i = 0; i < iterable.length; i++) {
561 func(iterable[i]);
562 }
563 } catch (e) {
564 if (e != MochiKit.Iter.StopIteration) {
565 throw e;
566 }
567 }
568 } else {
569 self = MochiKit.Iter;
570 self.exhaust(self.imap(func, iterable));
571 }
572 },
573
574 /** @id MochiKit.Iter.every */
575 every: function (iterable, func) {
576 var self = MochiKit.Iter;
577 try {
578 self.ifilterfalse(func, iterable).next();
579 return false;
580 } catch (e) {
581 if (e != self.StopIteration) {
582 throw e;
583 }
584 return true;
585 }
586 },
587
588 /** @id MochiKit.Iter.sorted */
589 sorted: function (iterable, /* optional */cmp) {
590 var rval = MochiKit.Iter.list(iterable);
591 if (arguments.length == 1) {
592 cmp = MochiKit.Base.compare;
593 }
594 rval.sort(cmp);
595 return rval;
596 },
597
598 /** @id MochiKit.Iter.reversed */
599 reversed: function (iterable) {
600 var rval = MochiKit.Iter.list(iterable);
601 rval.reverse();
602 return rval;
603 },
604
605 /** @id MochiKit.Iter.some */
606 some: function (iterable, func) {
607 var self = MochiKit.Iter;
608 try {
609 self.ifilter(func, iterable).next();
610 return true;
611 } catch (e) {
612 if (e != self.StopIteration) {
613 throw e;
614 }
615 return false;
616 }
617 },
618
619 /** @id MochiKit.Iter.iextend */
620 iextend: function (lst, iterable) {
621 if (MochiKit.Base.isArrayLike(iterable)) {
622 // fast-path for array-like
623 for (var i = 0; i < iterable.length; i++) {
624 lst.push(iterable[i]);
625 }
626 } else {
627 var self = MochiKit.Iter;
628 iterable = self.iter(iterable);
629 try {
630 while (true) {
631 lst.push(iterable.next());
632 }
633 } catch (e) {
634 if (e != self.StopIteration) {
635 throw e;
636 }
637 }
638 }
639 return lst;
640 },
641
642 /** @id MochiKit.Iter.groupby */
643 groupby: function(iterable, /* optional */ keyfunc) {
644 var m = MochiKit.Base;
645 var self = MochiKit.Iter;
646 if (arguments.length < 2) {
647 keyfunc = m.operator.identity;
648 }
649 iterable = self.iter(iterable);
650
651 // shared
652 var pk = undefined;
653 var k = undefined;
654 var v;
655
656 function fetch() {
657 v = iterable.next();
658 k = keyfunc(v);
659 };
660
661 function eat() {
662 var ret = v;
663 v = undefined;
664 return ret;
665 };
666
667 var first = true;
668 var compare = m.compare;
669 return {
670 repr: function () { return "groupby(...)"; },
671 next: function() {
672 // iterator-next
673
674 // iterate until meet next group
675 while (compare(k, pk) === 0) {
676 fetch();
677 if (first) {
678 first = false;
679 break;
680 }
681 }
682 pk = k;
683 return [k, {
684 next: function() {
685 // subiterator-next
686 if (v == undefined) { // Is there something to eat?
687 fetch();
688 }
689 if (compare(k, pk) !== 0) {
690 throw self.StopIteration;
691 }
692 return eat();
693 }
694 }];
695 }
696 };
697 },
698
699 /** @id MochiKit.Iter.groupby_as_array */
700 groupby_as_array: function (iterable, /* optional */ keyfunc) {
701 var m = MochiKit.Base;
702 var self = MochiKit.Iter;
703 if (arguments.length < 2) {
704 keyfunc = m.operator.identity;
705 }
706
707 iterable = self.iter(iterable);
708 var result = [];
709 var first = true;
710 var prev_key;
711 var compare = m.compare;
712 while (true) {
713 try {
714 var value = iterable.next();
715 var key = keyfunc(value);
716 } catch (e) {
717 if (e == self.StopIteration) {
718 break;
719 }
720 throw e;
721 }
722 if (first || compare(key, prev_key) !== 0) {
723 var values = [];
724 result.push([key, values]);
725 }
726 values.push(value);
727 first = false;
728 prev_key = key;
729 }
730 return result;
731 },
732
733 /** @id MochiKit.Iter.arrayLikeIter */
734 arrayLikeIter: function (iterable) {
735 var i = 0;
736 return {
737 repr: function () { return "arrayLikeIter(...)"; },
738 toString: MochiKit.Base.forwardCall("repr"),
739 next: function () {
740 if (i >= iterable.length) {
741 throw MochiKit.Iter.StopIteration;
742 }
743 return iterable[i++];
744 }
745 };
746 },
747
748 /** @id MochiKit.Iter.hasIterateNext */
749 hasIterateNext: function (iterable) {
750 return (iterable && typeof(iterable.iterateNext) == "function");
751 },
752
753 /** @id MochiKit.Iter.iterateNextIter */
754 iterateNextIter: function (iterable) {
755 return {
756 repr: function () { return "iterateNextIter(...)"; },
757 toString: MochiKit.Base.forwardCall("repr"),
758 next: function () {
759 var rval = iterable.iterateNext();
760 if (rval === null || rval === undefined) {
761 throw MochiKit.Iter.StopIteration;
762 }
763 return rval;
764 }
765 };
766 }
767 });
768
769
770 MochiKit.Iter.EXPORT_OK = [
771 "iteratorRegistry",
772 "arrayLikeIter",
773 "hasIterateNext",
774 "iterateNextIter"
775 ];
776
777 MochiKit.Iter.EXPORT = [
778 "StopIteration",
779 "registerIteratorFactory",
780 "iter",
781 "count",
782 "cycle",
783 "repeat",
784 "next",
785 "izip",
786 "ifilter",
787 "ifilterfalse",
788 "islice",
789 "imap",
790 "applymap",
791 "chain",
792 "takewhile",
793 "dropwhile",
794 "tee",
795 "list",
796 "reduce",
797 "range",
798 "sum",
799 "exhaust",
800 "forEach",
801 "every",
802 "sorted",
803 "reversed",
804 "some",
805 "iextend",
806 "groupby",
807 "groupby_as_array"
808 ];
809
810 MochiKit.Iter.__new__ = function () {
811 var m = MochiKit.Base;
812 // Re-use StopIteration if exists (e.g. SpiderMonkey)
813 if (typeof(StopIteration) != "undefined") {
814 this.StopIteration = StopIteration;
815 } else {
816 /** @id MochiKit.Iter.StopIteration */
817 this.StopIteration = new m.NamedError("StopIteration");
818 }
819 this.iteratorRegistry = new m.AdapterRegistry();
820 // Register the iterator factory for arrays
821 this.registerIteratorFactory(
822 "arrayLike",
823 m.isArrayLike,
824 this.arrayLikeIter
825 );
826
827 this.registerIteratorFactory(
828 "iterateNext",
829 this.hasIterateNext,
830 this.iterateNextIter
831 );
832
833 this.EXPORT_TAGS = {
834 ":common": this.EXPORT,
835 ":all": m.concat(this.EXPORT, this.EXPORT_OK)
836 };
837
838 m.nameFunctions(this);
839
840 };
841
842 MochiKit.Iter.__new__();
843
844 //
845 // XXX: Internet Explorer blows
846 //
847 if (MochiKit.__export__) {
848 reduce = MochiKit.Iter.reduce;
849 }
850
851 MochiKit.Base._exportSymbols(this, MochiKit.Iter);
@@ -0,0 +1,321 b''
1 /***
2
3 MochiKit.Logging 1.4
4
5 See <http://mochikit.com/> for documentation, downloads, license, etc.
6
7 (c) 2005 Bob Ippolito. All rights Reserved.
8
9 ***/
10
11 if (typeof(dojo) != 'undefined') {
12 dojo.provide('MochiKit.Logging');
13 dojo.require('MochiKit.Base');
14 }
15
16 if (typeof(JSAN) != 'undefined') {
17 JSAN.use("MochiKit.Base", []);
18 }
19
20 try {
21 if (typeof(MochiKit.Base) == 'undefined') {
22 throw "";
23 }
24 } catch (e) {
25 throw "MochiKit.Logging depends on MochiKit.Base!";
26 }
27
28 if (typeof(MochiKit.Logging) == 'undefined') {
29 MochiKit.Logging = {};
30 }
31
32 MochiKit.Logging.NAME = "MochiKit.Logging";
33 MochiKit.Logging.VERSION = "1.4";
34 MochiKit.Logging.__repr__ = function () {
35 return "[" + this.NAME + " " + this.VERSION + "]";
36 };
37
38 MochiKit.Logging.toString = function () {
39 return this.__repr__();
40 };
41
42
43 MochiKit.Logging.EXPORT = [
44 "LogLevel",
45 "LogMessage",
46 "Logger",
47 "alertListener",
48 "logger",
49 "log",
50 "logError",
51 "logDebug",
52 "logFatal",
53 "logWarning"
54 ];
55
56
57 MochiKit.Logging.EXPORT_OK = [
58 "logLevelAtLeast",
59 "isLogMessage",
60 "compareLogMessage"
61 ];
62
63
64 /** @id MochiKit.Logging.LogMessage */
65 MochiKit.Logging.LogMessage = function (num, level, info) {
66 this.num = num;
67 this.level = level;
68 this.info = info;
69 this.timestamp = new Date();
70 };
71
72 MochiKit.Logging.LogMessage.prototype = {
73 /** @id MochiKit.Logging.LogMessage.prototype.repr */
74 repr: function () {
75 var m = MochiKit.Base;
76 return 'LogMessage(' +
77 m.map(
78 m.repr,
79 [this.num, this.level, this.info]
80 ).join(', ') + ')';
81 },
82 /** @id MochiKit.Logging.LogMessage.prototype.toString */
83 toString: MochiKit.Base.forwardCall("repr")
84 };
85
86 MochiKit.Base.update(MochiKit.Logging, {
87 /** @id MochiKit.Logging.logLevelAtLeast */
88 logLevelAtLeast: function (minLevel) {
89 var self = MochiKit.Logging;
90 if (typeof(minLevel) == 'string') {
91 minLevel = self.LogLevel[minLevel];
92 }
93 return function (msg) {
94 var msgLevel = msg.level;
95 if (typeof(msgLevel) == 'string') {
96 msgLevel = self.LogLevel[msgLevel];
97 }
98 return msgLevel >= minLevel;
99 };
100 },
101
102 /** @id MochiKit.Logging.isLogMessage */
103 isLogMessage: function (/* ... */) {
104 var LogMessage = MochiKit.Logging.LogMessage;
105 for (var i = 0; i < arguments.length; i++) {
106 if (!(arguments[i] instanceof LogMessage)) {
107 return false;
108 }
109 }
110 return true;
111 },
112
113 /** @id MochiKit.Logging.compareLogMessage */
114 compareLogMessage: function (a, b) {
115 return MochiKit.Base.compare([a.level, a.info], [b.level, b.info]);
116 },
117
118 /** @id MochiKit.Logging.alertListener */
119 alertListener: function (msg) {
120 alert(
121 "num: " + msg.num +
122 "\nlevel: " + msg.level +
123 "\ninfo: " + msg.info.join(" ")
124 );
125 }
126
127 });
128
129 /** @id MochiKit.Logging.Logger */
130 MochiKit.Logging.Logger = function (/* optional */maxSize) {
131 this.counter = 0;
132 if (typeof(maxSize) == 'undefined' || maxSize === null) {
133 maxSize = -1;
134 }
135 this.maxSize = maxSize;
136 this._messages = [];
137 this.listeners = {};
138 this.useNativeConsole = false;
139 };
140
141 MochiKit.Logging.Logger.prototype = {
142 /** @id MochiKit.Logging.Logger.prototype.clear */
143 clear: function () {
144 this._messages.splice(0, this._messages.length);
145 },
146
147 /** @id MochiKit.Logging.Logger.prototype.logToConsole */
148 logToConsole: function (msg) {
149 if (typeof(window) != "undefined" && window.console
150 && window.console.log) {
151 // Safari and FireBug 0.4
152 // Percent replacement is a workaround for cute Safari crashing bug
153 window.console.log(msg.replace(/%/g, '\uFF05'));
154 } else if (typeof(opera) != "undefined" && opera.postError) {
155 // Opera
156 opera.postError(msg);
157 } else if (typeof(printfire) == "function") {
158 // FireBug 0.3 and earlier
159 printfire(msg);
160 } else if (typeof(Debug) != "undefined" && Debug.writeln) {
161 // IE Web Development Helper (?)
162 // http://www.nikhilk.net/Entry.aspx?id=93
163 Debug.writeln(msg);
164 } else if (typeof(debug) != "undefined" && debug.trace) {
165 // Atlas framework (?)
166 // http://www.nikhilk.net/Entry.aspx?id=93
167 debug.trace(msg);
168 }
169 },
170
171 /** @id MochiKit.Logging.Logger.prototype.dispatchListeners */
172 dispatchListeners: function (msg) {
173 for (var k in this.listeners) {
174 var pair = this.listeners[k];
175 if (pair.ident != k || (pair[0] && !pair[0](msg))) {
176 continue;
177 }
178 pair[1](msg);
179 }
180 },
181
182 /** @id MochiKit.Logging.Logger.prototype.addListener */
183 addListener: function (ident, filter, listener) {
184 if (typeof(filter) == 'string') {
185 filter = MochiKit.Logging.logLevelAtLeast(filter);
186 }
187 var entry = [filter, listener];
188 entry.ident = ident;
189 this.listeners[ident] = entry;
190 },
191
192 /** @id MochiKit.Logging.Logger.prototype.removeListener */
193 removeListener: function (ident) {
194 delete this.listeners[ident];
195 },
196
197 /** @id MochiKit.Logging.Logger.prototype.baseLog */
198 baseLog: function (level, message/*, ...*/) {
199 var msg = new MochiKit.Logging.LogMessage(
200 this.counter,
201 level,
202 MochiKit.Base.extend(null, arguments, 1)
203 );
204 this._messages.push(msg);
205 this.dispatchListeners(msg);
206 if (this.useNativeConsole) {
207 this.logToConsole(msg.level + ": " + msg.info.join(" "));
208 }
209 this.counter += 1;
210 while (this.maxSize >= 0 && this._messages.length > this.maxSize) {
211 this._messages.shift();
212 }
213 },
214
215 /** @id MochiKit.Logging.Logger.prototype.getMessages */
216 getMessages: function (howMany) {
217 var firstMsg = 0;
218 if (!(typeof(howMany) == 'undefined' || howMany === null)) {
219 firstMsg = Math.max(0, this._messages.length - howMany);
220 }
221 return this._messages.slice(firstMsg);
222 },
223
224 /** @id MochiKit.Logging.Logger.prototype.getMessageText */
225 getMessageText: function (howMany) {
226 if (typeof(howMany) == 'undefined' || howMany === null) {
227 howMany = 30;
228 }
229 var messages = this.getMessages(howMany);
230 if (messages.length) {
231 var lst = map(function (m) {
232 return '\n [' + m.num + '] ' + m.level + ': ' + m.info.join(' ');
233 }, messages);
234 lst.unshift('LAST ' + messages.length + ' MESSAGES:');
235 return lst.join('');
236 }
237 return '';
238 },
239
240 /** @id MochiKit.Logging.Logger.prototype.debuggingBookmarklet */
241 debuggingBookmarklet: function (inline) {
242 if (typeof(MochiKit.LoggingPane) == "undefined") {
243 alert(this.getMessageText());
244 } else {
245 MochiKit.LoggingPane.createLoggingPane(inline || false);
246 }
247 }
248 };
249
250 MochiKit.Logging.__new__ = function () {
251 this.LogLevel = {
252 ERROR: 40,
253 FATAL: 50,
254 WARNING: 30,
255 INFO: 20,
256 DEBUG: 10
257 };
258
259 var m = MochiKit.Base;
260 m.registerComparator("LogMessage",
261 this.isLogMessage,
262 this.compareLogMessage
263 );
264
265 var partial = m.partial;
266
267 var Logger = this.Logger;
268 var baseLog = Logger.prototype.baseLog;
269 m.update(this.Logger.prototype, {
270 debug: partial(baseLog, 'DEBUG'),
271 log: partial(baseLog, 'INFO'),
272 error: partial(baseLog, 'ERROR'),
273 fatal: partial(baseLog, 'FATAL'),
274 warning: partial(baseLog, 'WARNING')
275 });
276
277 // indirectly find logger so it can be replaced
278 var self = this;
279 var connectLog = function (name) {
280 return function () {
281 self.logger[name].apply(self.logger, arguments);
282 };
283 };
284
285 /** @id MochiKit.Logging.log */
286 this.log = connectLog('log');
287 /** @id MochiKit.Logging.logError */
288 this.logError = connectLog('error');
289 /** @id MochiKit.Logging.logDebug */
290 this.logDebug = connectLog('debug');
291 /** @id MochiKit.Logging.logFatal */
292 this.logFatal = connectLog('fatal');
293 /** @id MochiKit.Logging.logWarning */
294 this.logWarning = connectLog('warning');
295 this.logger = new Logger();
296 this.logger.useNativeConsole = true;
297
298 this.EXPORT_TAGS = {
299 ":common": this.EXPORT,
300 ":all": m.concat(this.EXPORT, this.EXPORT_OK)
301 };
302
303 m.nameFunctions(this);
304
305 };
306
307 if (typeof(printfire) == "undefined" &&
308 typeof(document) != "undefined" && document.createEvent &&
309 typeof(dispatchEvent) != "undefined") {
310 // FireBug really should be less lame about this global function
311 printfire = function () {
312 printfire.args = arguments;
313 var ev = document.createEvent("Events");
314 ev.initEvent("printfire", false, true);
315 dispatchEvent(ev);
316 };
317 }
318
319 MochiKit.Logging.__new__();
320
321 MochiKit.Base._exportSymbols(this, MochiKit.Logging);
@@ -0,0 +1,374 b''
1 /***
2
3 MochiKit.LoggingPane 1.4
4
5 See <http://mochikit.com/> for documentation, downloads, license, etc.
6
7 (c) 2005 Bob Ippolito. All rights Reserved.
8
9 ***/
10
11 if (typeof(dojo) != 'undefined') {
12 dojo.provide('MochiKit.LoggingPane');
13 dojo.require('MochiKit.Logging');
14 dojo.require('MochiKit.Base');
15 }
16
17 if (typeof(JSAN) != 'undefined') {
18 JSAN.use("MochiKit.Logging", []);
19 JSAN.use("MochiKit.Base", []);
20 }
21
22 try {
23 if (typeof(MochiKit.Base) == 'undefined' || typeof(MochiKit.Logging) == 'undefined') {
24 throw "";
25 }
26 } catch (e) {
27 throw "MochiKit.LoggingPane depends on MochiKit.Base and MochiKit.Logging!";
28 }
29
30 if (typeof(MochiKit.LoggingPane) == 'undefined') {
31 MochiKit.LoggingPane = {};
32 }
33
34 MochiKit.LoggingPane.NAME = "MochiKit.LoggingPane";
35 MochiKit.LoggingPane.VERSION = "1.4";
36 MochiKit.LoggingPane.__repr__ = function () {
37 return "[" + this.NAME + " " + this.VERSION + "]";
38 };
39
40 MochiKit.LoggingPane.toString = function () {
41 return this.__repr__();
42 };
43
44 /** @id MochiKit.LoggingPane.createLoggingPane */
45 MochiKit.LoggingPane.createLoggingPane = function (inline/* = false */) {
46 var m = MochiKit.LoggingPane;
47 inline = !(!inline);
48 if (m._loggingPane && m._loggingPane.inline != inline) {
49 m._loggingPane.closePane();
50 m._loggingPane = null;
51 }
52 if (!m._loggingPane || m._loggingPane.closed) {
53 m._loggingPane = new m.LoggingPane(inline, MochiKit.Logging.logger);
54 }
55 return m._loggingPane;
56 };
57
58 /** @id MochiKit.LoggingPane.LoggingPane */
59 MochiKit.LoggingPane.LoggingPane = function (inline/* = false */, logger/* = MochiKit.Logging.logger */) {
60
61 /* Use a div if inline, pop up a window if not */
62 /* Create the elements */
63 if (typeof(logger) == "undefined" || logger === null) {
64 logger = MochiKit.Logging.logger;
65 }
66 this.logger = logger;
67 var update = MochiKit.Base.update;
68 var updatetree = MochiKit.Base.updatetree;
69 var bind = MochiKit.Base.bind;
70 var clone = MochiKit.Base.clone;
71 var win = window;
72 var uid = "_MochiKit_LoggingPane";
73 if (typeof(MochiKit.DOM) != "undefined") {
74 win = MochiKit.DOM.currentWindow();
75 }
76 if (!inline) {
77 // name the popup with the base URL for uniqueness
78 var url = win.location.href.split("?")[0].replace(/[#:\/.><&-]/g, "_");
79 var name = uid + "_" + url;
80 var nwin = win.open("", name, "dependent,resizable,height=200");
81 if (!nwin) {
82 alert("Not able to open debugging window due to pop-up blocking.");
83 return undefined;
84 }
85 nwin.document.write(
86 '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" '
87 + '"http://www.w3.org/TR/html4/loose.dtd">'
88 + '<html><head><title>[MochiKit.LoggingPane]</title></head>'
89 + '<body></body></html>'
90 );
91 nwin.document.close();
92 nwin.document.title += ' ' + win.document.title;
93 win = nwin;
94 }
95 var doc = win.document;
96 this.doc = doc;
97
98 // Connect to the debug pane if it already exists (i.e. in a window orphaned by the page being refreshed)
99 var debugPane = doc.getElementById(uid);
100 var existing_pane = !!debugPane;
101 if (debugPane && typeof(debugPane.loggingPane) != "undefined") {
102 debugPane.loggingPane.logger = this.logger;
103 debugPane.loggingPane.buildAndApplyFilter();
104 return debugPane.loggingPane;
105 }
106
107 if (existing_pane) {
108 // clear any existing contents
109 var child;
110 while ((child = debugPane.firstChild)) {
111 debugPane.removeChild(child);
112 }
113 } else {
114 debugPane = doc.createElement("div");
115 debugPane.id = uid;
116 }
117 debugPane.loggingPane = this;
118 var levelFilterField = doc.createElement("input");
119 var infoFilterField = doc.createElement("input");
120 var filterButton = doc.createElement("button");
121 var loadButton = doc.createElement("button");
122 var clearButton = doc.createElement("button");
123 var closeButton = doc.createElement("button");
124 var logPaneArea = doc.createElement("div");
125 var logPane = doc.createElement("div");
126
127 /* Set up the functions */
128 var listenerId = uid + "_Listener";
129 this.colorTable = clone(this.colorTable);
130 var messages = [];
131 var messageFilter = null;
132
133 /** @id MochiKit.LoggingPane.messageLevel */
134 var messageLevel = function (msg) {
135 var level = msg.level;
136 if (typeof(level) == "number") {
137 level = MochiKit.Logging.LogLevel[level];
138 }
139 return level;
140 };
141
142 /** @id MochiKit.LoggingPane.messageText */
143 var messageText = function (msg) {
144 return msg.info.join(" ");
145 };
146
147 /** @id MochiKit.LoggingPane.addMessageText */
148 var addMessageText = bind(function (msg) {
149 var level = messageLevel(msg);
150 var text = messageText(msg);
151 var c = this.colorTable[level];
152 var p = doc.createElement("span");
153 p.className = "MochiKit-LogMessage MochiKit-LogLevel-" + level;
154 p.style.cssText = "margin: 0px; white-space: -moz-pre-wrap; white-space: -o-pre-wrap; white-space: pre-wrap; white-space: pre-line; word-wrap: break-word; wrap-option: emergency; color: " + c;
155 p.appendChild(doc.createTextNode(level + ": " + text));
156 logPane.appendChild(p);
157 logPane.appendChild(doc.createElement("br"));
158 if (logPaneArea.offsetHeight > logPaneArea.scrollHeight) {
159 logPaneArea.scrollTop = 0;
160 } else {
161 logPaneArea.scrollTop = logPaneArea.scrollHeight;
162 }
163 }, this);
164
165 /** @id MochiKit.LoggingPane.addMessage */
166 var addMessage = function (msg) {
167 messages[messages.length] = msg;
168 addMessageText(msg);
169 };
170
171 /** @id MochiKit.LoggingPane.buildMessageFilter */
172 var buildMessageFilter = function () {
173 var levelre, infore;
174 try {
175 /* Catch any exceptions that might arise due to invalid regexes */
176 levelre = new RegExp(levelFilterField.value);
177 infore = new RegExp(infoFilterField.value);
178 } catch(e) {
179 /* If there was an error with the regexes, do no filtering */
180 logDebug("Error in filter regex: " + e.message);
181 return null;
182 }
183
184 return function (msg) {
185 return (
186 levelre.test(messageLevel(msg)) &&
187 infore.test(messageText(msg))
188 );
189 };
190 };
191
192 /** @id MochiKit.LoggingPane.clearMessagePane */
193 var clearMessagePane = function () {
194 while (logPane.firstChild) {
195 logPane.removeChild(logPane.firstChild);
196 }
197 };
198
199 /** @id MochiKit.LoggingPane.clearMessages */
200 var clearMessages = function () {
201 messages = [];
202 clearMessagePane();
203 };
204
205 /** @id MochiKit.LoggingPane.closePane */
206 var closePane = bind(function () {
207 if (this.closed) {
208 return;
209 }
210 this.closed = true;
211 if (MochiKit.LoggingPane._loggingPane == this) {
212 MochiKit.LoggingPane._loggingPane = null;
213 }
214 this.logger.removeListener(listenerId);
215 try {
216 try {
217 debugPane.loggingPane = null;
218 } catch(e) { logFatal("Bookmarklet was closed incorrectly."); }
219 if (inline) {
220 debugPane.parentNode.removeChild(debugPane);
221 } else {
222 this.win.close();
223 }
224 } catch(e) {}
225 }, this);
226
227 /** @id MochiKit.LoggingPane.filterMessages */
228 var filterMessages = function () {
229 clearMessagePane();
230
231 for (var i = 0; i < messages.length; i++) {
232 var msg = messages[i];
233 if (messageFilter === null || messageFilter(msg)) {
234 addMessageText(msg);
235 }
236 }
237 };
238
239 this.buildAndApplyFilter = function () {
240 messageFilter = buildMessageFilter();
241
242 filterMessages();
243
244 this.logger.removeListener(listenerId);
245 this.logger.addListener(listenerId, messageFilter, addMessage);
246 };
247
248
249 /** @id MochiKit.LoggingPane.loadMessages */
250 var loadMessages = bind(function () {
251 messages = this.logger.getMessages();
252 filterMessages();
253 }, this);
254
255 /** @id MochiKit.LoggingPane.filterOnEnter */
256 var filterOnEnter = bind(function (event) {
257 event = event || window.event;
258 key = event.which || event.keyCode;
259 if (key == 13) {
260 this.buildAndApplyFilter();
261 }
262 }, this);
263
264 /* Create the debug pane */
265 var style = "display: block; z-index: 1000; left: 0px; bottom: 0px; position: fixed; width: 100%; background-color: white; font: " + this.logFont;
266 if (inline) {
267 style += "; height: 10em; border-top: 2px solid black";
268 } else {
269 style += "; height: 100%;";
270 }
271 debugPane.style.cssText = style;
272
273 if (!existing_pane) {
274 doc.body.appendChild(debugPane);
275 }
276
277 /* Create the filter fields */
278 style = {"cssText": "width: 33%; display: inline; font: " + this.logFont};
279
280 updatetree(levelFilterField, {
281 "value": "FATAL|ERROR|WARNING|INFO|DEBUG",
282 "onkeypress": filterOnEnter,
283 "style": style
284 });
285 debugPane.appendChild(levelFilterField);
286
287 updatetree(infoFilterField, {
288 "value": ".*",
289 "onkeypress": filterOnEnter,
290 "style": style
291 });
292 debugPane.appendChild(infoFilterField);
293
294 /* Create the buttons */
295 style = "width: 8%; display:inline; font: " + this.logFont;
296
297 filterButton.appendChild(doc.createTextNode("Filter"));
298 filterButton.onclick = bind("buildAndApplyFilter", this);
299 filterButton.style.cssText = style;
300 debugPane.appendChild(filterButton);
301
302 loadButton.appendChild(doc.createTextNode("Load"));
303 loadButton.onclick = loadMessages;
304 loadButton.style.cssText = style;
305 debugPane.appendChild(loadButton);
306
307 clearButton.appendChild(doc.createTextNode("Clear"));
308 clearButton.onclick = clearMessages;
309 clearButton.style.cssText = style;
310 debugPane.appendChild(clearButton);
311
312 closeButton.appendChild(doc.createTextNode("Close"));
313 closeButton.onclick = closePane;
314 closeButton.style.cssText = style;
315 debugPane.appendChild(closeButton);
316
317 /* Create the logging pane */
318 logPaneArea.style.cssText = "overflow: auto; width: 100%";
319 logPane.style.cssText = "width: 100%; height: " + (inline ? "8em" : "100%");
320
321 logPaneArea.appendChild(logPane);
322 debugPane.appendChild(logPaneArea);
323
324 this.buildAndApplyFilter();
325 loadMessages();
326
327 if (inline) {
328 this.win = undefined;
329 } else {
330 this.win = win;
331 }
332 this.inline = inline;
333 this.closePane = closePane;
334 this.closed = false;
335
336
337 return this;
338 };
339
340 MochiKit.LoggingPane.LoggingPane.prototype = {
341 "logFont": "8pt Verdana,sans-serif",
342 "colorTable": {
343 "ERROR": "red",
344 "FATAL": "darkred",
345 "WARNING": "blue",
346 "INFO": "black",
347 "DEBUG": "green"
348 }
349 };
350
351
352 MochiKit.LoggingPane.EXPORT_OK = [
353 "LoggingPane"
354 ];
355
356 MochiKit.LoggingPane.EXPORT = [
357 "createLoggingPane"
358 ];
359
360 MochiKit.LoggingPane.__new__ = function () {
361 this.EXPORT_TAGS = {
362 ":common": this.EXPORT,
363 ":all": MochiKit.Base.concat(this.EXPORT, this.EXPORT_OK)
364 };
365
366 MochiKit.Base.nameFunctions(this);
367
368 MochiKit.LoggingPane._loggingPane = null;
369
370 };
371
372 MochiKit.LoggingPane.__new__();
373
374 MochiKit.Base._exportSymbols(this, MochiKit.LoggingPane);
@@ -0,0 +1,154 b''
1 /***
2
3 MochiKit.MochiKit 1.4
4
5 See <http://mochikit.com/> for documentation, downloads, license, etc.
6
7 (c) 2005 Bob Ippolito. All rights Reserved.
8
9 ***/
10
11 if (typeof(MochiKit) == 'undefined') {
12 MochiKit = {};
13 }
14
15 if (typeof(MochiKit.MochiKit) == 'undefined') {
16 /** @id MochiKit.MochiKit */
17 MochiKit.MochiKit = {};
18 }
19
20 MochiKit.MochiKit.NAME = "MochiKit.MochiKit";
21 MochiKit.MochiKit.VERSION = "1.4";
22 MochiKit.MochiKit.__repr__ = function () {
23 return "[" + this.NAME + " " + this.VERSION + "]";
24 };
25
26 /** @id MochiKit.MochiKit.toString */
27 MochiKit.MochiKit.toString = function () {
28 return this.__repr__();
29 };
30
31 /** @id MochiKit.MochiKit.SUBMODULES */
32 MochiKit.MochiKit.SUBMODULES = [
33 "Base",
34 "Iter",
35 "Logging",
36 "DateTime",
37 "Format",
38 "Async",
39 "DOM",
40 "Selector",
41 "Style",
42 "LoggingPane",
43 "Color",
44 "Signal",
45 "Position",
46 "Visual"
47 ];
48
49 if (typeof(JSAN) != 'undefined' || typeof(dojo) != 'undefined') {
50 if (typeof(dojo) != 'undefined') {
51 dojo.provide('MochiKit.MochiKit');
52 dojo.require("MochiKit.*");
53 }
54 if (typeof(JSAN) != 'undefined') {
55 (function (lst) {
56 for (var i = 0; i < lst.length; i++) {
57 JSAN.use("MochiKit." + lst[i], []);
58 }
59 })(MochiKit.MochiKit.SUBMODULES);
60 }
61 (function () {
62 var extend = MochiKit.Base.extend;
63 var self = MochiKit.MochiKit;
64 var modules = self.SUBMODULES;
65 var EXPORT = [];
66 var EXPORT_OK = [];
67 var EXPORT_TAGS = {};
68 var i, k, m, all;
69 for (i = 0; i < modules.length; i++) {
70 m = MochiKit[modules[i]];
71 extend(EXPORT, m.EXPORT);
72 extend(EXPORT_OK, m.EXPORT_OK);
73 for (k in m.EXPORT_TAGS) {
74 EXPORT_TAGS[k] = extend(EXPORT_TAGS[k], m.EXPORT_TAGS[k]);
75 }
76 all = m.EXPORT_TAGS[":all"];
77 if (!all) {
78 all = extend(null, m.EXPORT, m.EXPORT_OK);
79 }
80 var j;
81 for (j = 0; j < all.length; j++) {
82 k = all[j];
83 self[k] = m[k];
84 }
85 }
86 self.EXPORT = EXPORT;
87 self.EXPORT_OK = EXPORT_OK;
88 self.EXPORT_TAGS = EXPORT_TAGS;
89 }());
90
91 } else {
92 if (typeof(MochiKit.__compat__) == 'undefined') {
93 MochiKit.__compat__ = true;
94 }
95 (function () {
96 if (typeof(document) == "undefined") {
97 return;
98 }
99 var scripts = document.getElementsByTagName("script");
100 var kXULNSURI = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
101 var base = null;
102 var baseElem = null;
103 var allScripts = {};
104 var i;
105 for (i = 0; i < scripts.length; i++) {
106 var src = scripts[i].getAttribute("src");
107 if (!src) {
108 continue;
109 }
110 allScripts[src] = true;
111 if (src.match(/MochiKit.js$/)) {
112 base = src.substring(0, src.lastIndexOf('MochiKit.js'));
113 baseElem = scripts[i];
114 }
115 }
116 if (base === null) {
117 return;
118 }
119 var modules = MochiKit.MochiKit.SUBMODULES;
120 for (var i = 0; i < modules.length; i++) {
121 if (MochiKit[modules[i]]) {
122 continue;
123 }
124 var uri = base + modules[i] + '.js';
125 if (uri in allScripts) {
126 continue;
127 }
128 if (document.documentElement &&
129 document.documentElement.namespaceURI == kXULNSURI) {
130 // XUL
131 var s = document.createElementNS(kXULNSURI, 'script');
132 s.setAttribute("id", "MochiKit_" + base + modules[i]);
133 s.setAttribute("src", uri);
134 s.setAttribute("type", "application/x-javascript");
135 baseElem.parentNode.appendChild(s);
136 } else {
137 // HTML
138 /*
139 DOM can not be used here because Safari does
140 deferred loading of scripts unless they are
141 in the document or inserted with document.write
142
143 This is not XHTML compliant. If you want XHTML
144 compliance then you must use the packed version of MochiKit
145 or include each script individually (basically unroll
146 these document.write calls into your XHTML source)
147
148 */
149 document.write('<script src="' + uri +
150 '" type="text/javascript"></script>');
151 }
152 };
153 })();
154 }
@@ -0,0 +1,115 b''
1 /***
2
3 MochiKit.MockDOM 1.4
4
5 See <http://mochikit.com/> for documentation, downloads, license, etc.
6
7 (c) 2005 Bob Ippolito. All rights Reserved.
8
9 ***/
10
11 if (typeof(MochiKit) == "undefined") {
12 MochiKit = {};
13 }
14
15 if (typeof(MochiKit.MockDOM) == "undefined") {
16 MochiKit.MockDOM = {};
17 }
18
19 MochiKit.MockDOM.NAME = "MochiKit.MockDOM";
20 MochiKit.MockDOM.VERSION = "1.4";
21
22 MochiKit.MockDOM.__repr__ = function () {
23 return "[" + this.NAME + " " + this.VERSION + "]";
24 };
25
26 /** @id MochiKit.MockDOM.toString */
27 MochiKit.MockDOM.toString = function () {
28 return this.__repr__();
29 };
30
31 /** @id MochiKit.MockDOM.createDocument */
32 MochiKit.MockDOM.createDocument = function () {
33 var doc = new MochiKit.MockDOM.MockElement("DOCUMENT");
34 doc.body = doc.createElement("BODY");
35 doc.appendChild(doc.body);
36 return doc;
37 };
38
39 /** @id MochiKit.MockDOM.MockElement */
40 MochiKit.MockDOM.MockElement = function (name, data, ownerDocument) {
41 this.tagName = this.nodeName = name.toUpperCase();
42 this.ownerDocument = ownerDocument || null;
43 if (name == "DOCUMENT") {
44 this.nodeType = 9;
45 this.childNodes = [];
46 } else if (typeof(data) == "string") {
47 this.nodeValue = data;
48 this.nodeType = 3;
49 } else {
50 this.nodeType = 1;
51 this.childNodes = [];
52 }
53 if (name.substring(0, 1) == "<") {
54 var nameattr = name.substring(
55 name.indexOf('"') + 1, name.lastIndexOf('"'));
56 name = name.substring(1, name.indexOf(" "));
57 this.tagName = this.nodeName = name.toUpperCase();
58 this.setAttribute("name", nameattr);
59 }
60 };
61
62 MochiKit.MockDOM.MockElement.prototype = {
63 /** @id MochiKit.MockDOM.MockElement.prototype.createElement */
64 createElement: function (tagName) {
65 return new MochiKit.MockDOM.MockElement(tagName, null, this.nodeType == 9 ? this : this.ownerDocument);
66 },
67 /** @id MochiKit.MockDOM.MockElement.prototype.createTextNode */
68 createTextNode: function (text) {
69 return new MochiKit.MockDOM.MockElement("text", text, this.nodeType == 9 ? this : this.ownerDocument);
70 },
71 /** @id MochiKit.MockDOM.MockElement.prototype.setAttribute */
72 setAttribute: function (name, value) {
73 this[name] = value;
74 },
75 /** @id MochiKit.MockDOM.MockElement.prototype.getAttribute */
76 getAttribute: function (name) {
77 return this[name];
78 },
79 /** @id MochiKit.MockDOM.MockElement.prototype.appendChild */
80 appendChild: function (child) {
81 this.childNodes.push(child);
82 },
83 /** @id MochiKit.MockDOM.MockElement.prototype.toString */
84 toString: function () {
85 return "MockElement(" + this.tagName + ")";
86 },
87 /** @id MochiKit.MockDOM.MockElement.prototype.getElementsByTagName */
88 getElementsByTagName: function (tagName) {
89 var foundElements = [];
90 MochiKit.Base.nodeWalk(this, function(node){
91 if (tagName == '*' || tagName == node.tagName) {
92 foundElements.push(node);
93 return node.childNodes;
94 }
95 });
96 return foundElements;
97 }
98 };
99
100 /** @id MochiKit.MockDOM.EXPORT_OK */
101 MochiKit.MockDOM.EXPORT_OK = [
102 "mockElement",
103 "createDocument"
104 ];
105
106 /** @id MochiKit.MockDOM.EXPORT */
107 MochiKit.MockDOM.EXPORT = [
108 "document"
109 ];
110
111 MochiKit.MockDOM.__new__ = function () {
112 this.document = this.createDocument();
113 };
114
115 MochiKit.MockDOM.__new__();
@@ -0,0 +1,258 b''
1 /***
2
3 MochiKit.Position 1.4
4
5 See <http://mochikit.com/> for documentation, downloads, license, etc.
6
7 (c) 2005-2006 Bob Ippolito and others. All rights Reserved.
8
9 ***/
10
11 if (typeof(dojo) != 'undefined') {
12 dojo.provide('MochiKit.Position');
13 dojo.require('MochiKit.Base');
14 dojo.require('MochiKit.DOM');
15 dojo.require('MochiKit.Style');
16 }
17 if (typeof(JSAN) != 'undefined') {
18 JSAN.use('MochiKit.Base', []);
19 JSAN.use('MochiKit.DOM', []);
20 JSAN.use('MochiKit.Style', []);
21 }
22
23 try {
24 if (typeof(MochiKit.Base) == 'undefined' ||
25 typeof(MochiKit.Style) == 'undefined' ||
26 typeof(MochiKit.DOM) == 'undefined') {
27 throw '';
28 }
29 } catch (e) {
30 throw 'MochiKit.Style depends on MochiKit.Base, MochiKit.DOM, and MochiKit.Style!';
31 }
32
33 if (typeof(MochiKit.Position) == 'undefined') {
34 MochiKit.Position = {};
35 }
36
37 MochiKit.Position.NAME = 'MochiKit.Position';
38 MochiKit.Position.VERSION = '1.4';
39 MochiKit.Position.__repr__ = function () {
40 return '[' + this.NAME + ' ' + this.VERSION + ']';
41 };
42 MochiKit.Position.toString = function () {
43 return this.__repr__();
44 };
45
46 MochiKit.Position.EXPORT_OK = [];
47
48 MochiKit.Position.EXPORT = [
49 ];
50
51
52 MochiKit.Base.update(MochiKit.Position, {
53 // set to true if needed, warning: firefox performance problems
54 // NOT neeeded for page scrolling, only if draggable contained in
55 // scrollable elements
56 includeScrollOffsets: false,
57
58 /** @id MochiKit.Position.prepare */
59 prepare: function () {
60 var deltaX = window.pageXOffset
61 || document.documentElement.scrollLeft
62 || document.body.scrollLeft
63 || 0;
64 var deltaY = window.pageYOffset
65 || document.documentElement.scrollTop
66 || document.body.scrollTop
67 || 0;
68 this.windowOffset = new MochiKit.Style.Coordinates(deltaX, deltaY);
69 },
70
71 /** @id MochiKit.Position.cumulativeOffset */
72 cumulativeOffset: function (element) {
73 var valueT = 0;
74 var valueL = 0;
75 do {
76 valueT += element.offsetTop || 0;
77 valueL += element.offsetLeft || 0;
78 element = element.offsetParent;
79 } while (element);
80 return new MochiKit.Style.Coordinates(valueL, valueT);
81 },
82
83 /** @id MochiKit.Position.realOffset */
84 realOffset: function (element) {
85 var valueT = 0;
86 var valueL = 0;
87 do {
88 valueT += element.scrollTop || 0;
89 valueL += element.scrollLeft || 0;
90 element = element.parentNode;
91 } while (element);
92 return new MochiKit.Style.Coordinates(valueL, valueT);
93 },
94
95 /** @id MochiKit.Position.within */
96 within: function (element, x, y) {
97 if (this.includeScrollOffsets) {
98 return this.withinIncludingScrolloffsets(element, x, y);
99 }
100 this.xcomp = x;
101 this.ycomp = y;
102 this.offset = this.cumulativeOffset(element);
103 if (element.style.position == "fixed") {
104 this.offset.x += this.windowOffset.x;
105 this.offset.y += this.windowOffset.y;
106 }
107
108 return (y >= this.offset.y &&
109 y < this.offset.y + element.offsetHeight &&
110 x >= this.offset.x &&
111 x < this.offset.x + element.offsetWidth);
112 },
113
114 /** @id MochiKit.Position.withinIncludingScrolloffsets */
115 withinIncludingScrolloffsets: function (element, x, y) {
116 var offsetcache = this.realOffset(element);
117
118 this.xcomp = x + offsetcache.x - this.windowOffset.x;
119 this.ycomp = y + offsetcache.y - this.windowOffset.y;
120 this.offset = this.cumulativeOffset(element);
121
122 return (this.ycomp >= this.offset.y &&
123 this.ycomp < this.offset.y + element.offsetHeight &&
124 this.xcomp >= this.offset.x &&
125 this.xcomp < this.offset.x + element.offsetWidth);
126 },
127
128 // within must be called directly before
129 /** @id MochiKit.Position.overlap */
130 overlap: function (mode, element) {
131 if (!mode) {
132 return 0;
133 }
134 if (mode == 'vertical') {
135 return ((this.offset.y + element.offsetHeight) - this.ycomp) /
136 element.offsetHeight;
137 }
138 if (mode == 'horizontal') {
139 return ((this.offset.x + element.offsetWidth) - this.xcomp) /
140 element.offsetWidth;
141 }
142 },
143
144 /** @id MochiKit.Position.absolutize */
145 absolutize: function (element) {
146 element = MochiKit.DOM.getElement(element);
147 if (element.style.position == 'absolute') {
148 return;
149 }
150 MochiKit.Position.prepare();
151
152 var offsets = MochiKit.Position.positionedOffset(element);
153 var width = element.clientWidth;
154 var height = element.clientHeight;
155
156 var oldStyle = {
157 'position': element.style.position,
158 'left': offsets.x - parseFloat(element.style.left || 0),
159 'top': offsets.y - parseFloat(element.style.top || 0),
160 'width': element.style.width,
161 'height': element.style.height
162 };
163
164 element.style.position = 'absolute';
165 element.style.top = offsets.y + 'px';
166 element.style.left = offsets.x + 'px';
167 element.style.width = width + 'px';
168 element.style.height = height + 'px';
169
170 return oldStyle;
171 },
172
173 /** @id MochiKit.Position.positionedOffset */
174 positionedOffset: function (element) {
175 var valueT = 0, valueL = 0;
176 do {
177 valueT += element.offsetTop || 0;
178 valueL += element.offsetLeft || 0;
179 element = element.offsetParent;
180 if (element) {
181 p = MochiKit.Style.getStyle(element, 'position');
182 if (p == 'relative' || p == 'absolute') {
183 break;
184 }
185 }
186 } while (element);
187 return new MochiKit.Style.Coordinates(valueL, valueT);
188 },
189
190 /** @id MochiKit.Position.relativize */
191 relativize: function (element, oldPos) {
192 element = MochiKit.DOM.getElement(element);
193 if (element.style.position == 'relative') {
194 return;
195 }
196 MochiKit.Position.prepare();
197
198 var top = parseFloat(element.style.top || 0) -
199 (oldPos['top'] || 0);
200 var left = parseFloat(element.style.left || 0) -
201 (oldPos['left'] || 0);
202
203 element.style.position = oldPos['position'];
204 element.style.top = top + 'px';
205 element.style.left = left + 'px';
206 element.style.width = oldPos['width'];
207 element.style.height = oldPos['height'];
208 },
209
210 /** @id MochiKit.Position.clone */
211 clone: function (source, target) {
212 source = MochiKit.DOM.getElement(source);
213 target = MochiKit.DOM.getElement(target);
214 target.style.position = 'absolute';
215 var offsets = this.cumulativeOffset(source);
216 target.style.top = offsets.y + 'px';
217 target.style.left = offsets.x + 'px';
218 target.style.width = source.offsetWidth + 'px';
219 target.style.height = source.offsetHeight + 'px';
220 },
221
222 /** @id MochiKit.Position.page */
223 page: function (forElement) {
224 var valueT = 0;
225 var valueL = 0;
226
227 var element = forElement;
228 do {
229 valueT += element.offsetTop || 0;
230 valueL += element.offsetLeft || 0;
231
232 // Safari fix
233 if (element.offsetParent == document.body && MochiKit.Style.getStyle(element, 'position') == 'absolute') {
234 break;
235 }
236 } while (element = element.offsetParent);
237
238 element = forElement;
239 do {
240 valueT -= element.scrollTop || 0;
241 valueL -= element.scrollLeft || 0;
242 } while (element = element.parentNode);
243
244 return new MochiKit.Style.Coordinates(valueL, valueT);
245 }
246 });
247
248 MochiKit.Position.__new__ = function (win) {
249 var m = MochiKit.Base;
250 this.EXPORT_TAGS = {
251 ':common': this.EXPORT,
252 ':all': m.concat(this.EXPORT, this.EXPORT_OK)
253 };
254
255 m.nameFunctions(this);
256 };
257
258 MochiKit.Position.__new__(this); No newline at end of file
@@ -0,0 +1,431 b''
1 /***
2
3 MochiKit.Selector 1.4
4
5 See <http://mochikit.com/> for documentation, downloads, license, etc.
6
7 (c) 2005 Bob Ippolito and others. All rights Reserved.
8
9 ***/
10
11 if (typeof(dojo) != 'undefined') {
12 dojo.provide('MochiKit.Selector');
13 dojo.require('MochiKit.Base');
14 dojo.require('MochiKit.DOM');
15 dojo.require('MochiKit.Iter');
16 }
17
18 if (typeof(JSAN) != 'undefined') {
19 JSAN.use("MochiKit.Base", []);
20 JSAN.use("MochiKit.DOM", []);
21 JSAN.use("MochiKit.Iter", []);
22 }
23
24 try {
25 if (typeof(MochiKit.Base) === 'undefined' ||
26 typeof(MochiKit.DOM) === 'undefined' ||
27 typeof(MochiKit.Iter) === 'undefined') {
28 throw "";
29 }
30 } catch (e) {
31 throw "MochiKit.Selector depends on MochiKit.Base, MochiKit.DOM and MochiKit.Iter!";
32 }
33
34 if (typeof(MochiKit.Selector) == 'undefined') {
35 MochiKit.Selector = {};
36 }
37
38 MochiKit.Selector.NAME = "MochiKit.Selector";
39
40 MochiKit.Selector.VERSION = "1.4";
41
42 MochiKit.Selector.__repr__ = function () {
43 return "[" + this.NAME + " " + this.VERSION + "]";
44 };
45
46 MochiKit.Selector.toString = function () {
47 return this.__repr__();
48 };
49
50 MochiKit.Selector.EXPORT = [
51 "Selector",
52 "findChildElements",
53 "findDocElements",
54 "$$"
55 ];
56
57 MochiKit.Selector.EXPORT_OK = [
58 ];
59
60 MochiKit.Selector.Selector = function (expression) {
61 this.params = {classNames: [], pseudoClassNames: []};
62 this.expression = expression.toString().replace(/(^\s+|\s+$)/g, '');
63 this.parseExpression();
64 this.compileMatcher();
65 };
66
67 MochiKit.Selector.Selector.prototype = {
68 /***
69
70 Selector class: convenient object to make CSS selections.
71
72 ***/
73 __class__: MochiKit.Selector.Selector,
74
75 /** @id MochiKit.Selector.Selector.prototype.parseExpression */
76 parseExpression: function () {
77 function abort(message) {
78 throw 'Parse error in selector: ' + message;
79 }
80
81 if (this.expression == '') {
82 abort('empty expression');
83 }
84
85 var repr = MochiKit.Base.repr;
86 var params = this.params;
87 var expr = this.expression;
88 var match, modifier, clause, rest;
89 while (match = expr.match(/^(.*)\[([a-z0-9_:-]+?)(?:([~\|!^$*]?=)(?:"([^"]*)"|([^\]\s]*)))?\]$/i)) {
90 params.attributes = params.attributes || [];
91 params.attributes.push({name: match[2], operator: match[3], value: match[4] || match[5] || ''});
92 expr = match[1];
93 }
94
95 if (expr == '*') {
96 return this.params.wildcard = true;
97 }
98
99 while (match = expr.match(/^([^a-z0-9_-])?([a-z0-9_-]+(?:\([^)]*\))?)(.*)/i)) {
100 modifier = match[1];
101 clause = match[2];
102 rest = match[3];
103 switch (modifier) {
104 case '#':
105 params.id = clause;
106 break;
107 case '.':
108 params.classNames.push(clause);
109 break;
110 case ':':
111 params.pseudoClassNames.push(clause);
112 break;
113 case '':
114 case undefined:
115 params.tagName = clause.toUpperCase();
116 break;
117 default:
118 abort(repr(expr));
119 }
120 expr = rest;
121 }
122
123 if (expr.length > 0) {
124 abort(repr(expr));
125 }
126 },
127
128 /** @id MochiKit.Selector.Selector.prototype.buildMatchExpression */
129 buildMatchExpression: function () {
130 var repr = MochiKit.Base.repr;
131 var params = this.params;
132 var conditions = [];
133 var clause, i;
134
135 function childElements(element) {
136 return "MochiKit.Base.filter(function (node) { return node.nodeType == 1; }, " + element + ".childNodes)";
137 }
138
139 if (params.wildcard) {
140 conditions.push('true');
141 }
142 if (clause = params.id) {
143 conditions.push('element.id == ' + repr(clause));
144 }
145 if (clause = params.tagName) {
146 conditions.push('element.tagName.toUpperCase() == ' + repr(clause));
147 }
148 if ((clause = params.classNames).length > 0) {
149 for (i = 0; i < clause.length; i++) {
150 conditions.push('MochiKit.DOM.hasElementClass(element, ' + repr(clause[i]) + ')');
151 }
152 }
153 if ((clause = params.pseudoClassNames).length > 0) {
154 for (i = 0; i < clause.length; i++) {
155 var match = clause[i].match(/^([^(]+)(?:\((.*)\))?$/);
156 var pseudoClass = match[1];
157 var pseudoClassArgument = match[2];
158 switch (pseudoClass) {
159 case 'root':
160 conditions.push('element.nodeType == 9 || element === element.ownerDocument.documentElement'); break;
161 case 'nth-child':
162 case 'nth-last-child':
163 case 'nth-of-type':
164 case 'nth-last-of-type':
165 match = pseudoClassArgument.match(/^((?:(\d+)n\+)?(\d+)|odd|even)$/);
166 if (!match) {
167 throw "Invalid argument to pseudo element nth-child: " + pseudoClassArgument;
168 }
169 var a, b;
170 if (match[0] == 'odd') {
171 a = 2;
172 b = 1;
173 } else if (match[0] == 'even') {
174 a = 2;
175 b = 0;
176 } else {
177 a = match[2] && parseInt(match) || null;
178 b = parseInt(match[3]);
179 }
180 conditions.push('this.nthChild(element,' + a + ',' + b
181 + ',' + !!pseudoClass.match('^nth-last') // Reverse
182 + ',' + !!pseudoClass.match('of-type$') // Restrict to same tagName
183 + ')');
184 break;
185 case 'first-child':
186 conditions.push('this.nthChild(element, null, 1)');
187 break;
188 case 'last-child':
189 conditions.push('this.nthChild(element, null, 1, true)');
190 break;
191 case 'first-of-type':
192 conditions.push('this.nthChild(element, null, 1, false, true)');
193 break;
194 case 'last-of-type':
195 conditions.push('this.nthChild(element, null, 1, true, true)');
196 break;
197 case 'only-child':
198 conditions.push(childElements('element.parentNode') + '.length == 1');
199 break;
200 case 'only-of-type':
201 conditions.push('MochiKit.Base.filter(function (node) { return node.tagName == element.tagName; }, ' + childElements('element.parentNode') + ').length == 1');
202 break;
203 case 'empty':
204 conditions.push('element.childNodes.length == 0');
205 break;
206 case 'enabled':
207 conditions.push('(this.isUIElement(element) && element.disabled === false)');
208 break;
209 case 'disabled':
210 conditions.push('(this.isUIElement(element) && element.disabled === true)');
211 break;
212 case 'checked':
213 conditions.push('(this.isUIElement(element) && element.checked === true)');
214 break;
215 case 'not':
216 var subselector = new MochiKit.Selector.Selector(pseudoClassArgument);
217 conditions.push('!( ' + subselector.buildMatchExpression() + ')')
218 break;
219 }
220 }
221 }
222 if (clause = params.attributes) {
223 MochiKit.Base.map(function (attribute) {
224 var value = 'MochiKit.DOM.getNodeAttribute(element, ' + repr(attribute.name) + ')';
225 var splitValueBy = function (delimiter) {
226 return value + '.split(' + repr(delimiter) + ')';
227 }
228
229 switch (attribute.operator) {
230 case '=':
231 conditions.push(value + ' == ' + repr(attribute.value));
232 break;
233 case '~=':
234 conditions.push(value + ' && MochiKit.Base.findValue(' + splitValueBy(' ') + ', ' + repr(attribute.value) + ') > -1');
235 break;
236 case '^=':
237 conditions.push(value + '.substring(0, ' + attribute.value.length + ') == ' + repr(attribute.value));
238 break;
239 case '$=':
240 conditions.push(value + '.substring(' + value + '.length - ' + attribute.value.length + ') == ' + repr(attribute.value));
241 break;
242 case '*=':
243 conditions.push(value + '.match(' + repr(attribute.value) + ')');
244 break;
245 case '|=':
246 conditions.push(
247 value + ' && ' + splitValueBy('-') + '[0].toUpperCase() == ' + repr(attribute.value.toUpperCase())
248 );
249 break;
250 case '!=':
251 conditions.push(value + ' != ' + repr(attribute.value));
252 break;
253 case '':
254 case undefined:
255 conditions.push(value + ' != null');
256 break;
257 default:
258 throw 'Unknown operator ' + attribute.operator + ' in selector';
259 }
260 }, clause);
261 }
262
263 return conditions.join(' && ');
264 },
265
266 /** @id MochiKit.Selector.Selector.prototype.compileMatcher */
267 compileMatcher: function () {
268 this.match = new Function('element', 'if (!element.tagName) return false; \
269 return ' + this.buildMatchExpression());
270 },
271
272 /** @id MochiKit.Selector.Selector.prototype.nthChild */
273 nthChild: function (element, a, b, reverse, sametag){
274 var siblings = MochiKit.Base.filter(function (node) {
275 return node.nodeType == 1;
276 }, element.parentNode.childNodes);
277 if (sametag) {
278 siblings = MochiKit.Base.filter(function (node) {
279 return node.tagName == element.tagName;
280 }, siblings);
281 }
282 if (reverse) {
283 siblings = MochiKit.Iter.reversed(siblings);
284 }
285 if (a) {
286 var actualIndex = MochiKit.Base.findIdentical(siblings, element);
287 return ((actualIndex + 1 - b) / a) % 1 == 0;
288 } else {
289 return b == MochiKit.Base.findIdentical(siblings, element) + 1;
290 }
291 },
292
293 /** @id MochiKit.Selector.Selector.prototype.isUIElement */
294 isUIElement: function (element) {
295 return MochiKit.Base.findValue(['input', 'button', 'select', 'option', 'textarea', 'object'],
296 element.tagName.toLowerCase()) > -1;
297 },
298
299 /** @id MochiKit.Selector.Selector.prototype.findElements */
300 findElements: function (scope, axis) {
301 var element;
302
303 if (axis == undefined) {
304 axis = "";
305 }
306
307 function inScope(element, scope) {
308 if (axis == "") {
309 return MochiKit.DOM.isChildNode(element, scope);
310 } else if (axis == ">") {
311 return element.parentNode == scope;
312 } else if (axis == "+") {
313 return element == nextSiblingElement(scope);
314 } else if (axis == "~") {
315 var sibling = scope;
316 while (sibling = nextSiblingElement(sibling)) {
317 if (element == sibling) {
318 return true;
319 }
320 }
321 return false;
322 } else {
323 throw "Invalid axis: " + axis;
324 }
325 }
326
327 if (element = MochiKit.DOM.getElement(this.params.id)) {
328 if (this.match(element)) {
329 if (!scope || inScope(element, scope)) {
330 return [element];
331 }
332 }
333 }
334
335 function nextSiblingElement(node) {
336 node = node.nextSibling;
337 while (node && node.nodeType != 1) {
338 node = node.nextSibling;
339 }
340 return node;
341 }
342
343 if (axis == "") {
344 scope = (scope || MochiKit.DOM.currentDocument()).getElementsByTagName(this.params.tagName || '*');
345 } else if (axis == ">") {
346 if (!scope) {
347 throw "> combinator not allowed without preceeding expression";
348 }
349 scope = MochiKit.Base.filter(function (node) {
350 return node.nodeType == 1;
351 }, scope.childNodes);
352 } else if (axis == "+") {
353 if (!scope) {
354 throw "+ combinator not allowed without preceeding expression";
355 }
356 scope = nextSiblingElement(scope) && [nextSiblingElement(scope)];
357 } else if (axis == "~") {
358 if (!scope) {
359 throw "~ combinator not allowed without preceeding expression";
360 }
361 var newscope = [];
362 while (nextSiblingElement(scope)) {
363 scope = nextSiblingElement(scope);
364 newscope.push(scope);
365 }
366 scope = newscope;
367 }
368
369 if (!scope) {
370 return [];
371 }
372
373 var results = MochiKit.Base.filter(MochiKit.Base.bind(function (scopeElt) {
374 return this.match(scopeElt);
375 }, this), scope);
376
377 return results;
378 },
379
380 /** @id MochiKit.Selector.Selector.prototype.repr */
381 repr: function () {
382 return 'Selector(' + this.expression + ')';
383 },
384
385 toString: MochiKit.Base.forwardCall("repr")
386 };
387
388 MochiKit.Base.update(MochiKit.Selector, {
389
390 /** @id MochiKit.Selector.findChildElements */
391 findChildElements: function (element, expressions) {
392 return MochiKit.Base.flattenArray(MochiKit.Base.map(function (expression) {
393 var nextScope = "";
394 return MochiKit.Iter.reduce(function (results, expr) {
395 if (match = expr.match(/^[>+~]$/)) {
396 nextScope = match[0];
397 return results;
398 } else {
399 var selector = new MochiKit.Selector.Selector(expr);
400 var elements = MochiKit.Iter.reduce(function (elements, result) {
401 return MochiKit.Base.extend(elements, selector.findElements(result || element, nextScope));
402 }, results, []);
403 nextScope = "";
404 return elements;
405 }
406 }, expression.replace(/(^\s+|\s+$)/g, '').split(/\s+/), [null]);
407 }, expressions));
408 },
409
410 findDocElements: function () {
411 return MochiKit.Selector.findChildElements(MochiKit.DOM.currentDocument(), arguments);
412 },
413
414 __new__: function () {
415 var m = MochiKit.Base;
416
417 this.$$ = this.findDocElements;
418
419 this.EXPORT_TAGS = {
420 ":common": this.EXPORT,
421 ":all": m.concat(this.EXPORT, this.EXPORT_OK)
422 };
423
424 m.nameFunctions(this);
425 }
426 });
427
428 MochiKit.Selector.__new__();
429
430 MochiKit.Base._exportSymbols(this, MochiKit.Selector);
431
This diff has been collapsed as it changes many lines, (903 lines changed) Show them Hide them
@@ -0,0 +1,903 b''
1 /***
2
3 MochiKit.Signal 1.4
4
5 See <http://mochikit.com/> for documentation, downloads, license, etc.
6
7 (c) 2006 Jonathan Gardner, Beau Hartshorne, Bob Ippolito. All rights Reserved.
8
9 ***/
10
11 if (typeof(dojo) != 'undefined') {
12 dojo.provide('MochiKit.Signal');
13 dojo.require('MochiKit.Base');
14 dojo.require('MochiKit.DOM');
15 dojo.require('MochiKit.Style');
16 }
17 if (typeof(JSAN) != 'undefined') {
18 JSAN.use('MochiKit.Base', []);
19 JSAN.use('MochiKit.DOM', []);
20 JSAN.use('MochiKit.Style', []);
21 }
22
23 try {
24 if (typeof(MochiKit.Base) == 'undefined') {
25 throw '';
26 }
27 } catch (e) {
28 throw 'MochiKit.Signal depends on MochiKit.Base!';
29 }
30
31 try {
32 if (typeof(MochiKit.DOM) == 'undefined') {
33 throw '';
34 }
35 } catch (e) {
36 throw 'MochiKit.Signal depends on MochiKit.DOM!';
37 }
38
39 try {
40 if (typeof(MochiKit.Style) == 'undefined') {
41 throw '';
42 }
43 } catch (e) {
44 throw 'MochiKit.Signal depends on MochiKit.Style!';
45 }
46
47 if (typeof(MochiKit.Signal) == 'undefined') {
48 MochiKit.Signal = {};
49 }
50
51 MochiKit.Signal.NAME = 'MochiKit.Signal';
52 MochiKit.Signal.VERSION = '1.4';
53
54 MochiKit.Signal._observers = [];
55
56 /** @id MochiKit.Signal.Event */
57 MochiKit.Signal.Event = function (src, e) {
58 this._event = e || window.event;
59 this._src = src;
60 };
61
62 MochiKit.Base.update(MochiKit.Signal.Event.prototype, {
63
64 __repr__: function () {
65 var repr = MochiKit.Base.repr;
66 var str = '{event(): ' + repr(this.event()) +
67 ', src(): ' + repr(this.src()) +
68 ', type(): ' + repr(this.type()) +
69 ', target(): ' + repr(this.target());
70
71 if (this.type() &&
72 this.type().indexOf('key') === 0 ||
73 this.type().indexOf('mouse') === 0 ||
74 this.type().indexOf('click') != -1 ||
75 this.type() == 'contextmenu') {
76 str += ', modifier(): ' + '{alt: ' + repr(this.modifier().alt) +
77 ', ctrl: ' + repr(this.modifier().ctrl) +
78 ', meta: ' + repr(this.modifier().meta) +
79 ', shift: ' + repr(this.modifier().shift) +
80 ', any: ' + repr(this.modifier().any) + '}';
81 }
82
83 if (this.type() && this.type().indexOf('key') === 0) {
84 str += ', key(): {code: ' + repr(this.key().code) +
85 ', string: ' + repr(this.key().string) + '}';
86 }
87
88 if (this.type() && (
89 this.type().indexOf('mouse') === 0 ||
90 this.type().indexOf('click') != -1 ||
91 this.type() == 'contextmenu')) {
92
93 str += ', mouse(): {page: ' + repr(this.mouse().page) +
94 ', client: ' + repr(this.mouse().client);
95
96 if (this.type() != 'mousemove') {
97 str += ', button: {left: ' + repr(this.mouse().button.left) +
98 ', middle: ' + repr(this.mouse().button.middle) +
99 ', right: ' + repr(this.mouse().button.right) + '}}';
100 } else {
101 str += '}';
102 }
103 }
104 if (this.type() == 'mouseover' || this.type() == 'mouseout') {
105 str += ', relatedTarget(): ' + repr(this.relatedTarget());
106 }
107 str += '}';
108 return str;
109 },
110
111 /** @id MochiKit.Signal.Event.prototype.toString */
112 toString: function () {
113 return this.__repr__();
114 },
115
116 /** @id MochiKit.Signal.Event.prototype.src */
117 src: function () {
118 return this._src;
119 },
120
121 /** @id MochiKit.Signal.Event.prototype.event */
122 event: function () {
123 return this._event;
124 },
125
126 /** @id MochiKit.Signal.Event.prototype.type */
127 type: function () {
128 return this._event.type || undefined;
129 },
130
131 /** @id MochiKit.Signal.Event.prototype.target */
132 target: function () {
133 return this._event.target || this._event.srcElement;
134 },
135
136 _relatedTarget: null,
137 /** @id MochiKit.Signal.Event.prototype.relatedTarget */
138 relatedTarget: function () {
139 if (this._relatedTarget !== null) {
140 return this._relatedTarget;
141 }
142
143 var elem = null;
144 if (this.type() == 'mouseover') {
145 elem = (this._event.relatedTarget ||
146 this._event.fromElement);
147 } else if (this.type() == 'mouseout') {
148 elem = (this._event.relatedTarget ||
149 this._event.toElement);
150 }
151 if (elem !== null) {
152 this._relatedTarget = elem;
153 return elem;
154 }
155
156 return undefined;
157 },
158
159 _modifier: null,
160 /** @id MochiKit.Signal.Event.prototype.modifier */
161 modifier: function () {
162 if (this._modifier !== null) {
163 return this._modifier;
164 }
165 var m = {};
166 m.alt = this._event.altKey;
167 m.ctrl = this._event.ctrlKey;
168 m.meta = this._event.metaKey || false; // IE and Opera punt here
169 m.shift = this._event.shiftKey;
170 m.any = m.alt || m.ctrl || m.shift || m.meta;
171 this._modifier = m;
172 return m;
173 },
174
175 _key: null,
176 /** @id MochiKit.Signal.Event.prototype.key */
177 key: function () {
178 if (this._key !== null) {
179 return this._key;
180 }
181 var k = {};
182 if (this.type() && this.type().indexOf('key') === 0) {
183
184 /*
185
186 If you're looking for a special key, look for it in keydown or
187 keyup, but never keypress. If you're looking for a Unicode
188 chracter, look for it with keypress, but never keyup or
189 keydown.
190
191 Notes:
192
193 FF key event behavior:
194 key event charCode keyCode
195 DOWN ku,kd 0 40
196 DOWN kp 0 40
197 ESC ku,kd 0 27
198 ESC kp 0 27
199 a ku,kd 0 65
200 a kp 97 0
201 shift+a ku,kd 0 65
202 shift+a kp 65 0
203 1 ku,kd 0 49
204 1 kp 49 0
205 shift+1 ku,kd 0 0
206 shift+1 kp 33 0
207
208 IE key event behavior:
209 (IE doesn't fire keypress events for special keys.)
210 key event keyCode
211 DOWN ku,kd 40
212 DOWN kp undefined
213 ESC ku,kd 27
214 ESC kp 27
215 a ku,kd 65
216 a kp 97
217 shift+a ku,kd 65
218 shift+a kp 65
219 1 ku,kd 49
220 1 kp 49
221 shift+1 ku,kd 49
222 shift+1 kp 33
223
224 Safari key event behavior:
225 (Safari sets charCode and keyCode to something crazy for
226 special keys.)
227 key event charCode keyCode
228 DOWN ku,kd 63233 40
229 DOWN kp 63233 63233
230 ESC ku,kd 27 27
231 ESC kp 27 27
232 a ku,kd 97 65
233 a kp 97 97
234 shift+a ku,kd 65 65
235 shift+a kp 65 65
236 1 ku,kd 49 49
237 1 kp 49 49
238 shift+1 ku,kd 33 49
239 shift+1 kp 33 33
240
241 */
242
243 /* look for special keys here */
244 if (this.type() == 'keydown' || this.type() == 'keyup') {
245 k.code = this._event.keyCode;
246 k.string = (MochiKit.Signal._specialKeys[k.code] ||
247 'KEY_UNKNOWN');
248 this._key = k;
249 return k;
250
251 /* look for characters here */
252 } else if (this.type() == 'keypress') {
253
254 /*
255
256 Special key behavior:
257
258 IE: does not fire keypress events for special keys
259 FF: sets charCode to 0, and sets the correct keyCode
260 Safari: sets keyCode and charCode to something stupid
261
262 */
263
264 k.code = 0;
265 k.string = '';
266
267 if (typeof(this._event.charCode) != 'undefined' &&
268 this._event.charCode !== 0 &&
269 !MochiKit.Signal._specialMacKeys[this._event.charCode]) {
270 k.code = this._event.charCode;
271 k.string = String.fromCharCode(k.code);
272 } else if (this._event.keyCode &&
273 typeof(this._event.charCode) == 'undefined') { // IE
274 k.code = this._event.keyCode;
275 k.string = String.fromCharCode(k.code);
276 }
277
278 this._key = k;
279 return k;
280 }
281 }
282 return undefined;
283 },
284
285 _mouse: null,
286 /** @id MochiKit.Signal.Event.prototype.mouse */
287 mouse: function () {
288 if (this._mouse !== null) {
289 return this._mouse;
290 }
291
292 var m = {};
293 var e = this._event;
294
295 if (this.type() && (
296 this.type().indexOf('mouse') === 0 ||
297 this.type().indexOf('click') != -1 ||
298 this.type() == 'contextmenu')) {
299
300 m.client = new MochiKit.Style.Coordinates(0, 0);
301 if (e.clientX || e.clientY) {
302 m.client.x = (!e.clientX || e.clientX < 0) ? 0 : e.clientX;
303 m.client.y = (!e.clientY || e.clientY < 0) ? 0 : e.clientY;
304 }
305
306 m.page = new MochiKit.Style.Coordinates(0, 0);
307 if (e.pageX || e.pageY) {
308 m.page.x = (!e.pageX || e.pageX < 0) ? 0 : e.pageX;
309 m.page.y = (!e.pageY || e.pageY < 0) ? 0 : e.pageY;
310 } else {
311 /*
312
313 The IE shortcut can be off by two. We fix it. See:
314 http://msdn.microsoft.com/workshop/author/dhtml/reference/methods/getboundingclientrect.asp
315
316 This is similar to the method used in
317 MochiKit.Style.getElementPosition().
318
319 */
320 var de = MochiKit.DOM._document.documentElement;
321 var b = MochiKit.DOM._document.body;
322
323 m.page.x = e.clientX +
324 (de.scrollLeft || b.scrollLeft) -
325 (de.clientLeft || 0);
326
327 m.page.y = e.clientY +
328 (de.scrollTop || b.scrollTop) -
329 (de.clientTop || 0);
330
331 }
332 if (this.type() != 'mousemove') {
333 m.button = {};
334 m.button.left = false;
335 m.button.right = false;
336 m.button.middle = false;
337
338 /* we could check e.button, but which is more consistent */
339 if (e.which) {
340 m.button.left = (e.which == 1);
341 m.button.middle = (e.which == 2);
342 m.button.right = (e.which == 3);
343
344 /*
345
346 Mac browsers and right click:
347
348 - Safari doesn't fire any click events on a right
349 click:
350 http://bugs.webkit.org/show_bug.cgi?id=6595
351
352 - Firefox fires the event, and sets ctrlKey = true
353
354 - Opera fires the event, and sets metaKey = true
355
356 oncontextmenu is fired on right clicks between
357 browsers and across platforms.
358
359 */
360
361 } else {
362 m.button.left = !!(e.button & 1);
363 m.button.right = !!(e.button & 2);
364 m.button.middle = !!(e.button & 4);
365 }
366 }
367 this._mouse = m;
368 return m;
369 }
370 return undefined;
371 },
372
373 /** @id MochiKit.Signal.Event.prototype.stop */
374 stop: function () {
375 this.stopPropagation();
376 this.preventDefault();
377 },
378
379 /** @id MochiKit.Signal.Event.prototype.stopPropagation */
380 stopPropagation: function () {
381 if (this._event.stopPropagation) {
382 this._event.stopPropagation();
383 } else {
384 this._event.cancelBubble = true;
385 }
386 },
387
388 /** @id MochiKit.Signal.Event.prototype.preventDefault */
389 preventDefault: function () {
390 if (this._event.preventDefault) {
391 this._event.preventDefault();
392 } else if (this._confirmUnload === null) {
393 this._event.returnValue = false;
394 }
395 },
396
397 _confirmUnload: null,
398
399 /** @id MochiKit.Signal.Event.prototype.confirmUnload */
400 confirmUnload: function (msg) {
401 if (this.type() == 'beforeunload') {
402 this._confirmUnload = msg;
403 this._event.returnValue = msg;
404 }
405 }
406 });
407
408 /* Safari sets keyCode to these special values onkeypress. */
409 MochiKit.Signal._specialMacKeys = {
410 3: 'KEY_ENTER',
411 63289: 'KEY_NUM_PAD_CLEAR',
412 63276: 'KEY_PAGE_UP',
413 63277: 'KEY_PAGE_DOWN',
414 63275: 'KEY_END',
415 63273: 'KEY_HOME',
416 63234: 'KEY_ARROW_LEFT',
417 63232: 'KEY_ARROW_UP',
418 63235: 'KEY_ARROW_RIGHT',
419 63233: 'KEY_ARROW_DOWN',
420 63302: 'KEY_INSERT',
421 63272: 'KEY_DELETE'
422 };
423
424 /* for KEY_F1 - KEY_F12 */
425 (function () {
426 var _specialMacKeys = MochiKit.Signal._specialMacKeys;
427 for (i = 63236; i <= 63242; i++) {
428 // no F0
429 _specialMacKeys[i] = 'KEY_F' + (i - 63236 + 1);
430 }
431 })();
432
433 /* Standard keyboard key codes. */
434 MochiKit.Signal._specialKeys = {
435 8: 'KEY_BACKSPACE',
436 9: 'KEY_TAB',
437 12: 'KEY_NUM_PAD_CLEAR', // weird, for Safari and Mac FF only
438 13: 'KEY_ENTER',
439 16: 'KEY_SHIFT',
440 17: 'KEY_CTRL',
441 18: 'KEY_ALT',
442 19: 'KEY_PAUSE',
443 20: 'KEY_CAPS_LOCK',
444 27: 'KEY_ESCAPE',
445 32: 'KEY_SPACEBAR',
446 33: 'KEY_PAGE_UP',
447 34: 'KEY_PAGE_DOWN',
448 35: 'KEY_END',
449 36: 'KEY_HOME',
450 37: 'KEY_ARROW_LEFT',
451 38: 'KEY_ARROW_UP',
452 39: 'KEY_ARROW_RIGHT',
453 40: 'KEY_ARROW_DOWN',
454 44: 'KEY_PRINT_SCREEN',
455 45: 'KEY_INSERT',
456 46: 'KEY_DELETE',
457 59: 'KEY_SEMICOLON', // weird, for Safari and IE only
458 91: 'KEY_WINDOWS_LEFT',
459 92: 'KEY_WINDOWS_RIGHT',
460 93: 'KEY_SELECT',
461 106: 'KEY_NUM_PAD_ASTERISK',
462 107: 'KEY_NUM_PAD_PLUS_SIGN',
463 109: 'KEY_NUM_PAD_HYPHEN-MINUS',
464 110: 'KEY_NUM_PAD_FULL_STOP',
465 111: 'KEY_NUM_PAD_SOLIDUS',
466 144: 'KEY_NUM_LOCK',
467 145: 'KEY_SCROLL_LOCK',
468 186: 'KEY_SEMICOLON',
469 187: 'KEY_EQUALS_SIGN',
470 188: 'KEY_COMMA',
471 189: 'KEY_HYPHEN-MINUS',
472 190: 'KEY_FULL_STOP',
473 191: 'KEY_SOLIDUS',
474 192: 'KEY_GRAVE_ACCENT',
475 219: 'KEY_LEFT_SQUARE_BRACKET',
476 220: 'KEY_REVERSE_SOLIDUS',
477 221: 'KEY_RIGHT_SQUARE_BRACKET',
478 222: 'KEY_APOSTROPHE'
479 // undefined: 'KEY_UNKNOWN'
480 };
481
482 (function () {
483 /* for KEY_0 - KEY_9 */
484 var _specialKeys = MochiKit.Signal._specialKeys;
485 for (var i = 48; i <= 57; i++) {
486 _specialKeys[i] = 'KEY_' + (i - 48);
487 }
488
489 /* for KEY_A - KEY_Z */
490 for (i = 65; i <= 90; i++) {
491 _specialKeys[i] = 'KEY_' + String.fromCharCode(i);
492 }
493
494 /* for KEY_NUM_PAD_0 - KEY_NUM_PAD_9 */
495 for (i = 96; i <= 105; i++) {
496 _specialKeys[i] = 'KEY_NUM_PAD_' + (i - 96);
497 }
498
499 /* for KEY_F1 - KEY_F12 */
500 for (i = 112; i <= 123; i++) {
501 // no F0
502 _specialKeys[i] = 'KEY_F' + (i - 112 + 1);
503 }
504 })();
505
506 /* Internal object to keep track of created signals. */
507 MochiKit.Signal.Ident = function (ident) {
508 this.source = ident.source;
509 this.signal = ident.signal;
510 this.listener = ident.listener;
511 this.isDOM = ident.isDOM;
512 this.objOrFunc = ident.objOrFunc;
513 this.funcOrStr = ident.funcOrStr;
514 this.connected = ident.connected;
515 };
516
517 MochiKit.Signal.Ident.prototype = {};
518
519 MochiKit.Base.update(MochiKit.Signal, {
520
521 __repr__: function () {
522 return '[' + this.NAME + ' ' + this.VERSION + ']';
523 },
524
525 toString: function () {
526 return this.__repr__();
527 },
528
529 _unloadCache: function () {
530 var self = MochiKit.Signal;
531 var observers = self._observers;
532
533 for (var i = 0; i < observers.length; i++) {
534 if (observers[i].signal !== 'onload' && observers[i].signal !== 'onunload') {
535 self._disconnect(observers[i]);
536 }
537 }
538 },
539
540 _listener: function (src, sig, func, obj, isDOM) {
541 var self = MochiKit.Signal;
542 var E = self.Event;
543 if (!isDOM) {
544 /* We don't want to re-bind already bound methods */
545 if (typeof(func.im_self) == 'undefined') {
546 return MochiKit.Base.bind(func, obj);
547 } else {
548 return func;
549 }
550 }
551 obj = obj || src;
552 if (typeof(func) == "string") {
553 if (sig === 'onload' || sig === 'onunload') {
554 return function (nativeEvent) {
555 obj[func].apply(obj, [new E(src, nativeEvent)]);
556
557 var ident = new MochiKit.Signal.Ident({
558 source: src, signal: sig, objOrFunc: obj, funcOrStr: func});
559
560 MochiKit.Signal._disconnect(ident);
561 };
562 } else {
563 return function (nativeEvent) {
564 obj[func].apply(obj, [new E(src, nativeEvent)]);
565 };
566 }
567 } else {
568 if (sig === 'onload' || sig === 'onunload') {
569 return function (nativeEvent) {
570 func.apply(obj, [new E(src, nativeEvent)]);
571
572 var ident = new MochiKit.Signal.Ident({
573 source: src, signal: sig, objOrFunc: func});
574
575 MochiKit.Signal._disconnect(ident);
576 };
577 } else {
578 return function (nativeEvent) {
579 func.apply(obj, [new E(src, nativeEvent)]);
580 };
581 }
582 }
583 },
584
585 _browserAlreadyHasMouseEnterAndLeave: function () {
586 return /MSIE/.test(navigator.userAgent);
587 },
588
589 _mouseEnterListener: function (src, sig, func, obj) {
590 var E = MochiKit.Signal.Event;
591 return function (nativeEvent) {
592 var e = new E(src, nativeEvent);
593 try {
594 e.relatedTarget().nodeName;
595 } catch (err) {
596 /* probably hit a permission denied error; possibly one of
597 * firefox's screwy anonymous DIVs inside an input element.
598 * Allow this event to propogate up.
599 */
600 return;
601 }
602 e.stop();
603 if (MochiKit.DOM.isChildNode(e.relatedTarget(), src)) {
604 /* We've moved between our node and a child. Ignore. */
605 return;
606 }
607 e.type = function () { return sig; };
608 if (typeof(func) == "string") {
609 return obj[func].apply(obj, [e]);
610 } else {
611 return func.apply(obj, [e]);
612 }
613 };
614 },
615
616 _getDestPair: function (objOrFunc, funcOrStr) {
617 var obj = null;
618 var func = null;
619 if (typeof(funcOrStr) != 'undefined') {
620 obj = objOrFunc;
621 func = funcOrStr;
622 if (typeof(funcOrStr) == 'string') {
623 if (typeof(objOrFunc[funcOrStr]) != "function") {
624 throw new Error("'funcOrStr' must be a function on 'objOrFunc'");
625 }
626 } else if (typeof(funcOrStr) != 'function') {
627 throw new Error("'funcOrStr' must be a function or string");
628 }
629 } else if (typeof(objOrFunc) != "function") {
630 throw new Error("'objOrFunc' must be a function if 'funcOrStr' is not given");
631 } else {
632 func = objOrFunc;
633 }
634 return [obj, func];
635 },
636
637 /** @id MochiKit.Signal.connect */
638 connect: function (src, sig, objOrFunc/* optional */, funcOrStr) {
639 src = MochiKit.DOM.getElement(src);
640 var self = MochiKit.Signal;
641
642 if (typeof(sig) != 'string') {
643 throw new Error("'sig' must be a string");
644 }
645
646 var destPair = self._getDestPair(objOrFunc, funcOrStr);
647 var obj = destPair[0];
648 var func = destPair[1];
649 if (typeof(obj) == 'undefined' || obj === null) {
650 obj = src;
651 }
652
653 var isDOM = !!(src.addEventListener || src.attachEvent);
654 if (isDOM && (sig === "onmouseenter" || sig === "onmouseleave")
655 && !self._browserAlreadyHasMouseEnterAndLeave()) {
656 var listener = self._mouseEnterListener(src, sig.substr(2), func, obj);
657 if (sig === "onmouseenter") {
658 sig = "onmouseover";
659 } else {
660 sig = "onmouseout";
661 }
662 } else {
663 var listener = self._listener(src, sig, func, obj, isDOM);
664 }
665
666 if (src.addEventListener) {
667 src.addEventListener(sig.substr(2), listener, false);
668 } else if (src.attachEvent) {
669 src.attachEvent(sig, listener); // useCapture unsupported
670 }
671
672 var ident = new MochiKit.Signal.Ident({
673 source: src,
674 signal: sig,
675 listener: listener,
676 isDOM: isDOM,
677 objOrFunc: objOrFunc,
678 funcOrStr: funcOrStr,
679 connected: true
680 });
681 self._observers.push(ident);
682
683 if (!isDOM && typeof(src.__connect__) == 'function') {
684 var args = MochiKit.Base.extend([ident], arguments, 1);
685 src.__connect__.apply(src, args);
686 }
687
688 return ident;
689 },
690
691 _disconnect: function (ident) {
692 // already disconnected
693 if (!ident.connected) {
694 return;
695 }
696 ident.connected = false;
697 // check isDOM
698 if (!ident.isDOM) {
699 return;
700 }
701 var src = ident.source;
702 var sig = ident.signal;
703 var listener = ident.listener;
704
705 if (src.removeEventListener) {
706 src.removeEventListener(sig.substr(2), listener, false);
707 } else if (src.detachEvent) {
708 src.detachEvent(sig, listener); // useCapture unsupported
709 } else {
710 throw new Error("'src' must be a DOM element");
711 }
712 },
713
714 /** @id MochiKit.Signal.disconnect */
715 disconnect: function (ident) {
716 var self = MochiKit.Signal;
717 var observers = self._observers;
718 var m = MochiKit.Base;
719 if (arguments.length > 1) {
720 // compatibility API
721 var src = MochiKit.DOM.getElement(arguments[0]);
722 var sig = arguments[1];
723 var obj = arguments[2];
724 var func = arguments[3];
725 for (var i = observers.length - 1; i >= 0; i--) {
726 var o = observers[i];
727 if (o.source === src && o.signal === sig && o.objOrFunc === obj && o.funcOrStr === func) {
728 self._disconnect(o);
729 if (!self._lock) {
730 observers.splice(i, 1);
731 } else {
732 self._dirty = true;
733 }
734 return true;
735 }
736 }
737 } else {
738 var idx = m.findIdentical(observers, ident);
739 if (idx >= 0) {
740 self._disconnect(ident);
741 if (!self._lock) {
742 observers.splice(idx, 1);
743 } else {
744 self._dirty = true;
745 }
746 return true;
747 }
748 }
749 return false;
750 },
751
752 /** @id MochiKit.Signal.disconnectAllTo */
753 disconnectAllTo: function (objOrFunc, /* optional */funcOrStr) {
754 var self = MochiKit.Signal;
755 var observers = self._observers;
756 var disconnect = self._disconnect;
757 var locked = self._lock;
758 var dirty = self._dirty;
759 if (typeof(funcOrStr) === 'undefined') {
760 funcOrStr = null;
761 }
762 for (var i = observers.length - 1; i >= 0; i--) {
763 var ident = observers[i];
764 if (ident.objOrFunc === objOrFunc &&
765 (funcOrStr === null || ident.funcOrStr === funcOrStr)) {
766 disconnect(ident);
767 if (locked) {
768 dirty = true;
769 } else {
770 observers.splice(i, 1);
771 }
772 }
773 }
774 self._dirty = dirty;
775 },
776
777 /** @id MochiKit.Signal.disconnectAll */
778 disconnectAll: function (src/* optional */, sig) {
779 src = MochiKit.DOM.getElement(src);
780 var m = MochiKit.Base;
781 var signals = m.flattenArguments(m.extend(null, arguments, 1));
782 var self = MochiKit.Signal;
783 var disconnect = self._disconnect;
784 var observers = self._observers;
785 var i, ident;
786 var locked = self._lock;
787 var dirty = self._dirty;
788 if (signals.length === 0) {
789 // disconnect all
790 for (i = observers.length - 1; i >= 0; i--) {
791 ident = observers[i];
792 if (ident.source === src) {
793 disconnect(ident);
794 if (!locked) {
795 observers.splice(i, 1);
796 } else {
797 dirty = true;
798 }
799 }
800 }
801 } else {
802 var sigs = {};
803 for (i = 0; i < signals.length; i++) {
804 sigs[signals[i]] = true;
805 }
806 for (i = observers.length - 1; i >= 0; i--) {
807 ident = observers[i];
808 if (ident.source === src && ident.signal in sigs) {
809 disconnect(ident);
810 if (!locked) {
811 observers.splice(i, 1);
812 } else {
813 dirty = true;
814 }
815 }
816 }
817 }
818 self._dirty = dirty;
819 },
820
821 /** @id MochiKit.Signal.signal */
822 signal: function (src, sig) {
823 var self = MochiKit.Signal;
824 var observers = self._observers;
825 src = MochiKit.DOM.getElement(src);
826 var args = MochiKit.Base.extend(null, arguments, 2);
827 var errors = [];
828 self._lock = true;
829 for (var i = 0; i < observers.length; i++) {
830 var ident = observers[i];
831 if (ident.source === src && ident.signal === sig &&
832 ident.connected) {
833 try {
834 ident.listener.apply(src, args);
835 } catch (e) {
836 errors.push(e);
837 }
838 }
839 }
840 self._lock = false;
841 if (self._dirty) {
842 self._dirty = false;
843 for (var i = observers.length - 1; i >= 0; i--) {
844 if (!observers[i].connected) {
845 observers.splice(i, 1);
846 }
847 }
848 }
849 if (errors.length == 1) {
850 throw errors[0];
851 } else if (errors.length > 1) {
852 var e = new Error("Multiple errors thrown in handling 'sig', see errors property");
853 e.errors = errors;
854 throw e;
855 }
856 }
857
858 });
859
860 MochiKit.Signal.EXPORT_OK = [];
861
862 MochiKit.Signal.EXPORT = [
863 'connect',
864 'disconnect',
865 'signal',
866 'disconnectAll',
867 'disconnectAllTo'
868 ];
869
870 MochiKit.Signal.__new__ = function (win) {
871 var m = MochiKit.Base;
872 this._document = document;
873 this._window = win;
874 this._lock = false;
875 this._dirty = false;
876
877 try {
878 this.connect(window, 'onunload', this._unloadCache);
879 } catch (e) {
880 // pass: might not be a browser
881 }
882
883 this.EXPORT_TAGS = {
884 ':common': this.EXPORT,
885 ':all': m.concat(this.EXPORT, this.EXPORT_OK)
886 };
887
888 m.nameFunctions(this);
889 };
890
891 MochiKit.Signal.__new__(this);
892
893 //
894 // XXX: Internet Explorer blows
895 //
896 if (MochiKit.__export__) {
897 connect = MochiKit.Signal.connect;
898 disconnect = MochiKit.Signal.disconnect;
899 disconnectAll = MochiKit.Signal.disconnectAll;
900 signal = MochiKit.Signal.signal;
901 }
902
903 MochiKit.Base._exportSymbols(this, MochiKit.Signal);
This diff has been collapsed as it changes many lines, (589 lines changed) Show them Hide them
@@ -0,0 +1,589 b''
1 /***
2 Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
3 Mochi-ized By Thomas Herve (_firstname_@nimail.org)
4
5 See scriptaculous.js for full license.
6
7 ***/
8
9 if (typeof(dojo) != 'undefined') {
10 dojo.provide('MochiKit.Sortable');
11 dojo.require('MochiKit.Base');
12 dojo.require('MochiKit.DOM');
13 dojo.require('MochiKit.Iter');
14 }
15
16 if (typeof(JSAN) != 'undefined') {
17 JSAN.use("MochiKit.Base", []);
18 JSAN.use("MochiKit.DOM", []);
19 JSAN.use("MochiKit.Iter", []);
20 }
21
22 try {
23 if (typeof(MochiKit.Base) == 'undefined' ||
24 typeof(MochiKit.DOM) == 'undefined' ||
25 typeof(MochiKit.Iter) == 'undefined') {
26 throw "";
27 }
28 } catch (e) {
29 throw "MochiKit.DragAndDrop depends on MochiKit.Base, MochiKit.DOM and MochiKit.Iter!";
30 }
31
32 if (typeof(MochiKit.Sortable) == 'undefined') {
33 MochiKit.Sortable = {};
34 }
35
36 MochiKit.Sortable.NAME = 'MochiKit.Sortable';
37 MochiKit.Sortable.VERSION = '1.4';
38
39 MochiKit.Sortable.__repr__ = function () {
40 return '[' + this.NAME + ' ' + this.VERSION + ']';
41 };
42
43 MochiKit.Sortable.toString = function () {
44 return this.__repr__();
45 };
46
47 MochiKit.Sortable.EXPORT = [
48 ];
49
50 MochiKit.Sortable.EXPORT_OK = [
51 ];
52
53 MochiKit.Base.update(MochiKit.Sortable, {
54 /***
55
56 Manage sortables. Mainly use the create function to add a sortable.
57
58 ***/
59 sortables: {},
60
61 _findRootElement: function (element) {
62 while (element.tagName.toUpperCase() != "BODY") {
63 if (element.id && MochiKit.Sortable.sortables[element.id]) {
64 return element;
65 }
66 element = element.parentNode;
67 }
68 },
69
70 /** @id MochiKit.Sortable.options */
71 options: function (element) {
72 element = MochiKit.Sortable._findRootElement(MochiKit.DOM.getElement(element));
73 if (!element) {
74 return;
75 }
76 return MochiKit.Sortable.sortables[element.id];
77 },
78
79 /** @id MochiKit.Sortable.destroy */
80 destroy: function (element){
81 var s = MochiKit.Sortable.options(element);
82 var b = MochiKit.Base;
83 var d = MochiKit.DragAndDrop;
84
85 if (s) {
86 MochiKit.Signal.disconnect(s.startHandle);
87 MochiKit.Signal.disconnect(s.endHandle);
88 b.map(function (dr) {
89 d.Droppables.remove(dr);
90 }, s.droppables);
91 b.map(function (dr) {
92 dr.destroy();
93 }, s.draggables);
94
95 delete MochiKit.Sortable.sortables[s.element.id];
96 }
97 },
98
99 /** @id MochiKit.Sortable.create */
100 create: function (element, options) {
101 element = MochiKit.DOM.getElement(element);
102 var self = MochiKit.Sortable;
103
104 /** @id MochiKit.Sortable.options */
105 options = MochiKit.Base.update({
106
107 /** @id MochiKit.Sortable.element */
108 element: element,
109
110 /** @id MochiKit.Sortable.tag */
111 tag: 'li', // assumes li children, override with tag: 'tagname'
112
113 /** @id MochiKit.Sortable.dropOnEmpty */
114 dropOnEmpty: false,
115
116 /** @id MochiKit.Sortable.tree */
117 tree: false,
118
119 /** @id MochiKit.Sortable.treeTag */
120 treeTag: 'ul',
121
122 /** @id MochiKit.Sortable.overlap */
123 overlap: 'vertical', // one of 'vertical', 'horizontal'
124
125 /** @id MochiKit.Sortable.constraint */
126 constraint: 'vertical', // one of 'vertical', 'horizontal', false
127 // also takes array of elements (or ids); or false
128
129 /** @id MochiKit.Sortable.containment */
130 containment: [element],
131
132 /** @id MochiKit.Sortable.handle */
133 handle: false, // or a CSS class
134
135 /** @id MochiKit.Sortable.only */
136 only: false,
137
138 /** @id MochiKit.Sortable.hoverclass */
139 hoverclass: null,
140
141 /** @id MochiKit.Sortable.ghosting */
142 ghosting: false,
143
144 /** @id MochiKit.Sortable.scroll */
145 scroll: false,
146
147 /** @id MochiKit.Sortable.scrollSensitivity */
148 scrollSensitivity: 20,
149
150 /** @id MochiKit.Sortable.scrollSpeed */
151 scrollSpeed: 15,
152
153 /** @id MochiKit.Sortable.format */
154 format: /^[^_]*_(.*)$/,
155
156 /** @id MochiKit.Sortable.onChange */
157 onChange: MochiKit.Base.noop,
158
159 /** @id MochiKit.Sortable.onUpdate */
160 onUpdate: MochiKit.Base.noop,
161
162 /** @id MochiKit.Sortable.accept */
163 accept: null
164 }, options);
165
166 // clear any old sortable with same element
167 self.destroy(element);
168
169 // build options for the draggables
170 var options_for_draggable = {
171 revert: true,
172 ghosting: options.ghosting,
173 scroll: options.scroll,
174 scrollSensitivity: options.scrollSensitivity,
175 scrollSpeed: options.scrollSpeed,
176 constraint: options.constraint,
177 handle: options.handle
178 };
179
180 if (options.starteffect) {
181 options_for_draggable.starteffect = options.starteffect;
182 }
183
184 if (options.reverteffect) {
185 options_for_draggable.reverteffect = options.reverteffect;
186 } else if (options.ghosting) {
187 options_for_draggable.reverteffect = function (innerelement) {
188 innerelement.style.top = 0;
189 innerelement.style.left = 0;
190 };
191 }
192
193 if (options.endeffect) {
194 options_for_draggable.endeffect = options.endeffect;
195 }
196
197 if (options.zindex) {
198 options_for_draggable.zindex = options.zindex;
199 }
200
201 // build options for the droppables
202 var options_for_droppable = {
203 overlap: options.overlap,
204 containment: options.containment,
205 hoverclass: options.hoverclass,
206 onhover: self.onHover,
207 tree: options.tree,
208 accept: options.accept
209 }
210
211 var options_for_tree = {
212 onhover: self.onEmptyHover,
213 overlap: options.overlap,
214 containment: options.containment,
215 hoverclass: options.hoverclass,
216 accept: options.accept
217 }
218
219 // fix for gecko engine
220 MochiKit.DOM.removeEmptyTextNodes(element);
221
222 options.draggables = [];
223 options.droppables = [];
224
225 // drop on empty handling
226 if (options.dropOnEmpty || options.tree) {
227 new MochiKit.DragAndDrop.Droppable(element, options_for_tree);
228 options.droppables.push(element);
229 }
230 MochiKit.Base.map(function (e) {
231 // handles are per-draggable
232 var handle = options.handle ?
233 MochiKit.DOM.getFirstElementByTagAndClassName(null,
234 options.handle, e) : e;
235 options.draggables.push(
236 new MochiKit.DragAndDrop.Draggable(e,
237 MochiKit.Base.update(options_for_draggable,
238 {handle: handle})));
239 new MochiKit.DragAndDrop.Droppable(e, options_for_droppable);
240 if (options.tree) {
241 e.treeNode = element;
242 }
243 options.droppables.push(e);
244 }, (self.findElements(element, options) || []));
245
246 if (options.tree) {
247 MochiKit.Base.map(function (e) {
248 new MochiKit.DragAndDrop.Droppable(e, options_for_tree);
249 e.treeNode = element;
250 options.droppables.push(e);
251 }, (self.findTreeElements(element, options) || []));
252 }
253
254 // keep reference
255 self.sortables[element.id] = options;
256
257 options.lastValue = self.serialize(element);
258 options.startHandle = MochiKit.Signal.connect(MochiKit.DragAndDrop.Draggables, 'start',
259 MochiKit.Base.partial(self.onStart, element));
260 options.endHandle = MochiKit.Signal.connect(MochiKit.DragAndDrop.Draggables, 'end',
261 MochiKit.Base.partial(self.onEnd, element));
262 },
263
264 /** @id MochiKit.Sortable.onStart */
265 onStart: function (element, draggable) {
266 var self = MochiKit.Sortable;
267 var options = self.options(element);
268 options.lastValue = self.serialize(options.element);
269 },
270
271 /** @id MochiKit.Sortable.onEnd */
272 onEnd: function (element, draggable) {
273 var self = MochiKit.Sortable;
274 self.unmark();
275 var options = self.options(element);
276 if (options.lastValue != self.serialize(options.element)) {
277 options.onUpdate(options.element);
278 }
279 },
280
281 // return all suitable-for-sortable elements in a guaranteed order
282
283 /** @id MochiKit.Sortable.findElements */
284 findElements: function (element, options) {
285 return MochiKit.Sortable.findChildren(
286 element, options.only, options.tree ? true : false, options.tag);
287 },
288
289 /** @id MochiKit.Sortable.findTreeElements */
290 findTreeElements: function (element, options) {
291 return MochiKit.Sortable.findChildren(
292 element, options.only, options.tree ? true : false, options.treeTag);
293 },
294
295 /** @id MochiKit.Sortable.findChildren */
296 findChildren: function (element, only, recursive, tagName) {
297 if (!element.hasChildNodes()) {
298 return null;
299 }
300 tagName = tagName.toUpperCase();
301 if (only) {
302 only = MochiKit.Base.flattenArray([only]);
303 }
304 var elements = [];
305 MochiKit.Base.map(function (e) {
306 if (e.tagName &&
307 e.tagName.toUpperCase() == tagName &&
308 (!only ||
309 MochiKit.Iter.some(only, function (c) {
310 return MochiKit.DOM.hasElementClass(e, c);
311 }))) {
312 elements.push(e);
313 }
314 if (recursive) {
315 var grandchildren = MochiKit.Sortable.findChildren(e, only, recursive, tagName);
316 if (grandchildren && grandchildren.length > 0) {
317 elements = elements.concat(grandchildren);
318 }
319 }
320 }, element.childNodes);
321 return elements;
322 },
323
324 /** @id MochiKit.Sortable.onHover */
325 onHover: function (element, dropon, overlap) {
326 if (MochiKit.DOM.isParent(dropon, element)) {
327 return;
328 }
329 var self = MochiKit.Sortable;
330
331 if (overlap > .33 && overlap < .66 && self.options(dropon).tree) {
332 return;
333 } else if (overlap > 0.5) {
334 self.mark(dropon, 'before');
335 if (dropon.previousSibling != element) {
336 var oldParentNode = element.parentNode;
337 element.style.visibility = 'hidden'; // fix gecko rendering
338 dropon.parentNode.insertBefore(element, dropon);
339 if (dropon.parentNode != oldParentNode) {
340 self.options(oldParentNode).onChange(element);
341 }
342 self.options(dropon.parentNode).onChange(element);
343 }
344 } else {
345 self.mark(dropon, 'after');
346 var nextElement = dropon.nextSibling || null;
347 if (nextElement != element) {
348 var oldParentNode = element.parentNode;
349 element.style.visibility = 'hidden'; // fix gecko rendering
350 dropon.parentNode.insertBefore(element, nextElement);
351 if (dropon.parentNode != oldParentNode) {
352 self.options(oldParentNode).onChange(element);
353 }
354 self.options(dropon.parentNode).onChange(element);
355 }
356 }
357 },
358
359 _offsetSize: function (element, type) {
360 if (type == 'vertical' || type == 'height') {
361 return element.offsetHeight;
362 } else {
363 return element.offsetWidth;
364 }
365 },
366
367 /** @id MochiKit.Sortable.onEmptyHover */
368 onEmptyHover: function (element, dropon, overlap) {
369 var oldParentNode = element.parentNode;
370 var self = MochiKit.Sortable;
371 var droponOptions = self.options(dropon);
372
373 if (!MochiKit.DOM.isParent(dropon, element)) {
374 var index;
375
376 var children = self.findElements(dropon, {tag: droponOptions.tag,
377 only: droponOptions.only});
378 var child = null;
379
380 if (children) {
381 var offset = self._offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap);
382
383 for (index = 0; index < children.length; index += 1) {
384 if (offset - self._offsetSize(children[index], droponOptions.overlap) >= 0) {
385 offset -= self._offsetSize(children[index], droponOptions.overlap);
386 } else if (offset - (self._offsetSize (children[index], droponOptions.overlap) / 2) >= 0) {
387 child = index + 1 < children.length ? children[index + 1] : null;
388 break;
389 } else {
390 child = children[index];
391 break;
392 }
393 }
394 }
395
396 dropon.insertBefore(element, child);
397
398 self.options(oldParentNode).onChange(element);
399 droponOptions.onChange(element);
400 }
401 },
402
403 /** @id MochiKit.Sortable.unmark */
404 unmark: function () {
405 var m = MochiKit.Sortable._marker;
406 if (m) {
407 MochiKit.Style.hideElement(m);
408 }
409 },
410
411 /** @id MochiKit.Sortable.mark */
412 mark: function (dropon, position) {
413 // mark on ghosting only
414 var d = MochiKit.DOM;
415 var self = MochiKit.Sortable;
416 var sortable = self.options(dropon.parentNode);
417 if (sortable && !sortable.ghosting) {
418 return;
419 }
420
421 if (!self._marker) {
422 self._marker = d.getElement('dropmarker') ||
423 document.createElement('DIV');
424 MochiKit.Style.hideElement(self._marker);
425 d.addElementClass(self._marker, 'dropmarker');
426 self._marker.style.position = 'absolute';
427 document.getElementsByTagName('body').item(0).appendChild(self._marker);
428 }
429 var offsets = MochiKit.Position.cumulativeOffset(dropon);
430 self._marker.style.left = offsets.x + 'px';
431 self._marker.style.top = offsets.y + 'px';
432
433 if (position == 'after') {
434 if (sortable.overlap == 'horizontal') {
435 self._marker.style.left = (offsets.x + dropon.clientWidth) + 'px';
436 } else {
437 self._marker.style.top = (offsets.y + dropon.clientHeight) + 'px';
438 }
439 }
440 MochiKit.Style.showElement(self._marker);
441 },
442
443 _tree: function (element, options, parent) {
444 var self = MochiKit.Sortable;
445 var children = self.findElements(element, options) || [];
446
447 for (var i = 0; i < children.length; ++i) {
448 var match = children[i].id.match(options.format);
449
450 if (!match) {
451 continue;
452 }
453
454 var child = {
455 id: encodeURIComponent(match ? match[1] : null),
456 element: element,
457 parent: parent,
458 children: [],
459 position: parent.children.length,
460 container: self._findChildrenElement(children[i], options.treeTag.toUpperCase())
461 }
462
463 /* Get the element containing the children and recurse over it */
464 if (child.container) {
465 self._tree(child.container, options, child)
466 }
467
468 parent.children.push (child);
469 }
470
471 return parent;
472 },
473
474 /* Finds the first element of the given tag type within a parent element.
475 Used for finding the first LI[ST] within a L[IST]I[TEM].*/
476 _findChildrenElement: function (element, containerTag) {
477 if (element && element.hasChildNodes) {
478 containerTag = containerTag.toUpperCase();
479 for (var i = 0; i < element.childNodes.length; ++i) {
480 if (element.childNodes[i].tagName.toUpperCase() == containerTag) {
481 return element.childNodes[i];
482 }
483 }
484 }
485 return null;
486 },
487
488 /** @id MochiKit.Sortable.tree */
489 tree: function (element, options) {
490 element = MochiKit.DOM.getElement(element);
491 var sortableOptions = MochiKit.Sortable.options(element);
492 options = MochiKit.Base.update({
493 tag: sortableOptions.tag,
494 treeTag: sortableOptions.treeTag,
495 only: sortableOptions.only,
496 name: element.id,
497 format: sortableOptions.format
498 }, options || {});
499
500 var root = {
501 id: null,
502 parent: null,
503 children: new Array,
504 container: element,
505 position: 0
506 }
507
508 return MochiKit.Sortable._tree(element, options, root);
509 },
510
511 /**
512 * Specifies the sequence for the Sortable.
513 * @param {Node} element Element to use as the Sortable.
514 * @param {Object} newSequence New sequence to use.
515 * @param {Object} options Options to use fro the Sortable.
516 */
517 setSequence: function (element, newSequence, options) {
518 var self = MochiKit.Sortable;
519 var b = MochiKit.Base;
520 element = MochiKit.DOM.getElement(element);
521 options = b.update(self.options(element), options || {});
522
523 var nodeMap = {};
524 b.map(function (n) {
525 var m = n.id.match(options.format);
526 if (m) {
527 nodeMap[m[1]] = [n, n.parentNode];
528 }
529 n.parentNode.removeChild(n);
530 }, self.findElements(element, options));
531
532 b.map(function (ident) {
533 var n = nodeMap[ident];
534 if (n) {
535 n[1].appendChild(n[0]);
536 delete nodeMap[ident];
537 }
538 }, newSequence);
539 },
540
541 /* Construct a [i] index for a particular node */
542 _constructIndex: function (node) {
543 var index = '';
544 do {
545 if (node.id) {
546 index = '[' + node.position + ']' + index;
547 }
548 } while ((node = node.parent) != null);
549 return index;
550 },
551
552 /** @id MochiKit.Sortable.sequence */
553 sequence: function (element, options) {
554 element = MochiKit.DOM.getElement(element);
555 var self = MochiKit.Sortable;
556 var options = MochiKit.Base.update(self.options(element), options || {});
557
558 return MochiKit.Base.map(function (item) {
559 return item.id.match(options.format) ? item.id.match(options.format)[1] : '';
560 }, MochiKit.DOM.getElement(self.findElements(element, options) || []));
561 },
562
563 /**
564 * Serializes the content of a Sortable. Useful to send this content through a XMLHTTPRequest.
565 * These options override the Sortable options for the serialization only.
566 * @param {Node} element Element to serialize.
567 * @param {Object} options Serialization options.
568 */
569 serialize: function (element, options) {
570 element = MochiKit.DOM.getElement(element);
571 var self = MochiKit.Sortable;
572 options = MochiKit.Base.update(self.options(element), options || {});
573 var name = encodeURIComponent(options.name || element.id);
574
575 if (options.tree) {
576 return MochiKit.Base.flattenArray(MochiKit.Base.map(function (item) {
577 return [name + self._constructIndex(item) + "[id]=" +
578 encodeURIComponent(item.id)].concat(item.children.map(arguments.callee));
579 }, self.tree(element, options).children)).join('&');
580 } else {
581 return MochiKit.Base.map(function (item) {
582 return name + "[]=" + encodeURIComponent(item);
583 }, self.sequence(element, options)).join('&');
584 }
585 }
586 });
587
588 // trunk compatibility
589 MochiKit.Sortable.Sortable = MochiKit.Sortable;
@@ -0,0 +1,445 b''
1 /***
2
3 MochiKit.Style 1.4
4
5 See <http://mochikit.com/> for documentation, downloads, license, etc.
6
7 (c) 2005-2006 Bob Ippolito, Beau Hartshorne. All rights Reserved.
8
9 ***/
10
11 if (typeof(dojo) != 'undefined') {
12 dojo.provide('MochiKit.Style');
13 dojo.require('MochiKit.Base');
14 dojo.require('MochiKit.DOM');
15 }
16 if (typeof(JSAN) != 'undefined') {
17 JSAN.use('MochiKit.Base', []);
18 JSAN.use('MochiKit.DOM', []);
19 }
20
21 try {
22 if (typeof(MochiKit.Base) == 'undefined') {
23 throw '';
24 }
25 } catch (e) {
26 throw 'MochiKit.Style depends on MochiKit.Base!';
27 }
28
29 try {
30 if (typeof(MochiKit.DOM) == 'undefined') {
31 throw '';
32 }
33 } catch (e) {
34 throw 'MochiKit.Style depends on MochiKit.DOM!';
35 }
36
37
38 if (typeof(MochiKit.Style) == 'undefined') {
39 MochiKit.Style = {};
40 }
41
42 MochiKit.Style.NAME = 'MochiKit.Style';
43 MochiKit.Style.VERSION = '1.4';
44 MochiKit.Style.__repr__ = function () {
45 return '[' + this.NAME + ' ' + this.VERSION + ']';
46 };
47 MochiKit.Style.toString = function () {
48 return this.__repr__();
49 };
50
51 MochiKit.Style.EXPORT_OK = [];
52
53 MochiKit.Style.EXPORT = [
54 'setStyle',
55 'setOpacity',
56 'getStyle',
57 'getElementDimensions',
58 'elementDimensions', // deprecated
59 'setElementDimensions',
60 'getElementPosition',
61 'elementPosition', // deprecated
62 'setElementPosition',
63 'setDisplayForElement',
64 'hideElement',
65 'showElement',
66 'getViewportDimensions',
67 'getViewportPosition',
68 'Dimensions',
69 'Coordinates'
70 ];
71
72
73 /*
74
75 Dimensions
76
77 */
78 /** @id MochiKit.Style.Dimensions */
79 MochiKit.Style.Dimensions = function (w, h) {
80 this.w = w;
81 this.h = h;
82 };
83
84 MochiKit.Style.Dimensions.prototype.__repr__ = function () {
85 var repr = MochiKit.Base.repr;
86 return '{w: ' + repr(this.w) + ', h: ' + repr(this.h) + '}';
87 };
88
89 MochiKit.Style.Dimensions.prototype.toString = function () {
90 return this.__repr__();
91 };
92
93
94 /*
95
96 Coordinates
97
98 */
99 /** @id MochiKit.Style.Coordinates */
100 MochiKit.Style.Coordinates = function (x, y) {
101 this.x = x;
102 this.y = y;
103 };
104
105 MochiKit.Style.Coordinates.prototype.__repr__ = function () {
106 var repr = MochiKit.Base.repr;
107 return '{x: ' + repr(this.x) + ', y: ' + repr(this.y) + '}';
108 };
109
110 MochiKit.Style.Coordinates.prototype.toString = function () {
111 return this.__repr__();
112 };
113
114
115 MochiKit.Base.update(MochiKit.Style, {
116
117 /** @id MochiKit.Style.getStyle */
118 getStyle: function (elem, cssProperty) {
119 var dom = MochiKit.DOM;
120 var d = dom._document;
121
122 elem = dom.getElement(elem);
123 cssProperty = MochiKit.Base.camelize(cssProperty);
124
125 if (!elem || elem == d) {
126 return undefined;
127 }
128 if (cssProperty == 'opacity' && elem.filters) {
129 var opacity = (MochiKit.Style.getStyle(elem, 'filter') || '').match(/alpha\(opacity=(.*)\)/);
130 if (opacity && opacity[1]) {
131 return parseFloat(opacity[1]) / 100;
132 }
133 return 1.0;
134 }
135 var value = elem.style ? elem.style[cssProperty] : null;
136 if (!value) {
137 if (d.defaultView && d.defaultView.getComputedStyle) {
138 var css = d.defaultView.getComputedStyle(elem, null);
139 cssProperty = cssProperty.replace(/([A-Z])/g, '-$1'
140 ).toLowerCase(); // from dojo.style.toSelectorCase
141 value = css ? css.getPropertyValue(cssProperty) : null;
142 } else if (elem.currentStyle) {
143 value = elem.currentStyle[cssProperty];
144 }
145 }
146 if (cssProperty == 'opacity') {
147 value = parseFloat(value);
148 }
149
150 if (/Opera/.test(navigator.userAgent) && (MochiKit.Base.find(['left', 'top', 'right', 'bottom'], cssProperty) != -1)) {
151 if (MochiKit.Style.getStyle(elem, 'position') == 'static') {
152 value = 'auto';
153 }
154 }
155
156 return value == 'auto' ? null : value;
157 },
158
159 /** @id MochiKit.Style.setStyle */
160 setStyle: function (elem, style) {
161 elem = MochiKit.DOM.getElement(elem);
162 for (var name in style) {
163 if (name == 'opacity') {
164 MochiKit.Style.setOpacity(elem, style[name]);
165 } else {
166 elem.style[MochiKit.Base.camelize(name)] = style[name];
167 }
168 }
169 },
170
171 /** @id MochiKit.Style.setOpacity */
172 setOpacity: function (elem, o) {
173 elem = MochiKit.DOM.getElement(elem);
174 var self = MochiKit.Style;
175 if (o == 1) {
176 var toSet = /Gecko/.test(navigator.userAgent) && !(/Konqueror|AppleWebKit|KHTML/.test(navigator.userAgent));
177 elem.style["opacity"] = toSet ? 0.999999 : 1.0;
178 if (/MSIE/.test(navigator.userAgent)) {
179 elem.style['filter'] =
180 self.getStyle(elem, 'filter').replace(/alpha\([^\)]*\)/gi, '');
181 }
182 } else {
183 if (o < 0.00001) {
184 o = 0;
185 }
186 elem.style["opacity"] = o;
187 if (/MSIE/.test(navigator.userAgent)) {
188 elem.style['filter'] =
189 self.getStyle(elem, 'filter').replace(/alpha\([^\)]*\)/gi, '') + 'alpha(opacity=' + o * 100 + ')';
190 }
191 }
192 },
193
194 /*
195
196 getElementPosition is adapted from YAHOO.util.Dom.getXY v0.9.0.
197 Copyright: Copyright (c) 2006, Yahoo! Inc. All rights reserved.
198 License: BSD, http://developer.yahoo.net/yui/license.txt
199
200 */
201
202 /** @id MochiKit.Style.getElementPosition */
203 getElementPosition: function (elem, /* optional */relativeTo) {
204 var self = MochiKit.Style;
205 var dom = MochiKit.DOM;
206 elem = dom.getElement(elem);
207
208 if (!elem ||
209 (!(elem.x && elem.y) &&
210 (!elem.parentNode === null ||
211 self.getStyle(elem, 'display') == 'none'))) {
212 return undefined;
213 }
214
215 var c = new self.Coordinates(0, 0);
216 var box = null;
217 var parent = null;
218
219 var d = MochiKit.DOM._document;
220 var de = d.documentElement;
221 var b = d.body;
222
223 if (!elem.parentNode && elem.x && elem.y) {
224 /* it's just a MochiKit.Style.Coordinates object */
225 c.x += elem.x || 0;
226 c.y += elem.y || 0;
227 } else if (elem.getBoundingClientRect) { // IE shortcut
228 /*
229
230 The IE shortcut can be off by two. We fix it. See:
231 http://msdn.microsoft.com/workshop/author/dhtml/reference/methods/getboundingclientrect.asp
232
233 This is similar to the method used in
234 MochiKit.Signal.Event.mouse().
235
236 */
237 box = elem.getBoundingClientRect();
238
239 c.x += box.left +
240 (de.scrollLeft || b.scrollLeft) -
241 (de.clientLeft || 0);
242
243 c.y += box.top +
244 (de.scrollTop || b.scrollTop) -
245 (de.clientTop || 0);
246
247 } else if (elem.offsetParent) {
248 c.x += elem.offsetLeft;
249 c.y += elem.offsetTop;
250 parent = elem.offsetParent;
251
252 if (parent != elem) {
253 while (parent) {
254 c.x += parent.offsetLeft;
255 c.y += parent.offsetTop;
256 parent = parent.offsetParent;
257 }
258 }
259
260 /*
261
262 Opera < 9 and old Safari (absolute) incorrectly account for
263 body offsetTop and offsetLeft.
264
265 */
266 var ua = navigator.userAgent.toLowerCase();
267 if ((typeof(opera) != 'undefined' &&
268 parseFloat(opera.version()) < 9) ||
269 (ua.indexOf('AppleWebKit') != -1 &&
270 self.getStyle(elem, 'position') == 'absolute')) {
271
272 c.x -= b.offsetLeft;
273 c.y -= b.offsetTop;
274
275 }
276 }
277
278 if (typeof(relativeTo) != 'undefined') {
279 relativeTo = arguments.callee(relativeTo);
280 if (relativeTo) {
281 c.x -= (relativeTo.x || 0);
282 c.y -= (relativeTo.y || 0);
283 }
284 }
285
286 if (elem.parentNode) {
287 parent = elem.parentNode;
288 } else {
289 parent = null;
290 }
291
292 while (parent) {
293 var tagName = parent.tagName.toUpperCase();
294 if (tagName === 'BODY' || tagName === 'HTML') {
295 break;
296 }
297 var disp = self.getStyle(parent, 'display');
298 // Handle strange Opera bug for some display
299 if (disp != 'inline' && disp != 'table-row') {
300 c.x -= parent.scrollLeft;
301 c.y -= parent.scrollTop;
302 }
303 if (parent.parentNode) {
304 parent = parent.parentNode;
305 } else {
306 parent = null;
307 }
308 }
309
310 return c;
311 },
312
313 /** @id MochiKit.Style.setElementPosition */
314 setElementPosition: function (elem, newPos/* optional */, units) {
315 elem = MochiKit.DOM.getElement(elem);
316 if (typeof(units) == 'undefined') {
317 units = 'px';
318 }
319 var newStyle = {};
320 var isUndefNull = MochiKit.Base.isUndefinedOrNull;
321 if (!isUndefNull(newPos.x)) {
322 newStyle['left'] = newPos.x + units;
323 }
324 if (!isUndefNull(newPos.y)) {
325 newStyle['top'] = newPos.y + units;
326 }
327 MochiKit.DOM.updateNodeAttributes(elem, {'style': newStyle});
328 },
329
330 /** @id MochiKit.Style.getElementDimensions */
331 getElementDimensions: function (elem) {
332 var self = MochiKit.Style;
333 var dom = MochiKit.DOM;
334 if (typeof(elem.w) == 'number' || typeof(elem.h) == 'number') {
335 return new self.Dimensions(elem.w || 0, elem.h || 0);
336 }
337 elem = dom.getElement(elem);
338 if (!elem) {
339 return undefined;
340 }
341 var disp = self.getStyle(elem, 'display');
342 // display can be empty/undefined on WebKit/KHTML
343 if (disp != 'none' && disp !== '' && typeof(disp) != 'undefined') {
344 return new self.Dimensions(elem.offsetWidth || 0,
345 elem.offsetHeight || 0);
346 }
347 var s = elem.style;
348 var originalVisibility = s.visibility;
349 var originalPosition = s.position;
350 s.visibility = 'hidden';
351 s.position = 'absolute';
352 s.display = '';
353 var originalWidth = elem.offsetWidth;
354 var originalHeight = elem.offsetHeight;
355 s.display = 'none';
356 s.position = originalPosition;
357 s.visibility = originalVisibility;
358 return new self.Dimensions(originalWidth, originalHeight);
359 },
360
361 /** @id MochiKit.Style.setElementDimensions */
362 setElementDimensions: function (elem, newSize/* optional */, units) {
363 elem = MochiKit.DOM.getElement(elem);
364 if (typeof(units) == 'undefined') {
365 units = 'px';
366 }
367 var newStyle = {};
368 var isUndefNull = MochiKit.Base.isUndefinedOrNull;
369 if (!isUndefNull(newSize.w)) {
370 newStyle['width'] = newSize.w + units;
371 }
372 if (!isUndefNull(newSize.h)) {
373 newStyle['height'] = newSize.h + units;
374 }
375 MochiKit.DOM.updateNodeAttributes(elem, {'style': newStyle});
376 },
377
378 /** @id MochiKit.Style.setDisplayForElement */
379 setDisplayForElement: function (display, element/*, ...*/) {
380 var elements = MochiKit.Base.extend(null, arguments, 1);
381 var getElement = MochiKit.DOM.getElement;
382 for (var i = 0; i < elements.length; i++) {
383 element = getElement(elements[i]);
384 if (element) {
385 element.style.display = display;
386 }
387 }
388 },
389
390 /** @id MochiKit.Style.getViewportDimensions */
391 getViewportDimensions: function () {
392 var d = new MochiKit.Style.Dimensions();
393
394 var w = MochiKit.DOM._window;
395 var b = MochiKit.DOM._document.body;
396
397 if (w.innerWidth) {
398 d.w = w.innerWidth;
399 d.h = w.innerHeight;
400 } else if (b.parentElement.clientWidth) {
401 d.w = b.parentElement.clientWidth;
402 d.h = b.parentElement.clientHeight;
403 } else if (b && b.clientWidth) {
404 d.w = b.clientWidth;
405 d.h = b.clientHeight;
406 }
407 return d;
408 },
409
410 /** @id MochiKit.Style.getViewportPosition */
411 getViewportPosition: function () {
412 var c = new MochiKit.Style.Coordinates(0, 0);
413 var d = MochiKit.DOM._document;
414 var de = d.documentElement;
415 var db = d.body;
416 if (de && (de.scrollTop || de.scrollLeft)) {
417 c.x = de.scrollLeft;
418 c.y = de.scrollTop;
419 } else if (db) {
420 c.x = db.scrollLeft;
421 c.y = db.scrollTop;
422 }
423 return c;
424 },
425
426 __new__: function () {
427 var m = MochiKit.Base;
428
429 this.elementPosition = this.getElementPosition;
430 this.elementDimensions = this.getElementDimensions;
431
432 this.hideElement = m.partial(this.setDisplayForElement, 'none');
433 this.showElement = m.partial(this.setDisplayForElement, 'block');
434
435 this.EXPORT_TAGS = {
436 ':common': this.EXPORT,
437 ':all': m.concat(this.EXPORT, this.EXPORT_OK)
438 };
439
440 m.nameFunctions(this);
441 }
442 });
443
444 MochiKit.Style.__new__();
445 MochiKit.Base._exportSymbols(this, MochiKit.Style);
@@ -0,0 +1,181 b''
1 /***
2
3 MochiKit.Test 1.4
4
5 See <http://mochikit.com/> for documentation, downloads, license, etc.
6
7 (c) 2005 Bob Ippolito. All rights Reserved.
8
9 ***/
10
11 if (typeof(dojo) != 'undefined') {
12 dojo.provide('MochiKit.Test');
13 dojo.require('MochiKit.Base');
14 }
15
16 if (typeof(JSAN) != 'undefined') {
17 JSAN.use("MochiKit.Base", []);
18 }
19
20 try {
21 if (typeof(MochiKit.Base) == 'undefined') {
22 throw "";
23 }
24 } catch (e) {
25 throw "MochiKit.Test depends on MochiKit.Base!";
26 }
27
28 if (typeof(MochiKit.Test) == 'undefined') {
29 MochiKit.Test = {};
30 }
31
32 MochiKit.Test.NAME = "MochiKit.Test";
33 MochiKit.Test.VERSION = "1.4";
34 MochiKit.Test.__repr__ = function () {
35 return "[" + this.NAME + " " + this.VERSION + "]";
36 };
37
38 MochiKit.Test.toString = function () {
39 return this.__repr__();
40 };
41
42
43 MochiKit.Test.EXPORT = ["runTests"];
44 MochiKit.Test.EXPORT_OK = [];
45
46 MochiKit.Test.runTests = function (obj) {
47 if (typeof(obj) == "string") {
48 obj = JSAN.use(obj);
49 }
50 var suite = new MochiKit.Test.Suite();
51 suite.run(obj);
52 };
53
54 MochiKit.Test.Suite = function () {
55 this.testIndex = 0;
56 MochiKit.Base.bindMethods(this);
57 };
58
59 MochiKit.Test.Suite.prototype = {
60 run: function (obj) {
61 try {
62 obj(this);
63 } catch (e) {
64 this.traceback(e);
65 }
66 },
67 traceback: function (e) {
68 var items = MochiKit.Iter.sorted(MochiKit.Base.items(e));
69 print("not ok " + this.testIndex + " - Error thrown");
70 for (var i = 0; i < items.length; i++) {
71 var kv = items[i];
72 if (kv[0] == "stack") {
73 kv[1] = kv[1].split(/\n/)[0];
74 }
75 this.print("# " + kv.join(": "));
76 }
77 },
78 print: function (s) {
79 print(s);
80 },
81 is: function (got, expected, /* optional */message) {
82 var res = 1;
83 var msg = null;
84 try {
85 res = MochiKit.Base.compare(got, expected);
86 } catch (e) {
87 msg = "Can not compare " + typeof(got) + ":" + typeof(expected);
88 }
89 if (res) {
90 msg = "Expected value did not compare equal";
91 }
92 if (!res) {
93 return this.testResult(true, message);
94 }
95 return this.testResult(false, message,
96 [[msg], ["got:", got], ["expected:", expected]]);
97 },
98
99 testResult: function (pass, msg, failures) {
100 this.testIndex += 1;
101 if (pass) {
102 this.print("ok " + this.testIndex + " - " + msg);
103 return;
104 }
105 this.print("not ok " + this.testIndex + " - " + msg);
106 if (failures) {
107 for (var i = 0; i < failures.length; i++) {
108 this.print("# " + failures[i].join(" "));
109 }
110 }
111 },
112
113 isDeeply: function (got, expected, /* optional */message) {
114 var m = MochiKit.Base;
115 var res = 1;
116 try {
117 res = m.compare(got, expected);
118 } catch (e) {
119 // pass
120 }
121 if (res === 0) {
122 return this.ok(true, message);
123 }
124 var gk = m.keys(got);
125 var ek = m.keys(expected);
126 gk.sort();
127 ek.sort();
128 if (m.compare(gk, ek)) {
129 // differing keys
130 var cmp = {};
131 var i;
132 for (i = 0; i < gk.length; i++) {
133 cmp[gk[i]] = "got";
134 }
135 for (i = 0; i < ek.length; i++) {
136 if (ek[i] in cmp) {
137 delete cmp[ek[i]];
138 } else {
139 cmp[ek[i]] = "expected";
140 }
141 }
142 var diffkeys = m.keys(cmp);
143 diffkeys.sort();
144 var gotkeys = [];
145 var expkeys = [];
146 while (diffkeys.length) {
147 var k = diffkeys.shift();
148 if (k in Object.prototype) {
149 continue;
150 }
151 (cmp[k] == "got" ? gotkeys : expkeys).push(k);
152 }
153
154
155 }
156
157 return this.testResult((!res), msg,
158 (msg ? [["got:", got], ["expected:", expected]] : undefined)
159 );
160 },
161
162 ok: function (res, message) {
163 return this.testResult(res, message);
164 }
165 };
166
167 MochiKit.Test.__new__ = function () {
168 var m = MochiKit.Base;
169
170 this.EXPORT_TAGS = {
171 ":common": this.EXPORT,
172 ":all": m.concat(this.EXPORT, this.EXPORT_OK)
173 };
174
175 m.nameFunctions(this);
176
177 };
178
179 MochiKit.Test.__new__();
180
181 MochiKit.Base._exportSymbols(this, MochiKit.Test);
This diff has been collapsed as it changes many lines, (1981 lines changed) Show them Hide them
@@ -0,0 +1,1981 b''
1 /***
2
3 MochiKit.Visual 1.4
4
5 See <http://mochikit.com/> for documentation, downloads, license, etc.
6
7 (c) 2005 Bob Ippolito and others. All rights Reserved.
8
9 ***/
10
11 if (typeof(dojo) != 'undefined') {
12 dojo.provide('MochiKit.Visual');
13 dojo.require('MochiKit.Base');
14 dojo.require('MochiKit.DOM');
15 dojo.require('MochiKit.Style');
16 dojo.require('MochiKit.Color');
17 dojo.require('MochiKit.Position');
18 }
19
20 if (typeof(JSAN) != 'undefined') {
21 JSAN.use("MochiKit.Base", []);
22 JSAN.use("MochiKit.DOM", []);
23 JSAN.use("MochiKit.Style", []);
24 JSAN.use("MochiKit.Color", []);
25 JSAN.use("MochiKit.Position", []);
26 }
27
28 try {
29 if (typeof(MochiKit.Base) === 'undefined' ||
30 typeof(MochiKit.DOM) === 'undefined' ||
31 typeof(MochiKit.Style) === 'undefined' ||
32 typeof(MochiKit.Position) === 'undefined' ||
33 typeof(MochiKit.Color) === 'undefined') {
34 throw "";
35 }
36 } catch (e) {
37 throw "MochiKit.Visual depends on MochiKit.Base, MochiKit.DOM, MochiKit.Style, MochiKit.Position and MochiKit.Color!";
38 }
39
40 if (typeof(MochiKit.Visual) == "undefined") {
41 MochiKit.Visual = {};
42 }
43
44 MochiKit.Visual.NAME = "MochiKit.Visual";
45 MochiKit.Visual.VERSION = "1.4";
46
47 MochiKit.Visual.__repr__ = function () {
48 return "[" + this.NAME + " " + this.VERSION + "]";
49 };
50
51 MochiKit.Visual.toString = function () {
52 return this.__repr__();
53 };
54
55 MochiKit.Visual._RoundCorners = function (e, options) {
56 e = MochiKit.DOM.getElement(e);
57 this._setOptions(options);
58 if (this.options.__unstable__wrapElement) {
59 e = this._doWrap(e);
60 }
61
62 var color = this.options.color;
63 var C = MochiKit.Color.Color;
64 if (this.options.color === "fromElement") {
65 color = C.fromBackground(e);
66 } else if (!(color instanceof C)) {
67 color = C.fromString(color);
68 }
69 this.isTransparent = (color.asRGB().a <= 0);
70
71 var bgColor = this.options.bgColor;
72 if (this.options.bgColor === "fromParent") {
73 bgColor = C.fromBackground(e.offsetParent);
74 } else if (!(bgColor instanceof C)) {
75 bgColor = C.fromString(bgColor);
76 }
77
78 this._roundCornersImpl(e, color, bgColor);
79 };
80
81 MochiKit.Visual._RoundCorners.prototype = {
82 _doWrap: function (e) {
83 var parent = e.parentNode;
84 var doc = MochiKit.DOM.currentDocument();
85 if (typeof(doc.defaultView) === "undefined"
86 || doc.defaultView === null) {
87 return e;
88 }
89 var style = doc.defaultView.getComputedStyle(e, null);
90 if (typeof(style) === "undefined" || style === null) {
91 return e;
92 }
93 var wrapper = MochiKit.DOM.DIV({"style": {
94 display: "block",
95 // convert padding to margin
96 marginTop: style.getPropertyValue("padding-top"),
97 marginRight: style.getPropertyValue("padding-right"),
98 marginBottom: style.getPropertyValue("padding-bottom"),
99 marginLeft: style.getPropertyValue("padding-left"),
100 // remove padding so the rounding looks right
101 padding: "0px"
102 /*
103 paddingRight: "0px",
104 paddingLeft: "0px"
105 */
106 }});
107 wrapper.innerHTML = e.innerHTML;
108 e.innerHTML = "";
109 e.appendChild(wrapper);
110 return e;
111 },
112
113 _roundCornersImpl: function (e, color, bgColor) {
114 if (this.options.border) {
115 this._renderBorder(e, bgColor);
116 }
117 if (this._isTopRounded()) {
118 this._roundTopCorners(e, color, bgColor);
119 }
120 if (this._isBottomRounded()) {
121 this._roundBottomCorners(e, color, bgColor);
122 }
123 },
124
125 _renderBorder: function (el, bgColor) {
126 var borderValue = "1px solid " + this._borderColor(bgColor);
127 var borderL = "border-left: " + borderValue;
128 var borderR = "border-right: " + borderValue;
129 var style = "style='" + borderL + ";" + borderR + "'";
130 el.innerHTML = "<div " + style + ">" + el.innerHTML + "</div>";
131 },
132
133 _roundTopCorners: function (el, color, bgColor) {
134 var corner = this._createCorner(bgColor);
135 for (var i = 0; i < this.options.numSlices; i++) {
136 corner.appendChild(
137 this._createCornerSlice(color, bgColor, i, "top")
138 );
139 }
140 el.style.paddingTop = 0;
141 el.insertBefore(corner, el.firstChild);
142 },
143
144 _roundBottomCorners: function (el, color, bgColor) {
145 var corner = this._createCorner(bgColor);
146 for (var i = (this.options.numSlices - 1); i >= 0; i--) {
147 corner.appendChild(
148 this._createCornerSlice(color, bgColor, i, "bottom")
149 );
150 }
151 el.style.paddingBottom = 0;
152 el.appendChild(corner);
153 },
154
155 _createCorner: function (bgColor) {
156 var dom = MochiKit.DOM;
157 return dom.DIV({style: {backgroundColor: bgColor.toString()}});
158 },
159
160 _createCornerSlice: function (color, bgColor, n, position) {
161 var slice = MochiKit.DOM.SPAN();
162
163 var inStyle = slice.style;
164 inStyle.backgroundColor = color.toString();
165 inStyle.display = "block";
166 inStyle.height = "1px";
167 inStyle.overflow = "hidden";
168 inStyle.fontSize = "1px";
169
170 var borderColor = this._borderColor(color, bgColor);
171 if (this.options.border && n === 0) {
172 inStyle.borderTopStyle = "solid";
173 inStyle.borderTopWidth = "1px";
174 inStyle.borderLeftWidth = "0px";
175 inStyle.borderRightWidth = "0px";
176 inStyle.borderBottomWidth = "0px";
177 // assumes css compliant box model
178 inStyle.height = "0px";
179 inStyle.borderColor = borderColor.toString();
180 } else if (borderColor) {
181 inStyle.borderColor = borderColor.toString();
182 inStyle.borderStyle = "solid";
183 inStyle.borderWidth = "0px 1px";
184 }
185
186 if (!this.options.compact && (n == (this.options.numSlices - 1))) {
187 inStyle.height = "2px";
188 }
189
190 this._setMargin(slice, n, position);
191 this._setBorder(slice, n, position);
192
193 return slice;
194 },
195
196 _setOptions: function (options) {
197 this.options = {
198 corners: "all",
199 color: "fromElement",
200 bgColor: "fromParent",
201 blend: true,
202 border: false,
203 compact: false,
204 __unstable__wrapElement: false
205 };
206 MochiKit.Base.update(this.options, options);
207
208 this.options.numSlices = (this.options.compact ? 2 : 4);
209 },
210
211 _whichSideTop: function () {
212 var corners = this.options.corners;
213 if (this._hasString(corners, "all", "top")) {
214 return "";
215 }
216
217 var has_tl = (corners.indexOf("tl") != -1);
218 var has_tr = (corners.indexOf("tr") != -1);
219 if (has_tl && has_tr) {
220 return "";
221 }
222 if (has_tl) {
223 return "left";
224 }
225 if (has_tr) {
226 return "right";
227 }
228 return "";
229 },
230
231 _whichSideBottom: function () {
232 var corners = this.options.corners;
233 if (this._hasString(corners, "all", "bottom")) {
234 return "";
235 }
236
237 var has_bl = (corners.indexOf('bl') != -1);
238 var has_br = (corners.indexOf('br') != -1);
239 if (has_bl && has_br) {
240 return "";
241 }
242 if (has_bl) {
243 return "left";
244 }
245 if (has_br) {
246 return "right";
247 }
248 return "";
249 },
250
251 _borderColor: function (color, bgColor) {
252 if (color == "transparent") {
253 return bgColor;
254 } else if (this.options.border) {
255 return this.options.border;
256 } else if (this.options.blend) {
257 return bgColor.blendedColor(color);
258 }
259 return "";
260 },
261
262
263 _setMargin: function (el, n, corners) {
264 var marginSize = this._marginSize(n) + "px";
265 var whichSide = (
266 corners == "top" ? this._whichSideTop() : this._whichSideBottom()
267 );
268 var style = el.style;
269
270 if (whichSide == "left") {
271 style.marginLeft = marginSize;
272 style.marginRight = "0px";
273 } else if (whichSide == "right") {
274 style.marginRight = marginSize;
275 style.marginLeft = "0px";
276 } else {
277 style.marginLeft = marginSize;
278 style.marginRight = marginSize;
279 }
280 },
281
282 _setBorder: function (el, n, corners) {
283 var borderSize = this._borderSize(n) + "px";
284 var whichSide = (
285 corners == "top" ? this._whichSideTop() : this._whichSideBottom()
286 );
287
288 var style = el.style;
289 if (whichSide == "left") {
290 style.borderLeftWidth = borderSize;
291 style.borderRightWidth = "0px";
292 } else if (whichSide == "right") {
293 style.borderRightWidth = borderSize;
294 style.borderLeftWidth = "0px";
295 } else {
296 style.borderLeftWidth = borderSize;
297 style.borderRightWidth = borderSize;
298 }
299 },
300
301 _marginSize: function (n) {
302 if (this.isTransparent) {
303 return 0;
304 }
305
306 var o = this.options;
307 if (o.compact && o.blend) {
308 var smBlendedMarginSizes = [1, 0];
309 return smBlendedMarginSizes[n];
310 } else if (o.compact) {
311 var compactMarginSizes = [2, 1];
312 return compactMarginSizes[n];
313 } else if (o.blend) {
314 var blendedMarginSizes = [3, 2, 1, 0];
315 return blendedMarginSizes[n];
316 } else {
317 var marginSizes = [5, 3, 2, 1];
318 return marginSizes[n];
319 }
320 },
321
322 _borderSize: function (n) {
323 var o = this.options;
324 var borderSizes;
325 if (o.compact && (o.blend || this.isTransparent)) {
326 return 1;
327 } else if (o.compact) {
328 borderSizes = [1, 0];
329 } else if (o.blend) {
330 borderSizes = [2, 1, 1, 1];
331 } else if (o.border) {
332 borderSizes = [0, 2, 0, 0];
333 } else if (this.isTransparent) {
334 borderSizes = [5, 3, 2, 1];
335 } else {
336 return 0;
337 }
338 return borderSizes[n];
339 },
340
341 _hasString: function (str) {
342 for (var i = 1; i< arguments.length; i++) {
343 if (str.indexOf(arguments[i]) != -1) {
344 return true;
345 }
346 }
347 return false;
348 },
349
350 _isTopRounded: function () {
351 return this._hasString(this.options.corners,
352 "all", "top", "tl", "tr"
353 );
354 },
355
356 _isBottomRounded: function () {
357 return this._hasString(this.options.corners,
358 "all", "bottom", "bl", "br"
359 );
360 },
361
362 _hasSingleTextChild: function (el) {
363 return (el.childNodes.length == 1 && el.childNodes[0].nodeType == 3);
364 }
365 };
366
367 /** @id MochiKit.Visual.roundElement */
368 MochiKit.Visual.roundElement = function (e, options) {
369 new MochiKit.Visual._RoundCorners(e, options);
370 };
371
372 /** @id MochiKit.Visual.roundClass */
373 MochiKit.Visual.roundClass = function (tagName, className, options) {
374 var elements = MochiKit.DOM.getElementsByTagAndClassName(
375 tagName, className
376 );
377 for (var i = 0; i < elements.length; i++) {
378 MochiKit.Visual.roundElement(elements[i], options);
379 }
380 };
381
382 /** @id MochiKit.Visual.tagifyText */
383 MochiKit.Visual.tagifyText = function (element, /* optional */tagifyStyle) {
384 /***
385
386 Change a node text to character in tags.
387
388 @param tagifyStyle: the style to apply to character nodes, default to
389 'position: relative'.
390
391 ***/
392 tagifyStyle = tagifyStyle || 'position:relative';
393 if (/MSIE/.test(navigator.userAgent)) {
394 tagifyStyle += ';zoom:1';
395 }
396 element = MochiKit.DOM.getElement(element);
397 var ma = MochiKit.Base.map;
398 ma(function (child) {
399 if (child.nodeType == 3) {
400 ma(function (character) {
401 element.insertBefore(
402 MochiKit.DOM.SPAN({style: tagifyStyle},
403 character == ' ' ? String.fromCharCode(160) : character), child);
404 }, child.nodeValue.split(''));
405 MochiKit.DOM.removeElement(child);
406 }
407 }, element.childNodes);
408 };
409
410 /** @id MochiKit.Visual.forceRerendering */
411 MochiKit.Visual.forceRerendering = function (element) {
412 try {
413 element = MochiKit.DOM.getElement(element);
414 var n = document.createTextNode(' ');
415 element.appendChild(n);
416 element.removeChild(n);
417 } catch(e) {
418 }
419 };
420
421 /** @id MochiKit.Visual.multiple */
422 MochiKit.Visual.multiple = function (elements, effect, /* optional */options) {
423 /***
424
425 Launch the same effect subsequently on given elements.
426
427 ***/
428 options = MochiKit.Base.update({
429 speed: 0.1, delay: 0.0
430 }, options);
431 var masterDelay = options.delay;
432 var index = 0;
433 MochiKit.Base.map(function (innerelement) {
434 options.delay = index * options.speed + masterDelay;
435 new effect(innerelement, options);
436 index += 1;
437 }, elements);
438 };
439
440 MochiKit.Visual.PAIRS = {
441 'slide': ['slideDown', 'slideUp'],
442 'blind': ['blindDown', 'blindUp'],
443 'appear': ['appear', 'fade'],
444 'size': ['grow', 'shrink']
445 };
446
447 /** @id MochiKit.Visual.toggle */
448 MochiKit.Visual.toggle = function (element, /* optional */effect, /* optional */options) {
449 /***
450
451 Toggle an item between two state depending of its visibility, making
452 a effect between these states. Default effect is 'appear', can be
453 'slide' or 'blind'.
454
455 ***/
456 element = MochiKit.DOM.getElement(element);
457 effect = (effect || 'appear').toLowerCase();
458 options = MochiKit.Base.update({
459 queue: {position: 'end', scope: (element.id || 'global'), limit: 1}
460 }, options);
461 var v = MochiKit.Visual;
462 v[MochiKit.Style.getStyle(element, 'display') != 'none' ?
463 v.PAIRS[effect][1] : v.PAIRS[effect][0]](element, options);
464 };
465
466 /***
467
468 Transitions: define functions calculating variations depending of a position.
469
470 ***/
471
472 MochiKit.Visual.Transitions = {};
473
474 /** @id MochiKit.Visual.Transitions.linear */
475 MochiKit.Visual.Transitions.linear = function (pos) {
476 return pos;
477 };
478
479 /** @id MochiKit.Visual.Transitions.sinoidal */
480 MochiKit.Visual.Transitions.sinoidal = function (pos) {
481 return (-Math.cos(pos*Math.PI)/2) + 0.5;
482 };
483
484 /** @id MochiKit.Visual.Transitions.reverse */
485 MochiKit.Visual.Transitions.reverse = function (pos) {
486 return 1 - pos;
487 };
488
489 /** @id MochiKit.Visual.Transitions.flicker */
490 MochiKit.Visual.Transitions.flicker = function (pos) {
491 return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4;
492 };
493
494 /** @id MochiKit.Visual.Transitions.wobble */
495 MochiKit.Visual.Transitions.wobble = function (pos) {
496 return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
497 };
498
499 /** @id MochiKit.Visual.Transitions.pulse */
500 MochiKit.Visual.Transitions.pulse = function (pos, pulses) {
501 if (!pulses) {
502 return (Math.floor(pos*10) % 2 === 0 ?
503 (pos*10 - Math.floor(pos*10)) : 1 - (pos*10 - Math.floor(pos*10)));
504 }
505 return (Math.round((pos % (1/pulses)) * pulses) == 0 ?
506 ((pos * pulses * 2) - Math.floor(pos * pulses * 2)) :
507 1 - ((pos * pulses * 2) - Math.floor(pos * pulses * 2)));
508 };
509
510 /** @id MochiKit.Visual.Transitions.none */
511 MochiKit.Visual.Transitions.none = function (pos) {
512 return 0;
513 };
514
515 /** @id MochiKit.Visual.Transitions.full */
516 MochiKit.Visual.Transitions.full = function (pos) {
517 return 1;
518 };
519
520 /***
521
522 Core effects
523
524 ***/
525
526 MochiKit.Visual.ScopedQueue = function () {
527 var cls = arguments.callee;
528 if (!(this instanceof cls)) {
529 return new cls();
530 }
531 this.__init__();
532 };
533
534 MochiKit.Base.update(MochiKit.Visual.ScopedQueue.prototype, {
535 __init__: function () {
536 this.effects = [];
537 this.interval = null;
538 },
539
540 /** @id MochiKit.Visual.ScopedQueue.prototype.add */
541 add: function (effect) {
542 var timestamp = new Date().getTime();
543
544 var position = (typeof(effect.options.queue) == 'string') ?
545 effect.options.queue : effect.options.queue.position;
546
547 var ma = MochiKit.Base.map;
548 switch (position) {
549 case 'front':
550 // move unstarted effects after this effect
551 ma(function (e) {
552 if (e.state == 'idle') {
553 e.startOn += effect.finishOn;
554 e.finishOn += effect.finishOn;
555 }
556 }, this.effects);
557 break;
558 case 'end':
559 var finish;
560 // start effect after last queued effect has finished
561 ma(function (e) {
562 var i = e.finishOn;
563 if (i >= (finish || i)) {
564 finish = i;
565 }
566 }, this.effects);
567 timestamp = finish || timestamp;
568 break;
569 case 'break':
570 ma(function (e) {
571 e.finalize();
572 }, this.effects);
573 break;
574 }
575
576 effect.startOn += timestamp;
577 effect.finishOn += timestamp;
578 if (!effect.options.queue.limit ||
579 this.effects.length < effect.options.queue.limit) {
580 this.effects.push(effect);
581 }
582
583 if (!this.interval) {
584 this.interval = this.startLoop(MochiKit.Base.bind(this.loop, this),
585 40);
586 }
587 },
588
589 /** @id MochiKit.Visual.ScopedQueue.prototype.startLoop */
590 startLoop: function (func, interval) {
591 return setInterval(func, interval);
592 },
593
594 /** @id MochiKit.Visual.ScopedQueue.prototype.remove */
595 remove: function (effect) {
596 this.effects = MochiKit.Base.filter(function (e) {
597 return e != effect;
598 }, this.effects);
599 if (!this.effects.length) {
600 this.stopLoop(this.interval);
601 this.interval = null;
602 }
603 },
604
605 /** @id MochiKit.Visual.ScopedQueue.prototype.stopLoop */
606 stopLoop: function (interval) {
607 clearInterval(interval);
608 },
609
610 /** @id MochiKit.Visual.ScopedQueue.prototype.loop */
611 loop: function () {
612 var timePos = new Date().getTime();
613 MochiKit.Base.map(function (effect) {
614 effect.loop(timePos);
615 }, this.effects);
616 }
617 });
618
619 MochiKit.Visual.Queues = {
620 instances: {},
621
622 get: function (queueName) {
623 if (typeof(queueName) != 'string') {
624 return queueName;
625 }
626
627 if (!this.instances[queueName]) {
628 this.instances[queueName] = new MochiKit.Visual.ScopedQueue();
629 }
630 return this.instances[queueName];
631 }
632 };
633
634 MochiKit.Visual.Queue = MochiKit.Visual.Queues.get('global');
635
636 MochiKit.Visual.DefaultOptions = {
637 transition: MochiKit.Visual.Transitions.sinoidal,
638 duration: 1.0, // seconds
639 fps: 25.0, // max. 25fps due to MochiKit.Visual.Queue implementation
640 sync: false, // true for combining
641 from: 0.0,
642 to: 1.0,
643 delay: 0.0,
644 queue: 'parallel'
645 };
646
647 MochiKit.Visual.Base = function () {};
648
649 MochiKit.Visual.Base.prototype = {
650 /***
651
652 Basic class for all Effects. Define a looping mechanism called for each step
653 of an effect. Don't instantiate it, only subclass it.
654
655 ***/
656
657 __class__ : MochiKit.Visual.Base,
658
659 /** @id MochiKit.Visual.Base.prototype.start */
660 start: function (options) {
661 var v = MochiKit.Visual;
662 this.options = MochiKit.Base.setdefault(options,
663 v.DefaultOptions);
664 this.currentFrame = 0;
665 this.state = 'idle';
666 this.startOn = this.options.delay*1000;
667 this.finishOn = this.startOn + (this.options.duration*1000);
668 this.event('beforeStart');
669 if (!this.options.sync) {
670 v.Queues.get(typeof(this.options.queue) == 'string' ?
671 'global' : this.options.queue.scope).add(this);
672 }
673 },
674
675 /** @id MochiKit.Visual.Base.prototype.loop */
676 loop: function (timePos) {
677 if (timePos >= this.startOn) {
678 if (timePos >= this.finishOn) {
679 return this.finalize();
680 }
681 var pos = (timePos - this.startOn) / (this.finishOn - this.startOn);
682 var frame =
683 Math.round(pos * this.options.fps * this.options.duration);
684 if (frame > this.currentFrame) {
685 this.render(pos);
686 this.currentFrame = frame;
687 }
688 }
689 },
690
691 /** @id MochiKit.Visual.Base.prototype.render */
692 render: function (pos) {
693 if (this.state == 'idle') {
694 this.state = 'running';
695 this.event('beforeSetup');
696 this.setup();
697 this.event('afterSetup');
698 }
699 if (this.state == 'running') {
700 if (this.options.transition) {
701 pos = this.options.transition(pos);
702 }
703 pos *= (this.options.to - this.options.from);
704 pos += this.options.from;
705 this.event('beforeUpdate');
706 this.update(pos);
707 this.event('afterUpdate');
708 }
709 },
710
711 /** @id MochiKit.Visual.Base.prototype.cancel */
712 cancel: function () {
713 if (!this.options.sync) {
714 MochiKit.Visual.Queues.get(typeof(this.options.queue) == 'string' ?
715 'global' : this.options.queue.scope).remove(this);
716 }
717 this.state = 'finished';
718 },
719
720 /** @id MochiKit.Visual.Base.prototype.finalize */
721 finalize: function () {
722 this.render(1.0);
723 this.cancel();
724 this.event('beforeFinish');
725 this.finish();
726 this.event('afterFinish');
727 },
728
729 setup: function () {
730 },
731
732 finish: function () {
733 },
734
735 update: function (position) {
736 },
737
738 /** @id MochiKit.Visual.Base.prototype.event */
739 event: function (eventName) {
740 if (this.options[eventName + 'Internal']) {
741 this.options[eventName + 'Internal'](this);
742 }
743 if (this.options[eventName]) {
744 this.options[eventName](this);
745 }
746 },
747
748 /** @id MochiKit.Visual.Base.prototype.repr */
749 repr: function () {
750 return '[' + this.__class__.NAME + ', options:' +
751 MochiKit.Base.repr(this.options) + ']';
752 }
753 };
754
755 /** @id MochiKit.Visual.Parallel */
756 MochiKit.Visual.Parallel = function (effects, options) {
757 var cls = arguments.callee;
758 if (!(this instanceof cls)) {
759 return new cls(effects, options);
760 }
761
762 this.__init__(effects, options);
763 };
764
765 MochiKit.Visual.Parallel.prototype = new MochiKit.Visual.Base();
766
767 MochiKit.Base.update(MochiKit.Visual.Parallel.prototype, {
768 /***
769
770 Run multiple effects at the same time.
771
772 ***/
773
774 __class__ : MochiKit.Visual.Parallel,
775
776 __init__: function (effects, options) {
777 this.effects = effects || [];
778 this.start(options);
779 },
780
781 /** @id MochiKit.Visual.Parallel.prototype.update */
782 update: function (position) {
783 MochiKit.Base.map(function (effect) {
784 effect.render(position);
785 }, this.effects);
786 },
787
788 /** @id MochiKit.Visual.Parallel.prototype.finish */
789 finish: function () {
790 MochiKit.Base.map(function (effect) {
791 effect.finalize();
792 }, this.effects);
793 }
794 });
795
796 /** @id MochiKit.Visual.Opacity */
797 MochiKit.Visual.Opacity = function (element, options) {
798 var cls = arguments.callee;
799 if (!(this instanceof cls)) {
800 return new cls(element, options);
801 }
802 this.__init__(element, options);
803 };
804
805 MochiKit.Visual.Opacity.prototype = new MochiKit.Visual.Base();
806
807 MochiKit.Base.update(MochiKit.Visual.Opacity.prototype, {
808 /***
809
810 Change the opacity of an element.
811
812 @param options: 'from' and 'to' change the starting and ending opacities.
813 Must be between 0.0 and 1.0. Default to current opacity and 1.0.
814
815 ***/
816
817 __class__ : MochiKit.Visual.Opacity,
818
819 __init__: function (element, /* optional */options) {
820 var b = MochiKit.Base;
821 var s = MochiKit.Style;
822 this.element = MochiKit.DOM.getElement(element);
823 // make this work on IE on elements without 'layout'
824 if (this.element.currentStyle &&
825 (!this.element.currentStyle.hasLayout)) {
826 s.setStyle(this.element, {zoom: 1});
827 }
828 options = b.update({
829 from: s.getStyle(this.element, 'opacity') || 0.0,
830 to: 1.0
831 }, options);
832 this.start(options);
833 },
834
835 /** @id MochiKit.Visual.Opacity.prototype.update */
836 update: function (position) {
837 MochiKit.Style.setStyle(this.element, {'opacity': position});
838 }
839 });
840
841 /** @id MochiKit.Visual.Move.prototype */
842 MochiKit.Visual.Move = function (element, options) {
843 var cls = arguments.callee;
844 if (!(this instanceof cls)) {
845 return new cls(element, options);
846 }
847 this.__init__(element, options);
848 };
849
850 MochiKit.Visual.Move.prototype = new MochiKit.Visual.Base();
851
852 MochiKit.Base.update(MochiKit.Visual.Move.prototype, {
853 /***
854
855 Move an element between its current position to a defined position
856
857 @param options: 'x' and 'y' for final positions, default to 0, 0.
858
859 ***/
860
861 __class__ : MochiKit.Visual.Move,
862
863 __init__: function (element, /* optional */options) {
864 this.element = MochiKit.DOM.getElement(element);
865 options = MochiKit.Base.update({
866 x: 0,
867 y: 0,
868 mode: 'relative'
869 }, options);
870 this.start(options);
871 },
872
873 /** @id MochiKit.Visual.Move.prototype.setup */
874 setup: function () {
875 // Bug in Opera: Opera returns the 'real' position of a static element
876 // or relative element that does not have top/left explicitly set.
877 // ==> Always set top and left for position relative elements in your
878 // stylesheets (to 0 if you do not need them)
879 MochiKit.DOM.makePositioned(this.element);
880
881 var s = this.element.style;
882 var originalVisibility = s.visibility;
883 var originalDisplay = s.display;
884 if (originalDisplay == 'none') {
885 s.visibility = 'hidden';
886 s.display = '';
887 }
888
889 this.originalLeft = parseFloat(MochiKit.Style.getStyle(this.element, 'left') || '0');
890 this.originalTop = parseFloat(MochiKit.Style.getStyle(this.element, 'top') || '0');
891
892 if (this.options.mode == 'absolute') {
893 // absolute movement, so we need to calc deltaX and deltaY
894 this.options.x -= this.originalLeft;
895 this.options.y -= this.originalTop;
896 }
897 if (originalDisplay == 'none') {
898 s.visibility = originalVisibility;
899 s.display = originalDisplay;
900 }
901 },
902
903 /** @id MochiKit.Visual.Move.prototype.update */
904 update: function (position) {
905 MochiKit.Style.setStyle(this.element, {
906 left: Math.round(this.options.x * position + this.originalLeft) + 'px',
907 top: Math.round(this.options.y * position + this.originalTop) + 'px'
908 });
909 }
910 });
911
912 /** @id MochiKit.Visual.Scale */
913 MochiKit.Visual.Scale = function (element, percent, options) {
914 var cls = arguments.callee;
915 if (!(this instanceof cls)) {
916 return new cls(element, percent, options);
917 }
918 this.__init__(element, percent, options);
919 };
920
921 MochiKit.Visual.Scale.prototype = new MochiKit.Visual.Base();
922
923 MochiKit.Base.update(MochiKit.Visual.Scale.prototype, {
924 /***
925
926 Change the size of an element.
927
928 @param percent: final_size = percent*original_size
929
930 @param options: several options changing scale behaviour
931
932 ***/
933
934 __class__ : MochiKit.Visual.Scale,
935
936 __init__: function (element, percent, /* optional */options) {
937 this.element = MochiKit.DOM.getElement(element);
938 options = MochiKit.Base.update({
939 scaleX: true,
940 scaleY: true,
941 scaleContent: true,
942 scaleFromCenter: false,
943 scaleMode: 'box', // 'box' or 'contents' or {} with provided values
944 scaleFrom: 100.0,
945 scaleTo: percent
946 }, options);
947 this.start(options);
948 },
949
950 /** @id MochiKit.Visual.Scale.prototype.setup */
951 setup: function () {
952 this.restoreAfterFinish = this.options.restoreAfterFinish || false;
953 this.elementPositioning = MochiKit.Style.getStyle(this.element,
954 'position');
955
956 var ma = MochiKit.Base.map;
957 var b = MochiKit.Base.bind;
958 this.originalStyle = {};
959 ma(b(function (k) {
960 this.originalStyle[k] = this.element.style[k];
961 }, this), ['top', 'left', 'width', 'height', 'fontSize']);
962
963 this.originalTop = this.element.offsetTop;
964 this.originalLeft = this.element.offsetLeft;
965
966 var fontSize = MochiKit.Style.getStyle(this.element,
967 'font-size') || '100%';
968 ma(b(function (fontSizeType) {
969 if (fontSize.indexOf(fontSizeType) > 0) {
970 this.fontSize = parseFloat(fontSize);
971 this.fontSizeType = fontSizeType;
972 }
973 }, this), ['em', 'px', '%']);
974
975 this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;
976
977 if (/^content/.test(this.options.scaleMode)) {
978 this.dims = [this.element.scrollHeight, this.element.scrollWidth];
979 } else if (this.options.scaleMode == 'box') {
980 this.dims = [this.element.offsetHeight, this.element.offsetWidth];
981 } else {
982 this.dims = [this.options.scaleMode.originalHeight,
983 this.options.scaleMode.originalWidth];
984 }
985 },
986
987 /** @id MochiKit.Visual.Scale.prototype.update */
988 update: function (position) {
989 var currentScale = (this.options.scaleFrom/100.0) +
990 (this.factor * position);
991 if (this.options.scaleContent && this.fontSize) {
992 MochiKit.Style.setStyle(this.element, {
993 fontSize: this.fontSize * currentScale + this.fontSizeType
994 });
995 }
996 this.setDimensions(this.dims[0] * currentScale,
997 this.dims[1] * currentScale);
998 },
999
1000 /** @id MochiKit.Visual.Scale.prototype.finish */
1001 finish: function () {
1002 if (this.restoreAfterFinish) {
1003 MochiKit.Style.setStyle(this.element, this.originalStyle);
1004 }
1005 },
1006
1007 /** @id MochiKit.Visual.Scale.prototype.setDimensions */
1008 setDimensions: function (height, width) {
1009 var d = {};
1010 var r = Math.round;
1011 if (/MSIE/.test(navigator.userAgent)) {
1012 r = Math.ceil;
1013 }
1014 if (this.options.scaleX) {
1015 d.width = r(width) + 'px';
1016 }
1017 if (this.options.scaleY) {
1018 d.height = r(height) + 'px';
1019 }
1020 if (this.options.scaleFromCenter) {
1021 var topd = (height - this.dims[0])/2;
1022 var leftd = (width - this.dims[1])/2;
1023 if (this.elementPositioning == 'absolute') {
1024 if (this.options.scaleY) {
1025 d.top = this.originalTop - topd + 'px';
1026 }
1027 if (this.options.scaleX) {
1028 d.left = this.originalLeft - leftd + 'px';
1029 }
1030 } else {
1031 if (this.options.scaleY) {
1032 d.top = -topd + 'px';
1033 }
1034 if (this.options.scaleX) {
1035 d.left = -leftd + 'px';
1036 }
1037 }
1038 }
1039 MochiKit.Style.setStyle(this.element, d);
1040 }
1041 });
1042
1043 /** @id MochiKit.Visual.Highlight */
1044 MochiKit.Visual.Highlight = function (element, options) {
1045 var cls = arguments.callee;
1046 if (!(this instanceof cls)) {
1047 return new cls(element, options);
1048 }
1049 this.__init__(element, options);
1050 };
1051
1052 MochiKit.Visual.Highlight.prototype = new MochiKit.Visual.Base();
1053
1054 MochiKit.Base.update(MochiKit.Visual.Highlight.prototype, {
1055 /***
1056
1057 Highlight an item of the page.
1058
1059 @param options: 'startcolor' for choosing highlighting color, default
1060 to '#ffff99'.
1061
1062 ***/
1063
1064 __class__ : MochiKit.Visual.Highlight,
1065
1066 __init__: function (element, /* optional */options) {
1067 this.element = MochiKit.DOM.getElement(element);
1068 options = MochiKit.Base.update({
1069 startcolor: '#ffff99'
1070 }, options);
1071 this.start(options);
1072 },
1073
1074 /** @id MochiKit.Visual.Highlight.prototype.setup */
1075 setup: function () {
1076 var b = MochiKit.Base;
1077 var s = MochiKit.Style;
1078 // Prevent executing on elements not in the layout flow
1079 if (s.getStyle(this.element, 'display') == 'none') {
1080 this.cancel();
1081 return;
1082 }
1083 // Disable background image during the effect
1084 this.oldStyle = {
1085 backgroundImage: s.getStyle(this.element, 'background-image')
1086 };
1087 s.setStyle(this.element, {
1088 backgroundImage: 'none'
1089 });
1090
1091 if (!this.options.endcolor) {
1092 this.options.endcolor =
1093 MochiKit.Color.Color.fromBackground(this.element).toHexString();
1094 }
1095 if (b.isUndefinedOrNull(this.options.restorecolor)) {
1096 this.options.restorecolor = s.getStyle(this.element,
1097 'background-color');
1098 }
1099 // init color calculations
1100 this._base = b.map(b.bind(function (i) {
1101 return parseInt(
1102 this.options.startcolor.slice(i*2 + 1, i*2 + 3), 16);
1103 }, this), [0, 1, 2]);
1104 this._delta = b.map(b.bind(function (i) {
1105 return parseInt(this.options.endcolor.slice(i*2 + 1, i*2 + 3), 16)
1106 - this._base[i];
1107 }, this), [0, 1, 2]);
1108 },
1109
1110 /** @id MochiKit.Visual.Highlight.prototype.update */
1111 update: function (position) {
1112 var m = '#';
1113 MochiKit.Base.map(MochiKit.Base.bind(function (i) {
1114 m += MochiKit.Color.toColorPart(Math.round(this._base[i] +
1115 this._delta[i]*position));
1116 }, this), [0, 1, 2]);
1117 MochiKit.Style.setStyle(this.element, {
1118 backgroundColor: m
1119 });
1120 },
1121
1122 /** @id MochiKit.Visual.Highlight.prototype.finish */
1123 finish: function () {
1124 MochiKit.Style.setStyle(this.element,
1125 MochiKit.Base.update(this.oldStyle, {
1126 backgroundColor: this.options.restorecolor
1127 }));
1128 }
1129 });
1130
1131 /** @id MochiKit.Visual.ScrollTo */
1132 MochiKit.Visual.ScrollTo = function (element, options) {
1133 var cls = arguments.callee;
1134 if (!(this instanceof cls)) {
1135 return new cls(element, options);
1136 }
1137 this.__init__(element, options);
1138 };
1139
1140 MochiKit.Visual.ScrollTo.prototype = new MochiKit.Visual.Base();
1141
1142 MochiKit.Base.update(MochiKit.Visual.ScrollTo.prototype, {
1143 /***
1144
1145 Scroll to an element in the page.
1146
1147 ***/
1148
1149 __class__ : MochiKit.Visual.ScrollTo,
1150
1151 __init__: function (element, /* optional */options) {
1152 this.element = MochiKit.DOM.getElement(element);
1153 this.start(options);
1154 },
1155
1156 /** @id MochiKit.Visual.ScrollTo.prototype.setup */
1157 setup: function () {
1158 var p = MochiKit.Position;
1159 p.prepare();
1160 var offsets = p.cumulativeOffset(this.element);
1161 if (this.options.offset) {
1162 offsets.y += this.options.offset;
1163 }
1164 var max;
1165 if (window.innerHeight) {
1166 max = window.innerHeight - window.height;
1167 } else if (document.documentElement &&
1168 document.documentElement.clientHeight) {
1169 max = document.documentElement.clientHeight -
1170 document.body.scrollHeight;
1171 } else if (document.body) {
1172 max = document.body.clientHeight - document.body.scrollHeight;
1173 }
1174 this.scrollStart = p.windowOffset.y;
1175 this.delta = (offsets.y > max ? max : offsets.y) - this.scrollStart;
1176 },
1177
1178 /** @id MochiKit.Visual.ScrollTo.prototype.update */
1179 update: function (position) {
1180 var p = MochiKit.Position;
1181 p.prepare();
1182 window.scrollTo(p.windowOffset.x, this.scrollStart + (position * this.delta));
1183 }
1184 });
1185
1186 MochiKit.Visual.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/;
1187
1188 MochiKit.Visual.Morph = function (element, options) {
1189 var cls = arguments.callee;
1190 if (!(this instanceof cls)) {
1191 return new cls(element, options);
1192 }
1193 this.__init__(element, options);
1194 };
1195
1196 MochiKit.Visual.Morph.prototype = new MochiKit.Visual.Base();
1197
1198 MochiKit.Base.update(MochiKit.Visual.Morph.prototype, {
1199 /***
1200
1201 Morph effect: make a transformation from current style to the given style,
1202 automatically making a transition between the two.
1203
1204 ***/
1205
1206 __class__ : MochiKit.Visual.Morph,
1207
1208 __init__: function (element, /* optional */options) {
1209 this.element = MochiKit.DOM.getElement(element);
1210 this.start(options);
1211 },
1212
1213 /** @id MochiKit.Visual.Morph.prototype.setup */
1214 setup: function () {
1215 var b = MochiKit.Base;
1216 var style = this.options.style;
1217 this.styleStart = {};
1218 this.styleEnd = {};
1219 this.units = {};
1220 var value, unit;
1221 for (var s in style) {
1222 value = style[s];
1223 s = b.camelize(s);
1224 if (MochiKit.Visual.CSS_LENGTH.test(value)) {
1225 var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/);
1226 value = parseFloat(components[1]);
1227 unit = (components.length == 3) ? components[2] : null;
1228 this.styleEnd[s] = value;
1229 this.units[s] = unit;
1230 value = MochiKit.Style.getStyle(this.element, s);
1231 components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/);
1232 value = parseFloat(components[1]);
1233 this.styleStart[s] = value;
1234 } else {
1235 var c = MochiKit.Color.Color;
1236 value = c.fromString(value);
1237 if (value) {
1238 this.units[s] = "color";
1239 this.styleEnd[s] = value.toHexString();
1240 value = MochiKit.Style.getStyle(this.element, s);
1241 this.styleStart[s] = c.fromString(value).toHexString();
1242
1243 this.styleStart[s] = b.map(b.bind(function (i) {
1244 return parseInt(
1245 this.styleStart[s].slice(i*2 + 1, i*2 + 3), 16);
1246 }, this), [0, 1, 2]);
1247 this.styleEnd[s] = b.map(b.bind(function (i) {
1248 return parseInt(
1249 this.styleEnd[s].slice(i*2 + 1, i*2 + 3), 16);
1250 }, this), [0, 1, 2]);
1251 }
1252 }
1253 }
1254 },
1255
1256 /** @id MochiKit.Visual.Morph.prototype.update */
1257 update: function (position) {
1258 var value;
1259 for (var s in this.styleStart) {
1260 if (this.units[s] == "color") {
1261 var m = '#';
1262 var start = this.styleStart[s];
1263 var end = this.styleEnd[s];
1264 MochiKit.Base.map(MochiKit.Base.bind(function (i) {
1265 m += MochiKit.Color.toColorPart(Math.round(start[i] +
1266 (end[i] - start[i])*position));
1267 }, this), [0, 1, 2]);
1268 this.element.style[s] = m;
1269 } else {
1270 value = this.styleStart[s] + Math.round((this.styleEnd[s] - this.styleStart[s]) * position * 1000) / 1000 + this.units[s];
1271 this.element.style[s] = value;
1272 }
1273 }
1274 }
1275 });
1276
1277 /***
1278
1279 Combination effects.
1280
1281 ***/
1282
1283 /** @id MochiKit.Visual.fade */
1284 MochiKit.Visual.fade = function (element, /* optional */ options) {
1285 /***
1286
1287 Fade a given element: change its opacity and hide it in the end.
1288
1289 @param options: 'to' and 'from' to change opacity.
1290
1291 ***/
1292 var s = MochiKit.Style;
1293 var oldOpacity = s.getStyle(element, 'opacity');
1294 options = MochiKit.Base.update({
1295 from: s.getStyle(element, 'opacity') || 1.0,
1296 to: 0.0,
1297 afterFinishInternal: function (effect) {
1298 if (effect.options.to !== 0) {
1299 return;
1300 }
1301 s.hideElement(effect.element);
1302 s.setStyle(effect.element, {'opacity': oldOpacity});
1303 }
1304 }, options);
1305 return new MochiKit.Visual.Opacity(element, options);
1306 };
1307
1308 /** @id MochiKit.Visual.appear */
1309 MochiKit.Visual.appear = function (element, /* optional */ options) {
1310 /***
1311
1312 Make an element appear.
1313
1314 @param options: 'to' and 'from' to change opacity.
1315
1316 ***/
1317 var s = MochiKit.Style;
1318 var v = MochiKit.Visual;
1319 options = MochiKit.Base.update({
1320 from: (s.getStyle(element, 'display') == 'none' ? 0.0 :
1321 s.getStyle(element, 'opacity') || 0.0),
1322 to: 1.0,
1323 // force Safari to render floated elements properly
1324 afterFinishInternal: function (effect) {
1325 v.forceRerendering(effect.element);
1326 },
1327 beforeSetupInternal: function (effect) {
1328 s.setStyle(effect.element, {'opacity': effect.options.from});
1329 s.showElement(effect.element);
1330 }
1331 }, options);
1332 return new v.Opacity(element, options);
1333 };
1334
1335 /** @id MochiKit.Visual.puff */
1336 MochiKit.Visual.puff = function (element, /* optional */ options) {
1337 /***
1338
1339 'Puff' an element: grow it to double size, fading it and make it hidden.
1340
1341 ***/
1342 var s = MochiKit.Style;
1343 var v = MochiKit.Visual;
1344 element = MochiKit.DOM.getElement(element);
1345 var oldStyle = {
1346 position: s.getStyle(element, 'position'),
1347 top: element.style.top,
1348 left: element.style.left,
1349 width: element.style.width,
1350 height: element.style.height,
1351 opacity: s.getStyle(element, 'opacity')
1352 };
1353 options = MochiKit.Base.update({
1354 beforeSetupInternal: function (effect) {
1355 MochiKit.Position.absolutize(effect.effects[0].element);
1356 },
1357 afterFinishInternal: function (effect) {
1358 s.hideElement(effect.effects[0].element);
1359 s.setStyle(effect.effects[0].element, oldStyle);
1360 },
1361 scaleContent: true,
1362 scaleFromCenter: true
1363 }, options);
1364 return new v.Parallel(
1365 [new v.Scale(element, 200,
1366 {sync: true, scaleFromCenter: options.scaleFromCenter,
1367 scaleContent: options.scaleContent, restoreAfterFinish: true}),
1368 new v.Opacity(element, {sync: true, to: 0.0 })],
1369 options);
1370 };
1371
1372 /** @id MochiKit.Visual.blindUp */
1373 MochiKit.Visual.blindUp = function (element, /* optional */ options) {
1374 /***
1375
1376 Blind an element up: change its vertical size to 0.
1377
1378 ***/
1379 var d = MochiKit.DOM;
1380 element = d.getElement(element);
1381 var elemClip = d.makeClipping(element);
1382 options = MochiKit.Base.update({
1383 scaleContent: false,
1384 scaleX: false,
1385 restoreAfterFinish: true,
1386 afterFinishInternal: function (effect) {
1387 MochiKit.Style.hideElement(effect.element);
1388 d.undoClipping(effect.element, elemClip);
1389 }
1390 }, options);
1391
1392 return new MochiKit.Visual.Scale(element, 0, options);
1393 };
1394
1395 /** @id MochiKit.Visual.blindDown */
1396 MochiKit.Visual.blindDown = function (element, /* optional */ options) {
1397 /***
1398
1399 Blind an element down: restore its vertical size.
1400
1401 ***/
1402 var d = MochiKit.DOM;
1403 var s = MochiKit.Style;
1404 element = d.getElement(element);
1405 var elementDimensions = s.getElementDimensions(element);
1406 var elemClip;
1407 options = MochiKit.Base.update({
1408 scaleContent: false,
1409 scaleX: false,
1410 scaleFrom: 0,
1411 scaleMode: {originalHeight: elementDimensions.h,
1412 originalWidth: elementDimensions.w},
1413 restoreAfterFinish: true,
1414 afterSetupInternal: function (effect) {
1415 elemClip = d.makeClipping(effect.element);
1416 s.setStyle(effect.element, {height: '0px'});
1417 s.showElement(effect.element);
1418 },
1419 afterFinishInternal: function (effect) {
1420 d.undoClipping(effect.element, elemClip);
1421 }
1422 }, options);
1423 return new MochiKit.Visual.Scale(element, 100, options);
1424 };
1425
1426 /** @id MochiKit.Visual.switchOff */
1427 MochiKit.Visual.switchOff = function (element, /* optional */ options) {
1428 /***
1429
1430 Apply a switch-off-like effect.
1431
1432 ***/
1433 var d = MochiKit.DOM;
1434 element = d.getElement(element);
1435 var oldOpacity = MochiKit.Style.getStyle(element, 'opacity');
1436 var elemClip;
1437 options = MochiKit.Base.update({
1438 duration: 0.3,
1439 scaleFromCenter: true,
1440 scaleX: false,
1441 scaleContent: false,
1442 restoreAfterFinish: true,
1443 beforeSetupInternal: function (effect) {
1444 d.makePositioned(effect.element);
1445 elemClip = d.makeClipping(effect.element);
1446 },
1447 afterFinishInternal: function (effect) {
1448 MochiKit.Style.hideElement(effect.element);
1449 d.undoClipping(effect.element, elemClip);
1450 d.undoPositioned(effect.element);
1451 MochiKit.Style.setStyle(effect.element, {'opacity': oldOpacity});
1452 }
1453 }, options);
1454 var v = MochiKit.Visual;
1455 return new v.appear(element, {
1456 duration: 0.4,
1457 from: 0,
1458 transition: v.Transitions.flicker,
1459 afterFinishInternal: function (effect) {
1460 new v.Scale(effect.element, 1, options);
1461 }
1462 });
1463 };
1464
1465 /** @id MochiKit.Visual.dropOut */
1466 MochiKit.Visual.dropOut = function (element, /* optional */ options) {
1467 /***
1468
1469 Make an element fall and disappear.
1470
1471 ***/
1472 var d = MochiKit.DOM;
1473 var s = MochiKit.Style;
1474 element = d.getElement(element);
1475 var oldStyle = {
1476 top: s.getStyle(element, 'top'),
1477 left: s.getStyle(element, 'left'),
1478 opacity: s.getStyle(element, 'opacity')
1479 };
1480
1481 options = MochiKit.Base.update({
1482 duration: 0.5,
1483 distance: 100,
1484 beforeSetupInternal: function (effect) {
1485 d.makePositioned(effect.effects[0].element);
1486 },
1487 afterFinishInternal: function (effect) {
1488 s.hideElement(effect.effects[0].element);
1489 d.undoPositioned(effect.effects[0].element);
1490 s.setStyle(effect.effects[0].element, oldStyle);
1491 }
1492 }, options);
1493 var v = MochiKit.Visual;
1494 return new v.Parallel(
1495 [new v.Move(element, {x: 0, y: options.distance, sync: true}),
1496 new v.Opacity(element, {sync: true, to: 0.0})],
1497 options);
1498 };
1499
1500 /** @id MochiKit.Visual.shake */
1501 MochiKit.Visual.shake = function (element, /* optional */ options) {
1502 /***
1503
1504 Move an element from left to right several times.
1505
1506 ***/
1507 var d = MochiKit.DOM;
1508 var v = MochiKit.Visual;
1509 var s = MochiKit.Style;
1510 element = d.getElement(element);
1511 options = MochiKit.Base.update({
1512 x: -20,
1513 y: 0,
1514 duration: 0.05,
1515 afterFinishInternal: function (effect) {
1516 d.undoPositioned(effect.element);
1517 s.setStyle(effect.element, oldStyle);
1518 }
1519 }, options);
1520 var oldStyle = {
1521 top: s.getStyle(element, 'top'),
1522 left: s.getStyle(element, 'left') };
1523 return new v.Move(element,
1524 {x: 20, y: 0, duration: 0.05, afterFinishInternal: function (effect) {
1525 new v.Move(effect.element,
1526 {x: -40, y: 0, duration: 0.1, afterFinishInternal: function (effect) {
1527 new v.Move(effect.element,
1528 {x: 40, y: 0, duration: 0.1, afterFinishInternal: function (effect) {
1529 new v.Move(effect.element,
1530 {x: -40, y: 0, duration: 0.1, afterFinishInternal: function (effect) {
1531 new v.Move(effect.element,
1532 {x: 40, y: 0, duration: 0.1, afterFinishInternal: function (effect) {
1533 new v.Move(effect.element, options
1534 ) }}) }}) }}) }}) }});
1535 };
1536
1537 /** @id MochiKit.Visual.slideDown */
1538 MochiKit.Visual.slideDown = function (element, /* optional */ options) {
1539 /***
1540
1541 Slide an element down.
1542 It needs to have the content of the element wrapped in a container
1543 element with fixed height.
1544
1545 ***/
1546 var d = MochiKit.DOM;
1547 var b = MochiKit.Base;
1548 var s = MochiKit.Style;
1549 element = d.getElement(element);
1550 if (!element.firstChild) {
1551 throw "MochiKit.Visual.slideDown must be used on a element with a child";
1552 }
1553 d.removeEmptyTextNodes(element);
1554 var oldInnerBottom = s.getStyle(element.firstChild, 'bottom') || 0;
1555 var elementDimensions = s.getElementDimensions(element);
1556 var elemClip;
1557 options = b.update({
1558 scaleContent: false,
1559 scaleX: false,
1560 scaleFrom: 0,
1561 scaleMode: {originalHeight: elementDimensions.h,
1562 originalWidth: elementDimensions.w},
1563 restoreAfterFinish: true,
1564 afterSetupInternal: function (effect) {
1565 d.makePositioned(effect.element);
1566 d.makePositioned(effect.element.firstChild);
1567 if (/Opera/.test(navigator.userAgent)) {
1568 s.setStyle(effect.element, {top: ''});
1569 }
1570 elemClip = d.makeClipping(effect.element);
1571 s.setStyle(effect.element, {height: '0px'});
1572 s.showElement(effect.element);
1573 },
1574 afterUpdateInternal: function (effect) {
1575 s.setStyle(effect.element.firstChild,
1576 {bottom: (effect.dims[0] - effect.element.clientHeight) + 'px'});
1577 },
1578 afterFinishInternal: function (effect) {
1579 d.undoClipping(effect.element, elemClip);
1580 // IE will crash if child is undoPositioned first
1581 if (/MSIE/.test(navigator.userAgent)) {
1582 d.undoPositioned(effect.element);
1583 d.undoPositioned(effect.element.firstChild);
1584 } else {
1585 d.undoPositioned(effect.element.firstChild);
1586 d.undoPositioned(effect.element);
1587 }
1588 s.setStyle(effect.element.firstChild,
1589 {bottom: oldInnerBottom});
1590 }
1591 }, options);
1592
1593 return new MochiKit.Visual.Scale(element, 100, options);
1594 };
1595
1596 /** @id MochiKit.Visual.slideUp */
1597 MochiKit.Visual.slideUp = function (element, /* optional */ options) {
1598 /***
1599
1600 Slide an element up.
1601 It needs to have the content of the element wrapped in a container
1602 element with fixed height.
1603
1604 ***/
1605 var d = MochiKit.DOM;
1606 var b = MochiKit.Base;
1607 var s = MochiKit.Style;
1608 element = d.getElement(element);
1609 if (!element.firstChild) {
1610 throw "MochiKit.Visual.slideUp must be used on a element with a child";
1611 }
1612 d.removeEmptyTextNodes(element);
1613 var oldInnerBottom = s.getStyle(element.firstChild, 'bottom');
1614 var elemClip;
1615 options = b.update({
1616 scaleContent: false,
1617 scaleX: false,
1618 scaleMode: 'box',
1619 scaleFrom: 100,
1620 restoreAfterFinish: true,
1621 beforeStartInternal: function (effect) {
1622 d.makePositioned(effect.element);
1623 d.makePositioned(effect.element.firstChild);
1624 if (/Opera/.test(navigator.userAgent)) {
1625 s.setStyle(effect.element, {top: ''});
1626 }
1627 elemClip = d.makeClipping(effect.element);
1628 s.showElement(effect.element);
1629 },
1630 afterUpdateInternal: function (effect) {
1631 s.setStyle(effect.element.firstChild,
1632 {bottom: (effect.dims[0] - effect.element.clientHeight) + 'px'});
1633 },
1634 afterFinishInternal: function (effect) {
1635 s.hideElement(effect.element);
1636 d.undoClipping(effect.element, elemClip);
1637 d.undoPositioned(effect.element.firstChild);
1638 d.undoPositioned(effect.element);
1639 s.setStyle(effect.element.firstChild, {bottom: oldInnerBottom});
1640 }
1641 }, options);
1642 return new MochiKit.Visual.Scale(element, 0, options);
1643 };
1644
1645 // Bug in opera makes the TD containing this element expand for a instance
1646 // after finish
1647 /** @id MochiKit.Visual.squish */
1648 MochiKit.Visual.squish = function (element, /* optional */ options) {
1649 /***
1650
1651 Reduce an element and make it disappear.
1652
1653 ***/
1654 var d = MochiKit.DOM;
1655 var b = MochiKit.Base;
1656 var elemClip;
1657 options = b.update({
1658 restoreAfterFinish: true,
1659 beforeSetupInternal: function (effect) {
1660 elemClip = d.makeClipping(effect.element);
1661 },
1662 afterFinishInternal: function (effect) {
1663 MochiKit.Style.hideElement(effect.element);
1664 d.undoClipping(effect.element, elemClip);
1665 }
1666 }, options);
1667
1668 return new MochiKit.Visual.Scale(element, /Opera/.test(navigator.userAgent) ? 1 : 0, options);
1669 };
1670
1671 /** @id MochiKit.Visual.grow */
1672 MochiKit.Visual.grow = function (element, /* optional */ options) {
1673 /***
1674
1675 Grow an element to its original size. Make it zero-sized before
1676 if necessary.
1677
1678 ***/
1679 var d = MochiKit.DOM;
1680 var v = MochiKit.Visual;
1681 var s = MochiKit.Style;
1682 element = d.getElement(element);
1683 options = MochiKit.Base.update({
1684 direction: 'center',
1685 moveTransition: v.Transitions.sinoidal,
1686 scaleTransition: v.Transitions.sinoidal,
1687 opacityTransition: v.Transitions.full,
1688 scaleContent: true,
1689 scaleFromCenter: false
1690 }, options);
1691 var oldStyle = {
1692 top: element.style.top,
1693 left: element.style.left,
1694 height: element.style.height,
1695 width: element.style.width,
1696 opacity: s.getStyle(element, 'opacity')
1697 };
1698
1699 var dims = s.getElementDimensions(element);
1700 var initialMoveX, initialMoveY;
1701 var moveX, moveY;
1702
1703 switch (options.direction) {
1704 case 'top-left':
1705 initialMoveX = initialMoveY = moveX = moveY = 0;
1706 break;
1707 case 'top-right':
1708 initialMoveX = dims.w;
1709 initialMoveY = moveY = 0;
1710 moveX = -dims.w;
1711 break;
1712 case 'bottom-left':
1713 initialMoveX = moveX = 0;
1714 initialMoveY = dims.h;
1715 moveY = -dims.h;
1716 break;
1717 case 'bottom-right':
1718 initialMoveX = dims.w;
1719 initialMoveY = dims.h;
1720 moveX = -dims.w;
1721 moveY = -dims.h;
1722 break;
1723 case 'center':
1724 initialMoveX = dims.w / 2;
1725 initialMoveY = dims.h / 2;
1726 moveX = -dims.w / 2;
1727 moveY = -dims.h / 2;
1728 break;
1729 }
1730
1731 var optionsParallel = MochiKit.Base.update({
1732 beforeSetupInternal: function (effect) {
1733 s.setStyle(effect.effects[0].element, {height: '0px'});
1734 s.showElement(effect.effects[0].element);
1735 },
1736 afterFinishInternal: function (effect) {
1737 d.undoClipping(effect.effects[0].element);
1738 d.undoPositioned(effect.effects[0].element);
1739 s.setStyle(effect.effects[0].element, oldStyle);
1740 }
1741 }, options);
1742
1743 return new v.Move(element, {
1744 x: initialMoveX,
1745 y: initialMoveY,
1746 duration: 0.01,
1747 beforeSetupInternal: function (effect) {
1748 s.hideElement(effect.element);
1749 d.makeClipping(effect.element);
1750 d.makePositioned(effect.element);
1751 },
1752 afterFinishInternal: function (effect) {
1753 new v.Parallel(
1754 [new v.Opacity(effect.element, {
1755 sync: true, to: 1.0, from: 0.0,
1756 transition: options.opacityTransition
1757 }),
1758 new v.Move(effect.element, {
1759 x: moveX, y: moveY, sync: true,
1760 transition: options.moveTransition
1761 }),
1762 new v.Scale(effect.element, 100, {
1763 scaleMode: {originalHeight: dims.h,
1764 originalWidth: dims.w},
1765 sync: true,
1766 scaleFrom: /Opera/.test(navigator.userAgent) ? 1 : 0,
1767 transition: options.scaleTransition,
1768 scaleContent: options.scaleContent,
1769 scaleFromCenter: options.scaleFromCenter,
1770 restoreAfterFinish: true
1771 })
1772 ], optionsParallel
1773 );
1774 }
1775 });
1776 };
1777
1778 /** @id MochiKit.Visual.shrink */
1779 MochiKit.Visual.shrink = function (element, /* optional */ options) {
1780 /***
1781
1782 Shrink an element and make it disappear.
1783
1784 ***/
1785 var d = MochiKit.DOM;
1786 var v = MochiKit.Visual;
1787 var s = MochiKit.Style;
1788 element = d.getElement(element);
1789 options = MochiKit.Base.update({
1790 direction: 'center',
1791 moveTransition: v.Transitions.sinoidal,
1792 scaleTransition: v.Transitions.sinoidal,
1793 opacityTransition: v.Transitions.none,
1794 scaleContent: true,
1795 scaleFromCenter: false
1796 }, options);
1797 var oldStyle = {
1798 top: element.style.top,
1799 left: element.style.left,
1800 height: element.style.height,
1801 width: element.style.width,
1802 opacity: s.getStyle(element, 'opacity')
1803 };
1804
1805 var dims = s.getElementDimensions(element);
1806 var moveX, moveY;
1807
1808 switch (options.direction) {
1809 case 'top-left':
1810 moveX = moveY = 0;
1811 break;
1812 case 'top-right':
1813 moveX = dims.w;
1814 moveY = 0;
1815 break;
1816 case 'bottom-left':
1817 moveX = 0;
1818 moveY = dims.h;
1819 break;
1820 case 'bottom-right':
1821 moveX = dims.w;
1822 moveY = dims.h;
1823 break;
1824 case 'center':
1825 moveX = dims.w / 2;
1826 moveY = dims.h / 2;
1827 break;
1828 }
1829 var elemClip;
1830
1831 var optionsParallel = MochiKit.Base.update({
1832 beforeStartInternal: function (effect) {
1833 elemClip = d.makePositioned(effect.effects[0].element);
1834 d.makeClipping(effect.effects[0].element);
1835 },
1836 afterFinishInternal: function (effect) {
1837 s.hideElement(effect.effects[0].element);
1838 d.undoClipping(effect.effects[0].element, elemClip);
1839 d.undoPositioned(effect.effects[0].element);
1840 s.setStyle(effect.effects[0].element, oldStyle);
1841 }
1842 }, options);
1843
1844 return new v.Parallel(
1845 [new v.Opacity(element, {
1846 sync: true, to: 0.0, from: 1.0,
1847 transition: options.opacityTransition
1848 }),
1849 new v.Scale(element, /Opera/.test(navigator.userAgent) ? 1 : 0, {
1850 sync: true, transition: options.scaleTransition,
1851 scaleContent: options.scaleContent,
1852 scaleFromCenter: options.scaleFromCenter,
1853 restoreAfterFinish: true
1854 }),
1855 new v.Move(element, {
1856 x: moveX, y: moveY, sync: true, transition: options.moveTransition
1857 })
1858 ], optionsParallel
1859 );
1860 };
1861
1862 /** @id MochiKit.Visual.pulsate */
1863 MochiKit.Visual.pulsate = function (element, /* optional */ options) {
1864 /***
1865
1866 Pulse an element between appear/fade.
1867
1868 ***/
1869 var d = MochiKit.DOM;
1870 var v = MochiKit.Visual;
1871 var b = MochiKit.Base;
1872 var oldOpacity = MochiKit.Style.getStyle(element, 'opacity');
1873 options = b.update({
1874 duration: 3.0,
1875 from: 0,
1876 afterFinishInternal: function (effect) {
1877 MochiKit.Style.setStyle(effect.element, {'opacity': oldOpacity});
1878 }
1879 }, options);
1880 var transition = options.transition || v.Transitions.sinoidal;
1881 var reverser = b.bind(function (pos) {
1882 return transition(1 - v.Transitions.pulse(pos, options.pulses));
1883 }, transition);
1884 b.bind(reverser, transition);
1885 return new v.Opacity(element, b.update({
1886 transition: reverser}, options));
1887 };
1888
1889 /** @id MochiKit.Visual.fold */
1890 MochiKit.Visual.fold = function (element, /* optional */ options) {
1891 /***
1892
1893 Fold an element, first vertically, then horizontally.
1894
1895 ***/
1896 var d = MochiKit.DOM;
1897 var v = MochiKit.Visual;
1898 var s = MochiKit.Style;
1899 element = d.getElement(element);
1900 var oldStyle = {
1901 top: element.style.top,
1902 left: element.style.left,
1903 width: element.style.width,
1904 height: element.style.height
1905 };
1906 var elemClip = d.makeClipping(element);
1907 options = MochiKit.Base.update({
1908 scaleContent: false,
1909 scaleX: false,
1910 afterFinishInternal: function (effect) {
1911 new v.Scale(element, 1, {
1912 scaleContent: false,
1913 scaleY: false,
1914 afterFinishInternal: function (effect) {
1915 s.hideElement(effect.element);
1916 d.undoClipping(effect.element, elemClip);
1917 s.setStyle(effect.element, oldStyle);
1918 }
1919 });
1920 }
1921 }, options);
1922 return new v.Scale(element, 5, options);
1923 };
1924
1925
1926 // Compatibility with MochiKit 1.0
1927 MochiKit.Visual.Color = MochiKit.Color.Color;
1928 MochiKit.Visual.getElementsComputedStyle = MochiKit.DOM.computedStyle;
1929
1930 /* end of Rico adaptation */
1931
1932 MochiKit.Visual.__new__ = function () {
1933 var m = MochiKit.Base;
1934
1935 m.nameFunctions(this);
1936
1937 this.EXPORT_TAGS = {
1938 ":common": this.EXPORT,
1939 ":all": m.concat(this.EXPORT, this.EXPORT_OK)
1940 };
1941
1942 };
1943
1944 MochiKit.Visual.EXPORT = [
1945 "roundElement",
1946 "roundClass",
1947 "tagifyText",
1948 "multiple",
1949 "toggle",
1950 "Parallel",
1951 "Opacity",
1952 "Move",
1953 "Scale",
1954 "Highlight",
1955 "ScrollTo",
1956 "Morph",
1957 "fade",
1958 "appear",
1959 "puff",
1960 "blindUp",
1961 "blindDown",
1962 "switchOff",
1963 "dropOut",
1964 "shake",
1965 "slideDown",
1966 "slideUp",
1967 "squish",
1968 "grow",
1969 "shrink",
1970 "pulsate",
1971 "fold"
1972 ];
1973
1974 MochiKit.Visual.EXPORT_OK = [
1975 "Base",
1976 "PAIRS"
1977 ];
1978
1979 MochiKit.Visual.__new__();
1980
1981 MochiKit.Base._exportSymbols(this, MochiKit.Visual);
@@ -0,0 +1,10 b''
1 # encoding: utf-8
2 __docformat__ = "restructuredtext en"
3 #-------------------------------------------------------------------------------
4 # Copyright (C) 2005 Fernando Perez <fperez@colorado.edu>
5 # Brian E Granger <ellisonbg@gmail.com>
6 # Benjamin Ragan-Kelley <benjaminrk@gmail.com>
7 #
8 # Distributed under the terms of the BSD License. The full license is in
9 # the file COPYING, distributed as part of this software.
10 #-------------------------------------------------------------------------------
@@ -0,0 +1,18 b''
1 dojo.kwCompoundRequire({
2 "common": [
3 "MochiKit.Base",
4 "MochiKit.Iter",
5 "MochiKit.Logging",
6 "MochiKit.DateTime",
7 "MochiKit.Format",
8 "MochiKit.Async",
9 "MochiKit.DOM",
10 "MochiKit.Style",
11 "MochiKit.LoggingPane",
12 "MochiKit.Color",
13 "MochiKit.Signal",
14 "MochiKit.Position",
15 "MochiKit.Visual"
16 ]
17 });
18 dojo.provide("MochiKit.*");
@@ -0,0 +1,375 b''
1 // This is from the MochiKit ajax_tables example
2 /*
3
4 On page load, the SortableManager:
5
6 - Rips out all of the elements with the mochi-example class.
7 - Finds the elements with the mochi-template class and saves them for
8 later parsing with "MochiTAL".
9 - Finds the anchor tags with the mochi:dataformat attribute and gives them
10 onclick behvaiors to load new data, using their href as the data source.
11 This makes your XML or JSON look like a normal link to a search engine
12 (or javascript-disabled browser).
13 - Clones the thead element from the table because it will be replaced on each
14 sort.
15 - Sets up a default sort key of "object_name" and queues a load of the json
16 document.
17
18
19 On data load, the SortableManager:
20
21 - Parses the table data from the document (columns list, rows list-of-lists)
22 and turns them into a list of [{column:value, ...}] objects for easy sorting
23 and column order stability.
24 - Chooses the default (or previous) sort state and triggers a sort request
25
26
27 On sort request:
28
29 - Replaces the cloned thead element with a copy of it that has the sort
30 indicator (&uarr; or &darr;) for the most recently sorted column (matched up
31 to the first field in the th's mochi:sortcolumn attribute), and attaches
32 onclick, onmousedown, onmouseover, onmouseout behaviors to them. The second
33 field of mochi:sortcolumn attribute is used to perform a non-string sort.
34 - Performs the sort on the objects list. If the second field of
35 mochi:sortcolumn was not "str", then a custom function is used and the
36 results are stored away in a __sort__ key, which is then used to perform the
37 sort (read: shwartzian transform).
38 - Calls processMochiTAL on the page, which finds the mochi-template sections
39 and then looks for mochi:repeat and mochi:content attributes on them, using
40 the data object.
41
42 */
43
44 processMochiTAL = function (dom, data) {
45 /***
46
47 A TAL-esque template attribute language processor,
48 including content replacement and repeat
49
50 ***/
51
52 // nodeType == 1 is an element, we're leaving
53 // text nodes alone.
54 if (dom.nodeType != 1) {
55 return;
56 }
57 var attr;
58 // duplicate this element for each item in the
59 // given list, and then process the duplicated
60 // element again (sans mochi:repeat tag)
61 attr = getAttribute(dom, "mochi:repeat");
62 if (attr) {
63 dom.removeAttribute("mochi:repeat");
64 var parent = dom.parentNode;
65 attr = attr.split(" ");
66 var name = attr[0];
67 var lst = valueForKeyPath(data, attr[1]);
68 if (!lst) {
69 return;
70 }
71 for (var i = 0; i < lst.length; i++) {
72 data[name] = lst[i];
73 var newDOM = dom.cloneNode(true);
74 processMochiTAL(newDOM, data);
75 parent.insertBefore(newDOM, dom);
76 }
77 parent.removeChild(dom);
78 return;
79 }
80 // do content replacement if there's a mochi:content attribute
81 // on the element
82 attr = getAttribute(dom, "mochi:content");
83 if (attr) {
84 dom.removeAttribute("mochi:content");
85 replaceChildNodes(dom, valueForKeyPath(data, attr));
86 return;
87 }
88 // we make a shallow copy of the current list of child nodes
89 // because it *will* change if there's a mochi:repeat in there!
90 var nodes = list(dom.childNodes);
91 for (var i = 0; i < nodes.length; i++) {
92 processMochiTAL(nodes[i], data);
93 }
94 };
95
96 mouseOverFunc = function () {
97 addElementClass(this, "over");
98 };
99
100 mouseOutFunc = function () {
101 removeElementClass(this, "over");
102 };
103
104 ignoreEvent = function (ev) {
105 if (ev && ev.preventDefault) {
106 ev.preventDefault();
107 ev.stopPropagation();
108 } else if (typeof(event) != 'undefined') {
109 event.cancelBubble = false;
110 event.returnValue = false;
111 }
112 };
113
114 SortTransforms = {
115 "str": operator.identity,
116 "istr": function (s) { return s.toLowerCase(); },
117 /* "isoDate": isoDate*/
118 };
119
120 getAttribute = function (dom, key) {
121 try {
122 return dom.getAttribute(key);
123 } catch (e) {
124 return null;
125 }
126 };
127
128 loadFromDataAnchor = function (ev) {
129 ignoreEvent(ev);
130 var format = this.getAttribute("mochi:dataformat");
131 var href = this.href;
132 sortableManager.loadFromURL(format, href);
133 };
134
135 valueForKeyPath = function (data, keyPath) {
136 var chunks = keyPath.split(".");
137 while (chunks.length && data) {
138 data = data[chunks.shift()];
139 }
140 return data;
141 };
142
143
144 SortableManager = function () {
145 this.thead = null;
146 this.thead_proto = null;
147 this.tbody = null;
148 this.deferred = null;
149 this.columns = [];
150 this.rows = [];
151 this.templates = [];
152 this.sortState = {};
153 bindMethods(this);
154 };
155
156 SortableManager.prototype = {
157
158 "initialize": function (prefix, sortkey) {
159 // just rip all mochi-examples out of the DOM
160 var examples = getElementsByTagAndClassName(null, prefix+"-example");
161 while (examples.length) {
162 swapDOM(examples.pop(), null);
163 }
164 // make a template list
165 var templates = getElementsByTagAndClassName(null, prefix+"-template");
166 for (var i = 0; i < templates.length; i++) {
167 var template = templates[i];
168 var proto = template.cloneNode(true);
169 removeElementClass(proto, prefix+"-template");
170 this.templates.push({
171 "template": proto,
172 "node": template
173 });
174 }
175 // set up the data anchors to do loads
176 var anchors = getElementsByTagAndClassName("a", null);
177 for (var i = 0; i < anchors.length; i++) {
178 var node = anchors[i];
179 var format = getAttribute(node, "mochi:dataformat");
180 if (format) {
181 node.onclick = loadFromDataAnchor;
182 }
183 }
184
185 // to find sort columns
186 this.thead = getElementsByTagAndClassName("thead", prefix)[0];
187 this.thead_proto = this.thead.cloneNode(true);
188
189 this.sortkey = sortkey;
190 /* this.loadFromURL("json", "objects.json");*/
191 },
192
193 "loadFromURL": function (format, url) {
194 log('loadFromURL', format, url);
195 var d;
196 if (this.deferred) {
197 this.deferred.cancel();
198 }
199 if (format == "xml") {
200 var d = doXHR(url, {
201 mimeType: 'text/xml',
202 headers: {Accept: 'text/xml'}
203 });
204 d.addCallback(datatableFromXMLRequest);
205 } else if (format == "json") {
206 d = loadJSONDoc(url);
207 } else {
208 throw new TypeError("format " + repr(format) + " not supported");
209 }
210 // keep track of the current deferred, so that we can cancel it
211 this.deferred = d;
212 var self = this;
213 // on success or error, remove the current deferred because it has
214 // completed, and pass through the result or error
215 d.addBoth(function (res) {
216 self.deferred = null;
217 log('loadFromURL success');
218 return res;
219 });
220 // on success, tag the result with the format used so we can display
221 // it
222 d.addCallback(function (res) {
223 res.format = format;
224 return res;
225 });
226 // call this.initWithData(data) once it's ready
227 d.addCallback(this.initWithData);
228 // if anything goes wrong, except for a simple cancellation,
229 // then log the error and show the logger
230 d.addErrback(function (err) {
231 if (err instanceof CancelledError) {
232 return;
233 }
234 logError(err);
235 logger.debuggingBookmarklet();
236 });
237 return d;
238 },
239
240 "initWithData": function (data) {
241 /***
242
243 Initialize the SortableManager with a table object
244
245 ***/
246
247 // reformat to [{column:value, ...}, ...] style as the objects key
248 var objects = [];
249 var rows = data.rows;
250 var cols = data.columns;
251 for (var i = 0; i < rows.length; i++) {
252 var row = rows[i];
253 var object = {};
254 for (var j = 0; j < cols.length; j++) {
255 object[cols[j]] = row[j];
256 }
257 objects.push(object);
258 }
259 data.objects = objects;
260 this.data = data;
261 // perform a sort and display based upon the previous sort state,
262 // defaulting to an ascending sort if this is the first sort
263 var order = this.sortState[this.sortkey];
264 if (typeof(order) == 'undefined') {
265 order = true;
266 }
267 this.drawSortedRows(this.sortkey, order, false);
268
269 },
270
271 "onSortClick": function (name) {
272 /***
273
274 Return a sort function for click events
275
276 ***/
277 // save ourselves from doing a bind
278 var self = this;
279 // on click, flip the last sort order of that column and sort
280 return function () {
281 log('onSortClick', name);
282 var order = self.sortState[name];
283 if (typeof(order) == 'undefined') {
284 // if it's never been sorted by this column, sort ascending
285 order = true;
286 } else if (self.sortkey == name) {
287 // if this column was sorted most recently, flip the sort order
288 order = !((typeof(order) == 'undefined') ? false : order);
289 }
290 self.drawSortedRows(name, order, true);
291 };
292 },
293
294 "drawSortedRows": function (key, forward, clicked) {
295 /***
296
297 Draw the new sorted table body, and modify the column headers
298 if appropriate
299
300 ***/
301 log('drawSortedRows', key, forward);
302
303 // save it so we can flip next time
304 this.sortState[key] = forward;
305 this.sortkey = key;
306 var sortstyle;
307
308 // setup the sort columns
309 var thead = this.thead_proto.cloneNode(true);
310 var cols = thead.getElementsByTagName("th");
311 for (var i = 0; i < cols.length; i++) {
312 var col = cols[i];
313 var sortinfo = getAttribute(col, "mochi:sortcolumn").split(" ");
314 var sortkey = sortinfo[0];
315 col.onclick = this.onSortClick(sortkey);
316 col.onmousedown = ignoreEvent;
317 col.onmouseover = mouseOverFunc;
318 col.onmouseout = mouseOutFunc;
319 // if this is the sorted column
320 if (sortkey == key) {
321 sortstyle = sortinfo[1];
322 // \u2193 is down arrow, \u2191 is up arrow
323 // forward sorts mean the rows get bigger going down
324 var arrow = (forward ? "\u2193" : "\u2191");
325 // add the character to the column header
326 col.appendChild(SPAN(null, arrow));
327 if (clicked) {
328 col.onmouseover();
329 }
330 }
331 }
332 this.thead = swapDOM(this.thead, thead);
333
334 // apply a sort transform to a temporary column named __sort__,
335 // and do the sort based on that column
336 if (!sortstyle) {
337 sortstyle = "str";
338 }
339 var sortfunc = SortTransforms[sortstyle];
340 if (!sortfunc) {
341 throw new TypeError("unsupported sort style " + repr(sortstyle));
342 }
343 var objects = this.data.objects;
344 for (var i = 0; i < objects.length; i++) {
345 var object = objects[i];
346 object.__sort__ = sortfunc(object[key]);
347 }
348
349 // perform the sort based on the state given (forward or reverse)
350 var cmp = (forward ? keyComparator : reverseKeyComparator);
351 objects.sort(cmp("__sort__"));
352
353 // process every template with the given data
354 // and put the processed templates in the DOM
355 for (var i = 0; i < this.templates.length; i++) {
356 log('template', i, template);
357 var template = this.templates[i];
358 var dom = template.template.cloneNode(true);
359 processMochiTAL(dom, this.data);
360 template.node = swapDOM(template.node, dom);
361 }
362 //permission based coloring
363
364
365
366 }
367
368 };
369
370 // create the global SortableManager and initialize it on page load
371 sortableManager = new SortableManager();
372 sortableManager2 = new SortableManager();
373
374 addLoadEvent(function() {sortableManager.initialize("notebook", "dateModified")});
375 addLoadEvent(function() {sortableManager2.initialize("user", "username")});
@@ -0,0 +1,14 b''
1 ===================
2 External Packages
3 ===================
4
5 This directory contains external packages which we ship inside IPython for ease
6 of distributions for users.
7
8 Currently we include:
9
10 - configobj
11 - Itpl
12 - MochiKit
13 - simplegeneric
14 - validate
@@ -0,0 +1,16 b''
1 # encoding: utf-8
2
3 """
4 This package contains all third-party modules bundled with IPython.
5 """
6
7 __docformat__ = "restructuredtext en"
8
9 #-------------------------------------------------------------------------------
10 # Copyright (C) 2008 The IPython Development Team
11 #
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
14 #-------------------------------------------------------------------------------
15
16 __all__ = ['simplegeneric','twisted','Itpl', 'MochiKit']
This diff has been collapsed as it changes many lines, (2501 lines changed) Show them Hide them
@@ -0,0 +1,2501 b''
1 # configobj.py
2 # A config file reader/writer that supports nested sections in config files.
3 # Copyright (C) 2005-2008 Michael Foord, Nicola Larosa
4 # E-mail: fuzzyman AT voidspace DOT org DOT uk
5 # nico AT tekNico DOT net
6
7 # ConfigObj 4
8 # http://www.voidspace.org.uk/python/configobj.html
9
10 # Released subject to the BSD License
11 # Please see http://www.voidspace.org.uk/python/license.shtml
12
13 # Scripts maintained at http://www.voidspace.org.uk/python/index.shtml
14 # For information about bugfixes, updates and support, please join the
15 # ConfigObj mailing list:
16 # http://lists.sourceforge.net/lists/listinfo/configobj-develop
17 # Comments, suggestions and bug reports welcome.
18
19 from __future__ import generators
20
21 import sys
22 INTP_VER = sys.version_info[:2]
23 if INTP_VER < (2, 2):
24 raise RuntimeError("Python v.2.2 or later needed")
25
26 import os, re
27 compiler = None
28 try:
29 import compiler
30 except ImportError:
31 # for IronPython
32 pass
33 from types import StringTypes
34 from warnings import warn
35 try:
36 from codecs import BOM_UTF8, BOM_UTF16, BOM_UTF16_BE, BOM_UTF16_LE
37 except ImportError:
38 # Python 2.2 does not have these
39 # UTF-8
40 BOM_UTF8 = '\xef\xbb\xbf'
41 # UTF-16, little endian
42 BOM_UTF16_LE = '\xff\xfe'
43 # UTF-16, big endian
44 BOM_UTF16_BE = '\xfe\xff'
45 if sys.byteorder == 'little':
46 # UTF-16, native endianness
47 BOM_UTF16 = BOM_UTF16_LE
48 else:
49 # UTF-16, native endianness
50 BOM_UTF16 = BOM_UTF16_BE
51
52 # A dictionary mapping BOM to
53 # the encoding to decode with, and what to set the
54 # encoding attribute to.
55 BOMS = {
56 BOM_UTF8: ('utf_8', None),
57 BOM_UTF16_BE: ('utf16_be', 'utf_16'),
58 BOM_UTF16_LE: ('utf16_le', 'utf_16'),
59 BOM_UTF16: ('utf_16', 'utf_16'),
60 }
61 # All legal variants of the BOM codecs.
62 # TODO: the list of aliases is not meant to be exhaustive, is there a
63 # better way ?
64 BOM_LIST = {
65 'utf_16': 'utf_16',
66 'u16': 'utf_16',
67 'utf16': 'utf_16',
68 'utf-16': 'utf_16',
69 'utf16_be': 'utf16_be',
70 'utf_16_be': 'utf16_be',
71 'utf-16be': 'utf16_be',
72 'utf16_le': 'utf16_le',
73 'utf_16_le': 'utf16_le',
74 'utf-16le': 'utf16_le',
75 'utf_8': 'utf_8',
76 'u8': 'utf_8',
77 'utf': 'utf_8',
78 'utf8': 'utf_8',
79 'utf-8': 'utf_8',
80 }
81
82 # Map of encodings to the BOM to write.
83 BOM_SET = {
84 'utf_8': BOM_UTF8,
85 'utf_16': BOM_UTF16,
86 'utf16_be': BOM_UTF16_BE,
87 'utf16_le': BOM_UTF16_LE,
88 None: BOM_UTF8
89 }
90
91
92 def match_utf8(encoding):
93 return BOM_LIST.get(encoding.lower()) == 'utf_8'
94
95
96 # Quote strings used for writing values
97 squot = "'%s'"
98 dquot = '"%s"'
99 noquot = "%s"
100 wspace_plus = ' \r\t\n\v\t\'"'
101 tsquot = '"""%s"""'
102 tdquot = "'''%s'''"
103
104 try:
105 enumerate
106 except NameError:
107 def enumerate(obj):
108 """enumerate for Python 2.2."""
109 i = -1
110 for item in obj:
111 i += 1
112 yield i, item
113
114 try:
115 True, False
116 except NameError:
117 True, False = 1, 0
118
119
120 __version__ = '4.5.2'
121
122 __revision__ = '$Id: configobj.py 156 2006-01-31 14:57:08Z fuzzyman $'
123
124 __docformat__ = "restructuredtext en"
125
126 __all__ = (
127 '__version__',
128 'DEFAULT_INDENT_TYPE',
129 'DEFAULT_INTERPOLATION',
130 'ConfigObjError',
131 'NestingError',
132 'ParseError',
133 'DuplicateError',
134 'ConfigspecError',
135 'ConfigObj',
136 'SimpleVal',
137 'InterpolationError',
138 'InterpolationLoopError',
139 'MissingInterpolationOption',
140 'RepeatSectionError',
141 'ReloadError',
142 'UnreprError',
143 'UnknownType',
144 '__docformat__',
145 'flatten_errors',
146 )
147
148 DEFAULT_INTERPOLATION = 'configparser'
149 DEFAULT_INDENT_TYPE = ' '
150 MAX_INTERPOL_DEPTH = 10
151
152 OPTION_DEFAULTS = {
153 'interpolation': True,
154 'raise_errors': False,
155 'list_values': True,
156 'create_empty': False,
157 'file_error': False,
158 'configspec': None,
159 'stringify': True,
160 # option may be set to one of ('', ' ', '\t')
161 'indent_type': None,
162 'encoding': None,
163 'default_encoding': None,
164 'unrepr': False,
165 'write_empty_values': False,
166 }
167
168
169
170 def getObj(s):
171 s = "a=" + s
172 if compiler is None:
173 raise ImportError('compiler module not available')
174 p = compiler.parse(s)
175 return p.getChildren()[1].getChildren()[0].getChildren()[1]
176
177
178 class UnknownType(Exception):
179 pass
180
181
182 class Builder(object):
183
184 def build(self, o):
185 m = getattr(self, 'build_' + o.__class__.__name__, None)
186 if m is None:
187 raise UnknownType(o.__class__.__name__)
188 return m(o)
189
190 def build_List(self, o):
191 return map(self.build, o.getChildren())
192
193 def build_Const(self, o):
194 return o.value
195
196 def build_Dict(self, o):
197 d = {}
198 i = iter(map(self.build, o.getChildren()))
199 for el in i:
200 d[el] = i.next()
201 return d
202
203 def build_Tuple(self, o):
204 return tuple(self.build_List(o))
205
206 def build_Name(self, o):
207 if o.name == 'None':
208 return None
209 if o.name == 'True':
210 return True
211 if o.name == 'False':
212 return False
213
214 # An undefined Name
215 raise UnknownType('Undefined Name')
216
217 def build_Add(self, o):
218 real, imag = map(self.build_Const, o.getChildren())
219 try:
220 real = float(real)
221 except TypeError:
222 raise UnknownType('Add')
223 if not isinstance(imag, complex) or imag.real != 0.0:
224 raise UnknownType('Add')
225 return real+imag
226
227 def build_Getattr(self, o):
228 parent = self.build(o.expr)
229 return getattr(parent, o.attrname)
230
231 def build_UnarySub(self, o):
232 return -self.build_Const(o.getChildren()[0])
233
234 def build_UnaryAdd(self, o):
235 return self.build_Const(o.getChildren()[0])
236
237
238 _builder = Builder()
239
240
241 def unrepr(s):
242 if not s:
243 return s
244 return _builder.build(getObj(s))
245
246
247
248 class ConfigObjError(SyntaxError):
249 """
250 This is the base class for all errors that ConfigObj raises.
251 It is a subclass of SyntaxError.
252 """
253 def __init__(self, message='', line_number=None, line=''):
254 self.line = line
255 self.line_number = line_number
256 self.message = message
257 SyntaxError.__init__(self, message)
258
259
260 class NestingError(ConfigObjError):
261 """
262 This error indicates a level of nesting that doesn't match.
263 """
264
265
266 class ParseError(ConfigObjError):
267 """
268 This error indicates that a line is badly written.
269 It is neither a valid ``key = value`` line,
270 nor a valid section marker line.
271 """
272
273
274 class ReloadError(IOError):
275 """
276 A 'reload' operation failed.
277 This exception is a subclass of ``IOError``.
278 """
279 def __init__(self):
280 IOError.__init__(self, 'reload failed, filename is not set.')
281
282
283 class DuplicateError(ConfigObjError):
284 """
285 The keyword or section specified already exists.
286 """
287
288
289 class ConfigspecError(ConfigObjError):
290 """
291 An error occured whilst parsing a configspec.
292 """
293
294
295 class InterpolationError(ConfigObjError):
296 """Base class for the two interpolation errors."""
297
298
299 class InterpolationLoopError(InterpolationError):
300 """Maximum interpolation depth exceeded in string interpolation."""
301
302 def __init__(self, option):
303 InterpolationError.__init__(
304 self,
305 'interpolation loop detected in value "%s".' % option)
306
307
308 class RepeatSectionError(ConfigObjError):
309 """
310 This error indicates additional sections in a section with a
311 ``__many__`` (repeated) section.
312 """
313
314
315 class MissingInterpolationOption(InterpolationError):
316 """A value specified for interpolation was missing."""
317
318 def __init__(self, option):
319 InterpolationError.__init__(
320 self,
321 'missing option "%s" in interpolation.' % option)
322
323
324 class UnreprError(ConfigObjError):
325 """An error parsing in unrepr mode."""
326
327
328
329 class InterpolationEngine(object):
330 """
331 A helper class to help perform string interpolation.
332
333 This class is an abstract base class; its descendants perform
334 the actual work.
335 """
336
337 # compiled regexp to use in self.interpolate()
338 _KEYCRE = re.compile(r"%\(([^)]*)\)s")
339
340 def __init__(self, section):
341 # the Section instance that "owns" this engine
342 self.section = section
343
344
345 def interpolate(self, key, value):
346 def recursive_interpolate(key, value, section, backtrail):
347 """The function that does the actual work.
348
349 ``value``: the string we're trying to interpolate.
350 ``section``: the section in which that string was found
351 ``backtrail``: a dict to keep track of where we've been,
352 to detect and prevent infinite recursion loops
353
354 This is similar to a depth-first-search algorithm.
355 """
356 # Have we been here already?
357 if backtrail.has_key((key, section.name)):
358 # Yes - infinite loop detected
359 raise InterpolationLoopError(key)
360 # Place a marker on our backtrail so we won't come back here again
361 backtrail[(key, section.name)] = 1
362
363 # Now start the actual work
364 match = self._KEYCRE.search(value)
365 while match:
366 # The actual parsing of the match is implementation-dependent,
367 # so delegate to our helper function
368 k, v, s = self._parse_match(match)
369 if k is None:
370 # That's the signal that no further interpolation is needed
371 replacement = v
372 else:
373 # Further interpolation may be needed to obtain final value
374 replacement = recursive_interpolate(k, v, s, backtrail)
375 # Replace the matched string with its final value
376 start, end = match.span()
377 value = ''.join((value[:start], replacement, value[end:]))
378 new_search_start = start + len(replacement)
379 # Pick up the next interpolation key, if any, for next time
380 # through the while loop
381 match = self._KEYCRE.search(value, new_search_start)
382
383 # Now safe to come back here again; remove marker from backtrail
384 del backtrail[(key, section.name)]
385
386 return value
387
388 # Back in interpolate(), all we have to do is kick off the recursive
389 # function with appropriate starting values
390 value = recursive_interpolate(key, value, self.section, {})
391 return value
392
393
394 def _fetch(self, key):
395 """Helper function to fetch values from owning section.
396
397 Returns a 2-tuple: the value, and the section where it was found.
398 """
399 # switch off interpolation before we try and fetch anything !
400 save_interp = self.section.main.interpolation
401 self.section.main.interpolation = False
402
403 # Start at section that "owns" this InterpolationEngine
404 current_section = self.section
405 while True:
406 # try the current section first
407 val = current_section.get(key)
408 if val is not None:
409 break
410 # try "DEFAULT" next
411 val = current_section.get('DEFAULT', {}).get(key)
412 if val is not None:
413 break
414 # move up to parent and try again
415 # top-level's parent is itself
416 if current_section.parent is current_section:
417 # reached top level, time to give up
418 break
419 current_section = current_section.parent
420
421 # restore interpolation to previous value before returning
422 self.section.main.interpolation = save_interp
423 if val is None:
424 raise MissingInterpolationOption(key)
425 return val, current_section
426
427
428 def _parse_match(self, match):
429 """Implementation-dependent helper function.
430
431 Will be passed a match object corresponding to the interpolation
432 key we just found (e.g., "%(foo)s" or "$foo"). Should look up that
433 key in the appropriate config file section (using the ``_fetch()``
434 helper function) and return a 3-tuple: (key, value, section)
435
436 ``key`` is the name of the key we're looking for
437 ``value`` is the value found for that key
438 ``section`` is a reference to the section where it was found
439
440 ``key`` and ``section`` should be None if no further
441 interpolation should be performed on the resulting value
442 (e.g., if we interpolated "$$" and returned "$").
443 """
444 raise NotImplementedError()
445
446
447
448 class ConfigParserInterpolation(InterpolationEngine):
449 """Behaves like ConfigParser."""
450 _KEYCRE = re.compile(r"%\(([^)]*)\)s")
451
452 def _parse_match(self, match):
453 key = match.group(1)
454 value, section = self._fetch(key)
455 return key, value, section
456
457
458
459 class TemplateInterpolation(InterpolationEngine):
460 """Behaves like string.Template."""
461 _delimiter = '$'
462 _KEYCRE = re.compile(r"""
463 \$(?:
464 (?P<escaped>\$) | # Two $ signs
465 (?P<named>[_a-z][_a-z0-9]*) | # $name format
466 {(?P<braced>[^}]*)} # ${name} format
467 )
468 """, re.IGNORECASE | re.VERBOSE)
469
470 def _parse_match(self, match):
471 # Valid name (in or out of braces): fetch value from section
472 key = match.group('named') or match.group('braced')
473 if key is not None:
474 value, section = self._fetch(key)
475 return key, value, section
476 # Escaped delimiter (e.g., $$): return single delimiter
477 if match.group('escaped') is not None:
478 # Return None for key and section to indicate it's time to stop
479 return None, self._delimiter, None
480 # Anything else: ignore completely, just return it unchanged
481 return None, match.group(), None
482
483
484 interpolation_engines = {
485 'configparser': ConfigParserInterpolation,
486 'template': TemplateInterpolation,
487 }
488
489
490
491 class Section(dict):
492 """
493 A dictionary-like object that represents a section in a config file.
494
495 It does string interpolation if the 'interpolation' attribute
496 of the 'main' object is set to True.
497
498 Interpolation is tried first from this object, then from the 'DEFAULT'
499 section of this object, next from the parent and its 'DEFAULT' section,
500 and so on until the main object is reached.
501
502 A Section will behave like an ordered dictionary - following the
503 order of the ``scalars`` and ``sections`` attributes.
504 You can use this to change the order of members.
505
506 Iteration follows the order: scalars, then sections.
507 """
508
509 def __init__(self, parent, depth, main, indict=None, name=None):
510 """
511 * parent is the section above
512 * depth is the depth level of this section
513 * main is the main ConfigObj
514 * indict is a dictionary to initialise the section with
515 """
516 if indict is None:
517 indict = {}
518 dict.__init__(self)
519 # used for nesting level *and* interpolation
520 self.parent = parent
521 # used for the interpolation attribute
522 self.main = main
523 # level of nesting depth of this Section
524 self.depth = depth
525 # purely for information
526 self.name = name
527 #
528 self._initialise()
529 # we do this explicitly so that __setitem__ is used properly
530 # (rather than just passing to ``dict.__init__``)
531 for entry, value in indict.iteritems():
532 self[entry] = value
533
534
535 def _initialise(self):
536 # the sequence of scalar values in this Section
537 self.scalars = []
538 # the sequence of sections in this Section
539 self.sections = []
540 # for comments :-)
541 self.comments = {}
542 self.inline_comments = {}
543 # for the configspec
544 self.configspec = {}
545 self._order = []
546 self._configspec_comments = {}
547 self._configspec_inline_comments = {}
548 self._cs_section_comments = {}
549 self._cs_section_inline_comments = {}
550 # for defaults
551 self.defaults = []
552 self.default_values = {}
553
554
555 def _interpolate(self, key, value):
556 try:
557 # do we already have an interpolation engine?
558 engine = self._interpolation_engine
559 except AttributeError:
560 # not yet: first time running _interpolate(), so pick the engine
561 name = self.main.interpolation
562 if name == True: # note that "if name:" would be incorrect here
563 # backwards-compatibility: interpolation=True means use default
564 name = DEFAULT_INTERPOLATION
565 name = name.lower() # so that "Template", "template", etc. all work
566 class_ = interpolation_engines.get(name, None)
567 if class_ is None:
568 # invalid value for self.main.interpolation
569 self.main.interpolation = False
570 return value
571 else:
572 # save reference to engine so we don't have to do this again
573 engine = self._interpolation_engine = class_(self)
574 # let the engine do the actual work
575 return engine.interpolate(key, value)
576
577
578 def __getitem__(self, key):
579 """Fetch the item and do string interpolation."""
580 val = dict.__getitem__(self, key)
581 if self.main.interpolation and isinstance(val, StringTypes):
582 return self._interpolate(key, val)
583 return val
584
585
586 def __setitem__(self, key, value, unrepr=False):
587 """
588 Correctly set a value.
589
590 Making dictionary values Section instances.
591 (We have to special case 'Section' instances - which are also dicts)
592
593 Keys must be strings.
594 Values need only be strings (or lists of strings) if
595 ``main.stringify`` is set.
596
597 `unrepr`` must be set when setting a value to a dictionary, without
598 creating a new sub-section.
599 """
600 if not isinstance(key, StringTypes):
601 raise ValueError('The key "%s" is not a string.' % key)
602
603 # add the comment
604 if not self.comments.has_key(key):
605 self.comments[key] = []
606 self.inline_comments[key] = ''
607 # remove the entry from defaults
608 if key in self.defaults:
609 self.defaults.remove(key)
610 #
611 if isinstance(value, Section):
612 if not self.has_key(key):
613 self.sections.append(key)
614 dict.__setitem__(self, key, value)
615 elif isinstance(value, dict) and not unrepr:
616 # First create the new depth level,
617 # then create the section
618 if not self.has_key(key):
619 self.sections.append(key)
620 new_depth = self.depth + 1
621 dict.__setitem__(
622 self,
623 key,
624 Section(
625 self,
626 new_depth,
627 self.main,
628 indict=value,
629 name=key))
630 else:
631 if not self.has_key(key):
632 self.scalars.append(key)
633 if not self.main.stringify:
634 if isinstance(value, StringTypes):
635 pass
636 elif isinstance(value, (list, tuple)):
637 for entry in value:
638 if not isinstance(entry, StringTypes):
639 raise TypeError('Value is not a string "%s".' % entry)
640 else:
641 raise TypeError('Value is not a string "%s".' % value)
642 dict.__setitem__(self, key, value)
643
644
645 def __delitem__(self, key):
646 """Remove items from the sequence when deleting."""
647 dict. __delitem__(self, key)
648 if key in self.scalars:
649 self.scalars.remove(key)
650 else:
651 self.sections.remove(key)
652 del self.comments[key]
653 del self.inline_comments[key]
654
655
656 def get(self, key, default=None):
657 """A version of ``get`` that doesn't bypass string interpolation."""
658 try:
659 return self[key]
660 except KeyError:
661 return default
662
663
664 def update(self, indict):
665 """
666 A version of update that uses our ``__setitem__``.
667 """
668 for entry in indict:
669 self[entry] = indict[entry]
670
671
672 def pop(self, key, *args):
673 """
674 'D.pop(k[,d]) -> v, remove specified key and return the corresponding value.
675 If key is not found, d is returned if given, otherwise KeyError is raised'
676 """
677 val = dict.pop(self, key, *args)
678 if key in self.scalars:
679 del self.comments[key]
680 del self.inline_comments[key]
681 self.scalars.remove(key)
682 elif key in self.sections:
683 del self.comments[key]
684 del self.inline_comments[key]
685 self.sections.remove(key)
686 if self.main.interpolation and isinstance(val, StringTypes):
687 return self._interpolate(key, val)
688 return val
689
690
691 def popitem(self):
692 """Pops the first (key,val)"""
693 sequence = (self.scalars + self.sections)
694 if not sequence:
695 raise KeyError(": 'popitem(): dictionary is empty'")
696 key = sequence[0]
697 val = self[key]
698 del self[key]
699 return key, val
700
701
702 def clear(self):
703 """
704 A version of clear that also affects scalars/sections
705 Also clears comments and configspec.
706
707 Leaves other attributes alone :
708 depth/main/parent are not affected
709 """
710 dict.clear(self)
711 self.scalars = []
712 self.sections = []
713 self.comments = {}
714 self.inline_comments = {}
715 self.configspec = {}
716
717
718 def setdefault(self, key, default=None):
719 """A version of setdefault that sets sequence if appropriate."""
720 try:
721 return self[key]
722 except KeyError:
723 self[key] = default
724 return self[key]
725
726
727 def items(self):
728 """D.items() -> list of D's (key, value) pairs, as 2-tuples"""
729 return zip((self.scalars + self.sections), self.values())
730
731
732 def keys(self):
733 """D.keys() -> list of D's keys"""
734 return (self.scalars + self.sections)
735
736
737 def values(self):
738 """D.values() -> list of D's values"""
739 return [self[key] for key in (self.scalars + self.sections)]
740
741
742 def iteritems(self):
743 """D.iteritems() -> an iterator over the (key, value) items of D"""
744 return iter(self.items())
745
746
747 def iterkeys(self):
748 """D.iterkeys() -> an iterator over the keys of D"""
749 return iter((self.scalars + self.sections))
750
751 __iter__ = iterkeys
752
753
754 def itervalues(self):
755 """D.itervalues() -> an iterator over the values of D"""
756 return iter(self.values())
757
758
759 def __repr__(self):
760 """x.__repr__() <==> repr(x)"""
761 return '{%s}' % ', '.join([('%s: %s' % (repr(key), repr(self[key])))
762 for key in (self.scalars + self.sections)])
763
764 __str__ = __repr__
765 __str__.__doc__ = "x.__str__() <==> str(x)"
766
767
768 # Extra methods - not in a normal dictionary
769
770 def dict(self):
771 """
772 Return a deepcopy of self as a dictionary.
773
774 All members that are ``Section`` instances are recursively turned to
775 ordinary dictionaries - by calling their ``dict`` method.
776
777 >>> n = a.dict()
778 >>> n == a
779 1
780 >>> n is a
781 0
782 """
783 newdict = {}
784 for entry in self:
785 this_entry = self[entry]
786 if isinstance(this_entry, Section):
787 this_entry = this_entry.dict()
788 elif isinstance(this_entry, list):
789 # create a copy rather than a reference
790 this_entry = list(this_entry)
791 elif isinstance(this_entry, tuple):
792 # create a copy rather than a reference
793 this_entry = tuple(this_entry)
794 newdict[entry] = this_entry
795 return newdict
796
797
798 def merge(self, indict):
799 """
800 A recursive update - useful for merging config files.
801
802 >>> a = '''[section1]
803 ... option1 = True
804 ... [[subsection]]
805 ... more_options = False
806 ... # end of file'''.splitlines()
807 >>> b = '''# File is user.ini
808 ... [section1]
809 ... option1 = False
810 ... # end of file'''.splitlines()
811 >>> c1 = ConfigObj(b)
812 >>> c2 = ConfigObj(a)
813 >>> c2.merge(c1)
814 >>> c2
815 {'section1': {'option1': 'False', 'subsection': {'more_options': 'False'}}}
816 """
817 for key, val in indict.items():
818 if (key in self and isinstance(self[key], dict) and
819 isinstance(val, dict)):
820 self[key].merge(val)
821 else:
822 self[key] = val
823
824
825 def rename(self, oldkey, newkey):
826 """
827 Change a keyname to another, without changing position in sequence.
828
829 Implemented so that transformations can be made on keys,
830 as well as on values. (used by encode and decode)
831
832 Also renames comments.
833 """
834 if oldkey in self.scalars:
835 the_list = self.scalars
836 elif oldkey in self.sections:
837 the_list = self.sections
838 else:
839 raise KeyError('Key "%s" not found.' % oldkey)
840 pos = the_list.index(oldkey)
841 #
842 val = self[oldkey]
843 dict.__delitem__(self, oldkey)
844 dict.__setitem__(self, newkey, val)
845 the_list.remove(oldkey)
846 the_list.insert(pos, newkey)
847 comm = self.comments[oldkey]
848 inline_comment = self.inline_comments[oldkey]
849 del self.comments[oldkey]
850 del self.inline_comments[oldkey]
851 self.comments[newkey] = comm
852 self.inline_comments[newkey] = inline_comment
853
854
855 def walk(self, function, raise_errors=True,
856 call_on_sections=False, **keywargs):
857 """
858 Walk every member and call a function on the keyword and value.
859
860 Return a dictionary of the return values
861
862 If the function raises an exception, raise the errror
863 unless ``raise_errors=False``, in which case set the return value to
864 ``False``.
865
866 Any unrecognised keyword arguments you pass to walk, will be pased on
867 to the function you pass in.
868
869 Note: if ``call_on_sections`` is ``True`` then - on encountering a
870 subsection, *first* the function is called for the *whole* subsection,
871 and then recurses into it's members. This means your function must be
872 able to handle strings, dictionaries and lists. This allows you
873 to change the key of subsections as well as for ordinary members. The
874 return value when called on the whole subsection has to be discarded.
875
876 See the encode and decode methods for examples, including functions.
877
878 .. caution::
879
880 You can use ``walk`` to transform the names of members of a section
881 but you mustn't add or delete members.
882
883 >>> config = '''[XXXXsection]
884 ... XXXXkey = XXXXvalue'''.splitlines()
885 >>> cfg = ConfigObj(config)
886 >>> cfg
887 {'XXXXsection': {'XXXXkey': 'XXXXvalue'}}
888 >>> def transform(section, key):
889 ... val = section[key]
890 ... newkey = key.replace('XXXX', 'CLIENT1')
891 ... section.rename(key, newkey)
892 ... if isinstance(val, (tuple, list, dict)):
893 ... pass
894 ... else:
895 ... val = val.replace('XXXX', 'CLIENT1')
896 ... section[newkey] = val
897 >>> cfg.walk(transform, call_on_sections=True)
898 {'CLIENT1section': {'CLIENT1key': None}}
899 >>> cfg
900 {'CLIENT1section': {'CLIENT1key': 'CLIENT1value'}}
901 """
902 out = {}
903 # scalars first
904 for i in range(len(self.scalars)):
905 entry = self.scalars[i]
906 try:
907 val = function(self, entry, **keywargs)
908 # bound again in case name has changed
909 entry = self.scalars[i]
910 out[entry] = val
911 except Exception:
912 if raise_errors:
913 raise
914 else:
915 entry = self.scalars[i]
916 out[entry] = False
917 # then sections
918 for i in range(len(self.sections)):
919 entry = self.sections[i]
920 if call_on_sections:
921 try:
922 function(self, entry, **keywargs)
923 except Exception:
924 if raise_errors:
925 raise
926 else:
927 entry = self.sections[i]
928 out[entry] = False
929 # bound again in case name has changed
930 entry = self.sections[i]
931 # previous result is discarded
932 out[entry] = self[entry].walk(
933 function,
934 raise_errors=raise_errors,
935 call_on_sections=call_on_sections,
936 **keywargs)
937 return out
938
939
940 def decode(self, encoding):
941 """
942 Decode all strings and values to unicode, using the specified encoding.
943
944 Works with subsections and list values.
945
946 Uses the ``walk`` method.
947
948 Testing ``encode`` and ``decode``.
949 >>> m = ConfigObj(a)
950 >>> m.decode('ascii')
951 >>> def testuni(val):
952 ... for entry in val:
953 ... if not isinstance(entry, unicode):
954 ... print >> sys.stderr, type(entry)
955 ... raise AssertionError, 'decode failed.'
956 ... if isinstance(val[entry], dict):
957 ... testuni(val[entry])
958 ... elif not isinstance(val[entry], unicode):
959 ... raise AssertionError, 'decode failed.'
960 >>> testuni(m)
961 >>> m.encode('ascii')
962 >>> a == m
963 1
964 """
965 warn('use of ``decode`` is deprecated.', DeprecationWarning)
966 def decode(section, key, encoding=encoding, warn=True):
967 """ """
968 val = section[key]
969 if isinstance(val, (list, tuple)):
970 newval = []
971 for entry in val:
972 newval.append(entry.decode(encoding))
973 elif isinstance(val, dict):
974 newval = val
975 else:
976 newval = val.decode(encoding)
977 newkey = key.decode(encoding)
978 section.rename(key, newkey)
979 section[newkey] = newval
980 # using ``call_on_sections`` allows us to modify section names
981 self.walk(decode, call_on_sections=True)
982
983
984 def encode(self, encoding):
985 """
986 Encode all strings and values from unicode,
987 using the specified encoding.
988
989 Works with subsections and list values.
990 Uses the ``walk`` method.
991 """
992 warn('use of ``encode`` is deprecated.', DeprecationWarning)
993 def encode(section, key, encoding=encoding):
994 """ """
995 val = section[key]
996 if isinstance(val, (list, tuple)):
997 newval = []
998 for entry in val:
999 newval.append(entry.encode(encoding))
1000 elif isinstance(val, dict):
1001 newval = val
1002 else:
1003 newval = val.encode(encoding)
1004 newkey = key.encode(encoding)
1005 section.rename(key, newkey)
1006 section[newkey] = newval
1007 self.walk(encode, call_on_sections=True)
1008
1009
1010 def istrue(self, key):
1011 """A deprecated version of ``as_bool``."""
1012 warn('use of ``istrue`` is deprecated. Use ``as_bool`` method '
1013 'instead.', DeprecationWarning)
1014 return self.as_bool(key)
1015
1016
1017 def as_bool(self, key):
1018 """
1019 Accepts a key as input. The corresponding value must be a string or
1020 the objects (``True`` or 1) or (``False`` or 0). We allow 0 and 1 to
1021 retain compatibility with Python 2.2.
1022
1023 If the string is one of ``True``, ``On``, ``Yes``, or ``1`` it returns
1024 ``True``.
1025
1026 If the string is one of ``False``, ``Off``, ``No``, or ``0`` it returns
1027 ``False``.
1028
1029 ``as_bool`` is not case sensitive.
1030
1031 Any other input will raise a ``ValueError``.
1032
1033 >>> a = ConfigObj()
1034 >>> a['a'] = 'fish'
1035 >>> a.as_bool('a')
1036 Traceback (most recent call last):
1037 ValueError: Value "fish" is neither True nor False
1038 >>> a['b'] = 'True'
1039 >>> a.as_bool('b')
1040 1
1041 >>> a['b'] = 'off'
1042 >>> a.as_bool('b')
1043 0
1044 """
1045 val = self[key]
1046 if val == True:
1047 return True
1048 elif val == False:
1049 return False
1050 else:
1051 try:
1052 if not isinstance(val, StringTypes):
1053 # TODO: Why do we raise a KeyError here?
1054 raise KeyError()
1055 else:
1056 return self.main._bools[val.lower()]
1057 except KeyError:
1058 raise ValueError('Value "%s" is neither True nor False' % val)
1059
1060
1061 def as_int(self, key):
1062 """
1063 A convenience method which coerces the specified value to an integer.
1064
1065 If the value is an invalid literal for ``int``, a ``ValueError`` will
1066 be raised.
1067
1068 >>> a = ConfigObj()
1069 >>> a['a'] = 'fish'
1070 >>> a.as_int('a')
1071 Traceback (most recent call last):
1072 ValueError: invalid literal for int(): fish
1073 >>> a['b'] = '1'
1074 >>> a.as_int('b')
1075 1
1076 >>> a['b'] = '3.2'
1077 >>> a.as_int('b')
1078 Traceback (most recent call last):
1079 ValueError: invalid literal for int(): 3.2
1080 """
1081 return int(self[key])
1082
1083
1084 def as_float(self, key):
1085 """
1086 A convenience method which coerces the specified value to a float.
1087
1088 If the value is an invalid literal for ``float``, a ``ValueError`` will
1089 be raised.
1090
1091 >>> a = ConfigObj()
1092 >>> a['a'] = 'fish'
1093 >>> a.as_float('a')
1094 Traceback (most recent call last):
1095 ValueError: invalid literal for float(): fish
1096 >>> a['b'] = '1'
1097 >>> a.as_float('b')
1098 1.0
1099 >>> a['b'] = '3.2'
1100 >>> a.as_float('b')
1101 3.2000000000000002
1102 """
1103 return float(self[key])
1104
1105
1106 def restore_default(self, key):
1107 """
1108 Restore (and return) default value for the specified key.
1109
1110 This method will only work for a ConfigObj that was created
1111 with a configspec and has been validated.
1112
1113 If there is no default value for this key, ``KeyError`` is raised.
1114 """
1115 default = self.default_values[key]
1116 dict.__setitem__(self, key, default)
1117 if key not in self.defaults:
1118 self.defaults.append(key)
1119 return default
1120
1121
1122 def restore_defaults(self):
1123 """
1124 Recursively restore default values to all members
1125 that have them.
1126
1127 This method will only work for a ConfigObj that was created
1128 with a configspec and has been validated.
1129
1130 It doesn't delete or modify entries without default values.
1131 """
1132 for key in self.default_values:
1133 self.restore_default(key)
1134
1135 for section in self.sections:
1136 self[section].restore_defaults()
1137
1138
1139 class ConfigObj(Section):
1140 """An object to read, create, and write config files."""
1141
1142 _keyword = re.compile(r'''^ # line start
1143 (\s*) # indentation
1144 ( # keyword
1145 (?:".*?")| # double quotes
1146 (?:'.*?')| # single quotes
1147 (?:[^'"=].*?) # no quotes
1148 )
1149 \s*=\s* # divider
1150 (.*) # value (including list values and comments)
1151 $ # line end
1152 ''',
1153 re.VERBOSE)
1154
1155 _sectionmarker = re.compile(r'''^
1156 (\s*) # 1: indentation
1157 ((?:\[\s*)+) # 2: section marker open
1158 ( # 3: section name open
1159 (?:"\s*\S.*?\s*")| # at least one non-space with double quotes
1160 (?:'\s*\S.*?\s*')| # at least one non-space with single quotes
1161 (?:[^'"\s].*?) # at least one non-space unquoted
1162 ) # section name close
1163 ((?:\s*\])+) # 4: section marker close
1164 \s*(\#.*)? # 5: optional comment
1165 $''',
1166 re.VERBOSE)
1167
1168 # this regexp pulls list values out as a single string
1169 # or single values and comments
1170 # FIXME: this regex adds a '' to the end of comma terminated lists
1171 # workaround in ``_handle_value``
1172 _valueexp = re.compile(r'''^
1173 (?:
1174 (?:
1175 (
1176 (?:
1177 (?:
1178 (?:".*?")| # double quotes
1179 (?:'.*?')| # single quotes
1180 (?:[^'",\#][^,\#]*?) # unquoted
1181 )
1182 \s*,\s* # comma
1183 )* # match all list items ending in a comma (if any)
1184 )
1185 (
1186 (?:".*?")| # double quotes
1187 (?:'.*?')| # single quotes
1188 (?:[^'",\#\s][^,]*?)| # unquoted
1189 (?:(?<!,)) # Empty value
1190 )? # last item in a list - or string value
1191 )|
1192 (,) # alternatively a single comma - empty list
1193 )
1194 \s*(\#.*)? # optional comment
1195 $''',
1196 re.VERBOSE)
1197
1198 # use findall to get the members of a list value
1199 _listvalueexp = re.compile(r'''
1200 (
1201 (?:".*?")| # double quotes
1202 (?:'.*?')| # single quotes
1203 (?:[^'",\#].*?) # unquoted
1204 )
1205 \s*,\s* # comma
1206 ''',
1207 re.VERBOSE)
1208
1209 # this regexp is used for the value
1210 # when lists are switched off
1211 _nolistvalue = re.compile(r'''^
1212 (
1213 (?:".*?")| # double quotes
1214 (?:'.*?')| # single quotes
1215 (?:[^'"\#].*?)| # unquoted
1216 (?:) # Empty value
1217 )
1218 \s*(\#.*)? # optional comment
1219 $''',
1220 re.VERBOSE)
1221
1222 # regexes for finding triple quoted values on one line
1223 _single_line_single = re.compile(r"^'''(.*?)'''\s*(#.*)?$")
1224 _single_line_double = re.compile(r'^"""(.*?)"""\s*(#.*)?$')
1225 _multi_line_single = re.compile(r"^(.*?)'''\s*(#.*)?$")
1226 _multi_line_double = re.compile(r'^(.*?)"""\s*(#.*)?$')
1227
1228 _triple_quote = {
1229 "'''": (_single_line_single, _multi_line_single),
1230 '"""': (_single_line_double, _multi_line_double),
1231 }
1232
1233 # Used by the ``istrue`` Section method
1234 _bools = {
1235 'yes': True, 'no': False,
1236 'on': True, 'off': False,
1237 '1': True, '0': False,
1238 'true': True, 'false': False,
1239 }
1240
1241
1242 def __init__(self, infile=None, options=None, **kwargs):
1243 """
1244 Parse a config file or create a config file object.
1245
1246 ``ConfigObj(infile=None, options=None, **kwargs)``
1247 """
1248 # init the superclass
1249 Section.__init__(self, self, 0, self)
1250
1251 if infile is None:
1252 infile = []
1253 if options is None:
1254 options = {}
1255 else:
1256 options = dict(options)
1257
1258 # keyword arguments take precedence over an options dictionary
1259 options.update(kwargs)
1260
1261 defaults = OPTION_DEFAULTS.copy()
1262 # TODO: check the values too.
1263 for entry in options:
1264 if entry not in defaults:
1265 raise TypeError('Unrecognised option "%s".' % entry)
1266
1267 # Add any explicit options to the defaults
1268 defaults.update(options)
1269 self._initialise(defaults)
1270 configspec = defaults['configspec']
1271 self._original_configspec = configspec
1272 self._load(infile, configspec)
1273
1274
1275 def _load(self, infile, configspec):
1276 if isinstance(infile, StringTypes):
1277 self.filename = infile
1278 if os.path.isfile(infile):
1279 h = open(infile, 'rb')
1280 infile = h.read() or []
1281 h.close()
1282 elif self.file_error:
1283 # raise an error if the file doesn't exist
1284 raise IOError('Config file not found: "%s".' % self.filename)
1285 else:
1286 # file doesn't already exist
1287 if self.create_empty:
1288 # this is a good test that the filename specified
1289 # isn't impossible - like on a non-existent device
1290 h = open(infile, 'w')
1291 h.write('')
1292 h.close()
1293 infile = []
1294
1295 elif isinstance(infile, (list, tuple)):
1296 infile = list(infile)
1297
1298 elif isinstance(infile, dict):
1299 # initialise self
1300 # the Section class handles creating subsections
1301 if isinstance(infile, ConfigObj):
1302 # get a copy of our ConfigObj
1303 infile = infile.dict()
1304
1305 for entry in infile:
1306 self[entry] = infile[entry]
1307 del self._errors
1308
1309 if configspec is not None:
1310 self._handle_configspec(configspec)
1311 else:
1312 self.configspec = None
1313 return
1314
1315 elif hasattr(infile, 'read'):
1316 # This supports file like objects
1317 infile = infile.read() or []
1318 # needs splitting into lines - but needs doing *after* decoding
1319 # in case it's not an 8 bit encoding
1320 else:
1321 raise TypeError('infile must be a filename, file like object, or list of lines.')
1322
1323 if infile:
1324 # don't do it for the empty ConfigObj
1325 infile = self._handle_bom(infile)
1326 # infile is now *always* a list
1327 #
1328 # Set the newlines attribute (first line ending it finds)
1329 # and strip trailing '\n' or '\r' from lines
1330 for line in infile:
1331 if (not line) or (line[-1] not in ('\r', '\n', '\r\n')):
1332 continue
1333 for end in ('\r\n', '\n', '\r'):
1334 if line.endswith(end):
1335 self.newlines = end
1336 break
1337 break
1338
1339 infile = [line.rstrip('\r\n') for line in infile]
1340
1341 self._parse(infile)
1342 # if we had any errors, now is the time to raise them
1343 if self._errors:
1344 info = "at line %s." % self._errors[0].line_number
1345 if len(self._errors) > 1:
1346 msg = "Parsing failed with several errors.\nFirst error %s" % info
1347 error = ConfigObjError(msg)
1348 else:
1349 error = self._errors[0]
1350 # set the errors attribute; it's a list of tuples:
1351 # (error_type, message, line_number)
1352 error.errors = self._errors
1353 # set the config attribute
1354 error.config = self
1355 raise error
1356 # delete private attributes
1357 del self._errors
1358
1359 if configspec is None:
1360 self.configspec = None
1361 else:
1362 self._handle_configspec(configspec)
1363
1364
1365 def _initialise(self, options=None):
1366 if options is None:
1367 options = OPTION_DEFAULTS
1368
1369 # initialise a few variables
1370 self.filename = None
1371 self._errors = []
1372 self.raise_errors = options['raise_errors']
1373 self.interpolation = options['interpolation']
1374 self.list_values = options['list_values']
1375 self.create_empty = options['create_empty']
1376 self.file_error = options['file_error']
1377 self.stringify = options['stringify']
1378 self.indent_type = options['indent_type']
1379 self.encoding = options['encoding']
1380 self.default_encoding = options['default_encoding']
1381 self.BOM = False
1382 self.newlines = None
1383 self.write_empty_values = options['write_empty_values']
1384 self.unrepr = options['unrepr']
1385
1386 self.initial_comment = []
1387 self.final_comment = []
1388 self.configspec = {}
1389
1390 # Clear section attributes as well
1391 Section._initialise(self)
1392
1393
1394 def __repr__(self):
1395 return ('ConfigObj({%s})' %
1396 ', '.join([('%s: %s' % (repr(key), repr(self[key])))
1397 for key in (self.scalars + self.sections)]))
1398
1399
1400 def _handle_bom(self, infile):
1401 """
1402 Handle any BOM, and decode if necessary.
1403
1404 If an encoding is specified, that *must* be used - but the BOM should
1405 still be removed (and the BOM attribute set).
1406
1407 (If the encoding is wrongly specified, then a BOM for an alternative
1408 encoding won't be discovered or removed.)
1409
1410 If an encoding is not specified, UTF8 or UTF16 BOM will be detected and
1411 removed. The BOM attribute will be set. UTF16 will be decoded to
1412 unicode.
1413
1414 NOTE: This method must not be called with an empty ``infile``.
1415
1416 Specifying the *wrong* encoding is likely to cause a
1417 ``UnicodeDecodeError``.
1418
1419 ``infile`` must always be returned as a list of lines, but may be
1420 passed in as a single string.
1421 """
1422 if ((self.encoding is not None) and
1423 (self.encoding.lower() not in BOM_LIST)):
1424 # No need to check for a BOM
1425 # the encoding specified doesn't have one
1426 # just decode
1427 return self._decode(infile, self.encoding)
1428
1429 if isinstance(infile, (list, tuple)):
1430 line = infile[0]
1431 else:
1432 line = infile
1433 if self.encoding is not None:
1434 # encoding explicitly supplied
1435 # And it could have an associated BOM
1436 # TODO: if encoding is just UTF16 - we ought to check for both
1437 # TODO: big endian and little endian versions.
1438 enc = BOM_LIST[self.encoding.lower()]
1439 if enc == 'utf_16':
1440 # For UTF16 we try big endian and little endian
1441 for BOM, (encoding, final_encoding) in BOMS.items():
1442 if not final_encoding:
1443 # skip UTF8
1444 continue
1445 if infile.startswith(BOM):
1446 ### BOM discovered
1447 ##self.BOM = True
1448 # Don't need to remove BOM
1449 return self._decode(infile, encoding)
1450
1451 # If we get this far, will *probably* raise a DecodeError
1452 # As it doesn't appear to start with a BOM
1453 return self._decode(infile, self.encoding)
1454
1455 # Must be UTF8
1456 BOM = BOM_SET[enc]
1457 if not line.startswith(BOM):
1458 return self._decode(infile, self.encoding)
1459
1460 newline = line[len(BOM):]
1461
1462 # BOM removed
1463 if isinstance(infile, (list, tuple)):
1464 infile[0] = newline
1465 else:
1466 infile = newline
1467 self.BOM = True
1468 return self._decode(infile, self.encoding)
1469
1470 # No encoding specified - so we need to check for UTF8/UTF16
1471 for BOM, (encoding, final_encoding) in BOMS.items():
1472 if not line.startswith(BOM):
1473 continue
1474 else:
1475 # BOM discovered
1476 self.encoding = final_encoding
1477 if not final_encoding:
1478 self.BOM = True
1479 # UTF8
1480 # remove BOM
1481 newline = line[len(BOM):]
1482 if isinstance(infile, (list, tuple)):
1483 infile[0] = newline
1484 else:
1485 infile = newline
1486 # UTF8 - don't decode
1487 if isinstance(infile, StringTypes):
1488 return infile.splitlines(True)
1489 else:
1490 return infile
1491 # UTF16 - have to decode
1492 return self._decode(infile, encoding)
1493
1494 # No BOM discovered and no encoding specified, just return
1495 if isinstance(infile, StringTypes):
1496 # infile read from a file will be a single string
1497 return infile.splitlines(True)
1498 return infile
1499
1500
1501 def _a_to_u(self, aString):
1502 """Decode ASCII strings to unicode if a self.encoding is specified."""
1503 if self.encoding:
1504 return aString.decode('ascii')
1505 else:
1506 return aString
1507
1508
1509 def _decode(self, infile, encoding):
1510 """
1511 Decode infile to unicode. Using the specified encoding.
1512
1513 if is a string, it also needs converting to a list.
1514 """
1515 if isinstance(infile, StringTypes):
1516 # can't be unicode
1517 # NOTE: Could raise a ``UnicodeDecodeError``
1518 return infile.decode(encoding).splitlines(True)
1519 for i, line in enumerate(infile):
1520 if not isinstance(line, unicode):
1521 # NOTE: The isinstance test here handles mixed lists of unicode/string
1522 # NOTE: But the decode will break on any non-string values
1523 # NOTE: Or could raise a ``UnicodeDecodeError``
1524 infile[i] = line.decode(encoding)
1525 return infile
1526
1527
1528 def _decode_element(self, line):
1529 """Decode element to unicode if necessary."""
1530 if not self.encoding:
1531 return line
1532 if isinstance(line, str) and self.default_encoding:
1533 return line.decode(self.default_encoding)
1534 return line
1535
1536
1537 def _str(self, value):
1538 """
1539 Used by ``stringify`` within validate, to turn non-string values
1540 into strings.
1541 """
1542 if not isinstance(value, StringTypes):
1543 return str(value)
1544 else:
1545 return value
1546
1547
1548 def _parse(self, infile):
1549 """Actually parse the config file."""
1550 temp_list_values = self.list_values
1551 if self.unrepr:
1552 self.list_values = False
1553
1554 comment_list = []
1555 done_start = False
1556 this_section = self
1557 maxline = len(infile) - 1
1558 cur_index = -1
1559 reset_comment = False
1560
1561 while cur_index < maxline:
1562 if reset_comment:
1563 comment_list = []
1564 cur_index += 1
1565 line = infile[cur_index]
1566 sline = line.strip()
1567 # do we have anything on the line ?
1568 if not sline or sline.startswith('#'):
1569 reset_comment = False
1570 comment_list.append(line)
1571 continue
1572
1573 if not done_start:
1574 # preserve initial comment
1575 self.initial_comment = comment_list
1576 comment_list = []
1577 done_start = True
1578
1579 reset_comment = True
1580 # first we check if it's a section marker
1581 mat = self._sectionmarker.match(line)
1582 if mat is not None:
1583 # is a section line
1584 (indent, sect_open, sect_name, sect_close, comment) = mat.groups()
1585 if indent and (self.indent_type is None):
1586 self.indent_type = indent
1587 cur_depth = sect_open.count('[')
1588 if cur_depth != sect_close.count(']'):
1589 self._handle_error("Cannot compute the section depth at line %s.",
1590 NestingError, infile, cur_index)
1591 continue
1592
1593 if cur_depth < this_section.depth:
1594 # the new section is dropping back to a previous level
1595 try:
1596 parent = self._match_depth(this_section,
1597 cur_depth).parent
1598 except SyntaxError:
1599 self._handle_error("Cannot compute nesting level at line %s.",
1600 NestingError, infile, cur_index)
1601 continue
1602 elif cur_depth == this_section.depth:
1603 # the new section is a sibling of the current section
1604 parent = this_section.parent
1605 elif cur_depth == this_section.depth + 1:
1606 # the new section is a child the current section
1607 parent = this_section
1608 else:
1609 self._handle_error("Section too nested at line %s.",
1610 NestingError, infile, cur_index)
1611
1612 sect_name = self._unquote(sect_name)
1613 if parent.has_key(sect_name):
1614 self._handle_error('Duplicate section name at line %s.',
1615 DuplicateError, infile, cur_index)
1616 continue
1617
1618 # create the new section
1619 this_section = Section(
1620 parent,
1621 cur_depth,
1622 self,
1623 name=sect_name)
1624 parent[sect_name] = this_section
1625 parent.inline_comments[sect_name] = comment
1626 parent.comments[sect_name] = comment_list
1627 continue
1628 #
1629 # it's not a section marker,
1630 # so it should be a valid ``key = value`` line
1631 mat = self._keyword.match(line)
1632 if mat is None:
1633 # it neither matched as a keyword
1634 # or a section marker
1635 self._handle_error(
1636 'Invalid line at line "%s".',
1637 ParseError, infile, cur_index)
1638 else:
1639 # is a keyword value
1640 # value will include any inline comment
1641 (indent, key, value) = mat.groups()
1642 if indent and (self.indent_type is None):
1643 self.indent_type = indent
1644 # check for a multiline value
1645 if value[:3] in ['"""', "'''"]:
1646 try:
1647 (value, comment, cur_index) = self._multiline(
1648 value, infile, cur_index, maxline)
1649 except SyntaxError:
1650 self._handle_error(
1651 'Parse error in value at line %s.',
1652 ParseError, infile, cur_index)
1653 continue
1654 else:
1655 if self.unrepr:
1656 comment = ''
1657 try:
1658 value = unrepr(value)
1659 except Exception, e:
1660 if type(e) == UnknownType:
1661 msg = 'Unknown name or type in value at line %s.'
1662 else:
1663 msg = 'Parse error in value at line %s.'
1664 self._handle_error(msg, UnreprError, infile,
1665 cur_index)
1666 continue
1667 else:
1668 if self.unrepr:
1669 comment = ''
1670 try:
1671 value = unrepr(value)
1672 except Exception, e:
1673 if isinstance(e, UnknownType):
1674 msg = 'Unknown name or type in value at line %s.'
1675 else:
1676 msg = 'Parse error in value at line %s.'
1677 self._handle_error(msg, UnreprError, infile,
1678 cur_index)
1679 continue
1680 else:
1681 # extract comment and lists
1682 try:
1683 (value, comment) = self._handle_value(value)
1684 except SyntaxError:
1685 self._handle_error(
1686 'Parse error in value at line %s.',
1687 ParseError, infile, cur_index)
1688 continue
1689 #
1690 key = self._unquote(key)
1691 if this_section.has_key(key):
1692 self._handle_error(
1693 'Duplicate keyword name at line %s.',
1694 DuplicateError, infile, cur_index)
1695 continue
1696 # add the key.
1697 # we set unrepr because if we have got this far we will never
1698 # be creating a new section
1699 this_section.__setitem__(key, value, unrepr=True)
1700 this_section.inline_comments[key] = comment
1701 this_section.comments[key] = comment_list
1702 continue
1703 #
1704 if self.indent_type is None:
1705 # no indentation used, set the type accordingly
1706 self.indent_type = ''
1707
1708 # preserve the final comment
1709 if not self and not self.initial_comment:
1710 self.initial_comment = comment_list
1711 elif not reset_comment:
1712 self.final_comment = comment_list
1713 self.list_values = temp_list_values
1714
1715
1716 def _match_depth(self, sect, depth):
1717 """
1718 Given a section and a depth level, walk back through the sections
1719 parents to see if the depth level matches a previous section.
1720
1721 Return a reference to the right section,
1722 or raise a SyntaxError.
1723 """
1724 while depth < sect.depth:
1725 if sect is sect.parent:
1726 # we've reached the top level already
1727 raise SyntaxError()
1728 sect = sect.parent
1729 if sect.depth == depth:
1730 return sect
1731 # shouldn't get here
1732 raise SyntaxError()
1733
1734
1735 def _handle_error(self, text, ErrorClass, infile, cur_index):
1736 """
1737 Handle an error according to the error settings.
1738
1739 Either raise the error or store it.
1740 The error will have occured at ``cur_index``
1741 """
1742 line = infile[cur_index]
1743 cur_index += 1
1744 message = text % cur_index
1745 error = ErrorClass(message, cur_index, line)
1746 if self.raise_errors:
1747 # raise the error - parsing stops here
1748 raise error
1749 # store the error
1750 # reraise when parsing has finished
1751 self._errors.append(error)
1752
1753
1754 def _unquote(self, value):
1755 """Return an unquoted version of a value"""
1756 if (value[0] == value[-1]) and (value[0] in ('"', "'")):
1757 value = value[1:-1]
1758 return value
1759
1760
1761 def _quote(self, value, multiline=True):
1762 """
1763 Return a safely quoted version of a value.
1764
1765 Raise a ConfigObjError if the value cannot be safely quoted.
1766 If multiline is ``True`` (default) then use triple quotes
1767 if necessary.
1768
1769 Don't quote values that don't need it.
1770 Recursively quote members of a list and return a comma joined list.
1771 Multiline is ``False`` for lists.
1772 Obey list syntax for empty and single member lists.
1773
1774 If ``list_values=False`` then the value is only quoted if it contains
1775 a ``\n`` (is multiline) or '#'.
1776
1777 If ``write_empty_values`` is set, and the value is an empty string, it
1778 won't be quoted.
1779 """
1780 if multiline and self.write_empty_values and value == '':
1781 # Only if multiline is set, so that it is used for values not
1782 # keys, and not values that are part of a list
1783 return ''
1784
1785 if multiline and isinstance(value, (list, tuple)):
1786 if not value:
1787 return ','
1788 elif len(value) == 1:
1789 return self._quote(value[0], multiline=False) + ','
1790 return ', '.join([self._quote(val, multiline=False)
1791 for val in value])
1792 if not isinstance(value, StringTypes):
1793 if self.stringify:
1794 value = str(value)
1795 else:
1796 raise TypeError('Value "%s" is not a string.' % value)
1797
1798 if not value:
1799 return '""'
1800
1801 no_lists_no_quotes = not self.list_values and '\n' not in value and '#' not in value
1802 need_triple = multiline and ((("'" in value) and ('"' in value)) or ('\n' in value ))
1803 hash_triple_quote = multiline and not need_triple and ("'" in value) and ('"' in value) and ('#' in value)
1804 check_for_single = (no_lists_no_quotes or not need_triple) and not hash_triple_quote
1805
1806 if check_for_single:
1807 if not self.list_values:
1808 # we don't quote if ``list_values=False``
1809 quot = noquot
1810 # for normal values either single or double quotes will do
1811 elif '\n' in value:
1812 # will only happen if multiline is off - e.g. '\n' in key
1813 raise ConfigObjError('Value "%s" cannot be safely quoted.' % value)
1814 elif ((value[0] not in wspace_plus) and
1815 (value[-1] not in wspace_plus) and
1816 (',' not in value)):
1817 quot = noquot
1818 else:
1819 quot = self._get_single_quote(value)
1820 else:
1821 # if value has '\n' or "'" *and* '"', it will need triple quotes
1822 quot = self._get_triple_quote(value)
1823
1824 if quot == noquot and '#' in value and self.list_values:
1825 quot = self._get_single_quote(value)
1826
1827 return quot % value
1828
1829
1830 def _get_single_quote(self, value):
1831 if ("'" in value) and ('"' in value):
1832 raise ConfigObjError('Value "%s" cannot be safely quoted.' % value)
1833 elif '"' in value:
1834 quot = squot
1835 else:
1836 quot = dquot
1837 return quot
1838
1839
1840 def _get_triple_quote(self, value):
1841 if (value.find('"""') != -1) and (value.find("'''") != -1):
1842 raise ConfigObjError('Value "%s" cannot be safely quoted.' % value)
1843 if value.find('"""') == -1:
1844 quot = tdquot
1845 else:
1846 quot = tsquot
1847 return quot
1848
1849
1850 def _handle_value(self, value):
1851 """
1852 Given a value string, unquote, remove comment,
1853 handle lists. (including empty and single member lists)
1854 """
1855 # do we look for lists in values ?
1856 if not self.list_values:
1857 mat = self._nolistvalue.match(value)
1858 if mat is None:
1859 raise SyntaxError()
1860 # NOTE: we don't unquote here
1861 return mat.groups()
1862 #
1863 mat = self._valueexp.match(value)
1864 if mat is None:
1865 # the value is badly constructed, probably badly quoted,
1866 # or an invalid list
1867 raise SyntaxError()
1868 (list_values, single, empty_list, comment) = mat.groups()
1869 if (list_values == '') and (single is None):
1870 # change this if you want to accept empty values
1871 raise SyntaxError()
1872 # NOTE: note there is no error handling from here if the regex
1873 # is wrong: then incorrect values will slip through
1874 if empty_list is not None:
1875 # the single comma - meaning an empty list
1876 return ([], comment)
1877 if single is not None:
1878 # handle empty values
1879 if list_values and not single:
1880 # FIXME: the '' is a workaround because our regex now matches
1881 # '' at the end of a list if it has a trailing comma
1882 single = None
1883 else:
1884 single = single or '""'
1885 single = self._unquote(single)
1886 if list_values == '':
1887 # not a list value
1888 return (single, comment)
1889 the_list = self._listvalueexp.findall(list_values)
1890 the_list = [self._unquote(val) for val in the_list]
1891 if single is not None:
1892 the_list += [single]
1893 return (the_list, comment)
1894
1895
1896 def _multiline(self, value, infile, cur_index, maxline):
1897 """Extract the value, where we are in a multiline situation."""
1898 quot = value[:3]
1899 newvalue = value[3:]
1900 single_line = self._triple_quote[quot][0]
1901 multi_line = self._triple_quote[quot][1]
1902 mat = single_line.match(value)
1903 if mat is not None:
1904 retval = list(mat.groups())
1905 retval.append(cur_index)
1906 return retval
1907 elif newvalue.find(quot) != -1:
1908 # somehow the triple quote is missing
1909 raise SyntaxError()
1910 #
1911 while cur_index < maxline:
1912 cur_index += 1
1913 newvalue += '\n'
1914 line = infile[cur_index]
1915 if line.find(quot) == -1:
1916 newvalue += line
1917 else:
1918 # end of multiline, process it
1919 break
1920 else:
1921 # we've got to the end of the config, oops...
1922 raise SyntaxError()
1923 mat = multi_line.match(line)
1924 if mat is None:
1925 # a badly formed line
1926 raise SyntaxError()
1927 (value, comment) = mat.groups()
1928 return (newvalue + value, comment, cur_index)
1929
1930
1931 def _handle_configspec(self, configspec):
1932 """Parse the configspec."""
1933 # FIXME: Should we check that the configspec was created with the
1934 # correct settings ? (i.e. ``list_values=False``)
1935 if not isinstance(configspec, ConfigObj):
1936 try:
1937 configspec = ConfigObj(configspec,
1938 raise_errors=True,
1939 file_error=True,
1940 list_values=False)
1941 except ConfigObjError, e:
1942 # FIXME: Should these errors have a reference
1943 # to the already parsed ConfigObj ?
1944 raise ConfigspecError('Parsing configspec failed: %s' % e)
1945 except IOError, e:
1946 raise IOError('Reading configspec failed: %s' % e)
1947
1948 self._set_configspec_value(configspec, self)
1949
1950
1951 def _set_configspec_value(self, configspec, section):
1952 """Used to recursively set configspec values."""
1953 if '__many__' in configspec.sections:
1954 section.configspec['__many__'] = configspec['__many__']
1955 if len(configspec.sections) > 1:
1956 # FIXME: can we supply any useful information here ?
1957 raise RepeatSectionError()
1958
1959 if hasattr(configspec, 'initial_comment'):
1960 section._configspec_initial_comment = configspec.initial_comment
1961 section._configspec_final_comment = configspec.final_comment
1962 section._configspec_encoding = configspec.encoding
1963 section._configspec_BOM = configspec.BOM
1964 section._configspec_newlines = configspec.newlines
1965 section._configspec_indent_type = configspec.indent_type
1966
1967 for entry in configspec.scalars:
1968 section._configspec_comments[entry] = configspec.comments[entry]
1969 section._configspec_inline_comments[entry] = configspec.inline_comments[entry]
1970 section.configspec[entry] = configspec[entry]
1971 section._order.append(entry)
1972
1973 for entry in configspec.sections:
1974 if entry == '__many__':
1975 continue
1976
1977 section._cs_section_comments[entry] = configspec.comments[entry]
1978 section._cs_section_inline_comments[entry] = configspec.inline_comments[entry]
1979 if not section.has_key(entry):
1980 section[entry] = {}
1981 self._set_configspec_value(configspec[entry], section[entry])
1982
1983
1984 def _handle_repeat(self, section, configspec):
1985 """Dynamically assign configspec for repeated section."""
1986 try:
1987 section_keys = configspec.sections
1988 scalar_keys = configspec.scalars
1989 except AttributeError:
1990 section_keys = [entry for entry in configspec
1991 if isinstance(configspec[entry], dict)]
1992 scalar_keys = [entry for entry in configspec
1993 if not isinstance(configspec[entry], dict)]
1994
1995 if '__many__' in section_keys and len(section_keys) > 1:
1996 # FIXME: can we supply any useful information here ?
1997 raise RepeatSectionError()
1998
1999 scalars = {}
2000 sections = {}
2001 for entry in scalar_keys:
2002 val = configspec[entry]
2003 scalars[entry] = val
2004 for entry in section_keys:
2005 val = configspec[entry]
2006 if entry == '__many__':
2007 scalars[entry] = val
2008 continue
2009 sections[entry] = val
2010
2011 section.configspec = scalars
2012 for entry in sections:
2013 if not section.has_key(entry):
2014 section[entry] = {}
2015 self._handle_repeat(section[entry], sections[entry])
2016
2017
2018 def _write_line(self, indent_string, entry, this_entry, comment):
2019 """Write an individual line, for the write method"""
2020 # NOTE: the calls to self._quote here handles non-StringType values.
2021 if not self.unrepr:
2022 val = self._decode_element(self._quote(this_entry))
2023 else:
2024 val = repr(this_entry)
2025 return '%s%s%s%s%s' % (indent_string,
2026 self._decode_element(self._quote(entry, multiline=False)),
2027 self._a_to_u(' = '),
2028 val,
2029 self._decode_element(comment))
2030
2031
2032 def _write_marker(self, indent_string, depth, entry, comment):
2033 """Write a section marker line"""
2034 return '%s%s%s%s%s' % (indent_string,
2035 self._a_to_u('[' * depth),
2036 self._quote(self._decode_element(entry), multiline=False),
2037 self._a_to_u(']' * depth),
2038 self._decode_element(comment))
2039
2040
2041 def _handle_comment(self, comment):
2042 """Deal with a comment."""
2043 if not comment:
2044 return ''
2045 start = self.indent_type
2046 if not comment.startswith('#'):
2047 start += self._a_to_u(' # ')
2048 return (start + comment)
2049
2050
2051 # Public methods
2052
2053 def write(self, outfile=None, section=None):
2054 """
2055 Write the current ConfigObj as a file
2056
2057 tekNico: FIXME: use StringIO instead of real files
2058
2059 >>> filename = a.filename
2060 >>> a.filename = 'test.ini'
2061 >>> a.write()
2062 >>> a.filename = filename
2063 >>> a == ConfigObj('test.ini', raise_errors=True)
2064 1
2065 """
2066 if self.indent_type is None:
2067 # this can be true if initialised from a dictionary
2068 self.indent_type = DEFAULT_INDENT_TYPE
2069
2070 out = []
2071 cs = self._a_to_u('#')
2072 csp = self._a_to_u('# ')
2073 if section is None:
2074 int_val = self.interpolation
2075 self.interpolation = False
2076 section = self
2077 for line in self.initial_comment:
2078 line = self._decode_element(line)
2079 stripped_line = line.strip()
2080 if stripped_line and not stripped_line.startswith(cs):
2081 line = csp + line
2082 out.append(line)
2083
2084 indent_string = self.indent_type * section.depth
2085 for entry in (section.scalars + section.sections):
2086 if entry in section.defaults:
2087 # don't write out default values
2088 continue
2089 for comment_line in section.comments[entry]:
2090 comment_line = self._decode_element(comment_line.lstrip())
2091 if comment_line and not comment_line.startswith(cs):
2092 comment_line = csp + comment_line
2093 out.append(indent_string + comment_line)
2094 this_entry = section[entry]
2095 comment = self._handle_comment(section.inline_comments[entry])
2096
2097 if isinstance(this_entry, dict):
2098 # a section
2099 out.append(self._write_marker(
2100 indent_string,
2101 this_entry.depth,
2102 entry,
2103 comment))
2104 out.extend(self.write(section=this_entry))
2105 else:
2106 out.append(self._write_line(
2107 indent_string,
2108 entry,
2109 this_entry,
2110 comment))
2111
2112 if section is self:
2113 for line in self.final_comment:
2114 line = self._decode_element(line)
2115 stripped_line = line.strip()
2116 if stripped_line and not stripped_line.startswith(cs):
2117 line = csp + line
2118 out.append(line)
2119 self.interpolation = int_val
2120
2121 if section is not self:
2122 return out
2123
2124 if (self.filename is None) and (outfile is None):
2125 # output a list of lines
2126 # might need to encode
2127 # NOTE: This will *screw* UTF16, each line will start with the BOM
2128 if self.encoding:
2129 out = [l.encode(self.encoding) for l in out]
2130 if (self.BOM and ((self.encoding is None) or
2131 (BOM_LIST.get(self.encoding.lower()) == 'utf_8'))):
2132 # Add the UTF8 BOM
2133 if not out:
2134 out.append('')
2135 out[0] = BOM_UTF8 + out[0]
2136 return out
2137
2138 # Turn the list to a string, joined with correct newlines
2139 newline = self.newlines or os.linesep
2140 output = self._a_to_u(newline).join(out)
2141 if self.encoding:
2142 output = output.encode(self.encoding)
2143 if self.BOM and ((self.encoding is None) or match_utf8(self.encoding)):
2144 # Add the UTF8 BOM
2145 output = BOM_UTF8 + output
2146
2147 if not output.endswith(newline):
2148 output += newline
2149 if outfile is not None:
2150 outfile.write(output)
2151 else:
2152 h = open(self.filename, 'wb')
2153 h.write(output)
2154 h.close()
2155
2156
2157 def validate(self, validator, preserve_errors=False, copy=False,
2158 section=None):
2159 """
2160 Test the ConfigObj against a configspec.
2161
2162 It uses the ``validator`` object from *validate.py*.
2163
2164 To run ``validate`` on the current ConfigObj, call: ::
2165
2166 test = config.validate(validator)
2167
2168 (Normally having previously passed in the configspec when the ConfigObj
2169 was created - you can dynamically assign a dictionary of checks to the
2170 ``configspec`` attribute of a section though).
2171
2172 It returns ``True`` if everything passes, or a dictionary of
2173 pass/fails (True/False). If every member of a subsection passes, it
2174 will just have the value ``True``. (It also returns ``False`` if all
2175 members fail).
2176
2177 In addition, it converts the values from strings to their native
2178 types if their checks pass (and ``stringify`` is set).
2179
2180 If ``preserve_errors`` is ``True`` (``False`` is default) then instead
2181 of a marking a fail with a ``False``, it will preserve the actual
2182 exception object. This can contain info about the reason for failure.
2183 For example the ``VdtValueTooSmallError`` indicates that the value
2184 supplied was too small. If a value (or section) is missing it will
2185 still be marked as ``False``.
2186
2187 You must have the validate module to use ``preserve_errors=True``.
2188
2189 You can then use the ``flatten_errors`` function to turn your nested
2190 results dictionary into a flattened list of failures - useful for
2191 displaying meaningful error messages.
2192 """
2193 if section is None:
2194 if self.configspec is None:
2195 raise ValueError('No configspec supplied.')
2196 if preserve_errors:
2197 # We do this once to remove a top level dependency on the validate module
2198 # Which makes importing configobj faster
2199 from validate import VdtMissingValue
2200 self._vdtMissingValue = VdtMissingValue
2201 section = self
2202 #
2203 spec_section = section.configspec
2204 if copy and hasattr(section, '_configspec_initial_comment'):
2205 section.initial_comment = section._configspec_initial_comment
2206 section.final_comment = section._configspec_final_comment
2207 section.encoding = section._configspec_encoding
2208 section.BOM = section._configspec_BOM
2209 section.newlines = section._configspec_newlines
2210 section.indent_type = section._configspec_indent_type
2211
2212 if '__many__' in section.configspec:
2213 many = spec_section['__many__']
2214 # dynamically assign the configspecs
2215 # for the sections below
2216 for entry in section.sections:
2217 self._handle_repeat(section[entry], many)
2218 #
2219 out = {}
2220 ret_true = True
2221 ret_false = True
2222 order = [k for k in section._order if k in spec_section]
2223 order += [k for k in spec_section if k not in order]
2224 for entry in order:
2225 if entry == '__many__':
2226 continue
2227 if (not entry in section.scalars) or (entry in section.defaults):
2228 # missing entries
2229 # or entries from defaults
2230 missing = True
2231 val = None
2232 if copy and not entry in section.scalars:
2233 # copy comments
2234 section.comments[entry] = (
2235 section._configspec_comments.get(entry, []))
2236 section.inline_comments[entry] = (
2237 section._configspec_inline_comments.get(entry, ''))
2238 #
2239 else:
2240 missing = False
2241 val = section[entry]
2242 try:
2243 check = validator.check(spec_section[entry],
2244 val,
2245 missing=missing
2246 )
2247 except validator.baseErrorClass, e:
2248 if not preserve_errors or isinstance(e, self._vdtMissingValue):
2249 out[entry] = False
2250 else:
2251 # preserve the error
2252 out[entry] = e
2253 ret_false = False
2254 ret_true = False
2255 else:
2256 try:
2257 section.default_values.pop(entry, None)
2258 except AttributeError:
2259 # For Python 2.2 compatibility
2260 try:
2261 del section.default_values[entry]
2262 except KeyError:
2263 pass
2264
2265 if hasattr(validator, 'get_default_value'):
2266 try:
2267 section.default_values[entry] = validator.get_default_value(spec_section[entry])
2268 except KeyError:
2269 # No default
2270 pass
2271
2272 ret_false = False
2273 out[entry] = True
2274 if self.stringify or missing:
2275 # if we are doing type conversion
2276 # or the value is a supplied default
2277 if not self.stringify:
2278 if isinstance(check, (list, tuple)):
2279 # preserve lists
2280 check = [self._str(item) for item in check]
2281 elif missing and check is None:
2282 # convert the None from a default to a ''
2283 check = ''
2284 else:
2285 check = self._str(check)
2286 if (check != val) or missing:
2287 section[entry] = check
2288 if not copy and missing and entry not in section.defaults:
2289 section.defaults.append(entry)
2290 # Missing sections will have been created as empty ones when the
2291 # configspec was read.
2292 for entry in section.sections:
2293 # FIXME: this means DEFAULT is not copied in copy mode
2294 if section is self and entry == 'DEFAULT':
2295 continue
2296 if copy:
2297 section.comments[entry] = section._cs_section_comments[entry]
2298 section.inline_comments[entry] = (
2299 section._cs_section_inline_comments[entry])
2300 check = self.validate(validator, preserve_errors=preserve_errors,
2301 copy=copy, section=section[entry])
2302 out[entry] = check
2303 if check == False:
2304 ret_true = False
2305 elif check == True:
2306 ret_false = False
2307 else:
2308 ret_true = False
2309 ret_false = False
2310 #
2311 if ret_true:
2312 return True
2313 elif ret_false:
2314 return False
2315 return out
2316
2317
2318 def reset(self):
2319 """Clear ConfigObj instance and restore to 'freshly created' state."""
2320 self.clear()
2321 self._initialise()
2322 # FIXME: Should be done by '_initialise', but ConfigObj constructor (and reload)
2323 # requires an empty dictionary
2324 self.configspec = None
2325 # Just to be sure ;-)
2326 self._original_configspec = None
2327
2328
2329 def reload(self):
2330 """
2331 Reload a ConfigObj from file.
2332
2333 This method raises a ``ReloadError`` if the ConfigObj doesn't have
2334 a filename attribute pointing to a file.
2335 """
2336 if not isinstance(self.filename, StringTypes):
2337 raise ReloadError()
2338
2339 filename = self.filename
2340 current_options = {}
2341 for entry in OPTION_DEFAULTS:
2342 if entry == 'configspec':
2343 continue
2344 current_options[entry] = getattr(self, entry)
2345
2346 configspec = self._original_configspec
2347 current_options['configspec'] = configspec
2348
2349 self.clear()
2350 self._initialise(current_options)
2351 self._load(filename, configspec)
2352
2353
2354
2355 class SimpleVal(object):
2356 """
2357 A simple validator.
2358 Can be used to check that all members expected are present.
2359
2360 To use it, provide a configspec with all your members in (the value given
2361 will be ignored). Pass an instance of ``SimpleVal`` to the ``validate``
2362 method of your ``ConfigObj``. ``validate`` will return ``True`` if all
2363 members are present, or a dictionary with True/False meaning
2364 present/missing. (Whole missing sections will be replaced with ``False``)
2365 """
2366
2367 def __init__(self):
2368 self.baseErrorClass = ConfigObjError
2369
2370 def check(self, check, member, missing=False):
2371 """A dummy check method, always returns the value unchanged."""
2372 if missing:
2373 raise self.baseErrorClass()
2374 return member
2375
2376
2377 # Check / processing functions for options
2378 def flatten_errors(cfg, res, levels=None, results=None):
2379 """
2380 An example function that will turn a nested dictionary of results
2381 (as returned by ``ConfigObj.validate``) into a flat list.
2382
2383 ``cfg`` is the ConfigObj instance being checked, ``res`` is the results
2384 dictionary returned by ``validate``.
2385
2386 (This is a recursive function, so you shouldn't use the ``levels`` or
2387 ``results`` arguments - they are used by the function.
2388
2389 Returns a list of keys that failed. Each member of the list is a tuple :
2390 ::
2391
2392 ([list of sections...], key, result)
2393
2394 If ``validate`` was called with ``preserve_errors=False`` (the default)
2395 then ``result`` will always be ``False``.
2396
2397 *list of sections* is a flattened list of sections that the key was found
2398 in.
2399
2400 If the section was missing then key will be ``None``.
2401
2402 If the value (or section) was missing then ``result`` will be ``False``.
2403
2404 If ``validate`` was called with ``preserve_errors=True`` and a value
2405 was present, but failed the check, then ``result`` will be the exception
2406 object returned. You can use this as a string that describes the failure.
2407
2408 For example *The value "3" is of the wrong type*.
2409
2410 >>> import validate
2411 >>> vtor = validate.Validator()
2412 >>> my_ini = '''
2413 ... option1 = True
2414 ... [section1]
2415 ... option1 = True
2416 ... [section2]
2417 ... another_option = Probably
2418 ... [section3]
2419 ... another_option = True
2420 ... [[section3b]]
2421 ... value = 3
2422 ... value2 = a
2423 ... value3 = 11
2424 ... '''
2425 >>> my_cfg = '''
2426 ... option1 = boolean()
2427 ... option2 = boolean()
2428 ... option3 = boolean(default=Bad_value)
2429 ... [section1]
2430 ... option1 = boolean()
2431 ... option2 = boolean()
2432 ... option3 = boolean(default=Bad_value)
2433 ... [section2]
2434 ... another_option = boolean()
2435 ... [section3]
2436 ... another_option = boolean()
2437 ... [[section3b]]
2438 ... value = integer
2439 ... value2 = integer
2440 ... value3 = integer(0, 10)
2441 ... [[[section3b-sub]]]
2442 ... value = string
2443 ... [section4]
2444 ... another_option = boolean()
2445 ... '''
2446 >>> cs = my_cfg.split('\\n')
2447 >>> ini = my_ini.split('\\n')
2448 >>> cfg = ConfigObj(ini, configspec=cs)
2449 >>> res = cfg.validate(vtor, preserve_errors=True)
2450 >>> errors = []
2451 >>> for entry in flatten_errors(cfg, res):
2452 ... section_list, key, error = entry
2453 ... section_list.insert(0, '[root]')
2454 ... if key is not None:
2455 ... section_list.append(key)
2456 ... else:
2457 ... section_list.append('[missing]')
2458 ... section_string = ', '.join(section_list)
2459 ... errors.append((section_string, ' = ', error))
2460 >>> errors.sort()
2461 >>> for entry in errors:
2462 ... print entry[0], entry[1], (entry[2] or 0)
2463 [root], option2 = 0
2464 [root], option3 = the value "Bad_value" is of the wrong type.
2465 [root], section1, option2 = 0
2466 [root], section1, option3 = the value "Bad_value" is of the wrong type.
2467 [root], section2, another_option = the value "Probably" is of the wrong type.
2468 [root], section3, section3b, section3b-sub, [missing] = 0
2469 [root], section3, section3b, value2 = the value "a" is of the wrong type.
2470 [root], section3, section3b, value3 = the value "11" is too big.
2471 [root], section4, [missing] = 0
2472 """
2473 if levels is None:
2474 # first time called
2475 levels = []
2476 results = []
2477 if res is True:
2478 return results
2479 if res is False:
2480 results.append((levels[:], None, False))
2481 if levels:
2482 levels.pop()
2483 return results
2484 for (key, val) in res.items():
2485 if val == True:
2486 continue
2487 if isinstance(cfg.get(key), dict):
2488 # Go down one level
2489 levels.append(key)
2490 flatten_errors(cfg[key], val, levels, results)
2491 continue
2492 results.append((levels[:], key, val))
2493 #
2494 # Go up one level
2495 if levels:
2496 levels.pop()
2497 #
2498 return results
2499
2500
2501 """*A programming language is a medium of expression.* - Paul Graham"""
@@ -0,0 +1,130 b''
1 __all__ = ["generic"]
2
3 from types import ClassType, InstanceType
4 classtypes = type, ClassType
5
6 # This is version 0.6 of Philip J. Eby's simplegeneric module
7 # (http://cheeseshop.python.org/pypi/simplegeneric) patched to work
8 # with Python 2.3 (which doesn't support assigning to __name__)
9
10 def generic(func):
11 """Create a simple generic function"""
12
13 _sentinel = object()
14
15 def _by_class(*args, **kw):
16 cls = args[0].__class__
17 for t in type(cls.__name__, (cls,object), {}).__mro__:
18 f = _gbt(t, _sentinel)
19 if f is not _sentinel:
20 return f(*args, **kw)
21 else:
22 return func(*args, **kw)
23
24 _by_type = {object: func, InstanceType: _by_class}
25 _gbt = _by_type.get
26
27 def when_type(t):
28 """Decorator to add a method that will be called for type `t`"""
29 if not isinstance(t, classtypes):
30 raise TypeError(
31 "%r is not a type or class" % (t,)
32 )
33 def decorate(f):
34 if _by_type.setdefault(t,f) is not f:
35 raise TypeError(
36 "%r already has method for type %r" % (func, t)
37 )
38 return f
39 return decorate
40
41
42
43
44
45
46 _by_object = {}
47 _gbo = _by_object.get
48
49 def when_object(o):
50 """Decorator to add a method that will be called for object `o`"""
51 def decorate(f):
52 if _by_object.setdefault(id(o), (o,f))[1] is not f:
53 raise TypeError(
54 "%r already has method for object %r" % (func, o)
55 )
56 return f
57 return decorate
58
59
60 def dispatch(*args, **kw):
61 f = _gbo(id(args[0]), _sentinel)
62 if f is _sentinel:
63 for t in type(args[0]).__mro__:
64 f = _gbt(t, _sentinel)
65 if f is not _sentinel:
66 return f(*args, **kw)
67 else:
68 return func(*args, **kw)
69 else:
70 return f[1](*args, **kw)
71
72 try:
73 dispatch.__name__ = func.__name__
74 except TypeError:
75 pass
76 dispatch.__dict__ = func.__dict__.copy()
77 dispatch.__doc__ = func.__doc__
78 dispatch.__module__ = func.__module__
79
80 dispatch.when_type = when_type
81 dispatch.when_object = when_object
82 dispatch.default = func
83 dispatch.has_object = lambda o: id(o) in _by_object
84 dispatch.has_type = lambda t: t in _by_type
85 return dispatch
86
87
88
89
90 def test_suite():
91 import doctest
92 return doctest.DocFileSuite(
93 'README.txt',
94 optionflags=doctest.ELLIPSIS|doctest.REPORT_ONLY_FIRST_FAILURE,
95 )
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
This diff has been collapsed as it changes many lines, (1414 lines changed) Show them Hide them
@@ -0,0 +1,1414 b''
1 # validate.py
2 # A Validator object
3 # Copyright (C) 2005 Michael Foord, Mark Andrews, Nicola Larosa
4 # E-mail: fuzzyman AT voidspace DOT org DOT uk
5 # mark AT la-la DOT com
6 # nico AT tekNico DOT net
7
8 # This software is licensed under the terms of the BSD license.
9 # http://www.voidspace.org.uk/python/license.shtml
10 # Basically you're free to copy, modify, distribute and relicense it,
11 # So long as you keep a copy of the license with it.
12
13 # Scripts maintained at http://www.voidspace.org.uk/python/index.shtml
14 # For information about bugfixes, updates and support, please join the
15 # ConfigObj mailing list:
16 # http://lists.sourceforge.net/lists/listinfo/configobj-develop
17 # Comments, suggestions and bug reports welcome.
18
19 """
20 The Validator object is used to check that supplied values
21 conform to a specification.
22
23 The value can be supplied as a string - e.g. from a config file.
24 In this case the check will also *convert* the value to
25 the required type. This allows you to add validation
26 as a transparent layer to access data stored as strings.
27 The validation checks that the data is correct *and*
28 converts it to the expected type.
29
30 Some standard checks are provided for basic data types.
31 Additional checks are easy to write. They can be
32 provided when the ``Validator`` is instantiated or
33 added afterwards.
34
35 The standard functions work with the following basic data types :
36
37 * integers
38 * floats
39 * booleans
40 * strings
41 * ip_addr
42
43 plus lists of these datatypes
44
45 Adding additional checks is done through coding simple functions.
46
47 The full set of standard checks are :
48
49 * 'integer': matches integer values (including negative)
50 Takes optional 'min' and 'max' arguments : ::
51
52 integer()
53 integer(3, 9) # any value from 3 to 9
54 integer(min=0) # any positive value
55 integer(max=9)
56
57 * 'float': matches float values
58 Has the same parameters as the integer check.
59
60 * 'boolean': matches boolean values - ``True`` or ``False``
61 Acceptable string values for True are :
62 true, on, yes, 1
63 Acceptable string values for False are :
64 false, off, no, 0
65
66 Any other value raises an error.
67
68 * 'ip_addr': matches an Internet Protocol address, v.4, represented
69 by a dotted-quad string, i.e. '1.2.3.4'.
70
71 * 'string': matches any string.
72 Takes optional keyword args 'min' and 'max'
73 to specify min and max lengths of the string.
74
75 * 'list': matches any list.
76 Takes optional keyword args 'min', and 'max' to specify min and
77 max sizes of the list. (Always returns a list.)
78
79 * 'tuple': matches any tuple.
80 Takes optional keyword args 'min', and 'max' to specify min and
81 max sizes of the tuple. (Always returns a tuple.)
82
83 * 'int_list': Matches a list of integers.
84 Takes the same arguments as list.
85
86 * 'float_list': Matches a list of floats.
87 Takes the same arguments as list.
88
89 * 'bool_list': Matches a list of boolean values.
90 Takes the same arguments as list.
91
92 * 'ip_addr_list': Matches a list of IP addresses.
93 Takes the same arguments as list.
94
95 * 'string_list': Matches a list of strings.
96 Takes the same arguments as list.
97
98 * 'mixed_list': Matches a list with different types in
99 specific positions. List size must match
100 the number of arguments.
101
102 Each position can be one of :
103 'integer', 'float', 'ip_addr', 'string', 'boolean'
104
105 So to specify a list with two strings followed
106 by two integers, you write the check as : ::
107
108 mixed_list('string', 'string', 'integer', 'integer')
109
110 * 'pass': This check matches everything ! It never fails
111 and the value is unchanged.
112
113 It is also the default if no check is specified.
114
115 * 'option': This check matches any from a list of options.
116 You specify this check with : ::
117
118 option('option 1', 'option 2', 'option 3')
119
120 You can supply a default value (returned if no value is supplied)
121 using the default keyword argument.
122
123 You specify a list argument for default using a list constructor syntax in
124 the check : ::
125
126 checkname(arg1, arg2, default=list('val 1', 'val 2', 'val 3'))
127
128 A badly formatted set of arguments will raise a ``VdtParamError``.
129 """
130
131 __docformat__ = "restructuredtext en"
132
133 __version__ = '0.3.2'
134
135 __revision__ = '$Id: validate.py 123 2005-09-08 08:54:28Z fuzzyman $'
136
137 __all__ = (
138 '__version__',
139 'dottedQuadToNum',
140 'numToDottedQuad',
141 'ValidateError',
142 'VdtUnknownCheckError',
143 'VdtParamError',
144 'VdtTypeError',
145 'VdtValueError',
146 'VdtValueTooSmallError',
147 'VdtValueTooBigError',
148 'VdtValueTooShortError',
149 'VdtValueTooLongError',
150 'VdtMissingValue',
151 'Validator',
152 'is_integer',
153 'is_float',
154 'is_boolean',
155 'is_list',
156 'is_tuple',
157 'is_ip_addr',
158 'is_string',
159 'is_int_list',
160 'is_bool_list',
161 'is_float_list',
162 'is_string_list',
163 'is_ip_addr_list',
164 'is_mixed_list',
165 'is_option',
166 '__docformat__',
167 )
168
169
170 import sys
171 INTP_VER = sys.version_info[:2]
172 if INTP_VER < (2, 2):
173 raise RuntimeError("Python v.2.2 or later needed")
174
175 import re
176 StringTypes = (str, unicode)
177
178
179 _list_arg = re.compile(r'''
180 (?:
181 ([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*list\(
182 (
183 (?:
184 \s*
185 (?:
186 (?:".*?")| # double quotes
187 (?:'.*?')| # single quotes
188 (?:[^'",\s\)][^,\)]*?) # unquoted
189 )
190 \s*,\s*
191 )*
192 (?:
193 (?:".*?")| # double quotes
194 (?:'.*?')| # single quotes
195 (?:[^'",\s\)][^,\)]*?) # unquoted
196 )? # last one
197 )
198 \)
199 )
200 ''', re.VERBOSE) # two groups
201
202 _list_members = re.compile(r'''
203 (
204 (?:".*?")| # double quotes
205 (?:'.*?')| # single quotes
206 (?:[^'",\s=][^,=]*?) # unquoted
207 )
208 (?:
209 (?:\s*,\s*)|(?:\s*$) # comma
210 )
211 ''', re.VERBOSE) # one group
212
213 _paramstring = r'''
214 (?:
215 (
216 (?:
217 [a-zA-Z_][a-zA-Z0-9_]*\s*=\s*list\(
218 (?:
219 \s*
220 (?:
221 (?:".*?")| # double quotes
222 (?:'.*?')| # single quotes
223 (?:[^'",\s\)][^,\)]*?) # unquoted
224 )
225 \s*,\s*
226 )*
227 (?:
228 (?:".*?")| # double quotes
229 (?:'.*?')| # single quotes
230 (?:[^'",\s\)][^,\)]*?) # unquoted
231 )? # last one
232 \)
233 )|
234 (?:
235 (?:".*?")| # double quotes
236 (?:'.*?')| # single quotes
237 (?:[^'",\s=][^,=]*?)| # unquoted
238 (?: # keyword argument
239 [a-zA-Z_][a-zA-Z0-9_]*\s*=\s*
240 (?:
241 (?:".*?")| # double quotes
242 (?:'.*?')| # single quotes
243 (?:[^'",\s=][^,=]*?) # unquoted
244 )
245 )
246 )
247 )
248 (?:
249 (?:\s*,\s*)|(?:\s*$) # comma
250 )
251 )
252 '''
253
254 _matchstring = '^%s*' % _paramstring
255
256 # Python pre 2.2.1 doesn't have bool
257 try:
258 bool
259 except NameError:
260 def bool(val):
261 """Simple boolean equivalent function. """
262 if val:
263 return 1
264 else:
265 return 0
266
267
268 def dottedQuadToNum(ip):
269 """
270 Convert decimal dotted quad string to long integer
271
272 >>> dottedQuadToNum('1 ')
273 1L
274 >>> dottedQuadToNum(' 1.2')
275 16777218L
276 >>> dottedQuadToNum(' 1.2.3 ')
277 16908291L
278 >>> dottedQuadToNum('1.2.3.4')
279 16909060L
280 >>> dottedQuadToNum('1.2.3. 4')
281 Traceback (most recent call last):
282 ValueError: Not a good dotted-quad IP: 1.2.3. 4
283 >>> dottedQuadToNum('255.255.255.255')
284 4294967295L
285 >>> dottedQuadToNum('255.255.255.256')
286 Traceback (most recent call last):
287 ValueError: Not a good dotted-quad IP: 255.255.255.256
288 """
289
290 # import here to avoid it when ip_addr values are not used
291 import socket, struct
292
293 try:
294 return struct.unpack('!L',
295 socket.inet_aton(ip.strip()))[0]
296 except socket.error:
297 # bug in inet_aton, corrected in Python 2.3
298 if ip.strip() == '255.255.255.255':
299 return 0xFFFFFFFFL
300 else:
301 raise ValueError('Not a good dotted-quad IP: %s' % ip)
302 return
303
304
305 def numToDottedQuad(num):
306 """
307 Convert long int to dotted quad string
308
309 >>> numToDottedQuad(-1L)
310 Traceback (most recent call last):
311 ValueError: Not a good numeric IP: -1
312 >>> numToDottedQuad(1L)
313 '0.0.0.1'
314 >>> numToDottedQuad(16777218L)
315 '1.0.0.2'
316 >>> numToDottedQuad(16908291L)
317 '1.2.0.3'
318 >>> numToDottedQuad(16909060L)
319 '1.2.3.4'
320 >>> numToDottedQuad(4294967295L)
321 '255.255.255.255'
322 >>> numToDottedQuad(4294967296L)
323 Traceback (most recent call last):
324 ValueError: Not a good numeric IP: 4294967296
325 """
326
327 # import here to avoid it when ip_addr values are not used
328 import socket, struct
329
330 # no need to intercept here, 4294967295L is fine
331 try:
332 return socket.inet_ntoa(
333 struct.pack('!L', long(num)))
334 except (socket.error, struct.error, OverflowError):
335 raise ValueError('Not a good numeric IP: %s' % num)
336
337
338 class ValidateError(Exception):
339 """
340 This error indicates that the check failed.
341 It can be the base class for more specific errors.
342
343 Any check function that fails ought to raise this error.
344 (or a subclass)
345
346 >>> raise ValidateError
347 Traceback (most recent call last):
348 ValidateError
349 """
350
351
352 class VdtMissingValue(ValidateError):
353 """No value was supplied to a check that needed one."""
354
355
356 class VdtUnknownCheckError(ValidateError):
357 """An unknown check function was requested"""
358
359 def __init__(self, value):
360 """
361 >>> raise VdtUnknownCheckError('yoda')
362 Traceback (most recent call last):
363 VdtUnknownCheckError: the check "yoda" is unknown.
364 """
365 ValidateError.__init__(self, 'the check "%s" is unknown.' % (value,))
366
367
368 class VdtParamError(SyntaxError):
369 """An incorrect parameter was passed"""
370
371 def __init__(self, name, value):
372 """
373 >>> raise VdtParamError('yoda', 'jedi')
374 Traceback (most recent call last):
375 VdtParamError: passed an incorrect value "jedi" for parameter "yoda".
376 """
377 SyntaxError.__init__(self, 'passed an incorrect value "%s" for parameter "%s".' % (value, name))
378
379
380 class VdtTypeError(ValidateError):
381 """The value supplied was of the wrong type"""
382
383 def __init__(self, value):
384 """
385 >>> raise VdtTypeError('jedi')
386 Traceback (most recent call last):
387 VdtTypeError: the value "jedi" is of the wrong type.
388 """
389 ValidateError.__init__(self, 'the value "%s" is of the wrong type.' % (value,))
390
391
392 class VdtValueError(ValidateError):
393 """The value supplied was of the correct type, but was not an allowed value."""
394
395 def __init__(self, value):
396 """
397 >>> raise VdtValueError('jedi')
398 Traceback (most recent call last):
399 VdtValueError: the value "jedi" is unacceptable.
400 """
401 ValidateError.__init__(self, 'the value "%s" is unacceptable.' % (value,))
402
403
404 class VdtValueTooSmallError(VdtValueError):
405 """The value supplied was of the correct type, but was too small."""
406
407 def __init__(self, value):
408 """
409 >>> raise VdtValueTooSmallError('0')
410 Traceback (most recent call last):
411 VdtValueTooSmallError: the value "0" is too small.
412 """
413 ValidateError.__init__(self, 'the value "%s" is too small.' % (value,))
414
415
416 class VdtValueTooBigError(VdtValueError):
417 """The value supplied was of the correct type, but was too big."""
418
419 def __init__(self, value):
420 """
421 >>> raise VdtValueTooBigError('1')
422 Traceback (most recent call last):
423 VdtValueTooBigError: the value "1" is too big.
424 """
425 ValidateError.__init__(self, 'the value "%s" is too big.' % (value,))
426
427
428 class VdtValueTooShortError(VdtValueError):
429 """The value supplied was of the correct type, but was too short."""
430
431 def __init__(self, value):
432 """
433 >>> raise VdtValueTooShortError('jed')
434 Traceback (most recent call last):
435 VdtValueTooShortError: the value "jed" is too short.
436 """
437 ValidateError.__init__(
438 self,
439 'the value "%s" is too short.' % (value,))
440
441
442 class VdtValueTooLongError(VdtValueError):
443 """The value supplied was of the correct type, but was too long."""
444
445 def __init__(self, value):
446 """
447 >>> raise VdtValueTooLongError('jedie')
448 Traceback (most recent call last):
449 VdtValueTooLongError: the value "jedie" is too long.
450 """
451 ValidateError.__init__(self, 'the value "%s" is too long.' % (value,))
452
453
454 class Validator(object):
455 """
456 Validator is an object that allows you to register a set of 'checks'.
457 These checks take input and test that it conforms to the check.
458
459 This can also involve converting the value from a string into
460 the correct datatype.
461
462 The ``check`` method takes an input string which configures which
463 check is to be used and applies that check to a supplied value.
464
465 An example input string would be:
466 'int_range(param1, param2)'
467
468 You would then provide something like:
469
470 >>> def int_range_check(value, min, max):
471 ... # turn min and max from strings to integers
472 ... min = int(min)
473 ... max = int(max)
474 ... # check that value is of the correct type.
475 ... # possible valid inputs are integers or strings
476 ... # that represent integers
477 ... if not isinstance(value, (int, long, StringTypes)):
478 ... raise VdtTypeError(value)
479 ... elif isinstance(value, StringTypes):
480 ... # if we are given a string
481 ... # attempt to convert to an integer
482 ... try:
483 ... value = int(value)
484 ... except ValueError:
485 ... raise VdtValueError(value)
486 ... # check the value is between our constraints
487 ... if not min <= value:
488 ... raise VdtValueTooSmallError(value)
489 ... if not value <= max:
490 ... raise VdtValueTooBigError(value)
491 ... return value
492
493 >>> fdict = {'int_range': int_range_check}
494 >>> vtr1 = Validator(fdict)
495 >>> vtr1.check('int_range(20, 40)', '30')
496 30
497 >>> vtr1.check('int_range(20, 40)', '60')
498 Traceback (most recent call last):
499 VdtValueTooBigError: the value "60" is too big.
500
501 New functions can be added with : ::
502
503 >>> vtr2 = Validator()
504 >>> vtr2.functions['int_range'] = int_range_check
505
506 Or by passing in a dictionary of functions when Validator
507 is instantiated.
508
509 Your functions *can* use keyword arguments,
510 but the first argument should always be 'value'.
511
512 If the function doesn't take additional arguments,
513 the parentheses are optional in the check.
514 It can be written with either of : ::
515
516 keyword = function_name
517 keyword = function_name()
518
519 The first program to utilise Validator() was Michael Foord's
520 ConfigObj, an alternative to ConfigParser which supports lists and
521 can validate a config file using a config schema.
522 For more details on using Validator with ConfigObj see:
523 http://www.voidspace.org.uk/python/configobj.html
524 """
525
526 # this regex does the initial parsing of the checks
527 _func_re = re.compile(r'(.+?)\((.*)\)')
528
529 # this regex takes apart keyword arguments
530 _key_arg = re.compile(r'^([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.*)$')
531
532
533 # this regex finds keyword=list(....) type values
534 _list_arg = _list_arg
535
536 # this regex takes individual values out of lists - in one pass
537 _list_members = _list_members
538
539 # These regexes check a set of arguments for validity
540 # and then pull the members out
541 _paramfinder = re.compile(_paramstring, re.VERBOSE)
542 _matchfinder = re.compile(_matchstring, re.VERBOSE)
543
544
545 def __init__(self, functions=None):
546 """
547 >>> vtri = Validator()
548 """
549 self.functions = {
550 '': self._pass,
551 'integer': is_integer,
552 'float': is_float,
553 'boolean': is_boolean,
554 'ip_addr': is_ip_addr,
555 'string': is_string,
556 'list': is_list,
557 'tuple': is_tuple,
558 'int_list': is_int_list,
559 'float_list': is_float_list,
560 'bool_list': is_bool_list,
561 'ip_addr_list': is_ip_addr_list,
562 'string_list': is_string_list,
563 'mixed_list': is_mixed_list,
564 'pass': self._pass,
565 'option': is_option,
566 }
567 if functions is not None:
568 self.functions.update(functions)
569 # tekNico: for use by ConfigObj
570 self.baseErrorClass = ValidateError
571 self._cache = {}
572
573
574 def check(self, check, value, missing=False):
575 """
576 Usage: check(check, value)
577
578 Arguments:
579 check: string representing check to apply (including arguments)
580 value: object to be checked
581 Returns value, converted to correct type if necessary
582
583 If the check fails, raises a ``ValidateError`` subclass.
584
585 >>> vtor.check('yoda', '')
586 Traceback (most recent call last):
587 VdtUnknownCheckError: the check "yoda" is unknown.
588 >>> vtor.check('yoda()', '')
589 Traceback (most recent call last):
590 VdtUnknownCheckError: the check "yoda" is unknown.
591
592 >>> vtor.check('string(default="")', '', missing=True)
593 ''
594 """
595 fun_name, fun_args, fun_kwargs, default = self._parse_with_caching(check)
596
597 if missing:
598 if default is None:
599 # no information needed here - to be handled by caller
600 raise VdtMissingValue()
601 value = self._handle_none(default)
602
603 if value is None:
604 return None
605
606 return self._check_value(value, fun_name, fun_args, fun_kwargs)
607
608
609 def _handle_none(self, value):
610 if value == 'None':
611 value = None
612 elif value in ("'None'", '"None"'):
613 # Special case a quoted None
614 value = self._unquote(value)
615 return value
616
617
618 def _parse_with_caching(self, check):
619 if check in self._cache:
620 fun_name, fun_args, fun_kwargs, default = self._cache[check]
621 # We call list and dict below to work with *copies* of the data
622 # rather than the original (which are mutable of course)
623 fun_args = list(fun_args)
624 fun_kwargs = dict(fun_kwargs)
625 else:
626 fun_name, fun_args, fun_kwargs, default = self._parse_check(check)
627 fun_kwargs = dict((str(key), value) for (key, value) in fun_kwargs.items())
628 self._cache[check] = fun_name, list(fun_args), dict(fun_kwargs), default
629 return fun_name, fun_args, fun_kwargs, default
630
631
632 def _check_value(self, value, fun_name, fun_args, fun_kwargs):
633 try:
634 fun = self.functions[fun_name]
635 except KeyError:
636 raise VdtUnknownCheckError(fun_name)
637 else:
638 return fun(value, *fun_args, **fun_kwargs)
639
640
641 def _parse_check(self, check):
642 fun_match = self._func_re.match(check)
643 if fun_match:
644 fun_name = fun_match.group(1)
645 arg_string = fun_match.group(2)
646 arg_match = self._matchfinder.match(arg_string)
647 if arg_match is None:
648 # Bad syntax
649 raise VdtParamError('Bad syntax in check "%s".' % check)
650 fun_args = []
651 fun_kwargs = {}
652 # pull out args of group 2
653 for arg in self._paramfinder.findall(arg_string):
654 # args may need whitespace removing (before removing quotes)
655 arg = arg.strip()
656 listmatch = self._list_arg.match(arg)
657 if listmatch:
658 key, val = self._list_handle(listmatch)
659 fun_kwargs[key] = val
660 continue
661 keymatch = self._key_arg.match(arg)
662 if keymatch:
663 val = keymatch.group(2)
664 if not val in ("'None'", '"None"'):
665 # Special case a quoted None
666 val = self._unquote(val)
667 fun_kwargs[keymatch.group(1)] = val
668 continue
669
670 fun_args.append(self._unquote(arg))
671 else:
672 # allows for function names without (args)
673 return check, (), {}, None
674
675 # Default must be deleted if the value is specified too,
676 # otherwise the check function will get a spurious "default" keyword arg
677 try:
678 default = fun_kwargs.pop('default', None)
679 except AttributeError:
680 # Python 2.2 compatibility
681 default = None
682 try:
683 default = fun_kwargs['default']
684 del fun_kwargs['default']
685 except KeyError:
686 pass
687
688 return fun_name, fun_args, fun_kwargs, default
689
690
691 def _unquote(self, val):
692 """Unquote a value if necessary."""
693 if (len(val) >= 2) and (val[0] in ("'", '"')) and (val[0] == val[-1]):
694 val = val[1:-1]
695 return val
696
697
698 def _list_handle(self, listmatch):
699 """Take apart a ``keyword=list('val, 'val')`` type string."""
700 out = []
701 name = listmatch.group(1)
702 args = listmatch.group(2)
703 for arg in self._list_members.findall(args):
704 out.append(self._unquote(arg))
705 return name, out
706
707
708 def _pass(self, value):
709 """
710 Dummy check that always passes
711
712 >>> vtor.check('', 0)
713 0
714 >>> vtor.check('', '0')
715 '0'
716 """
717 return value
718
719
720 def get_default_value(self, check):
721 """
722 Given a check, return the default value for the check
723 (converted to the right type).
724
725 If the check doesn't specify a default value then a
726 ``KeyError`` will be raised.
727 """
728 fun_name, fun_args, fun_kwargs, default = self._parse_with_caching(check)
729 if default is None:
730 raise KeyError('Check "%s" has no default value.' % check)
731 value = self._handle_none(default)
732 if value is None:
733 return value
734 return self._check_value(value, fun_name, fun_args, fun_kwargs)
735
736
737 def _is_num_param(names, values, to_float=False):
738 """
739 Return numbers from inputs or raise VdtParamError.
740
741 Lets ``None`` pass through.
742 Pass in keyword argument ``to_float=True`` to
743 use float for the conversion rather than int.
744
745 >>> _is_num_param(('', ''), (0, 1.0))
746 [0, 1]
747 >>> _is_num_param(('', ''), (0, 1.0), to_float=True)
748 [0.0, 1.0]
749 >>> _is_num_param(('a'), ('a'))
750 Traceback (most recent call last):
751 VdtParamError: passed an incorrect value "a" for parameter "a".
752 """
753 fun = to_float and float or int
754 out_params = []
755 for (name, val) in zip(names, values):
756 if val is None:
757 out_params.append(val)
758 elif isinstance(val, (int, long, float, StringTypes)):
759 try:
760 out_params.append(fun(val))
761 except ValueError, e:
762 raise VdtParamError(name, val)
763 else:
764 raise VdtParamError(name, val)
765 return out_params
766
767
768 # built in checks
769 # you can override these by setting the appropriate name
770 # in Validator.functions
771 # note: if the params are specified wrongly in your input string,
772 # you will also raise errors.
773
774 def is_integer(value, min=None, max=None):
775 """
776 A check that tests that a given value is an integer (int, or long)
777 and optionally, between bounds. A negative value is accepted, while
778 a float will fail.
779
780 If the value is a string, then the conversion is done - if possible.
781 Otherwise a VdtError is raised.
782
783 >>> vtor.check('integer', '-1')
784 -1
785 >>> vtor.check('integer', '0')
786 0
787 >>> vtor.check('integer', 9)
788 9
789 >>> vtor.check('integer', 'a')
790 Traceback (most recent call last):
791 VdtTypeError: the value "a" is of the wrong type.
792 >>> vtor.check('integer', '2.2')
793 Traceback (most recent call last):
794 VdtTypeError: the value "2.2" is of the wrong type.
795 >>> vtor.check('integer(10)', '20')
796 20
797 >>> vtor.check('integer(max=20)', '15')
798 15
799 >>> vtor.check('integer(10)', '9')
800 Traceback (most recent call last):
801 VdtValueTooSmallError: the value "9" is too small.
802 >>> vtor.check('integer(10)', 9)
803 Traceback (most recent call last):
804 VdtValueTooSmallError: the value "9" is too small.
805 >>> vtor.check('integer(max=20)', '35')
806 Traceback (most recent call last):
807 VdtValueTooBigError: the value "35" is too big.
808 >>> vtor.check('integer(max=20)', 35)
809 Traceback (most recent call last):
810 VdtValueTooBigError: the value "35" is too big.
811 >>> vtor.check('integer(0, 9)', False)
812 0
813 """
814 (min_val, max_val) = _is_num_param(('min', 'max'), (min, max))
815 if not isinstance(value, (int, long, StringTypes)):
816 raise VdtTypeError(value)
817 if isinstance(value, StringTypes):
818 # if it's a string - does it represent an integer ?
819 try:
820 value = int(value)
821 except ValueError:
822 raise VdtTypeError(value)
823 if (min_val is not None) and (value < min_val):
824 raise VdtValueTooSmallError(value)
825 if (max_val is not None) and (value > max_val):
826 raise VdtValueTooBigError(value)
827 return value
828
829
830 def is_float(value, min=None, max=None):
831 """
832 A check that tests that a given value is a float
833 (an integer will be accepted), and optionally - that it is between bounds.
834
835 If the value is a string, then the conversion is done - if possible.
836 Otherwise a VdtError is raised.
837
838 This can accept negative values.
839
840 >>> vtor.check('float', '2')
841 2.0
842
843 From now on we multiply the value to avoid comparing decimals
844
845 >>> vtor.check('float', '-6.8') * 10
846 -68.0
847 >>> vtor.check('float', '12.2') * 10
848 122.0
849 >>> vtor.check('float', 8.4) * 10
850 84.0
851 >>> vtor.check('float', 'a')
852 Traceback (most recent call last):
853 VdtTypeError: the value "a" is of the wrong type.
854 >>> vtor.check('float(10.1)', '10.2') * 10
855 102.0
856 >>> vtor.check('float(max=20.2)', '15.1') * 10
857 151.0
858 >>> vtor.check('float(10.0)', '9.0')
859 Traceback (most recent call last):
860 VdtValueTooSmallError: the value "9.0" is too small.
861 >>> vtor.check('float(max=20.0)', '35.0')
862 Traceback (most recent call last):
863 VdtValueTooBigError: the value "35.0" is too big.
864 """
865 (min_val, max_val) = _is_num_param(
866 ('min', 'max'), (min, max), to_float=True)
867 if not isinstance(value, (int, long, float, StringTypes)):
868 raise VdtTypeError(value)
869 if not isinstance(value, float):
870 # if it's a string - does it represent a float ?
871 try:
872 value = float(value)
873 except ValueError:
874 raise VdtTypeError(value)
875 if (min_val is not None) and (value < min_val):
876 raise VdtValueTooSmallError(value)
877 if (max_val is not None) and (value > max_val):
878 raise VdtValueTooBigError(value)
879 return value
880
881
882 bool_dict = {
883 True: True, 'on': True, '1': True, 'true': True, 'yes': True,
884 False: False, 'off': False, '0': False, 'false': False, 'no': False,
885 }
886
887
888 def is_boolean(value):
889 """
890 Check if the value represents a boolean.
891
892 >>> vtor.check('boolean', 0)
893 0
894 >>> vtor.check('boolean', False)
895 0
896 >>> vtor.check('boolean', '0')
897 0
898 >>> vtor.check('boolean', 'off')
899 0
900 >>> vtor.check('boolean', 'false')
901 0
902 >>> vtor.check('boolean', 'no')
903 0
904 >>> vtor.check('boolean', 'nO')
905 0
906 >>> vtor.check('boolean', 'NO')
907 0
908 >>> vtor.check('boolean', 1)
909 1
910 >>> vtor.check('boolean', True)
911 1
912 >>> vtor.check('boolean', '1')
913 1
914 >>> vtor.check('boolean', 'on')
915 1
916 >>> vtor.check('boolean', 'true')
917 1
918 >>> vtor.check('boolean', 'yes')
919 1
920 >>> vtor.check('boolean', 'Yes')
921 1
922 >>> vtor.check('boolean', 'YES')
923 1
924 >>> vtor.check('boolean', '')
925 Traceback (most recent call last):
926 VdtTypeError: the value "" is of the wrong type.
927 >>> vtor.check('boolean', 'up')
928 Traceback (most recent call last):
929 VdtTypeError: the value "up" is of the wrong type.
930
931 """
932 if isinstance(value, StringTypes):
933 try:
934 return bool_dict[value.lower()]
935 except KeyError:
936 raise VdtTypeError(value)
937 # we do an equality test rather than an identity test
938 # this ensures Python 2.2 compatibilty
939 # and allows 0 and 1 to represent True and False
940 if value == False:
941 return False
942 elif value == True:
943 return True
944 else:
945 raise VdtTypeError(value)
946
947
948 def is_ip_addr(value):
949 """
950 Check that the supplied value is an Internet Protocol address, v.4,
951 represented by a dotted-quad string, i.e. '1.2.3.4'.
952
953 >>> vtor.check('ip_addr', '1 ')
954 '1'
955 >>> vtor.check('ip_addr', ' 1.2')
956 '1.2'
957 >>> vtor.check('ip_addr', ' 1.2.3 ')
958 '1.2.3'
959 >>> vtor.check('ip_addr', '1.2.3.4')
960 '1.2.3.4'
961 >>> vtor.check('ip_addr', '0.0.0.0')
962 '0.0.0.0'
963 >>> vtor.check('ip_addr', '255.255.255.255')
964 '255.255.255.255'
965 >>> vtor.check('ip_addr', '255.255.255.256')
966 Traceback (most recent call last):
967 VdtValueError: the value "255.255.255.256" is unacceptable.
968 >>> vtor.check('ip_addr', '1.2.3.4.5')
969 Traceback (most recent call last):
970 VdtValueError: the value "1.2.3.4.5" is unacceptable.
971 >>> vtor.check('ip_addr', '1.2.3. 4')
972 Traceback (most recent call last):
973 VdtValueError: the value "1.2.3. 4" is unacceptable.
974 >>> vtor.check('ip_addr', 0)
975 Traceback (most recent call last):
976 VdtTypeError: the value "0" is of the wrong type.
977 """
978 if not isinstance(value, StringTypes):
979 raise VdtTypeError(value)
980 value = value.strip()
981 try:
982 dottedQuadToNum(value)
983 except ValueError:
984 raise VdtValueError(value)
985 return value
986
987
988 def is_list(value, min=None, max=None):
989 """
990 Check that the value is a list of values.
991
992 You can optionally specify the minimum and maximum number of members.
993
994 It does no check on list members.
995
996 >>> vtor.check('list', ())
997 []
998 >>> vtor.check('list', [])
999 []
1000 >>> vtor.check('list', (1, 2))
1001 [1, 2]
1002 >>> vtor.check('list', [1, 2])
1003 [1, 2]
1004 >>> vtor.check('list(3)', (1, 2))
1005 Traceback (most recent call last):
1006 VdtValueTooShortError: the value "(1, 2)" is too short.
1007 >>> vtor.check('list(max=5)', (1, 2, 3, 4, 5, 6))
1008 Traceback (most recent call last):
1009 VdtValueTooLongError: the value "(1, 2, 3, 4, 5, 6)" is too long.
1010 >>> vtor.check('list(min=3, max=5)', (1, 2, 3, 4))
1011 [1, 2, 3, 4]
1012 >>> vtor.check('list', 0)
1013 Traceback (most recent call last):
1014 VdtTypeError: the value "0" is of the wrong type.
1015 >>> vtor.check('list', '12')
1016 Traceback (most recent call last):
1017 VdtTypeError: the value "12" is of the wrong type.
1018 """
1019 (min_len, max_len) = _is_num_param(('min', 'max'), (min, max))
1020 if isinstance(value, StringTypes):
1021 raise VdtTypeError(value)
1022 try:
1023 num_members = len(value)
1024 except TypeError:
1025 raise VdtTypeError(value)
1026 if min_len is not None and num_members < min_len:
1027 raise VdtValueTooShortError(value)
1028 if max_len is not None and num_members > max_len:
1029 raise VdtValueTooLongError(value)
1030 return list(value)
1031
1032
1033 def is_tuple(value, min=None, max=None):
1034 """
1035 Check that the value is a tuple of values.
1036
1037 You can optionally specify the minimum and maximum number of members.
1038
1039 It does no check on members.
1040
1041 >>> vtor.check('tuple', ())
1042 ()
1043 >>> vtor.check('tuple', [])
1044 ()
1045 >>> vtor.check('tuple', (1, 2))
1046 (1, 2)
1047 >>> vtor.check('tuple', [1, 2])
1048 (1, 2)
1049 >>> vtor.check('tuple(3)', (1, 2))
1050 Traceback (most recent call last):
1051 VdtValueTooShortError: the value "(1, 2)" is too short.
1052 >>> vtor.check('tuple(max=5)', (1, 2, 3, 4, 5, 6))
1053 Traceback (most recent call last):
1054 VdtValueTooLongError: the value "(1, 2, 3, 4, 5, 6)" is too long.
1055 >>> vtor.check('tuple(min=3, max=5)', (1, 2, 3, 4))
1056 (1, 2, 3, 4)
1057 >>> vtor.check('tuple', 0)
1058 Traceback (most recent call last):
1059 VdtTypeError: the value "0" is of the wrong type.
1060 >>> vtor.check('tuple', '12')
1061 Traceback (most recent call last):
1062 VdtTypeError: the value "12" is of the wrong type.
1063 """
1064 return tuple(is_list(value, min, max))
1065
1066
1067 def is_string(value, min=None, max=None):
1068 """
1069 Check that the supplied value is a string.
1070
1071 You can optionally specify the minimum and maximum number of members.
1072
1073 >>> vtor.check('string', '0')
1074 '0'
1075 >>> vtor.check('string', 0)
1076 Traceback (most recent call last):
1077 VdtTypeError: the value "0" is of the wrong type.
1078 >>> vtor.check('string(2)', '12')
1079 '12'
1080 >>> vtor.check('string(2)', '1')
1081 Traceback (most recent call last):
1082 VdtValueTooShortError: the value "1" is too short.
1083 >>> vtor.check('string(min=2, max=3)', '123')
1084 '123'
1085 >>> vtor.check('string(min=2, max=3)', '1234')
1086 Traceback (most recent call last):
1087 VdtValueTooLongError: the value "1234" is too long.
1088 """
1089 if not isinstance(value, StringTypes):
1090 raise VdtTypeError(value)
1091 (min_len, max_len) = _is_num_param(('min', 'max'), (min, max))
1092 try:
1093 num_members = len(value)
1094 except TypeError:
1095 raise VdtTypeError(value)
1096 if min_len is not None and num_members < min_len:
1097 raise VdtValueTooShortError(value)
1098 if max_len is not None and num_members > max_len:
1099 raise VdtValueTooLongError(value)
1100 return value
1101
1102
1103 def is_int_list(value, min=None, max=None):
1104 """
1105 Check that the value is a list of integers.
1106
1107 You can optionally specify the minimum and maximum number of members.
1108
1109 Each list member is checked that it is an integer.
1110
1111 >>> vtor.check('int_list', ())
1112 []
1113 >>> vtor.check('int_list', [])
1114 []
1115 >>> vtor.check('int_list', (1, 2))
1116 [1, 2]
1117 >>> vtor.check('int_list', [1, 2])
1118 [1, 2]
1119 >>> vtor.check('int_list', [1, 'a'])
1120 Traceback (most recent call last):
1121 VdtTypeError: the value "a" is of the wrong type.
1122 """
1123 return [is_integer(mem) for mem in is_list(value, min, max)]
1124
1125
1126 def is_bool_list(value, min=None, max=None):
1127 """
1128 Check that the value is a list of booleans.
1129
1130 You can optionally specify the minimum and maximum number of members.
1131
1132 Each list member is checked that it is a boolean.
1133
1134 >>> vtor.check('bool_list', ())
1135 []
1136 >>> vtor.check('bool_list', [])
1137 []
1138 >>> check_res = vtor.check('bool_list', (True, False))
1139 >>> check_res == [True, False]
1140 1
1141 >>> check_res = vtor.check('bool_list', [True, False])
1142 >>> check_res == [True, False]
1143 1
1144 >>> vtor.check('bool_list', [True, 'a'])
1145 Traceback (most recent call last):
1146 VdtTypeError: the value "a" is of the wrong type.
1147 """
1148 return [is_boolean(mem) for mem in is_list(value, min, max)]
1149
1150
1151 def is_float_list(value, min=None, max=None):
1152 """
1153 Check that the value is a list of floats.
1154
1155 You can optionally specify the minimum and maximum number of members.
1156
1157 Each list member is checked that it is a float.
1158
1159 >>> vtor.check('float_list', ())
1160 []
1161 >>> vtor.check('float_list', [])
1162 []
1163 >>> vtor.check('float_list', (1, 2.0))
1164 [1.0, 2.0]
1165 >>> vtor.check('float_list', [1, 2.0])
1166 [1.0, 2.0]
1167 >>> vtor.check('float_list', [1, 'a'])
1168 Traceback (most recent call last):
1169 VdtTypeError: the value "a" is of the wrong type.
1170 """
1171 return [is_float(mem) for mem in is_list(value, min, max)]
1172
1173
1174 def is_string_list(value, min=None, max=None):
1175 """
1176 Check that the value is a list of strings.
1177
1178 You can optionally specify the minimum and maximum number of members.
1179
1180 Each list member is checked that it is a string.
1181
1182 >>> vtor.check('string_list', ())
1183 []
1184 >>> vtor.check('string_list', [])
1185 []
1186 >>> vtor.check('string_list', ('a', 'b'))
1187 ['a', 'b']
1188 >>> vtor.check('string_list', ['a', 1])
1189 Traceback (most recent call last):
1190 VdtTypeError: the value "1" is of the wrong type.
1191 >>> vtor.check('string_list', 'hello')
1192 Traceback (most recent call last):
1193 VdtTypeError: the value "hello" is of the wrong type.
1194 """
1195 if isinstance(value, StringTypes):
1196 raise VdtTypeError(value)
1197 return [is_string(mem) for mem in is_list(value, min, max)]
1198
1199
1200 def is_ip_addr_list(value, min=None, max=None):
1201 """
1202 Check that the value is a list of IP addresses.
1203
1204 You can optionally specify the minimum and maximum number of members.
1205
1206 Each list member is checked that it is an IP address.
1207
1208 >>> vtor.check('ip_addr_list', ())
1209 []
1210 >>> vtor.check('ip_addr_list', [])
1211 []
1212 >>> vtor.check('ip_addr_list', ('1.2.3.4', '5.6.7.8'))
1213 ['1.2.3.4', '5.6.7.8']
1214 >>> vtor.check('ip_addr_list', ['a'])
1215 Traceback (most recent call last):
1216 VdtValueError: the value "a" is unacceptable.
1217 """
1218 return [is_ip_addr(mem) for mem in is_list(value, min, max)]
1219
1220
1221 fun_dict = {
1222 'integer': is_integer,
1223 'float': is_float,
1224 'ip_addr': is_ip_addr,
1225 'string': is_string,
1226 'boolean': is_boolean,
1227 }
1228
1229
1230 def is_mixed_list(value, *args):
1231 """
1232 Check that the value is a list.
1233 Allow specifying the type of each member.
1234 Work on lists of specific lengths.
1235
1236 You specify each member as a positional argument specifying type
1237
1238 Each type should be one of the following strings :
1239 'integer', 'float', 'ip_addr', 'string', 'boolean'
1240
1241 So you can specify a list of two strings, followed by
1242 two integers as :
1243
1244 mixed_list('string', 'string', 'integer', 'integer')
1245
1246 The length of the list must match the number of positional
1247 arguments you supply.
1248
1249 >>> mix_str = "mixed_list('integer', 'float', 'ip_addr', 'string', 'boolean')"
1250 >>> check_res = vtor.check(mix_str, (1, 2.0, '1.2.3.4', 'a', True))
1251 >>> check_res == [1, 2.0, '1.2.3.4', 'a', True]
1252 1
1253 >>> check_res = vtor.check(mix_str, ('1', '2.0', '1.2.3.4', 'a', 'True'))
1254 >>> check_res == [1, 2.0, '1.2.3.4', 'a', True]
1255 1
1256 >>> vtor.check(mix_str, ('b', 2.0, '1.2.3.4', 'a', True))
1257 Traceback (most recent call last):
1258 VdtTypeError: the value "b" is of the wrong type.
1259 >>> vtor.check(mix_str, (1, 2.0, '1.2.3.4', 'a'))
1260 Traceback (most recent call last):
1261 VdtValueTooShortError: the value "(1, 2.0, '1.2.3.4', 'a')" is too short.
1262 >>> vtor.check(mix_str, (1, 2.0, '1.2.3.4', 'a', 1, 'b'))
1263 Traceback (most recent call last):
1264 VdtValueTooLongError: the value "(1, 2.0, '1.2.3.4', 'a', 1, 'b')" is too long.
1265 >>> vtor.check(mix_str, 0)
1266 Traceback (most recent call last):
1267 VdtTypeError: the value "0" is of the wrong type.
1268
1269 This test requires an elaborate setup, because of a change in error string
1270 output from the interpreter between Python 2.2 and 2.3 .
1271
1272 >>> res_seq = (
1273 ... 'passed an incorrect value "',
1274 ... 'yoda',
1275 ... '" for parameter "mixed_list".',
1276 ... )
1277 >>> if INTP_VER == (2, 2):
1278 ... res_str = "".join(res_seq)
1279 ... else:
1280 ... res_str = "'".join(res_seq)
1281 >>> try:
1282 ... vtor.check('mixed_list("yoda")', ('a'))
1283 ... except VdtParamError, err:
1284 ... str(err) == res_str
1285 1
1286 """
1287 try:
1288 length = len(value)
1289 except TypeError:
1290 raise VdtTypeError(value)
1291 if length < len(args):
1292 raise VdtValueTooShortError(value)
1293 elif length > len(args):
1294 raise VdtValueTooLongError(value)
1295 try:
1296 return [fun_dict[arg](val) for arg, val in zip(args, value)]
1297 except KeyError, e:
1298 raise VdtParamError('mixed_list', e)
1299
1300
1301 def is_option(value, *options):
1302 """
1303 This check matches the value to any of a set of options.
1304
1305 >>> vtor.check('option("yoda", "jedi")', 'yoda')
1306 'yoda'
1307 >>> vtor.check('option("yoda", "jedi")', 'jed')
1308 Traceback (most recent call last):
1309 VdtValueError: the value "jed" is unacceptable.
1310 >>> vtor.check('option("yoda", "jedi")', 0)
1311 Traceback (most recent call last):
1312 VdtTypeError: the value "0" is of the wrong type.
1313 """
1314 if not isinstance(value, StringTypes):
1315 raise VdtTypeError(value)
1316 if not value in options:
1317 raise VdtValueError(value)
1318 return value
1319
1320
1321 def _test(value, *args, **keywargs):
1322 """
1323 A function that exists for test purposes.
1324
1325 >>> checks = [
1326 ... '3, 6, min=1, max=3, test=list(a, b, c)',
1327 ... '3',
1328 ... '3, 6',
1329 ... '3,',
1330 ... 'min=1, test="a b c"',
1331 ... 'min=5, test="a, b, c"',
1332 ... 'min=1, max=3, test="a, b, c"',
1333 ... 'min=-100, test=-99',
1334 ... 'min=1, max=3',
1335 ... '3, 6, test="36"',
1336 ... '3, 6, test="a, b, c"',
1337 ... '3, max=3, test=list("a", "b", "c")',
1338 ... '''3, max=3, test=list("'a'", 'b', "x=(c)")''',
1339 ... "test='x=fish(3)'",
1340 ... ]
1341 >>> v = Validator({'test': _test})
1342 >>> for entry in checks:
1343 ... print v.check(('test(%s)' % entry), 3)
1344 (3, ('3', '6'), {'test': ['a', 'b', 'c'], 'max': '3', 'min': '1'})
1345 (3, ('3',), {})
1346 (3, ('3', '6'), {})
1347 (3, ('3',), {})
1348 (3, (), {'test': 'a b c', 'min': '1'})
1349 (3, (), {'test': 'a, b, c', 'min': '5'})
1350 (3, (), {'test': 'a, b, c', 'max': '3', 'min': '1'})
1351 (3, (), {'test': '-99', 'min': '-100'})
1352 (3, (), {'max': '3', 'min': '1'})
1353 (3, ('3', '6'), {'test': '36'})
1354 (3, ('3', '6'), {'test': 'a, b, c'})
1355 (3, ('3',), {'test': ['a', 'b', 'c'], 'max': '3'})
1356 (3, ('3',), {'test': ["'a'", 'b', 'x=(c)'], 'max': '3'})
1357 (3, (), {'test': 'x=fish(3)'})
1358
1359 >>> v = Validator()
1360 >>> v.check('integer(default=6)', '3')
1361 3
1362 >>> v.check('integer(default=6)', None, True)
1363 6
1364 >>> v.get_default_value('integer(default=6)')
1365 6
1366 >>> v.get_default_value('float(default=6)')
1367 6.0
1368 >>> v.get_default_value('pass(default=None)')
1369 >>> v.get_default_value("string(default='None')")
1370 'None'
1371 >>> v.get_default_value('pass')
1372 Traceback (most recent call last):
1373 KeyError: 'Check "pass" has no default value.'
1374 >>> v.get_default_value('pass(default=list(1, 2, 3, 4))')
1375 ['1', '2', '3', '4']
1376
1377 >>> v = Validator()
1378 >>> v.check("pass(default=None)", None, True)
1379 >>> v.check("pass(default='None')", None, True)
1380 'None'
1381 >>> v.check('pass(default="None")', None, True)
1382 'None'
1383 >>> v.check('pass(default=list(1, 2, 3, 4))', None, True)
1384 ['1', '2', '3', '4']
1385
1386 Bug test for unicode arguments
1387 >>> v = Validator()
1388 >>> v.check(u'string(min=4)', u'test')
1389 u'test'
1390
1391 >>> v = Validator()
1392 >>> v.get_default_value(u'string(min=4, default="1234")')
1393 u'1234'
1394 >>> v.check(u'string(min=4, default="1234")', u'test')
1395 u'test'
1396
1397 >>> v = Validator()
1398 >>> default = v.get_default_value('string(default=None)')
1399 >>> default == None
1400 1
1401 """
1402 return (value, args, keywargs)
1403
1404
1405 if __name__ == '__main__':
1406 # run the code tests in doctest format
1407 import doctest
1408 m = sys.modules.get('__main__')
1409 globs = m.__dict__.copy()
1410 globs.update({
1411 'INTP_VER': INTP_VER,
1412 'vtor': Validator(),
1413 })
1414 doctest.testmod(m, globs=globs)
@@ -0,0 +1,170 b''
1 #!/usr/bin/env python
2 # encoding: utf-8
3
4 # GUID.py
5 # Version 2.6
6 #
7 # Copyright (c) 2006 Conan C. Albrecht
8 #
9 # Permission is hereby granted, free of charge, to any person obtaining a copy
10 # of this software and associated documentation files (the "Software"), to deal
11 # in the Software without restriction, including without limitation the rights
12 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 # copies of the Software, and to permit persons to whom the Software is furnished
14 # to do so, subject to the following conditions:
15 #
16 # The above copyright notice and this permission notice shall be included in all
17 # copies or substantial portions of the Software.
18 #
19 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
20 # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
21 # PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
22 # FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
23 # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24 # DEALINGS IN THE SOFTWARE.
25
26
27
28 ##################################################################################################
29 ### A globally-unique identifier made up of time and ip and 8 digits for a counter:
30 ### each GUID is 40 characters wide
31 ###
32 ### A globally unique identifier that combines ip, time, and a counter. Since the
33 ### time is listed first, you can sort records by guid. You can also extract the time
34 ### and ip if needed.
35 ###
36 ### Since the counter has eight hex characters, you can create up to
37 ### 0xffffffff (4294967295) GUIDs every millisecond. If your processor
38 ### is somehow fast enough to create more than that in a millisecond (looking
39 ### toward the future, of course), the function will wait until the next
40 ### millisecond to return.
41 ###
42 ### GUIDs make wonderful database keys. They require no access to the
43 ### database (to get the max index number), they are extremely unique, and they sort
44 ### automatically by time. GUIDs prevent key clashes when merging
45 ### two databases together, combining data, or generating keys in distributed
46 ### systems.
47 ###
48 ### There is an Internet Draft for UUIDs, but this module does not implement it.
49 ### If the draft catches on, perhaps I'll conform the module to it.
50 ###
51
52
53 # Changelog
54 # Sometime, 1997 Created the Java version of GUID
55 # Went through many versions in Java
56 # Sometime, 2002 Created the Python version of GUID, mirroring the Java version
57 # November 24, 2003 Changed Python version to be more pythonic, took out object and made just a module
58 # December 2, 2003 Fixed duplicating GUIDs. Sometimes they duplicate if multiples are created
59 # in the same millisecond (it checks the last 100 GUIDs now and has a larger random part)
60 # December 9, 2003 Fixed MAX_RANDOM, which was going over sys.maxint
61 # June 12, 2004 Allowed a custom IP address to be sent in rather than always using the
62 # local IP address.
63 # November 4, 2005 Changed the random part to a counter variable. Now GUIDs are totally
64 # unique and more efficient, as long as they are created by only
65 # on runtime on a given machine. The counter part is after the time
66 # part so it sorts correctly.
67 # November 8, 2005 The counter variable now starts at a random long now and cycles
68 # around. This is in case two guids are created on the same
69 # machine at the same millisecond (by different processes). Even though
70 # it is possible the GUID can be created, this makes it highly unlikely
71 # since the counter will likely be different.
72 # November 11, 2005 Fixed a bug in the new IP getting algorithm. Also, use IPv6 range
73 # for IP when we make it up (when it's no accessible)
74 # November 21, 2005 Added better IP-finding code. It finds IP address better now.
75 # January 5, 2006 Fixed a small bug caused in old versions of python (random module use)
76
77 import math
78 import socket
79 import random
80 import sys
81 import time
82 import threading
83
84
85
86 #############################
87 ### global module variables
88
89 #Makes a hex IP from a decimal dot-separated ip (eg: 127.0.0.1)
90 make_hexip = lambda ip: ''.join(["%04x" % long(i) for i in ip.split('.')]) # leave space for ip v6 (65K in each sub)
91
92 MAX_COUNTER = 0xfffffffe
93 counter = 0L
94 firstcounter = MAX_COUNTER
95 lasttime = 0
96 ip = ''
97 lock = threading.RLock()
98 try: # only need to get the IP addresss once
99 ip = socket.getaddrinfo(socket.gethostname(),0)[-1][-1][0]
100 hexip = make_hexip(ip)
101 except: # if we don't have an ip, default to someting in the 10.x.x.x private range
102 ip = '10'
103 rand = random.Random()
104 for i in range(3):
105 ip += '.' + str(rand.randrange(1, 0xffff)) # might as well use IPv6 range if we're making it up
106 hexip = make_hexip(ip)
107
108
109 #################################
110 ### Public module functions
111
112 def generate(ip=None):
113 '''Generates a new guid. A guid is unique in space and time because it combines
114 the machine IP with the current time in milliseconds. Be careful about sending in
115 a specified IP address because the ip makes it unique in space. You could send in
116 the same IP address that is created on another machine.
117 '''
118 global counter, firstcounter, lasttime
119 lock.acquire() # can't generate two guids at the same time
120 try:
121 parts = []
122
123 # do we need to wait for the next millisecond (are we out of counters?)
124 now = long(time.time() * 1000)
125 while lasttime == now and counter == firstcounter:
126 time.sleep(.01)
127 now = long(time.time() * 1000)
128
129 # time part
130 parts.append("%016x" % now)
131
132 # counter part
133 if lasttime != now: # time to start counter over since we have a different millisecond
134 firstcounter = long(random.uniform(1, MAX_COUNTER)) # start at random position
135 counter = firstcounter
136 counter += 1
137 if counter > MAX_COUNTER:
138 counter = 0
139 lasttime = now
140 parts.append("%08x" % (counter))
141
142 # ip part
143 parts.append(hexip)
144
145 # put them all together
146 return ''.join(parts)
147 finally:
148 lock.release()
149
150
151 def extract_time(guid):
152 '''Extracts the time portion out of the guid and returns the
153 number of seconds since the epoch as a float'''
154 return float(long(guid[0:16], 16)) / 1000.0
155
156
157 def extract_counter(guid):
158 '''Extracts the counter from the guid (returns the bits in decimal)'''
159 return int(guid[16:24], 16)
160
161
162 def extract_ip(guid):
163 '''Extracts the ip portion out of the guid and returns it
164 as a string like 10.10.10.10'''
165 # there's probably a more elegant way to do this
166 thisip = []
167 for index in range(24, 40, 4):
168 thisip.append(str(int(guid[index: index + 4], 16)))
169 return '.'.join(thisip)
170
@@ -0,0 +1,23 b''
1 # encoding: utf-8
2 """The IPython1 kernel.
3
4 The IPython kernel actually refers to three things:
5
6 * The IPython Engine
7 * The IPython Controller
8 * Clients to the IPython Controller
9
10 The kernel module implements the engine, controller and client and all the
11 network protocols needed for the various entities to talk to each other.
12
13 An end user should probably begin by looking at the `client.py` module
14 if they need blocking clients or in `asyncclient.py` if they want asynchronous,
15 deferred/Twisted using clients.
16 """
17 __docformat__ = "restructuredtext en"
18 #-------------------------------------------------------------------------------
19 # Copyright (C) 2008 The IPython Development Team
20 #
21 # Distributed under the terms of the BSD License. The full license is in
22 # the file COPYING, distributed as part of this software.
23 #------------------------------------------------------------------------------- No newline at end of file
@@ -0,0 +1,41 b''
1 # encoding: utf-8
2
3 """Asynchronous clients for the IPython controller.
4
5 This module has clients for using the various interfaces of the controller
6 in a fully asynchronous manner. This means that you will need to run the
7 Twisted reactor yourself and that all methods of the client classes return
8 deferreds to the result.
9
10 The main methods are are `get_*_client` and `get_client`.
11 """
12
13 __docformat__ = "restructuredtext en"
14
15 #-------------------------------------------------------------------------------
16 # Copyright (C) 2008 The IPython Development Team
17 #
18 # Distributed under the terms of the BSD License. The full license is in
19 # the file COPYING, distributed as part of this software.
20 #-------------------------------------------------------------------------------
21
22 #-------------------------------------------------------------------------------
23 # Imports
24 #-------------------------------------------------------------------------------
25
26 from IPython.kernel import codeutil
27 from IPython.kernel.clientconnector import ClientConnector
28
29 # Other things that the user will need
30 from IPython.kernel.task import Task
31 from IPython.kernel.error import CompositeError
32
33 #-------------------------------------------------------------------------------
34 # Code
35 #-------------------------------------------------------------------------------
36
37 _client_tub = ClientConnector()
38 get_multiengine_client = _client_tub.get_multiengine_client
39 get_task_client = _client_tub.get_task_client
40 get_client = _client_tub.get_client
41
@@ -0,0 +1,96 b''
1 # encoding: utf-8
2
3 """This module contains blocking clients for the controller interfaces.
4
5 Unlike the clients in `asyncclient.py`, the clients in this module are fully
6 blocking. This means that methods on the clients return the actual results
7 rather than a deferred to the result. Also, we manage the Twisted reactor
8 for you. This is done by running the reactor in a thread.
9
10 The main classes in this module are:
11
12 * MultiEngineClient
13 * TaskClient
14 * Task
15 * CompositeError
16 """
17
18 __docformat__ = "restructuredtext en"
19
20 #-------------------------------------------------------------------------------
21 # Copyright (C) 2008 The IPython Development Team
22 #
23 # Distributed under the terms of the BSD License. The full license is in
24 # the file COPYING, distributed as part of this software.
25 #-------------------------------------------------------------------------------
26
27 #-------------------------------------------------------------------------------
28 # Imports
29 #-------------------------------------------------------------------------------
30
31 import sys
32
33 # from IPython.tools import growl
34 # growl.start("IPython1 Client")
35
36
37 from twisted.internet import reactor
38 from IPython.kernel.clientconnector import ClientConnector
39 from IPython.kernel.twistedutil import ReactorInThread
40 from IPython.kernel.twistedutil import blockingCallFromThread
41
42 # These enable various things
43 from IPython.kernel import codeutil
44 import IPython.kernel.magic
45
46 # Other things that the user will need
47 from IPython.kernel.task import Task
48 from IPython.kernel.error import CompositeError
49
50 #-------------------------------------------------------------------------------
51 # Code
52 #-------------------------------------------------------------------------------
53
54 _client_tub = ClientConnector()
55
56
57 def get_multiengine_client(furl_or_file=''):
58 """Get the blocking MultiEngine client.
59
60 :Parameters:
61 furl_or_file : str
62 A furl or a filename containing a furl. If empty, the
63 default furl_file will be used
64
65 :Returns:
66 The connected MultiEngineClient instance
67 """
68 client = blockingCallFromThread(_client_tub.get_multiengine_client,
69 furl_or_file)
70 return client.adapt_to_blocking_client()
71
72 def get_task_client(furl_or_file=''):
73 """Get the blocking Task client.
74
75 :Parameters:
76 furl_or_file : str
77 A furl or a filename containing a furl. If empty, the
78 default furl_file will be used
79
80 :Returns:
81 The connected TaskClient instance
82 """
83 client = blockingCallFromThread(_client_tub.get_task_client,
84 furl_or_file)
85 return client.adapt_to_blocking_client()
86
87
88 MultiEngineClient = get_multiengine_client
89 TaskClient = get_task_client
90
91
92
93 # Now we start the reactor in a thread
94 rit = ReactorInThread()
95 rit.setDaemon(True)
96 rit.start() No newline at end of file
@@ -0,0 +1,150 b''
1 # encoding: utf-8
2
3 """A class for handling client connections to the controller."""
4
5 __docformat__ = "restructuredtext en"
6
7 #-------------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
13
14 #-------------------------------------------------------------------------------
15 # Imports
16 #-------------------------------------------------------------------------------
17
18 from twisted.internet import defer
19
20 from IPython.kernel.fcutil import Tub, UnauthenticatedTub
21
22 from IPython.kernel.config import config_manager as kernel_config_manager
23 from IPython.config.cutils import import_item
24 from IPython.kernel.fcutil import find_furl
25
26 co = kernel_config_manager.get_config_obj()
27 client_co = co['client']
28
29 #-------------------------------------------------------------------------------
30 # The ClientConnector class
31 #-------------------------------------------------------------------------------
32
33 class ClientConnector(object):
34 """
35 This class gets remote references from furls and returns the wrapped clients.
36
37 This class is also used in `client.py` and `asyncclient.py` to create
38 a single per client-process Tub.
39 """
40
41 def __init__(self):
42 self._remote_refs = {}
43 self.tub = Tub()
44 self.tub.startService()
45
46 def get_reference(self, furl_or_file):
47 """
48 Get a remote reference using a furl or a file containing a furl.
49
50 Remote references are cached locally so once a remote reference
51 has been retrieved for a given furl, the cached version is
52 returned.
53
54 :Parameters:
55 furl_or_file : str
56 A furl or a filename containing a furl
57
58 :Returns:
59 A deferred to a remote reference
60 """
61 furl = find_furl(furl_or_file)
62 if furl in self._remote_refs:
63 d = defer.succeed(self._remote_refs[furl])
64 else:
65 d = self.tub.getReference(furl)
66 d.addCallback(self.save_ref, furl)
67 return d
68
69 def save_ref(self, ref, furl):
70 """
71 Cache a remote reference by its furl.
72 """
73 self._remote_refs[furl] = ref
74 return ref
75
76 def get_task_client(self, furl_or_file=''):
77 """
78 Get the task controller client.
79
80 This method is a simple wrapper around `get_client` that allow
81 `furl_or_file` to be empty, in which case, the furls is taken
82 from the default furl file given in the configuration.
83
84 :Parameters:
85 furl_or_file : str
86 A furl or a filename containing a furl. If empty, the
87 default furl_file will be used
88
89 :Returns:
90 A deferred to the actual client class
91 """
92 task_co = client_co['client_interfaces']['task']
93 if furl_or_file:
94 ff = furl_or_file
95 else:
96 ff = task_co['furl_file']
97 return self.get_client(ff)
98
99 def get_multiengine_client(self, furl_or_file=''):
100 """
101 Get the multiengine controller client.
102
103 This method is a simple wrapper around `get_client` that allow
104 `furl_or_file` to be empty, in which case, the furls is taken
105 from the default furl file given in the configuration.
106
107 :Parameters:
108 furl_or_file : str
109 A furl or a filename containing a furl. If empty, the
110 default furl_file will be used
111
112 :Returns:
113 A deferred to the actual client class
114 """
115 task_co = client_co['client_interfaces']['multiengine']
116 if furl_or_file:
117 ff = furl_or_file
118 else:
119 ff = task_co['furl_file']
120 return self.get_client(ff)
121
122 def get_client(self, furl_or_file):
123 """
124 Get a remote reference and wrap it in a client by furl.
125
126 This method first gets a remote reference and then calls its
127 `get_client_name` method to find the apprpriate client class
128 that should be used to wrap the remote reference.
129
130 :Parameters:
131 furl_or_file : str
132 A furl or a filename containing a furl
133
134 :Returns:
135 A deferred to the actual client class
136 """
137 furl = find_furl(furl_or_file)
138 d = self.get_reference(furl)
139 def wrap_remote_reference(rr):
140 d = rr.callRemote('get_client_name')
141 d.addCallback(lambda name: import_item(name))
142 def adapt(client_interface):
143 client = client_interface(rr)
144 client.tub = self.tub
145 return client
146 d.addCallback(adapt)
147
148 return d
149 d.addCallback(wrap_remote_reference)
150 return d
@@ -0,0 +1,32 b''
1 # encoding: utf-8
2
3 """General client interfaces."""
4
5 __docformat__ = "restructuredtext en"
6
7 #-------------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
13
14 #-------------------------------------------------------------------------------
15 # Imports
16 #-------------------------------------------------------------------------------
17
18 from zope.interface import Interface, implements
19
20 class IFCClientInterfaceProvider(Interface):
21
22 def remote_get_client_name():
23 """Return a string giving the class which implements a client-side interface.
24
25 The client side of any foolscap connection initially gets a remote reference.
26 Some class is needed to adapt that reference to an interface. This...
27 """
28
29 class IBlockingClientAdaptor(Interface):
30
31 def adapt_to_blocking_client():
32 """""" No newline at end of file
@@ -0,0 +1,39 b''
1 # encoding: utf-8
2
3 """Utilities to enable code objects to be pickled.
4
5 Any process that import this module will be able to pickle code objects. This
6 includes the func_code attribute of any function. Once unpickled, new
7 functions can be built using new.function(code, globals()). Eventually
8 we need to automate all of this so that functions themselves can be pickled.
9
10 Reference: A. Tremols, P Cogolo, "Python Cookbook," p 302-305
11 """
12
13 __docformat__ = "restructuredtext en"
14
15 #-------------------------------------------------------------------------------
16 # Copyright (C) 2008 The IPython Development Team
17 #
18 # Distributed under the terms of the BSD License. The full license is in
19 # the file COPYING, distributed as part of this software.
20 #-------------------------------------------------------------------------------
21
22 #-------------------------------------------------------------------------------
23 # Imports
24 #-------------------------------------------------------------------------------
25
26 import new, types, copy_reg
27
28 def code_ctor(*args):
29 return new.code(*args)
30
31 def reduce_code(co):
32 if co.co_freevars or co.co_cellvars:
33 raise ValueError("Sorry, cannot pickle code objects with closures")
34 return code_ctor, (co.co_argcount, co.co_nlocals, co.co_stacksize,
35 co.co_flags, co.co_code, co.co_consts, co.co_names,
36 co.co_varnames, co.co_filename, co.co_name, co.co_firstlineno,
37 co.co_lnotab)
38
39 copy_reg.pickle(types.CodeType, reduce_code) No newline at end of file
@@ -0,0 +1,125 b''
1 # encoding: utf-8
2
3 """Default kernel configuration."""
4
5 __docformat__ = "restructuredtext en"
6
7 #-------------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
13
14 #-------------------------------------------------------------------------------
15 # Imports
16 #-------------------------------------------------------------------------------
17
18 from IPython.external.configobj import ConfigObj
19 from IPython.config.api import ConfigObjManager
20 from IPython.config.cutils import get_ipython_dir
21
22 default_kernel_config = ConfigObj()
23
24 try:
25 ipython_dir = get_ipython_dir() + '/'
26 except:
27 # This will defaults to the cwd
28 ipython_dir = ''
29
30 #-------------------------------------------------------------------------------
31 # Engine Configuration
32 #-------------------------------------------------------------------------------
33
34 engine_config = dict(
35 logfile = '', # Empty means log to stdout
36 furl_file = ipython_dir + 'ipcontroller-engine.furl'
37 )
38
39 #-------------------------------------------------------------------------------
40 # MPI Configuration
41 #-------------------------------------------------------------------------------
42
43 mpi_config = dict(
44 mpi4py = """from mpi4py import MPI as mpi
45 mpi.size = mpi.COMM_WORLD.Get_size()
46 mpi.rank = mpi.COMM_WORLD.Get_rank()
47 """,
48 pytrilinos = """from PyTrilinos import Epetra
49 class SimpleStruct:
50 pass
51 mpi = SimpleStruct()
52 mpi.rank = 0
53 mpi.size = 0
54 """,
55 default = ''
56 )
57
58 #-------------------------------------------------------------------------------
59 # Controller Configuration
60 #-------------------------------------------------------------------------------
61
62 controller_config = dict(
63
64 logfile = '', # Empty means log to stdout
65 import_statement = '',
66
67 engine_tub = dict(
68 ip = '', # Empty string means all interfaces
69 port = 0, # 0 means pick a port for me
70 location = '', # Empty string means try to set automatically
71 secure = True,
72 cert_file = ipython_dir + 'ipcontroller-engine.pem',
73 ),
74 engine_fc_interface = 'IPython.kernel.enginefc.IFCControllerBase',
75 engine_furl_file = ipython_dir + 'ipcontroller-engine.furl',
76
77 controller_interfaces = dict(
78 # multiengine = dict(
79 # controller_interface = 'IPython.kernel.multiengine.IMultiEngine',
80 # fc_interface = 'IPython.kernel.multienginefc.IFCMultiEngine',
81 # furl_file = 'ipcontroller-mec.furl'
82 # ),
83 task = dict(
84 controller_interface = 'IPython.kernel.task.ITaskController',
85 fc_interface = 'IPython.kernel.taskfc.IFCTaskController',
86 furl_file = ipython_dir + 'ipcontroller-tc.furl'
87 ),
88 multiengine = dict(
89 controller_interface = 'IPython.kernel.multiengine.IMultiEngine',
90 fc_interface = 'IPython.kernel.multienginefc.IFCSynchronousMultiEngine',
91 furl_file = ipython_dir + 'ipcontroller-mec.furl'
92 )
93 ),
94
95 client_tub = dict(
96 ip = '', # Empty string means all interfaces
97 port = 0, # 0 means pick a port for me
98 location = '', # Empty string means try to set automatically
99 secure = True,
100 cert_file = ipython_dir + 'ipcontroller-client.pem'
101 )
102 )
103
104 #-------------------------------------------------------------------------------
105 # Client Configuration
106 #-------------------------------------------------------------------------------
107
108 client_config = dict(
109 client_interfaces = dict(
110 task = dict(
111 furl_file = ipython_dir + 'ipcontroller-tc.furl'
112 ),
113 multiengine = dict(
114 furl_file = ipython_dir + 'ipcontroller-mec.furl'
115 )
116 )
117 )
118
119 default_kernel_config['engine'] = engine_config
120 default_kernel_config['mpi'] = mpi_config
121 default_kernel_config['controller'] = controller_config
122 default_kernel_config['client'] = client_config
123
124
125 config_manager = ConfigObjManager(default_kernel_config, 'IPython.kernel.ini') No newline at end of file
@@ -0,0 +1,178 b''
1 # encoding: utf-8
2 # -*- test-case-name: IPython.kernel.test.test_contexts -*-
3 """Context managers for IPython.
4
5 Python 2.5 introduced the `with` statement, which is based on the context
6 manager protocol. This module offers a few context managers for common cases,
7 which can also be useful as templates for writing new, application-specific
8 managers.
9 """
10
11 from __future__ import with_statement
12
13 __docformat__ = "restructuredtext en"
14
15 #-------------------------------------------------------------------------------
16 # Copyright (C) 2008 The IPython Development Team
17 #
18 # Distributed under the terms of the BSD License. The full license is in
19 # the file COPYING, distributed as part of this software.
20 #-------------------------------------------------------------------------------
21
22 #-------------------------------------------------------------------------------
23 # Imports
24 #-------------------------------------------------------------------------------
25
26 import linecache
27 import sys
28
29 from twisted.internet.error import ConnectionRefusedError
30
31 from IPython.ultraTB import _fixed_getinnerframes, findsource
32 from IPython import ipapi
33
34 from IPython.kernel import error
35
36 #---------------------------------------------------------------------------
37 # Utility functions needed by all context managers.
38 #---------------------------------------------------------------------------
39
40 def remote():
41 """Raises a special exception meant to be caught by context managers.
42 """
43 m = 'Special exception to stop local execution of parallel code.'
44 raise error.StopLocalExecution(m)
45
46
47 def strip_whitespace(source,require_remote=True):
48 """strip leading whitespace from input source.
49
50 :Parameters:
51
52 """
53 remote_mark = 'remote()'
54 # Expand tabs to avoid any confusion.
55 wsource = [l.expandtabs(4) for l in source]
56 # Detect the indentation level
57 done = False
58 for line in wsource:
59 if line.isspace():
60 continue
61 for col,char in enumerate(line):
62 if char != ' ':
63 done = True
64 break
65 if done:
66 break
67 # Now we know how much leading space there is in the code. Next, we
68 # extract up to the first line that has less indentation.
69 # WARNINGS: we skip comments that may be misindented, but we do NOT yet
70 # detect triple quoted strings that may have flush left text.
71 for lno,line in enumerate(wsource):
72 lead = line[:col]
73 if lead.isspace():
74 continue
75 else:
76 if not lead.lstrip().startswith('#'):
77 break
78 # The real 'with' source is up to lno
79 src_lines = [l[col:] for l in wsource[:lno+1]]
80
81 # Finally, check that the source's first non-comment line begins with the
82 # special call 'remote()'
83 if require_remote:
84 for nline,line in enumerate(src_lines):
85 if line.isspace() or line.startswith('#'):
86 continue
87 if line.startswith(remote_mark):
88 break
89 else:
90 raise ValueError('%s call missing at the start of code' %
91 remote_mark)
92 out_lines = src_lines[nline+1:]
93 else:
94 # If the user specified that the remote() call wasn't mandatory
95 out_lines = src_lines
96
97 # src = ''.join(out_lines) # dbg
98 #print 'SRC:\n<<<<<<<>>>>>>>\n%s<<<<<>>>>>>' % src # dbg
99 return ''.join(out_lines)
100
101 class RemoteContextBase(object):
102 def __init__(self):
103 self.ip = ipapi.get()
104
105 def _findsource_file(self,f):
106 linecache.checkcache()
107 s = findsource(f.f_code)
108 lnum = f.f_lineno
109 wsource = s[0][f.f_lineno:]
110 return strip_whitespace(wsource)
111
112 def _findsource_ipython(self,f):
113 from IPython import ipapi
114 self.ip = ipapi.get()
115 buf = self.ip.IP.input_hist_raw[-1].splitlines()[1:]
116 wsource = [l+'\n' for l in buf ]
117
118 return strip_whitespace(wsource)
119
120 def findsource(self,frame):
121 local_ns = frame.f_locals
122 global_ns = frame.f_globals
123 if frame.f_code.co_filename == '<ipython console>':
124 src = self._findsource_ipython(frame)
125 else:
126 src = self._findsource_file(frame)
127 return src
128
129 def __enter__(self):
130 raise NotImplementedError
131
132 def __exit__ (self, etype, value, tb):
133 if issubclass(etype,error.StopLocalExecution):
134 return True
135
136 class RemoteMultiEngine(RemoteContextBase):
137 def __init__(self,mec):
138 self.mec = mec
139 RemoteContextBase.__init__(self)
140
141 def __enter__(self):
142 src = self.findsource(sys._getframe(1))
143 return self.mec.execute(src)
144
145
146 # XXX - Temporary hackish testing, we'll move this into proper tests right
147 # away
148
149 if __name__ == '__main__':
150
151 # XXX - for now, we need a running cluster to be started separately. The
152 # daemon work is almost finished, and will make much of this unnecessary.
153 from IPython.kernel import client
154 mec = client.MultiEngineClient(('127.0.0.1',10105))
155
156 try:
157 mec.get_ids()
158 except ConnectionRefusedError:
159 import os, time
160 os.system('ipcluster -n 2 &')
161 time.sleep(2)
162 mec = client.MultiEngineClient(('127.0.0.1',10105))
163
164 mec.block = False
165
166 import itertools
167 c = itertools.count()
168
169 parallel = RemoteMultiEngine(mec)
170
171 with parallel as pr:
172 # A comment
173 remote() # this means the code below only runs remotely
174 print 'Hello remote world'
175 x = 3.14
176 # Comments are OK
177 # Even misindented.
178 y = x+1
@@ -0,0 +1,376 b''
1 # encoding: utf-8
2 # -*- test-case-name: IPython.kernel.test.test_controllerservice -*-
3
4 """A Twisted Service for the IPython Controller.
5
6 The IPython Controller:
7
8 * Listens for Engines to connect and then manages access to those engines.
9 * Listens for clients and passes commands from client to the Engines.
10 * Exposes an asynchronous interfaces to the Engines which themselves can block.
11 * Acts as a gateway to the Engines.
12
13 The design of the controller is somewhat abstract to allow flexibility in how
14 the controller is presented to clients. This idea is that there is a basic
15 ControllerService class that allows engines to connect to it. But, this
16 basic class has no client interfaces. To expose client interfaces developers
17 provide an adapter that makes the ControllerService look like something. For
18 example, one client interface might support task farming and another might
19 support interactive usage. The important thing is that by using interfaces
20 and adapters, a single controller can be accessed from multiple interfaces.
21 Furthermore, by adapting various client interfaces to various network
22 protocols, each client interface can be exposed to multiple network protocols.
23 See multiengine.py for an example of how to adapt the ControllerService
24 to a client interface.
25 """
26
27 __docformat__ = "restructuredtext en"
28
29 #-------------------------------------------------------------------------------
30 # Copyright (C) 2008 The IPython Development Team
31 #
32 # Distributed under the terms of the BSD License. The full license is in
33 # the file COPYING, distributed as part of this software.
34 #-------------------------------------------------------------------------------
35
36 #-------------------------------------------------------------------------------
37 # Imports
38 #-------------------------------------------------------------------------------
39
40 import os, sys
41
42 from twisted.application import service
43 from twisted.internet import defer, reactor
44 from twisted.python import log, components
45 from zope.interface import Interface, implements, Attribute
46 import zope.interface as zi
47
48 from IPython.kernel.engineservice import \
49 IEngineCore, \
50 IEngineSerialized, \
51 IEngineQueued
52
53 from IPython.config import cutils
54 from IPython.kernel import codeutil
55
56 #-------------------------------------------------------------------------------
57 # Interfaces for the Controller
58 #-------------------------------------------------------------------------------
59
60 class IControllerCore(Interface):
61 """Basic methods any controller must have.
62
63 This is basically the aspect of the controller relevant to the
64 engines and does not assume anything about how the engines will
65 be presented to a client.
66 """
67
68 engines = Attribute("A dict of engine ids and engine instances.")
69
70 def register_engine(remoteEngine, id=None, ip=None, port=None,
71 pid=None):
72 """Register new remote engine.
73
74 The controller can use the ip, port, pid of the engine to do useful things
75 like kill the engines.
76
77 :Parameters:
78 remoteEngine
79 An implementer of IEngineCore, IEngineSerialized and IEngineQueued.
80 id : int
81 Requested id.
82 ip : str
83 IP address the engine is running on.
84 port : int
85 Port the engine is on.
86 pid : int
87 pid of the running engine.
88
89 :Returns: A dict of {'id':id} and possibly other key, value pairs.
90 """
91
92 def unregister_engine(id):
93 """Handle a disconnecting engine.
94
95 :Parameters:
96 id
97 The integer engine id of the engine to unregister.
98 """
99
100 def on_register_engine_do(f, includeID, *args, **kwargs):
101 """Call ``f(*args, **kwargs)`` when an engine is registered.
102
103 :Parameters:
104 includeID : int
105 If True the first argument to f will be the id of the engine.
106 """
107
108 def on_unregister_engine_do(f, includeID, *args, **kwargs):
109 """Call ``f(*args, **kwargs)`` when an engine is unregistered.
110
111 :Parameters:
112 includeID : int
113 If True the first argument to f will be the id of the engine.
114 """
115
116 def on_register_engine_do_not(f):
117 """Stop calling f on engine registration"""
118
119 def on_unregister_engine_do_not(f):
120 """Stop calling f on engine unregistration"""
121
122 def on_n_engines_registered_do(n, f, *arg, **kwargs):
123 """Call f(*args, **kwargs) the first time the nth engine registers."""
124
125 class IControllerBase(IControllerCore):
126 """The basic controller interface."""
127 pass
128
129
130 #-------------------------------------------------------------------------------
131 # Implementation of the ControllerService
132 #-------------------------------------------------------------------------------
133
134 class ControllerService(object, service.Service):
135 """A basic Controller represented as a Twisted Service.
136
137 This class doesn't implement any client notification mechanism. That
138 is up to adapted subclasses.
139 """
140
141 # I also pick up the IService interface by inheritance from service.Service
142 implements(IControllerBase)
143 name = 'ControllerService'
144
145 def __init__(self, maxEngines=511, saveIDs=False):
146 self.saveIDs = saveIDs
147 self.engines = {}
148 self.availableIDs = range(maxEngines,-1,-1) # [511,...,0]
149 self._onRegister = []
150 self._onUnregister = []
151 self._onNRegistered = []
152
153 #---------------------------------------------------------------------------
154 # Methods used to save the engine info to a log file
155 #---------------------------------------------------------------------------
156
157 def _buildEngineInfoString(self, id, ip, port, pid):
158 if id is None:
159 id = -99
160 if ip is None:
161 ip = "-99"
162 if port is None:
163 port = -99
164 if pid is None:
165 pid = -99
166 return "Engine Info: %d %s %d %d" % (id, ip , port, pid)
167
168 def _logEngineInfo(self, id, ip, port, pid):
169 log.msg(self._buildEngineInfoString(id,ip,port,pid))
170
171 def _getEngineInfoLogFile(self):
172 # Store all logs inside the ipython directory
173 ipdir = cutils.get_ipython_dir()
174 pjoin = os.path.join
175 logdir_base = pjoin(ipdir,'log')
176 if not os.path.isdir(logdir_base):
177 os.makedirs(logdir_base)
178 logfile = os.path.join(logdir_base,'ipcontroller-%s-engine-info.log' % os.getpid())
179 return logfile
180
181 def _logEngineInfoToFile(self, id, ip, port, pid):
182 """Log info about an engine to a log file.
183
184 When an engine registers with a ControllerService, the ControllerService
185 saves information about the engine to a log file. That information
186 can be useful for various purposes, such as killing hung engines, etc.
187
188 This method takes the assigned id, ip/port and pid of the engine
189 and saves it to a file of the form:
190
191 ~/.ipython/log/ipcontroller-###-engine-info.log
192
193 where ### is the pid of the controller.
194
195 Each line of this file has the form:
196
197 Engine Info: ip ip port pid
198
199 If any of the entries are not known, they are replaced by -99.
200 """
201
202 fname = self._getEngineInfoLogFile()
203 f = open(fname, 'a')
204 s = self._buildEngineInfoString(id,ip,port,pid)
205 f.write(s + '\n')
206 f.close()
207
208 #---------------------------------------------------------------------------
209 # IControllerCore methods
210 #---------------------------------------------------------------------------
211
212 def register_engine(self, remoteEngine, id=None,
213 ip=None, port=None, pid=None):
214 """Register new engine connection"""
215
216 # What happens if these assertions fail?
217 assert IEngineCore.providedBy(remoteEngine), \
218 "engine passed to register_engine doesn't provide IEngineCore"
219 assert IEngineSerialized.providedBy(remoteEngine), \
220 "engine passed to register_engine doesn't provide IEngineSerialized"
221 assert IEngineQueued.providedBy(remoteEngine), \
222 "engine passed to register_engine doesn't provide IEngineQueued"
223 assert isinstance(id, int) or id is None, \
224 "id to register_engine must be an integer or None"
225 assert isinstance(ip, str) or ip is None, \
226 "ip to register_engine must be a string or None"
227 assert isinstance(port, int) or port is None, \
228 "port to register_engine must be an integer or None"
229 assert isinstance(pid, int) or pid is None, \
230 "pid to register_engine must be an integer or None"
231
232 desiredID = id
233 if desiredID in self.engines.keys():
234 desiredID = None
235
236 if desiredID in self.availableIDs:
237 getID = desiredID
238 self.availableIDs.remove(desiredID)
239 else:
240 getID = self.availableIDs.pop()
241 remoteEngine.id = getID
242 remoteEngine.service = self
243 self.engines[getID] = remoteEngine
244
245 # Log the Engine Information for monitoring purposes
246 self._logEngineInfoToFile(getID, ip, port, pid)
247
248 msg = "registered engine with id: %i" %getID
249 log.msg(msg)
250
251 for i in range(len(self._onRegister)):
252 (f,args,kwargs,ifid) = self._onRegister[i]
253 try:
254 if ifid:
255 f(getID, *args, **kwargs)
256 else:
257 f(*args, **kwargs)
258 except:
259 self._onRegister.pop(i)
260
261 # Call functions when the nth engine is registered and them remove them
262 for i, (n, f, args, kwargs) in enumerate(self._onNRegistered):
263 if len(self.engines.keys()) == n:
264 try:
265 try:
266 f(*args, **kwargs)
267 except:
268 log.msg("Function %r failed when the %ith engine registered" % (f, n))
269 finally:
270 self._onNRegistered.pop(i)
271
272 return {'id':getID}
273
274 def unregister_engine(self, id):
275 """Unregister engine by id."""
276
277 assert isinstance(id, int) or id is None, \
278 "id to unregister_engine must be an integer or None"
279
280 msg = "unregistered engine with id: %i" %id
281 log.msg(msg)
282 try:
283 del self.engines[id]
284 except KeyError:
285 log.msg("engine with id %i was not registered" % id)
286 else:
287 if not self.saveIDs:
288 self.availableIDs.append(id)
289 # Sort to assign lower ids first
290 self.availableIDs.sort(reverse=True)
291 else:
292 log.msg("preserving id %i" %id)
293
294 for i in range(len(self._onUnregister)):
295 (f,args,kwargs,ifid) = self._onUnregister[i]
296 try:
297 if ifid:
298 f(id, *args, **kwargs)
299 else:
300 f(*args, **kwargs)
301 except:
302 self._onUnregister.pop(i)
303
304 def on_register_engine_do(self, f, includeID, *args, **kwargs):
305 assert callable(f), "f must be callable"
306 self._onRegister.append((f,args,kwargs,includeID))
307
308 def on_unregister_engine_do(self, f, includeID, *args, **kwargs):
309 assert callable(f), "f must be callable"
310 self._onUnregister.append((f,args,kwargs,includeID))
311
312 def on_register_engine_do_not(self, f):
313 for i in range(len(self._onRegister)):
314 g = self._onRegister[i][0]
315 if f == g:
316 self._onRegister.pop(i)
317 return
318
319 def on_unregister_engine_do_not(self, f):
320 for i in range(len(self._onUnregister)):
321 g = self._onUnregister[i][0]
322 if f == g:
323 self._onUnregister.pop(i)
324 return
325
326 def on_n_engines_registered_do(self, n, f, *args, **kwargs):
327 if len(self.engines.keys()) >= n:
328 f(*args, **kwargs)
329 else:
330 self._onNRegistered.append((n,f,args,kwargs))
331
332
333 #-------------------------------------------------------------------------------
334 # Base class for adapting controller to different client APIs
335 #-------------------------------------------------------------------------------
336
337 class ControllerAdapterBase(object):
338 """All Controller adapters should inherit from this class.
339
340 This class provides a wrapped version of the IControllerBase interface that
341 can be used to easily create new custom controllers. Subclasses of this
342 will provide a full implementation of IControllerBase.
343
344 This class doesn't implement any client notification mechanism. That
345 is up to subclasses.
346 """
347
348 implements(IControllerBase)
349
350 def __init__(self, controller):
351 self.controller = controller
352 # Needed for IControllerCore
353 self.engines = self.controller.engines
354
355 def register_engine(self, remoteEngine, id=None,
356 ip=None, port=None, pid=None):
357 return self.controller.register_engine(remoteEngine,
358 id, ip, port, pid)
359
360 def unregister_engine(self, id):
361 return self.controller.unregister_engine(id)
362
363 def on_register_engine_do(self, f, includeID, *args, **kwargs):
364 return self.controller.on_register_engine_do(f, includeID, *args, **kwargs)
365
366 def on_unregister_engine_do(self, f, includeID, *args, **kwargs):
367 return self.controller.on_unregister_engine_do(f, includeID, *args, **kwargs)
368
369 def on_register_engine_do_not(self, f):
370 return self.controller.on_register_engine_do_not(f)
371
372 def on_unregister_engine_do_not(self, f):
373 return self.controller.on_unregister_engine_do_not(f)
374
375 def on_n_engines_registered_do(self, n, f, *args, **kwargs):
376 return self.controller.on_n_engines_registered_do(n, f, *args, **kwargs)
@@ -0,0 +1,16 b''
1 # encoding: utf-8
2
3 """The IPython Core."""
4
5 __docformat__ = "restructuredtext en"
6
7 #-------------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
13
14 #-------------------------------------------------------------------------------
15 # Imports
16 #------------------------------------------------------------------------------- No newline at end of file
@@ -0,0 +1,25 b''
1 # encoding: utf-8
2
3 __docformat__ = "restructuredtext en"
4
5 #-------------------------------------------------------------------------------
6 # Copyright (C) 2008 The IPython Development Team
7 #
8 # Distributed under the terms of the BSD License. The full license is in
9 # the file COPYING, distributed as part of this software.
10 #-------------------------------------------------------------------------------
11
12 #-------------------------------------------------------------------------------
13 # Imports
14 #-------------------------------------------------------------------------------
15
16 from IPython.external.configobj import ConfigObj
17 from IPython.config.api import ConfigObjManager
18
19 default_core_config = ConfigObj()
20 default_core_config['shell'] = dict(
21 shell_class = 'IPython.kernel.core.interpreter.Interpreter',
22 import_statement = ''
23 )
24
25 config_manager = ConfigObjManager(default_core_config, 'IPython.kernel.core.ini') No newline at end of file
@@ -0,0 +1,70 b''
1 # encoding: utf-8
2
3 """Objects for replacing sys.displayhook()."""
4
5 __docformat__ = "restructuredtext en"
6
7 #-------------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
13
14 #-------------------------------------------------------------------------------
15 # Imports
16 #-------------------------------------------------------------------------------
17
18 class IDisplayFormatter(object):
19 """ Objects conforming to this interface will be responsible for formatting
20 representations of objects that pass through sys.displayhook() during an
21 interactive interpreter session.
22 """
23
24 # The kind of formatter.
25 kind = 'display'
26
27 # The unique identifier for this formatter.
28 identifier = None
29
30
31 def __call__(self, obj):
32 """ Return a formatted representation of an object.
33
34 Return None if one cannot return a representation in this format.
35 """
36
37 raise NotImplementedError
38
39
40 class ReprDisplayFormatter(IDisplayFormatter):
41 """ Return the repr() string representation of an object.
42 """
43
44 # The unique identifier for this formatter.
45 identifier = 'repr'
46
47
48 def __call__(self, obj):
49 """ Return a formatted representation of an object.
50 """
51
52 return repr(obj)
53
54
55 class PPrintDisplayFormatter(IDisplayFormatter):
56 """ Return a pretty-printed string representation of an object.
57 """
58
59 # The unique identifier for this formatter.
60 identifier = 'pprint'
61
62
63 def __call__(self, obj):
64 """ Return a formatted representation of an object.
65 """
66
67 import pprint
68 return pprint.pformat(obj)
69
70
@@ -0,0 +1,100 b''
1 # encoding: utf-8
2
3 """Manager for replacing sys.displayhook()."""
4
5 __docformat__ = "restructuredtext en"
6
7 #-------------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
13
14 #-------------------------------------------------------------------------------
15 # Imports
16 #-------------------------------------------------------------------------------
17
18 # Standard library imports.
19 import sys
20
21
22
23 class DisplayTrap(object):
24 """ Object to trap and format objects passing through sys.displayhook().
25
26 This trap maintains two lists of callables: formatters and callbacks. The
27 formatters take the *last* object that has gone through since the trap was
28 set and returns a string representation. Callbacks are executed on *every*
29 object that passes through the displayhook and does not return anything.
30 """
31
32 def __init__(self, formatters=None, callbacks=None):
33 # A list of formatters to apply. Each should be an instance conforming
34 # to the IDisplayFormatter interface.
35 if formatters is None:
36 formatters = []
37 self.formatters = formatters
38
39 # A list of callables, each of which should be executed *every* time an
40 # object passes through sys.displayhook().
41 if callbacks is None:
42 callbacks = []
43 self.callbacks = callbacks
44
45 # The last object to pass through the displayhook.
46 self.obj = None
47
48 # The previous hook before we replace it.
49 self.old_hook = None
50
51 def hook(self, obj):
52 """ This method actually implements the hook.
53 """
54
55 # Run through the list of callbacks and trigger all of them.
56 for callback in self.callbacks:
57 callback(obj)
58
59 # Store the object for formatting.
60 self.obj = obj
61
62 def set(self):
63 """ Set the hook.
64 """
65
66 if sys.displayhook is not self.hook:
67 self.old_hook = sys.displayhook
68 sys.displayhook = self.hook
69
70 def unset(self):
71 """ Unset the hook.
72 """
73
74 sys.displayhook = self.old_hook
75
76 def clear(self):
77 """ Reset the stored object.
78 """
79
80 self.obj = None
81
82 def add_to_message(self, message):
83 """ Add the formatted display of the objects to the message dictionary
84 being returned from the interpreter to its listeners.
85 """
86
87 # If there was no displayed object (or simply None), then don't add
88 # anything.
89 if self.obj is None:
90 return
91
92 # Go through the list of formatters and let them add their formatting.
93 display = {}
94 for formatter in self.formatters:
95 representation = formatter(self.obj)
96 if representation is not None:
97 display[formatter.identifier] = representation
98
99 message['display'] = display
100
@@ -0,0 +1,41 b''
1 # encoding: utf-8
2
3 """
4 error.py
5
6 We declare here a class hierarchy for all exceptions produced by IPython, in
7 cases where we don't just raise one from the standard library.
8 """
9
10 __docformat__ = "restructuredtext en"
11
12 #-------------------------------------------------------------------------------
13 # Copyright (C) 2008 The IPython Development Team
14 #
15 # Distributed under the terms of the BSD License. The full license is in
16 # the file COPYING, distributed as part of this software.
17 #-------------------------------------------------------------------------------
18
19 #-------------------------------------------------------------------------------
20 # Imports
21 #-------------------------------------------------------------------------------
22
23
24 class IPythonError(Exception):
25 """Base exception that all of our exceptions inherit from.
26
27 This can be raised by code that doesn't have any more specific
28 information."""
29
30 pass
31
32 # Exceptions associated with the controller objects
33 class ControllerError(IPythonError): pass
34
35 class ControllerCreationError(ControllerError): pass
36
37
38 # Exceptions associated with the Engines
39 class EngineError(IPythonError): pass
40
41 class EngineCreationError(EngineError): pass
@@ -0,0 +1,137 b''
1 # encoding: utf-8
2
3 """ Manage the input and output history of the interpreter and the
4 frontend.
5
6 There are 2 different history objects, one that lives in the interpreter,
7 and one that lives in the frontend. They are synced with a diff at each
8 execution of a command, as the interpreter history is a real stack, its
9 existing entries are not mutable.
10 """
11
12 __docformat__ = "restructuredtext en"
13
14 #-------------------------------------------------------------------------------
15 # Copyright (C) 2008 The IPython Development Team
16 #
17 # Distributed under the terms of the BSD License. The full license is in
18 # the file COPYING, distributed as part of this software.
19 #-------------------------------------------------------------------------------
20
21 #-------------------------------------------------------------------------------
22 # Imports
23 #-------------------------------------------------------------------------------
24
25 from copy import copy
26
27 # Local imports.
28 from util import InputList
29
30
31 ##############################################################################
32 class History(object):
33 """ An object managing the input and output history.
34 """
35
36 def __init__(self, input_cache=None, output_cache=None):
37
38 # Stuff that IPython adds to the namespace.
39 self.namespace_additions = dict(
40 _ = None,
41 __ = None,
42 ___ = None,
43 )
44
45 # A list to store input commands.
46 if input_cache is None:
47 input_cache =InputList([])
48 self.input_cache = input_cache
49
50 # A dictionary to store trapped output.
51 if output_cache is None:
52 output_cache = {}
53 self.output_cache = output_cache
54
55 def get_history_item(self, index):
56 """ Returns the history string at index, where index is the
57 distance from the end (positive).
58 """
59 if index>0 and index<len(self.input_cache):
60 return self.input_cache[index]
61
62
63 ##############################################################################
64 class InterpreterHistory(History):
65 """ An object managing the input and output history at the interpreter
66 level.
67 """
68
69 def setup_namespace(self, namespace):
70 """ Add the input and output caches into the interpreter's namespace
71 with IPython-conventional names.
72
73 Parameters
74 ----------
75 namespace : dict
76 """
77
78 namespace['In'] = self.input_cache
79 namespace['_ih'] = self.input_cache
80 namespace['Out'] = self.output_cache
81 namespace['_oh'] = self.output_cache
82
83 def update_history(self, interpreter, python):
84 """ Update the history objects that this object maintains and the
85 interpreter's namespace.
86
87 Parameters
88 ----------
89 interpreter : Interpreter
90 python : str
91 The real Python code that was translated and actually executed.
92 """
93
94 number = interpreter.current_cell_number
95
96 new_obj = interpreter.display_trap.obj
97 if new_obj is not None:
98 self.namespace_additions['___'] = self.namespace_additions['__']
99 self.namespace_additions['__'] = self.namespace_additions['_']
100 self.namespace_additions['_'] = new_obj
101 self.output_cache[number] = new_obj
102
103 interpreter.user_ns.update(self.namespace_additions)
104 self.input_cache.add(number, python)
105
106
107 def get_history_item(self, index):
108 """ Returns the history string at index, where index is the
109 distance from the end (positive).
110 """
111 if index>0 and index<(len(self.input_cache)-1):
112 return self.input_cache[-index]
113
114 def get_input_cache(self):
115 return copy(self.input_cache)
116
117 def get_input_after(self, index):
118 """ Returns the list of the commands entered after index.
119 """
120 # We need to call directly list.__getslice__, because this object
121 # is not a real list.
122 return list.__getslice__(self.input_cache, index,
123 len(self.input_cache))
124
125
126 ##############################################################################
127 class FrontEndHistory(History):
128 """ An object managing the input and output history at the frontend.
129 It is used as a local cache to reduce network latency problems
130 and multiple users editing the same thing.
131 """
132
133 def add_items(self, item_list):
134 """ Adds the given command list to the stack of executed
135 commands.
136 """
137 self.input_cache.extend(item_list)
This diff has been collapsed as it changes many lines, (749 lines changed) Show them Hide them
@@ -0,0 +1,749 b''
1 # encoding: utf-8
2
3 """Central interpreter object for an IPython engine.
4
5 The interpreter is the object whose job is to process lines of user input and
6 actually execute them in the user's namespace.
7 """
8
9 __docformat__ = "restructuredtext en"
10
11 #-------------------------------------------------------------------------------
12 # Copyright (C) 2008 The IPython Development Team
13 #
14 # Distributed under the terms of the BSD License. The full license is in
15 # the file COPYING, distributed as part of this software.
16 #-------------------------------------------------------------------------------
17
18 #-------------------------------------------------------------------------------
19 # Imports
20 #-------------------------------------------------------------------------------
21
22 # Standard library imports.
23 from compiler.ast import Discard
24 from types import FunctionType
25
26 import __builtin__
27 import codeop
28 import compiler
29 import pprint
30 import sys
31 import traceback
32
33 # Local imports.
34 from IPython.kernel.core import ultraTB
35 from IPython.kernel.core.display_trap import DisplayTrap
36 from IPython.kernel.core.macro import Macro
37 from IPython.kernel.core.prompts import CachedOutput
38 from IPython.kernel.core.traceback_trap import TracebackTrap
39 from IPython.kernel.core.util import Bunch, system_shell
40 from IPython.external.Itpl import ItplNS
41
42 # Global constants
43 COMPILER_ERROR = 'error'
44 INCOMPLETE_INPUT = 'incomplete'
45 COMPLETE_INPUT = 'complete'
46
47 ##############################################################################
48 # TEMPORARY!!! fake configuration, while we decide whether to use tconfig or
49 # not
50
51 rc = Bunch()
52 rc.cache_size = 100
53 rc.pprint = True
54 rc.separate_in = '\n'
55 rc.separate_out = '\n'
56 rc.separate_out2 = ''
57 rc.prompt_in1 = r'In [\#]: '
58 rc.prompt_in2 = r' .\\D.: '
59 rc.prompt_out = ''
60 rc.prompts_pad_left = False
61
62 ##############################################################################
63
64 # Top-level utilities
65 def default_display_formatters():
66 """ Return a list of default display formatters.
67 """
68
69 from display_formatter import PPrintDisplayFormatter, ReprDisplayFormatter
70 return [PPrintDisplayFormatter(), ReprDisplayFormatter()]
71
72 def default_traceback_formatters():
73 """ Return a list of default traceback formatters.
74 """
75
76 from traceback_formatter import PlainTracebackFormatter
77 return [PlainTracebackFormatter()]
78
79 # Top-level classes
80 class NotDefined(object): pass
81
82 class Interpreter(object):
83 """ An interpreter object.
84
85 fixme: needs to negotiate available formatters with frontends.
86
87 Important: the interpeter should be built so that it exposes a method
88 for each attribute/method of its sub-object. This way it can be
89 replaced by a network adapter.
90 """
91
92 def __init__(self, user_ns=None, global_ns=None,translator=None,
93 magic=None, display_formatters=None,
94 traceback_formatters=None, output_trap=None, history=None,
95 message_cache=None, filename='<string>', config=None):
96
97 # The local/global namespaces for code execution
98 local_ns = user_ns # compatibility name
99 if local_ns is None:
100 local_ns = {}
101 self.user_ns = local_ns
102 # The local namespace
103 if global_ns is None:
104 global_ns = {}
105 self.user_global_ns = global_ns
106
107 # An object that will translate commands into executable Python.
108 # The current translator does not work properly so for now we are going
109 # without!
110 # if translator is None:
111 # from IPython.kernel.core.translator import IPythonTranslator
112 # translator = IPythonTranslator()
113 self.translator = translator
114
115 # An object that maintains magic commands.
116 if magic is None:
117 from IPython.kernel.core.magic import Magic
118 magic = Magic(self)
119 self.magic = magic
120
121 # A list of formatters for the displayhook.
122 if display_formatters is None:
123 display_formatters = default_display_formatters()
124 self.display_formatters = display_formatters
125
126 # A list of formatters for tracebacks.
127 if traceback_formatters is None:
128 traceback_formatters = default_traceback_formatters()
129 self.traceback_formatters = traceback_formatters
130
131 # The object trapping stdout/stderr.
132 if output_trap is None:
133 from IPython.kernel.core.output_trap import OutputTrap
134 output_trap = OutputTrap()
135 self.output_trap = output_trap
136
137 # An object that manages the history.
138 if history is None:
139 from IPython.kernel.core.history import InterpreterHistory
140 history = InterpreterHistory()
141 self.history = history
142 self.get_history_item = history.get_history_item
143 self.get_history_input_cache = history.get_input_cache
144 self.get_history_input_after = history.get_input_after
145
146 # An object that caches all of the return messages.
147 if message_cache is None:
148 from IPython.kernel.core.message_cache import SimpleMessageCache
149 message_cache = SimpleMessageCache()
150 self.message_cache = message_cache
151
152 # The "filename" of the code that is executed in this interpreter.
153 self.filename = filename
154
155 # An object that contains much configuration information.
156 if config is None:
157 # fixme: Move this constant elsewhere!
158 config = Bunch(ESC_MAGIC='%')
159 self.config = config
160
161 # Hook managers.
162 # fixme: make the display callbacks configurable. In the meantime,
163 # enable macros.
164 self.display_trap = DisplayTrap(
165 formatters=self.display_formatters,
166 callbacks=[self._possible_macro],
167 )
168 self.traceback_trap = TracebackTrap(
169 formatters=self.traceback_formatters)
170
171 # This is used temporarily for reformating exceptions in certain
172 # cases. It will go away once the ultraTB stuff is ported
173 # to ipython1
174 self.tbHandler = ultraTB.FormattedTB(color_scheme='NoColor',
175 mode='Context',
176 tb_offset=2)
177
178 # An object that can compile commands and remember __future__
179 # statements.
180 self.command_compiler = codeop.CommandCompiler()
181
182 # A replacement for the raw_input() and input() builtins. Change these
183 # attributes later to configure them.
184 self.raw_input_builtin = raw_input
185 self.input_builtin = input
186
187 # The number of the current cell.
188 self.current_cell_number = 1
189
190 # Initialize cache, set in/out prompts and printing system
191 self.outputcache = CachedOutput(self,
192 rc.cache_size,
193 rc.pprint,
194 input_sep = rc.separate_in,
195 output_sep = rc.separate_out,
196 output_sep2 = rc.separate_out2,
197 ps1 = rc.prompt_in1,
198 ps2 = rc.prompt_in2,
199 ps_out = rc.prompt_out,
200 pad_left = rc.prompts_pad_left)
201
202 # Need to decide later if this is the right approach, but clients
203 # commonly use sys.ps1/2, so it may be best to just set them here
204 sys.ps1 = self.outputcache.prompt1.p_str
205 sys.ps2 = self.outputcache.prompt2.p_str
206
207 # This is the message dictionary assigned temporarily when running the
208 # code.
209 self.message = None
210
211 self.setup_namespace()
212
213
214 #### Public 'Interpreter' interface ########################################
215
216 def formatTraceback(self, et, ev, tb, message=''):
217 """Put a formatted version of the traceback into value and reraise.
218
219 When exceptions have to be sent over the network, the traceback
220 needs to be put into the value of the exception in a nicely
221 formatted way. The method takes the type, value and tb of an
222 exception and puts a string representation of the tb into the
223 value of the exception and reraises it.
224
225 Currently this method uses the ultraTb formatter from IPython trunk.
226 Eventually it should simply use the traceback formatters in core
227 that are loaded into self.tracback_trap.formatters.
228 """
229 tbinfo = self.tbHandler.text(et,ev,tb)
230 ev._ipython_traceback_text = tbinfo
231 return et, ev, tb
232
233 def execute(self, commands, raiseException=True):
234 """ Execute some IPython commands.
235
236 1. Translate them into Python.
237 2. Run them.
238 3. Trap stdout/stderr.
239 4. Trap sys.displayhook().
240 5. Trap exceptions.
241 6. Return a message object.
242
243 Parameters
244 ----------
245 commands : str
246 The raw commands that the user typed into the prompt.
247
248 Returns
249 -------
250 message : dict
251 The dictionary of responses. See the README.txt in this directory
252 for an explanation of the format.
253 """
254
255 # Create a message dictionary with all of the information we will be
256 # returning to the frontend and other listeners.
257 message = self.setup_message()
258
259 # Massage the input and store the raw and translated commands into
260 # a dict.
261 user_input = dict(raw=commands)
262 if self.translator is not None:
263 python = self.translator(commands, message)
264 if python is None:
265 # Something went wrong with the translation. The translator
266 # should have added an appropriate entry to the message object.
267 return message
268 else:
269 python = commands
270 user_input['translated'] = python
271 message['input'] = user_input
272
273 # Set the message object so that any magics executed in the code have
274 # access.
275 self.message = message
276
277 # Set all of the output/exception traps.
278 self.set_traps()
279
280 # Actually execute the Python code.
281 status = self.execute_python(python)
282
283 # Unset all of the traps.
284 self.unset_traps()
285
286 # Unset the message object.
287 self.message = None
288
289 # Update the history variables in the namespace.
290 # E.g. In, Out, _, __, ___
291 if self.history is not None:
292 self.history.update_history(self, python)
293
294 # Let all of the traps contribute to the message and then clear their
295 # stored information.
296 self.output_trap.add_to_message(message)
297 self.output_trap.clear()
298 self.display_trap.add_to_message(message)
299 self.display_trap.clear()
300 self.traceback_trap.add_to_message(message)
301 # Pull out the type, value and tb of the current exception
302 # before clearing it.
303 einfo = self.traceback_trap.args
304 self.traceback_trap.clear()
305
306 # Cache the message.
307 self.message_cache.add_message(self.current_cell_number, message)
308
309 # Bump the number.
310 self.current_cell_number += 1
311
312 # This conditional lets the execute method either raise any
313 # exception that has occured in user code OR return the message
314 # dict containing the traceback and other useful info.
315 if raiseException and einfo:
316 raise einfo[0],einfo[1],einfo[2]
317 else:
318 return message
319
320 def generate_prompt(self, is_continuation):
321 """Calculate and return a string with the prompt to display.
322
323 :Parameters:
324 is_continuation : bool
325 Whether the input line is continuing multiline input or not, so
326 that a proper continuation prompt can be computed."""
327
328 if is_continuation:
329 return str(self.outputcache.prompt2)
330 else:
331 return str(self.outputcache.prompt1)
332
333 def execute_python(self, python):
334 """ Actually run the Python code in the namespace.
335
336 :Parameters:
337
338 python : str
339 Pure, exec'able Python code. Special IPython commands should have
340 already been translated into pure Python.
341 """
342
343 # We use a CommandCompiler instance to compile the code so as to keep
344 # track of __future__ imports.
345 try:
346 commands = self.split_commands(python)
347 except (SyntaxError, IndentationError), e:
348 # Save the exc_info so compilation related exceptions can be
349 # reraised
350 self.traceback_trap.args = sys.exc_info()
351 self.pack_exception(self.message,e)
352 return None
353
354 for cmd in commands:
355 try:
356 code = self.command_compiler(cmd, self.filename, 'single')
357 except (SyntaxError, OverflowError, ValueError), e:
358 self.traceback_trap.args = sys.exc_info()
359 self.pack_exception(self.message,e)
360 # No point in continuing if one block raised
361 return None
362 else:
363 self.execute_block(code)
364
365 def execute_block(self,code):
366 """Execute a single block of code in the user namespace.
367
368 Return value: a flag indicating whether the code to be run completed
369 successfully:
370
371 - 0: successful execution.
372 - 1: an error occurred.
373 """
374
375 outflag = 1 # start by assuming error, success will reset it
376 try:
377 exec code in self.user_ns
378 outflag = 0
379 except SystemExit:
380 self.resetbuffer()
381 self.traceback_trap.args = sys.exc_info()
382 except:
383 self.traceback_trap.args = sys.exc_info()
384
385 return outflag
386
387 def execute_macro(self, macro):
388 """ Execute the value of a macro.
389
390 Parameters
391 ----------
392 macro : Macro
393 """
394
395 python = macro.value
396 if self.translator is not None:
397 python = self.translator(python)
398 self.execute_python(python)
399
400 def getCommand(self, i=None):
401 """Gets the ith message in the message_cache.
402
403 This is implemented here for compatibility with the old ipython1 shell
404 I am not sure we need this though. I even seem to remember that we
405 were going to get rid of it.
406 """
407 return self.message_cache.get_message(i)
408
409 def reset(self):
410 """Reset the interpreter.
411
412 Currently this only resets the users variables in the namespace.
413 In the future we might want to also reset the other stateful
414 things like that the Interpreter has, like In, Out, etc.
415 """
416 self.user_ns.clear()
417 self.setup_namespace()
418
419 def complete(self,line,text=None, pos=None):
420 """Complete the given text.
421
422 :Parameters:
423
424 text : str
425 Text fragment to be completed on. Typically this is
426 """
427 # fixme: implement
428 raise NotImplementedError
429
430 def push(self, ns):
431 """ Put value into the namespace with name key.
432
433 Parameters
434 ----------
435 **kwds
436 """
437
438 self.user_ns.update(ns)
439
440 def push_function(self, ns):
441 # First set the func_globals for all functions to self.user_ns
442 new_kwds = {}
443 for k, v in ns.iteritems():
444 if not isinstance(v, FunctionType):
445 raise TypeError("function object expected")
446 new_kwds[k] = FunctionType(v.func_code, self.user_ns)
447 self.user_ns.update(new_kwds)
448
449 def pack_exception(self,message,exc):
450 message['exception'] = exc.__class__
451 message['exception_value'] = \
452 traceback.format_exception_only(exc.__class__, exc)
453
454 def feed_block(self, source, filename='<input>', symbol='single'):
455 """Compile some source in the interpreter.
456
457 One several things can happen:
458
459 1) The input is incorrect; compile_command() raised an
460 exception (SyntaxError or OverflowError).
461
462 2) The input is incomplete, and more input is required;
463 compile_command() returned None. Nothing happens.
464
465 3) The input is complete; compile_command() returned a code
466 object. The code is executed by calling self.runcode() (which
467 also handles run-time exceptions, except for SystemExit).
468
469 The return value is:
470
471 - True in case 2
472
473 - False in the other cases, unless an exception is raised, where
474 None is returned instead. This can be used by external callers to
475 know whether to continue feeding input or not.
476
477 The return value can be used to decide whether to use sys.ps1 or
478 sys.ps2 to prompt the next line."""
479
480 self.message = self.setup_message()
481
482 try:
483 code = self.command_compiler(source,filename,symbol)
484 except (OverflowError, SyntaxError, IndentationError, ValueError ), e:
485 # Case 1
486 self.traceback_trap.args = sys.exc_info()
487 self.pack_exception(self.message,e)
488 return COMPILER_ERROR,False
489
490 if code is None:
491 # Case 2: incomplete input. This means that the input can span
492 # multiple lines. But we still need to decide when to actually
493 # stop taking user input. Later we'll add auto-indentation support
494 # somehow. In the meantime, we'll just stop if there are two lines
495 # of pure whitespace at the end.
496 last_two = source.rsplit('\n',2)[-2:]
497 print 'last two:',last_two # dbg
498 if len(last_two)==2 and all(s.isspace() for s in last_two):
499 return COMPLETE_INPUT,False
500 else:
501 return INCOMPLETE_INPUT, True
502 else:
503 # Case 3
504 return COMPLETE_INPUT, False
505
506 def pull(self, keys):
507 """ Get an item out of the namespace by key.
508
509 Parameters
510 ----------
511 key : str
512
513 Returns
514 -------
515 value : object
516
517 Raises
518 ------
519 TypeError if the key is not a string.
520 NameError if the object doesn't exist.
521 """
522
523 if isinstance(keys, str):
524 result = self.user_ns.get(keys, NotDefined())
525 if isinstance(result, NotDefined):
526 raise NameError('name %s is not defined' % keys)
527 elif isinstance(keys, (list, tuple)):
528 result = []
529 for key in keys:
530 if not isinstance(key, str):
531 raise TypeError("objects must be keyed by strings.")
532 else:
533 r = self.user_ns.get(key, NotDefined())
534 if isinstance(r, NotDefined):
535 raise NameError('name %s is not defined' % key)
536 else:
537 result.append(r)
538 if len(keys)==1:
539 result = result[0]
540 else:
541 raise TypeError("keys must be a strong or a list/tuple of strings")
542 return result
543
544 def pull_function(self, keys):
545 return self.pull(keys)
546
547 #### Interactive user API ##################################################
548
549 def ipsystem(self, command):
550 """ Execute a command in a system shell while expanding variables in the
551 current namespace.
552
553 Parameters
554 ----------
555 command : str
556 """
557
558 # Expand $variables.
559 command = self.var_expand(command)
560
561 system_shell(command,
562 header='IPython system call: ',
563 verbose=self.rc.system_verbose,
564 )
565
566 def ipmagic(self, arg_string):
567 """ Call a magic function by name.
568
569 ipmagic('name -opt foo bar') is equivalent to typing at the ipython
570 prompt:
571
572 In[1]: %name -opt foo bar
573
574 To call a magic without arguments, simply use ipmagic('name').
575
576 This provides a proper Python function to call IPython's magics in any
577 valid Python code you can type at the interpreter, including loops and
578 compound statements. It is added by IPython to the Python builtin
579 namespace upon initialization.
580
581 Parameters
582 ----------
583 arg_string : str
584 A string containing the name of the magic function to call and any
585 additional arguments to be passed to the magic.
586
587 Returns
588 -------
589 something : object
590 The return value of the actual object.
591 """
592
593 # Taken from IPython.
594 raise NotImplementedError('Not ported yet')
595
596 args = arg_string.split(' ', 1)
597 magic_name = args[0]
598 magic_name = magic_name.lstrip(self.config.ESC_MAGIC)
599
600 try:
601 magic_args = args[1]
602 except IndexError:
603 magic_args = ''
604 fn = getattr(self.magic, 'magic_'+magic_name, None)
605 if fn is None:
606 self.error("Magic function `%s` not found." % magic_name)
607 else:
608 magic_args = self.var_expand(magic_args)
609 return fn(magic_args)
610
611
612 #### Private 'Interpreter' interface #######################################
613
614 def setup_message(self):
615 """Return a message object.
616
617 This method prepares and returns a message dictionary. This dict
618 contains the various fields that are used to transfer information about
619 execution, results, tracebacks, etc, to clients (either in or out of
620 process ones). Because of the need to work with possibly out of
621 process clients, this dict MUST contain strictly pickle-safe values.
622 """
623
624 return dict(number=self.current_cell_number)
625
626 def setup_namespace(self):
627 """ Add things to the namespace.
628 """
629
630 self.user_ns.setdefault('__name__', '__main__')
631 self.user_ns.setdefault('__builtins__', __builtin__)
632 self.user_ns['__IP'] = self
633 if self.raw_input_builtin is not None:
634 self.user_ns['raw_input'] = self.raw_input_builtin
635 if self.input_builtin is not None:
636 self.user_ns['input'] = self.input_builtin
637
638 builtin_additions = dict(
639 ipmagic=self.ipmagic,
640 )
641 __builtin__.__dict__.update(builtin_additions)
642
643 if self.history is not None:
644 self.history.setup_namespace(self.user_ns)
645
646 def set_traps(self):
647 """ Set all of the output, display, and traceback traps.
648 """
649
650 self.output_trap.set()
651 self.display_trap.set()
652 self.traceback_trap.set()
653
654 def unset_traps(self):
655 """ Unset all of the output, display, and traceback traps.
656 """
657
658 self.output_trap.unset()
659 self.display_trap.unset()
660 self.traceback_trap.unset()
661
662 def split_commands(self, python):
663 """ Split multiple lines of code into discrete commands that can be
664 executed singly.
665
666 Parameters
667 ----------
668 python : str
669 Pure, exec'able Python code.
670
671 Returns
672 -------
673 commands : list of str
674 Separate commands that can be exec'ed independently.
675 """
676
677 # compiler.parse treats trailing spaces after a newline as a
678 # SyntaxError. This is different than codeop.CommandCompiler, which
679 # will compile the trailng spaces just fine. We simply strip any
680 # trailing whitespace off. Passing a string with trailing whitespace
681 # to exec will fail however. There seems to be some inconsistency in
682 # how trailing whitespace is handled, but this seems to work.
683 python = python.strip()
684
685 # The compiler module will parse the code into an abstract syntax tree.
686 ast = compiler.parse(python)
687
688 # Uncomment to help debug the ast tree
689 # for n in ast.node:
690 # print n.lineno,'->',n
691
692 # Each separate command is available by iterating over ast.node. The
693 # lineno attribute is the line number (1-indexed) beginning the commands
694 # suite.
695 # lines ending with ";" yield a Discard Node that doesn't have a lineno
696 # attribute. These nodes can and should be discarded. But there are
697 # other situations that cause Discard nodes that shouldn't be discarded.
698 # We might eventually discover other cases where lineno is None and have
699 # to put in a more sophisticated test.
700 linenos = [x.lineno-1 for x in ast.node if x.lineno is not None]
701
702 # When we finally get the slices, we will need to slice all the way to
703 # the end even though we don't have a line number for it. Fortunately,
704 # None does the job nicely.
705 linenos.append(None)
706 lines = python.splitlines()
707
708 # Create a list of atomic commands.
709 cmds = []
710 for i, j in zip(linenos[:-1], linenos[1:]):
711 cmd = lines[i:j]
712 if cmd:
713 cmds.append('\n'.join(cmd)+'\n')
714
715 return cmds
716
717 def error(self, text):
718 """ Pass an error message back to the shell.
719
720 Preconditions
721 -------------
722 This should only be called when self.message is set. In other words,
723 when code is being executed.
724
725 Parameters
726 ----------
727 text : str
728 """
729
730 errors = self.message.get('IPYTHON_ERROR', [])
731 errors.append(text)
732
733 def var_expand(self, template):
734 """ Expand $variables in the current namespace using Itpl.
735
736 Parameters
737 ----------
738 template : str
739 """
740
741 return str(ItplNS(template, self.user_ns))
742
743 def _possible_macro(self, obj):
744 """ If the object is a macro, execute it.
745 """
746
747 if isinstance(obj, Macro):
748 self.execute_macro(obj)
749
@@ -0,0 +1,34 b''
1 # encoding: utf-8
2
3 """Support for interactive macros in IPython"""
4
5 __docformat__ = "restructuredtext en"
6
7 #-------------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
13
14 #-------------------------------------------------------------------------------
15 # Imports
16 #-------------------------------------------------------------------------------
17
18 class Macro:
19 """Simple class to store the value of macros as strings.
20
21 This allows us to later exec them by checking when something is an
22 instance of this class."""
23
24 def __init__(self,data):
25
26 # store the macro value, as a single string which can be evaluated by
27 # runlines()
28 self.value = ''.join(data).rstrip()+'\n'
29
30 def __str__(self):
31 return self.value
32
33 def __repr__(self):
34 return 'IPython.macro.Macro(%s)' % repr(self.value) No newline at end of file
@@ -0,0 +1,147 b''
1 # encoding: utf-8
2
3 __docformat__ = "restructuredtext en"
4
5 #-------------------------------------------------------------------------------
6 # Copyright (C) 2008 The IPython Development Team
7 #
8 # Distributed under the terms of the BSD License. The full license is in
9 # the file COPYING, distributed as part of this software.
10 #-------------------------------------------------------------------------------
11
12 #-------------------------------------------------------------------------------
13 # Imports
14 #-------------------------------------------------------------------------------
15
16 import os
17 import __builtin__
18
19 # Local imports.
20 from util import Bunch
21
22
23 # fixme: RTK thinks magics should be implemented as separate classes rather than
24 # methods on a single class. This would give us the ability to plug new magics
25 # in and configure them separately.
26
27 class Magic(object):
28 """ An object that maintains magic functions.
29 """
30
31 def __init__(self, interpreter, config=None):
32 # A reference to the interpreter.
33 self.interpreter = interpreter
34
35 # A reference to the configuration object.
36 if config is None:
37 # fixme: we need a better place to store this information.
38 config = Bunch(ESC_MAGIC='%')
39 self.config = config
40
41 def has_magic(self, name):
42 """ Return True if this object provides a given magic.
43
44 Parameters
45 ----------
46 name : str
47 """
48
49 return hasattr(self, 'magic_' + name)
50
51 def object_find(self, name):
52 """ Find an object in the available namespaces.
53
54 fixme: this should probably be moved elsewhere. The interpreter?
55 """
56
57 name = name.strip()
58
59 # Namespaces to search.
60 # fixme: implement internal and alias namespaces.
61 user_ns = self.interpreter.user_ns
62 internal_ns = {}
63 builtin_ns = __builtin__.__dict__
64 alias_ns = {}
65
66 # Order the namespaces.
67 namespaces = [
68 ('Interactive', user_ns),
69 ('IPython internal', internal_ns),
70 ('Python builtin', builtin_ns),
71 ('Alias', alias_ns),
72 ]
73
74 # Initialize all results.
75 found = False
76 obj = None
77 space = None
78 ds = None
79 ismagic = False
80 isalias = False
81
82 # Look for the given name by splitting it in parts. If the head is
83 # found, then we look for all the remaining parts as members, and only
84 # declare success if we can find them all.
85 parts = name.split('.')
86 head, rest = parts[0], parts[1:]
87 for nsname, ns in namespaces:
88 try:
89 obj = ns[head]
90 except KeyError:
91 continue
92 else:
93 for part in rest:
94 try:
95 obj = getattr(obj, part)
96 except:
97 # Blanket except b/c some badly implemented objects
98 # allow __getattr__ to raise exceptions other than
99 # AttributeError, which then crashes us.
100 break
101 else:
102 # If we finish the for loop (no break), we got all members
103 found = True
104 space = nsname
105 isalias = (ns == alias_ns)
106 break # namespace loop
107
108 # Try to see if it is a magic.
109 if not found:
110 if name.startswith(self.config.ESC_MAGIC):
111 name = name[1:]
112 obj = getattr(self, 'magic_' + name, None)
113 if obj is not None:
114 found = True
115 space = 'IPython internal'
116 ismagic = True
117
118 # Last try: special-case some literals like '', [], {}, etc:
119 if not found and head in ["''", '""', '[]', '{}', '()']:
120 obj = eval(head)
121 found = True
122 space = 'Interactive'
123
124 return dict(
125 found=found,
126 obj=obj,
127 namespace=space,
128 ismagic=ismagic,
129 isalias=isalias,
130 )
131
132
133
134
135
136 def magic_pwd(self, parameter_s=''):
137 """ Return the current working directory path.
138 """
139 return os.getcwd()
140
141 def magic_env(self, parameter_s=''):
142 """ List environment variables.
143 """
144
145 return os.environ.data
146
147
@@ -0,0 +1,98 b''
1 # encoding: utf-8
2
3 """Storage for the responses from the interpreter."""
4
5 __docformat__ = "restructuredtext en"
6
7 #-------------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
13
14 #-------------------------------------------------------------------------------
15 # Imports
16 #-------------------------------------------------------------------------------
17
18
19 class IMessageCache(object):
20 """ Storage for the response from the interpreter.
21 """
22
23 def add_message(self, i, message):
24 """ Add a message dictionary to the cache.
25
26 Parameters
27 ----------
28 i : int
29 message : dict
30 """
31
32 def get_message(self, i=None):
33 """ Get the message from the cache.
34
35 Parameters
36 ----------
37 i : int, optional
38 The number of the message. If not provided, return the
39 highest-numbered message.
40
41 Returns
42 -------
43 message : dict
44
45 Raises
46 ------
47 IndexError if the message does not exist in the cache.
48 """
49
50
51 class SimpleMessageCache(object):
52 """ Simple dictionary-based, in-memory storage of the responses from the
53 interpreter.
54 """
55
56 def __init__(self):
57 self.cache = {}
58
59 def add_message(self, i, message):
60 """ Add a message dictionary to the cache.
61
62 Parameters
63 ----------
64 i : int
65 message : dict
66 """
67
68 self.cache[i] = message
69
70 def get_message(self, i=None):
71 """ Get the message from the cache.
72
73 Parameters
74 ----------
75 i : int, optional
76 The number of the message. If not provided, return the
77 highest-numbered message.
78
79 Returns
80 -------
81 message : dict
82
83 Raises
84 ------
85 IndexError if the message does not exist in the cache.
86 """
87 if i is None:
88 keys = self.cache.keys()
89 if len(keys) == 0:
90 raise IndexError("index %r out of range" % i)
91 else:
92 i = max(self.cache.keys())
93 try:
94 return self.cache[i]
95 except KeyError:
96 # IndexError is more appropriate, here.
97 raise IndexError("index %r out of range" % i)
98
@@ -0,0 +1,99 b''
1 # encoding: utf-8
2
3 """ Trap stdout/stderr."""
4
5 __docformat__ = "restructuredtext en"
6
7 #-------------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
13
14 #-------------------------------------------------------------------------------
15 # Imports
16 #-------------------------------------------------------------------------------
17
18 import sys
19 from cStringIO import StringIO
20
21
22 class OutputTrap(object):
23 """ Object which can trap text sent to stdout and stderr.
24 """
25
26 def __init__(self):
27 # Filelike objects to store stdout/stderr text.
28 self.out = StringIO()
29 self.err = StringIO()
30
31 # Boolean to check if the stdout/stderr hook is set.
32 self.out_set = False
33 self.err_set = False
34
35 @property
36 def out_text(self):
37 """ Return the text currently in the stdout buffer.
38 """
39 return self.out.getvalue()
40
41 @property
42 def err_text(self):
43 """ Return the text currently in the stderr buffer.
44 """
45 return self.err.getvalue()
46
47 def set(self):
48 """ Set the hooks.
49 """
50
51 if sys.stdout is not self.out:
52 self._out_save = sys.stdout
53 sys.stdout = self.out
54 self.out_set = True
55
56 if sys.stderr is not self.err:
57 self._err_save = sys.stderr
58 sys.stderr = self.err
59 self.err_set = True
60
61 def unset(self):
62 """ Remove the hooks.
63 """
64
65 sys.stdout = self._out_save
66 self.out_set = False
67
68 sys.stderr = self._err_save
69 self.err_set = False
70
71 def clear(self):
72 """ Clear out the buffers.
73 """
74
75 self.out.close()
76 self.out = StringIO()
77
78 self.err.close()
79 self.err = StringIO()
80
81 def add_to_message(self, message):
82 """ Add the text from stdout and stderr to the message from the
83 interpreter to its listeners.
84
85 Parameters
86 ----------
87 message : dict
88 """
89
90 out_text = self.out_text
91 if out_text:
92 message['stdout'] = out_text
93
94 err_text = self.err_text
95 if err_text:
96 message['stderr'] = err_text
97
98
99
This diff has been collapsed as it changes many lines, (591 lines changed) Show them Hide them
@@ -0,0 +1,591 b''
1 # encoding: utf-8
2
3 """Classes for handling input/output prompts."""
4
5 __docformat__ = "restructuredtext en"
6
7 #-------------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
13
14 #-------------------------------------------------------------------------------
15 # Imports
16 #-------------------------------------------------------------------------------
17
18 from IPython import Release
19 __author__ = '%s <%s>' % Release.authors['Fernando']
20 __license__ = Release.license
21 __version__ = Release.version
22
23 #****************************************************************************
24 # Required modules
25 import __builtin__
26 import os
27 import socket
28 import sys
29 import time
30
31 # IPython's own
32 from IPython.external.Itpl import ItplNS
33 from macro import Macro
34
35 # Temporarily use this until it is ported to ipython1
36
37 from IPython import ColorANSI
38 from IPython.ipstruct import Struct
39 from IPython.genutils import *
40 from IPython.ipapi import TryNext
41
42 #****************************************************************************
43 #Color schemes for Prompts.
44
45 PromptColors = ColorANSI.ColorSchemeTable()
46 InputColors = ColorANSI.InputTermColors # just a shorthand
47 Colors = ColorANSI.TermColors # just a shorthand
48
49
50 __PColNoColor = ColorANSI.ColorScheme(
51 'NoColor',
52 in_prompt = InputColors.NoColor, # Input prompt
53 in_number = InputColors.NoColor, # Input prompt number
54 in_prompt2 = InputColors.NoColor, # Continuation prompt
55 in_normal = InputColors.NoColor, # color off (usu. Colors.Normal)
56
57 out_prompt = Colors.NoColor, # Output prompt
58 out_number = Colors.NoColor, # Output prompt number
59
60 normal = Colors.NoColor # color off (usu. Colors.Normal)
61 )
62
63 PromptColors.add_scheme(__PColNoColor)
64
65 # make some schemes as instances so we can copy them for modification easily:
66 __PColLinux = __PColNoColor.copy('Linux')
67 # Don't forget to enter it into the table!
68 PromptColors.add_scheme(__PColLinux)
69 __PColLightBG = __PColLinux.copy('LightBG')
70 PromptColors.add_scheme(__PColLightBG)
71
72 del Colors,InputColors
73
74 #-----------------------------------------------------------------------------
75 def multiple_replace(dict, text):
76 """ Replace in 'text' all occurences of any key in the given
77 dictionary by its corresponding value. Returns the new string."""
78
79 # Function by Xavier Defrang, originally found at:
80 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/81330
81
82 # Create a regular expression from the dictionary keys
83 regex = re.compile("(%s)" % "|".join(map(re.escape, dict.keys())))
84 # For each match, look-up corresponding value in dictionary
85 return regex.sub(lambda mo: dict[mo.string[mo.start():mo.end()]], text)
86
87 #-----------------------------------------------------------------------------
88 # Special characters that can be used in prompt templates, mainly bash-like
89
90 # If $HOME isn't defined (Windows), make it an absurd string so that it can
91 # never be expanded out into '~'. Basically anything which can never be a
92 # reasonable directory name will do, we just want the $HOME -> '~' operation
93 # to become a no-op. We pre-compute $HOME here so it's not done on every
94 # prompt call.
95
96 # FIXME:
97
98 # - This should be turned into a class which does proper namespace management,
99 # since the prompt specials need to be evaluated in a certain namespace.
100 # Currently it's just globals, which need to be managed manually by code
101 # below.
102
103 # - I also need to split up the color schemes from the prompt specials
104 # somehow. I don't have a clean design for that quite yet.
105
106 HOME = os.environ.get("HOME","//////:::::ZZZZZ,,,~~~")
107
108 # We precompute a few more strings here for the prompt_specials, which are
109 # fixed once ipython starts. This reduces the runtime overhead of computing
110 # prompt strings.
111 USER = os.environ.get("USER")
112 HOSTNAME = socket.gethostname()
113 HOSTNAME_SHORT = HOSTNAME.split(".")[0]
114 ROOT_SYMBOL = "$#"[os.name=='nt' or os.getuid()==0]
115
116 prompt_specials_color = {
117 # Prompt/history count
118 '%n' : '${self.col_num}' '${self.cache.prompt_count}' '${self.col_p}',
119 r'\#': '${self.col_num}' '${self.cache.prompt_count}' '${self.col_p}',
120 # Just the prompt counter number, WITHOUT any coloring wrappers, so users
121 # can get numbers displayed in whatever color they want.
122 r'\N': '${self.cache.prompt_count}',
123 # Prompt/history count, with the actual digits replaced by dots. Used
124 # mainly in continuation prompts (prompt_in2)
125 r'\D': '${"."*len(str(self.cache.prompt_count))}',
126 # Current working directory
127 r'\w': '${os.getcwd()}',
128 # Current time
129 r'\t' : '${time.strftime("%H:%M:%S")}',
130 # Basename of current working directory.
131 # (use os.sep to make this portable across OSes)
132 r'\W' : '${os.getcwd().split("%s")[-1]}' % os.sep,
133 # These X<N> are an extension to the normal bash prompts. They return
134 # N terms of the path, after replacing $HOME with '~'
135 r'\X0': '${os.getcwd().replace("%s","~")}' % HOME,
136 r'\X1': '${self.cwd_filt(1)}',
137 r'\X2': '${self.cwd_filt(2)}',
138 r'\X3': '${self.cwd_filt(3)}',
139 r'\X4': '${self.cwd_filt(4)}',
140 r'\X5': '${self.cwd_filt(5)}',
141 # Y<N> are similar to X<N>, but they show '~' if it's the directory
142 # N+1 in the list. Somewhat like %cN in tcsh.
143 r'\Y0': '${self.cwd_filt2(0)}',
144 r'\Y1': '${self.cwd_filt2(1)}',
145 r'\Y2': '${self.cwd_filt2(2)}',
146 r'\Y3': '${self.cwd_filt2(3)}',
147 r'\Y4': '${self.cwd_filt2(4)}',
148 r'\Y5': '${self.cwd_filt2(5)}',
149 # Hostname up to first .
150 r'\h': HOSTNAME_SHORT,
151 # Full hostname
152 r'\H': HOSTNAME,
153 # Username of current user
154 r'\u': USER,
155 # Escaped '\'
156 '\\\\': '\\',
157 # Newline
158 r'\n': '\n',
159 # Carriage return
160 r'\r': '\r',
161 # Release version
162 r'\v': __version__,
163 # Root symbol ($ or #)
164 r'\$': ROOT_SYMBOL,
165 }
166
167 # A copy of the prompt_specials dictionary but with all color escapes removed,
168 # so we can correctly compute the prompt length for the auto_rewrite method.
169 prompt_specials_nocolor = prompt_specials_color.copy()
170 prompt_specials_nocolor['%n'] = '${self.cache.prompt_count}'
171 prompt_specials_nocolor[r'\#'] = '${self.cache.prompt_count}'
172
173 # Add in all the InputTermColors color escapes as valid prompt characters.
174 # They all get added as \\C_COLORNAME, so that we don't have any conflicts
175 # with a color name which may begin with a letter used by any other of the
176 # allowed specials. This of course means that \\C will never be allowed for
177 # anything else.
178 input_colors = ColorANSI.InputTermColors
179 for _color in dir(input_colors):
180 if _color[0] != '_':
181 c_name = r'\C_'+_color
182 prompt_specials_color[c_name] = getattr(input_colors,_color)
183 prompt_specials_nocolor[c_name] = ''
184
185 # we default to no color for safety. Note that prompt_specials is a global
186 # variable used by all prompt objects.
187 prompt_specials = prompt_specials_nocolor
188
189 #-----------------------------------------------------------------------------
190 def str_safe(arg):
191 """Convert to a string, without ever raising an exception.
192
193 If str(arg) fails, <ERROR: ... > is returned, where ... is the exception
194 error message."""
195
196 try:
197 out = str(arg)
198 except UnicodeError:
199 try:
200 out = arg.encode('utf_8','replace')
201 except Exception,msg:
202 # let's keep this little duplication here, so that the most common
203 # case doesn't suffer from a double try wrapping.
204 out = '<ERROR: %s>' % msg
205 except Exception,msg:
206 out = '<ERROR: %s>' % msg
207 return out
208
209 class BasePrompt(object):
210 """Interactive prompt similar to Mathematica's."""
211
212 def _get_p_template(self):
213 return self._p_template
214
215 def _set_p_template(self,val):
216 self._p_template = val
217 self.set_p_str()
218
219 p_template = property(_get_p_template,_set_p_template,
220 doc='Template for prompt string creation')
221
222 def __init__(self,cache,sep,prompt,pad_left=False):
223
224 # Hack: we access information about the primary prompt through the
225 # cache argument. We need this, because we want the secondary prompt
226 # to be aligned with the primary one. Color table info is also shared
227 # by all prompt classes through the cache. Nice OO spaghetti code!
228 self.cache = cache
229 self.sep = sep
230
231 # regexp to count the number of spaces at the end of a prompt
232 # expression, useful for prompt auto-rewriting
233 self.rspace = re.compile(r'(\s*)$')
234 # Flag to left-pad prompt strings to match the length of the primary
235 # prompt
236 self.pad_left = pad_left
237
238 # Set template to create each actual prompt (where numbers change).
239 # Use a property
240 self.p_template = prompt
241 self.set_p_str()
242
243 def set_p_str(self):
244 """ Set the interpolating prompt strings.
245
246 This must be called every time the color settings change, because the
247 prompt_specials global may have changed."""
248
249 import os,time # needed in locals for prompt string handling
250 loc = locals()
251 self.p_str = ItplNS('%s%s%s' %
252 ('${self.sep}${self.col_p}',
253 multiple_replace(prompt_specials, self.p_template),
254 '${self.col_norm}'),self.cache.user_ns,loc)
255
256 self.p_str_nocolor = ItplNS(multiple_replace(prompt_specials_nocolor,
257 self.p_template),
258 self.cache.user_ns,loc)
259
260 def write(self,msg): # dbg
261 sys.stdout.write(msg)
262 return ''
263
264 def __str__(self):
265 """Return a string form of the prompt.
266
267 This for is useful for continuation and output prompts, since it is
268 left-padded to match lengths with the primary one (if the
269 self.pad_left attribute is set)."""
270
271 out_str = str_safe(self.p_str)
272 if self.pad_left:
273 # We must find the amount of padding required to match lengths,
274 # taking the color escapes (which are invisible on-screen) into
275 # account.
276 esc_pad = len(out_str) - len(str_safe(self.p_str_nocolor))
277 format = '%%%ss' % (len(str(self.cache.last_prompt))+esc_pad)
278 return format % out_str
279 else:
280 return out_str
281
282 # these path filters are put in as methods so that we can control the
283 # namespace where the prompt strings get evaluated
284 def cwd_filt(self,depth):
285 """Return the last depth elements of the current working directory.
286
287 $HOME is always replaced with '~'.
288 If depth==0, the full path is returned."""
289
290 cwd = os.getcwd().replace(HOME,"~")
291 out = os.sep.join(cwd.split(os.sep)[-depth:])
292 if out:
293 return out
294 else:
295 return os.sep
296
297 def cwd_filt2(self,depth):
298 """Return the last depth elements of the current working directory.
299
300 $HOME is always replaced with '~'.
301 If depth==0, the full path is returned."""
302
303 cwd = os.getcwd().replace(HOME,"~").split(os.sep)
304 if '~' in cwd and len(cwd) == depth+1:
305 depth += 1
306 out = os.sep.join(cwd[-depth:])
307 if out:
308 return out
309 else:
310 return os.sep
311
312 class Prompt1(BasePrompt):
313 """Input interactive prompt similar to Mathematica's."""
314
315 def __init__(self,cache,sep='\n',prompt='In [\\#]: ',pad_left=True):
316 BasePrompt.__init__(self,cache,sep,prompt,pad_left)
317
318 def set_colors(self):
319 self.set_p_str()
320 Colors = self.cache.color_table.active_colors # shorthand
321 self.col_p = Colors.in_prompt
322 self.col_num = Colors.in_number
323 self.col_norm = Colors.in_normal
324 # We need a non-input version of these escapes for the '--->'
325 # auto-call prompts used in the auto_rewrite() method.
326 self.col_p_ni = self.col_p.replace('\001','').replace('\002','')
327 self.col_norm_ni = Colors.normal
328
329 def __str__(self):
330 self.cache.prompt_count += 1
331 self.cache.last_prompt = str_safe(self.p_str_nocolor).split('\n')[-1]
332 return str_safe(self.p_str)
333
334 def auto_rewrite(self):
335 """Print a string of the form '--->' which lines up with the previous
336 input string. Useful for systems which re-write the user input when
337 handling automatically special syntaxes."""
338
339 curr = str(self.cache.last_prompt)
340 nrspaces = len(self.rspace.search(curr).group())
341 return '%s%s>%s%s' % (self.col_p_ni,'-'*(len(curr)-nrspaces-1),
342 ' '*nrspaces,self.col_norm_ni)
343
344 class PromptOut(BasePrompt):
345 """Output interactive prompt similar to Mathematica's."""
346
347 def __init__(self,cache,sep='',prompt='Out[\\#]: ',pad_left=True):
348 BasePrompt.__init__(self,cache,sep,prompt,pad_left)
349 if not self.p_template:
350 self.__str__ = lambda: ''
351
352 def set_colors(self):
353 self.set_p_str()
354 Colors = self.cache.color_table.active_colors # shorthand
355 self.col_p = Colors.out_prompt
356 self.col_num = Colors.out_number
357 self.col_norm = Colors.normal
358
359 class Prompt2(BasePrompt):
360 """Interactive continuation prompt."""
361
362 def __init__(self,cache,prompt=' .\\D.: ',pad_left=True):
363 self.cache = cache
364 self.p_template = prompt
365 self.pad_left = pad_left
366 self.set_p_str()
367
368 def set_p_str(self):
369 import os,time # needed in locals for prompt string handling
370 loc = locals()
371 self.p_str = ItplNS('%s%s%s' %
372 ('${self.col_p2}',
373 multiple_replace(prompt_specials, self.p_template),
374 '$self.col_norm'),
375 self.cache.user_ns,loc)
376 self.p_str_nocolor = ItplNS(multiple_replace(prompt_specials_nocolor,
377 self.p_template),
378 self.cache.user_ns,loc)
379
380 def set_colors(self):
381 self.set_p_str()
382 Colors = self.cache.color_table.active_colors
383 self.col_p2 = Colors.in_prompt2
384 self.col_norm = Colors.in_normal
385 # FIXME (2004-06-16) HACK: prevent crashes for users who haven't
386 # updated their prompt_in2 definitions. Remove eventually.
387 self.col_p = Colors.out_prompt
388 self.col_num = Colors.out_number
389
390
391 #-----------------------------------------------------------------------------
392 class CachedOutput:
393 """Class for printing output from calculations while keeping a cache of
394 reults. It dynamically creates global variables prefixed with _ which
395 contain these results.
396
397 Meant to be used as a sys.displayhook replacement, providing numbered
398 prompts and cache services.
399
400 Initialize with initial and final values for cache counter (this defines
401 the maximum size of the cache."""
402
403 def __init__(self,shell,cache_size,Pprint,
404 colors='NoColor',input_sep='\n',
405 output_sep='\n',output_sep2='',
406 ps1 = None, ps2 = None,ps_out = None,pad_left=True):
407
408 cache_size_min = 3
409 if cache_size <= 0:
410 self.do_full_cache = 0
411 cache_size = 0
412 elif cache_size < cache_size_min:
413 self.do_full_cache = 0
414 cache_size = 0
415 warn('caching was disabled (min value for cache size is %s).' %
416 cache_size_min,level=3)
417 else:
418 self.do_full_cache = 1
419
420 self.cache_size = cache_size
421 self.input_sep = input_sep
422
423 # we need a reference to the user-level namespace
424 self.shell = shell
425 self.user_ns = shell.user_ns
426 # and to the user's input
427 self.input_hist = shell.history.input_cache
428
429 # Set input prompt strings and colors
430 if cache_size == 0:
431 if ps1.find('%n') > -1 or ps1.find(r'\#') > -1 \
432 or ps1.find(r'\N') > -1:
433 ps1 = '>>> '
434 if ps2.find('%n') > -1 or ps2.find(r'\#') > -1 \
435 or ps2.find(r'\N') > -1:
436 ps2 = '... '
437 self.ps1_str = self._set_prompt_str(ps1,'In [\\#]: ','>>> ')
438 self.ps2_str = self._set_prompt_str(ps2,' .\\D.: ','... ')
439 self.ps_out_str = self._set_prompt_str(ps_out,'Out[\\#]: ','')
440
441 self.color_table = PromptColors
442 self.prompt1 = Prompt1(self,sep=input_sep,prompt=self.ps1_str,
443 pad_left=pad_left)
444 self.prompt2 = Prompt2(self,prompt=self.ps2_str,pad_left=pad_left)
445 self.prompt_out = PromptOut(self,sep='',prompt=self.ps_out_str,
446 pad_left=pad_left)
447 self.set_colors(colors)
448
449 # other more normal stuff
450 # b/c each call to the In[] prompt raises it by 1, even the first.
451 self.prompt_count = 0
452 # Store the last prompt string each time, we need it for aligning
453 # continuation and auto-rewrite prompts
454 self.last_prompt = ''
455 self.Pprint = Pprint
456 self.output_sep = output_sep
457 self.output_sep2 = output_sep2
458 self._,self.__,self.___ = '','',''
459 self.pprint_types = map(type,[(),[],{}])
460
461 # these are deliberately global:
462 to_user_ns = {'_':self._,'__':self.__,'___':self.___}
463 self.user_ns.update(to_user_ns)
464
465 def _set_prompt_str(self,p_str,cache_def,no_cache_def):
466 if p_str is None:
467 if self.do_full_cache:
468 return cache_def
469 else:
470 return no_cache_def
471 else:
472 return p_str
473
474 def set_colors(self,colors):
475 """Set the active color scheme and configure colors for the three
476 prompt subsystems."""
477
478 # FIXME: the prompt_specials global should be gobbled inside this
479 # class instead. Do it when cleaning up the whole 3-prompt system.
480 global prompt_specials
481 if colors.lower()=='nocolor':
482 prompt_specials = prompt_specials_nocolor
483 else:
484 prompt_specials = prompt_specials_color
485
486 self.color_table.set_active_scheme(colors)
487 self.prompt1.set_colors()
488 self.prompt2.set_colors()
489 self.prompt_out.set_colors()
490
491 def __call__(self,arg=None):
492 """Printing with history cache management.
493
494 This is invoked everytime the interpreter needs to print, and is
495 activated by setting the variable sys.displayhook to it."""
496
497 # If something injected a '_' variable in __builtin__, delete
498 # ipython's automatic one so we don't clobber that. gettext() in
499 # particular uses _, so we need to stay away from it.
500 if '_' in __builtin__.__dict__:
501 try:
502 del self.user_ns['_']
503 except KeyError:
504 pass
505 if arg is not None:
506 cout_write = Term.cout.write # fast lookup
507 # first handle the cache and counters
508
509 # do not print output if input ends in ';'
510 if self.input_hist[self.prompt_count].endswith(';\n'):
511 return
512 # don't use print, puts an extra space
513 cout_write(self.output_sep)
514 outprompt = self.shell.hooks.generate_output_prompt()
515 if self.do_full_cache:
516 cout_write(outprompt)
517
518 # and now call a possibly user-defined print mechanism
519 manipulated_val = self.display(arg)
520
521 # user display hooks can change the variable to be stored in
522 # output history
523
524 if manipulated_val is not None:
525 arg = manipulated_val
526
527 # avoid recursive reference when displaying _oh/Out
528 if arg is not self.user_ns['_oh']:
529 self.update(arg)
530
531 cout_write(self.output_sep2)
532 Term.cout.flush()
533
534 def _display(self,arg):
535 """Default printer method, uses pprint.
536
537 Do ip.set_hook("result_display", my_displayhook) for custom result
538 display, e.g. when your own objects need special formatting.
539 """
540 try:
541 return IPython.generics.result_display(arg)
542 except TryNext:
543 return self.shell.hooks.result_display(arg)
544
545 # Assign the default display method:
546 display = _display
547
548 def update(self,arg):
549 #print '***cache_count', self.cache_count # dbg
550 if len(self.user_ns['_oh']) >= self.cache_size and self.do_full_cache:
551 warn('Output cache limit (currently '+
552 `self.cache_size`+' entries) hit.\n'
553 'Flushing cache and resetting history counter...\n'
554 'The only history variables available will be _,__,___ and _1\n'
555 'with the current result.')
556
557 self.flush()
558 # Don't overwrite '_' and friends if '_' is in __builtin__ (otherwise
559 # we cause buggy behavior for things like gettext).
560 if '_' not in __builtin__.__dict__:
561 self.___ = self.__
562 self.__ = self._
563 self._ = arg
564 self.user_ns.update({'_':self._,'__':self.__,'___':self.___})
565
566 # hackish access to top-level namespace to create _1,_2... dynamically
567 to_main = {}
568 if self.do_full_cache:
569 new_result = '_'+`self.prompt_count`
570 to_main[new_result] = arg
571 self.user_ns.update(to_main)
572 self.user_ns['_oh'][self.prompt_count] = arg
573
574 def flush(self):
575 if not self.do_full_cache:
576 raise ValueError,"You shouldn't have reached the cache flush "\
577 "if full caching is not enabled!"
578 # delete auto-generated vars from global namespace
579
580 for n in range(1,self.prompt_count + 1):
581 key = '_'+`n`
582 try:
583 del self.user_ns[key]
584 except: pass
585 self.user_ns['_oh'].clear()
586
587 if '_' not in __builtin__.__dict__:
588 self.user_ns.update({'_':None,'__':None, '___':None})
589 import gc
590 gc.collect() # xxx needed?
591
@@ -0,0 +1,357 b''
1 # encoding: utf-8
2 # -*- test-case-name: IPython.test.test_shell -*-
3
4 """The core IPython Shell"""
5
6 __docformat__ = "restructuredtext en"
7
8 #-------------------------------------------------------------------------------
9 # Copyright (C) 2008 The IPython Development Team
10 #
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
13 #-------------------------------------------------------------------------------
14
15 #-------------------------------------------------------------------------------
16 # Imports
17 #-------------------------------------------------------------------------------
18
19 import pprint
20 import signal
21 import sys
22 import threading
23 import time
24
25 from code import InteractiveConsole, softspace
26 from StringIO import StringIO
27
28 from IPython.OutputTrap import OutputTrap
29 from IPython import ultraTB
30
31 from IPython.kernel.error import NotDefined
32
33
34 class InteractiveShell(InteractiveConsole):
35 """The Basic IPython Shell class.
36
37 This class provides the basic capabilities of IPython. Currently
38 this class does not do anything IPython specific. That is, it is
39 just a python shell.
40
41 It is modelled on code.InteractiveConsole, but adds additional
42 capabilities. These additional capabilities are what give IPython
43 its power.
44
45 The current version of this class is meant to be a prototype that guides
46 the future design of the IPython core. This class must not use Twisted
47 in any way, but it must be designed in a way that makes it easy to
48 incorporate into Twisted and hook network protocols up to.
49
50 Some of the methods of this class comprise the official IPython core
51 interface. These methods must be tread safe and they must return types
52 that can be easily serialized by protocols such as PB, XML-RPC and SOAP.
53 Locks have been provided for making the methods thread safe, but additional
54 locks can be added as needed.
55
56 Any method that is meant to be a part of the official interface must also
57 be declared in the kernel.coreservice.ICoreService interface. Eventually
58 all other methods should have single leading underscores to note that they
59 are not designed to be 'public.' Currently, because this class inherits
60 from code.InteractiveConsole there are many private methods w/o leading
61 underscores. The interface should be as simple as possible and methods
62 should not be added to the interface unless they really need to be there.
63
64 Note:
65
66 - For now I am using methods named put/get to move objects in/out of the
67 users namespace. Originally, I was calling these methods push/pull, but
68 because code.InteractiveConsole already has a push method, I had to use
69 something different. Eventually, we probably won't subclass this class
70 so we can call these methods whatever we want. So, what do we want to
71 call them?
72 - We need a way of running the trapping of stdout/stderr in different ways.
73 We should be able to i) trap, ii) not trap at all or iii) trap and echo
74 things to stdout and stderr.
75 - How should errors be handled? Should exceptions be raised?
76 - What should methods that don't compute anything return? The default of
77 None?
78 """
79
80 def __init__(self, locals=None, filename="<console>"):
81 """Creates a new TrappingInteractiveConsole object."""
82 InteractiveConsole.__init__(self,locals,filename)
83 self._trap = OutputTrap(debug=0)
84 self._stdin = []
85 self._stdout = []
86 self._stderr = []
87 self._last_type = self._last_traceback = self._last_value = None
88 #self._namespace_lock = threading.Lock()
89 #self._command_lock = threading.Lock()
90 self.lastCommandIndex = -1
91 # I am using this user defined signal to interrupt the currently
92 # running command. I am not sure if this is the best way, but
93 # it is working!
94 # This doesn't work on Windows as it doesn't have this signal.
95 #signal.signal(signal.SIGUSR1, self._handleSIGUSR1)
96
97 # An exception handler. Experimental: later we need to make the
98 # modes/colors available to user configuration, etc.
99 self.tbHandler = ultraTB.FormattedTB(color_scheme='NoColor',
100 mode='Context',
101 tb_offset=2)
102
103 def _handleSIGUSR1(self, signum, frame):
104 """Handle the SIGUSR1 signal by printing to stderr."""
105 print>>sys.stderr, "Command stopped."
106
107 def _prefilter(self, line, more):
108 return line
109
110 def _trapRunlines(self, lines):
111 """
112 This executes the python source code, source, in the
113 self.locals namespace and traps stdout and stderr. Upon
114 exiting, self.out and self.err contain the values of
115 stdout and stderr for the last executed command only.
116 """
117
118 # Execute the code
119 #self._namespace_lock.acquire()
120 self._trap.flush()
121 self._trap.trap()
122 self._runlines(lines)
123 self.lastCommandIndex += 1
124 self._trap.release()
125 #self._namespace_lock.release()
126
127 # Save stdin, stdout and stderr to lists
128 #self._command_lock.acquire()
129 self._stdin.append(lines)
130 self._stdout.append(self.prune_output(self._trap.out.getvalue()))
131 self._stderr.append(self.prune_output(self._trap.err.getvalue()))
132 #self._command_lock.release()
133
134 def prune_output(self, s):
135 """Only return the first and last 1600 chars of stdout and stderr.
136
137 Something like this is required to make sure that the engine and
138 controller don't become overwhelmed by the size of stdout/stderr.
139 """
140 if len(s) > 3200:
141 return s[:1600] + '\n............\n' + s[-1600:]
142 else:
143 return s
144
145 # Lifted from iplib.InteractiveShell
146 def _runlines(self,lines):
147 """Run a string of one or more lines of source.
148
149 This method is capable of running a string containing multiple source
150 lines, as if they had been entered at the IPython prompt. Since it
151 exposes IPython's processing machinery, the given strings can contain
152 magic calls (%magic), special shell access (!cmd), etc."""
153
154 # We must start with a clean buffer, in case this is run from an
155 # interactive IPython session (via a magic, for example).
156 self.resetbuffer()
157 lines = lines.split('\n')
158 more = 0
159 for line in lines:
160 # skip blank lines so we don't mess up the prompt counter, but do
161 # NOT skip even a blank line if we are in a code block (more is
162 # true)
163 if line or more:
164 more = self.push((self._prefilter(line,more)))
165 # IPython's runsource returns None if there was an error
166 # compiling the code. This allows us to stop processing right
167 # away, so the user gets the error message at the right place.
168 if more is None:
169 break
170 # final newline in case the input didn't have it, so that the code
171 # actually does get executed
172 if more:
173 self.push('\n')
174
175 def runcode(self, code):
176 """Execute a code object.
177
178 When an exception occurs, self.showtraceback() is called to
179 display a traceback. All exceptions are caught except
180 SystemExit, which is reraised.
181
182 A note about KeyboardInterrupt: this exception may occur
183 elsewhere in this code, and may not always be caught. The
184 caller should be prepared to deal with it.
185
186 """
187
188 self._last_type = self._last_traceback = self._last_value = None
189 try:
190 exec code in self.locals
191 except:
192 # Since the exception info may need to travel across the wire, we
193 # pack it in right away. Note that we are abusing the exception
194 # value to store a fully formatted traceback, since the stack can
195 # not be serialized for network transmission.
196 et,ev,tb = sys.exc_info()
197 self._last_type = et
198 self._last_traceback = tb
199 tbinfo = self.tbHandler.text(et,ev,tb)
200 # Construct a meaningful traceback message for shipping over the
201 # wire.
202 buf = pprint.pformat(self.buffer)
203 try:
204 ename = et.__name__
205 except:
206 ename = et
207 msg = """\
208 %(ev)s
209 ***************************************************************************
210 An exception occurred in an IPython engine while executing user code.
211
212 Current execution buffer (lines being run):
213 %(buf)s
214
215 A full traceback from the actual engine:
216 %(tbinfo)s
217 ***************************************************************************
218 """ % locals()
219 self._last_value = msg
220 else:
221 if softspace(sys.stdout, 0):
222 print
223
224 ##################################################################
225 # Methods that are a part of the official interface
226 #
227 # These methods should also be put in the
228 # kernel.coreservice.ICoreService interface.
229 #
230 # These methods must conform to certain restrictions that allow
231 # them to be exposed to various network protocols:
232 #
233 # - As much as possible, these methods must return types that can be
234 # serialized by PB, XML-RPC and SOAP. None is OK.
235 # - Every method must be thread safe. There are some locks provided
236 # for this purpose, but new, specialized locks can be added to the
237 # class.
238 ##################################################################
239
240 # Methods for running code
241
242 def exc_info(self):
243 """Return exception information much like sys.exc_info().
244
245 This method returns the same (etype,evalue,tb) tuple as sys.exc_info,
246 but from the last time that the engine had an exception fire."""
247
248 return self._last_type,self._last_value,self._last_traceback
249
250 def execute(self, lines):
251 self._trapRunlines(lines)
252 if self._last_type is None:
253 return self.getCommand()
254 else:
255 raise self._last_type(self._last_value)
256
257 # Methods for working with the namespace
258
259 def put(self, key, value):
260 """Put value into locals namespace with name key.
261
262 I have often called this push(), but in this case the
263 InteractiveConsole class already defines a push() method that
264 is different.
265 """
266
267 if not isinstance(key, str):
268 raise TypeError, "Objects must be keyed by strings."
269 self.update({key:value})
270
271 def get(self, key):
272 """Gets an item out of the self.locals dict by key.
273
274 Raise NameError if the object doesn't exist.
275
276 I have often called this pull(). I still like that better.
277 """
278
279 class NotDefined(object):
280 """A class to signify an objects that is not in the users ns."""
281 pass
282
283 if not isinstance(key, str):
284 raise TypeError, "Objects must be keyed by strings."
285 result = self.locals.get(key, NotDefined())
286 if isinstance(result, NotDefined):
287 raise NameError('name %s is not defined' % key)
288 else:
289 return result
290
291
292 def update(self, dictOfData):
293 """Loads a dict of key value pairs into the self.locals namespace."""
294 if not isinstance(dictOfData, dict):
295 raise TypeError, "update() takes a dict object."
296 #self._namespace_lock.acquire()
297 self.locals.update(dictOfData)
298 #self._namespace_lock.release()
299
300 # Methods for getting stdout/stderr/stdin
301
302 def reset(self):
303 """Reset the InteractiveShell."""
304
305 #self._command_lock.acquire()
306 self._stdin = []
307 self._stdout = []
308 self._stderr = []
309 self.lastCommandIndex = -1
310 #self._command_lock.release()
311
312 #self._namespace_lock.acquire()
313 # preserve id, mpi objects
314 mpi = self.locals.get('mpi', None)
315 id = self.locals.get('id', None)
316 del self.locals
317 self.locals = {'mpi': mpi, 'id': id}
318 #self._namespace_lock.release()
319
320 def getCommand(self,i=None):
321 """Get the stdin/stdout/stderr of command i."""
322
323 #self._command_lock.acquire()
324
325
326 if i is not None and not isinstance(i, int):
327 raise TypeError("Command index not an int: " + str(i))
328
329 if i in range(self.lastCommandIndex + 1):
330 inResult = self._stdin[i]
331 outResult = self._stdout[i]
332 errResult = self._stderr[i]
333 cmdNum = i
334 elif i is None and self.lastCommandIndex >= 0:
335 inResult = self._stdin[self.lastCommandIndex]
336 outResult = self._stdout[self.lastCommandIndex]
337 errResult = self._stderr[self.lastCommandIndex]
338 cmdNum = self.lastCommandIndex
339 else:
340 inResult = None
341 outResult = None
342 errResult = None
343
344 #self._command_lock.release()
345
346 if inResult is not None:
347 return dict(commandIndex=cmdNum, stdin=inResult, stdout=outResult, stderr=errResult)
348 else:
349 raise IndexError("Command with index %s does not exist" % str(i))
350
351 def getLastCommandIndex(self):
352 """Get the index of the last command."""
353 #self._command_lock.acquire()
354 ind = self.lastCommandIndex
355 #self._command_lock.release()
356 return ind
357
@@ -0,0 +1,10 b''
1 # encoding: utf-8
2
3 __docformat__ = "restructuredtext en"
4
5 #-------------------------------------------------------------------------------
6 # Copyright (C) 2008 The IPython Development Team
7 #
8 # Distributed under the terms of the BSD License. The full license is in
9 # the file COPYING, distributed as part of this software.
10 #------------------------------------------------------------------------------- No newline at end of file
@@ -0,0 +1,67 b''
1 # encoding: utf-8
2
3 """This file contains unittests for the shell.py module."""
4
5 __docformat__ = "restructuredtext en"
6
7 #-------------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
13
14 #-------------------------------------------------------------------------------
15 # Imports
16 #-------------------------------------------------------------------------------
17
18 import unittest
19 from IPython.kernel.core import shell
20
21 resultKeys = ('commandIndex', 'stdin', 'stdout', 'stderr')
22
23 class BasicShellTest(unittest.TestCase):
24
25 def setUp(self):
26 self.s = shell.InteractiveShell()
27
28 def testExecute(self):
29 commands = [(0,"a = 5","",""),
30 (1,"b = 10","",""),
31 (2,"c = a + b","",""),
32 (3,"print c","15\n",""),
33 (4,"import math","",""),
34 (5,"2.0*math.pi","6.2831853071795862\n","")]
35 for c in commands:
36 result = self.s.execute(c[1])
37 self.assertEquals(result, dict(zip(resultKeys,c)))
38
39 def testPutGet(self):
40 objs = [10,"hi there",1.2342354,{"p":(1,2)}]
41 for o in objs:
42 self.s.put("key",o)
43 value = self.s.get("key")
44 self.assertEquals(value,o)
45 self.assertRaises(TypeError, self.s.put,10)
46 self.assertRaises(TypeError, self.s.get,10)
47 self.s.reset()
48 self.assertRaises(NameError, self.s.get, 'a')
49
50 def testUpdate(self):
51 d = {"a": 10, "b": 34.3434, "c": "hi there"}
52 self.s.update(d)
53 for k in d.keys():
54 value = self.s.get(k)
55 self.assertEquals(value, d[k])
56 self.assertRaises(TypeError, self.s.update, [1,2,2])
57
58 def testCommand(self):
59 self.assertRaises(IndexError,self.s.getCommand)
60 self.s.execute("a = 5")
61 self.assertEquals(self.s.getCommand(), dict(zip(resultKeys, (0,"a = 5","",""))))
62 self.assertEquals(self.s.getCommand(0), dict(zip(resultKeys, (0,"a = 5","",""))))
63 self.s.reset()
64 self.assertEquals(self.s.getLastCommandIndex(),-1)
65 self.assertRaises(IndexError,self.s.getCommand)
66
67 No newline at end of file
@@ -0,0 +1,62 b''
1 # encoding: utf-8
2
3 """Some formatter objects to extract traceback information by replacing
4 sys.excepthook()."""
5
6 __docformat__ = "restructuredtext en"
7
8 #-------------------------------------------------------------------------------
9 # Copyright (C) 2008 The IPython Development Team
10 #
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
13 #-------------------------------------------------------------------------------
14
15 #-------------------------------------------------------------------------------
16 # Imports
17 #-------------------------------------------------------------------------------
18
19 import traceback
20
21
22 class ITracebackFormatter(object):
23 """ Objects conforming to this interface will format tracebacks into other
24 objects.
25 """
26
27 # The kind of formatter.
28 kind = 'traceback'
29
30 # The unique identifier for this formatter.
31 identifier = None
32
33
34 def __call__(self, exc_type, exc_value, exc_traceback):
35 """ Return a formatted representation of a traceback.
36 """
37
38 raise NotImplementedError
39
40
41 class PlainTracebackFormatter(ITracebackFormatter):
42 """ Return a string with the regular traceback information.
43 """
44
45 # The unique identifier for this formatter.
46 identifier = 'plain'
47
48
49 def __init__(self, limit=None):
50 # The maximum number of stack levels to go back.
51 # None implies all stack levels are returned.
52 self.limit = limit
53
54 def __call__(self, exc_type, exc_value, exc_traceback):
55 """ Return a string with the regular traceback information.
56 """
57
58 lines = traceback.format_tb(exc_traceback, self.limit)
59 lines.append('%s: %s' % (exc_type.__name__, exc_value))
60 return '\n'.join(lines)
61
62
@@ -0,0 +1,83 b''
1 # encoding: utf-8
2
3 """Object to manage sys.excepthook()."""
4
5 __docformat__ = "restructuredtext en"
6
7 #-------------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
13
14 #-------------------------------------------------------------------------------
15 # Imports
16 #-------------------------------------------------------------------------------
17
18 import sys
19
20
21 class TracebackTrap(object):
22 """ Object to trap and format tracebacks.
23 """
24
25 def __init__(self, formatters=None):
26 # A list of formatters to apply.
27 if formatters is None:
28 formatters = []
29 self.formatters = formatters
30
31 # All of the traceback information provided to sys.excepthook().
32 self.args = None
33
34 # The previous hook before we replace it.
35 self.old_hook = None
36
37
38 def hook(self, *args):
39 """ This method actually implements the hook.
40 """
41
42 self.args = args
43
44 def set(self):
45 """ Set the hook.
46 """
47
48 if sys.excepthook is not self.hook:
49 self.old_hook = sys.excepthook
50 sys.excepthook = self.hook
51
52 def unset(self):
53 """ Unset the hook.
54 """
55
56 sys.excepthook = self.old_hook
57
58 def clear(self):
59 """ Remove the stored traceback.
60 """
61
62 self.args = None
63
64 def add_to_message(self, message):
65 """ Add the formatted display of the traceback to the message dictionary
66 being returned from the interpreter to its listeners.
67
68 Parameters
69 ----------
70 message : dict
71 """
72
73 # If there was no traceback, then don't add anything.
74 if self.args is None:
75 return
76
77 # Go through the list of formatters and let them add their formatting.
78 traceback = {}
79 for formatter in self.formatters:
80 traceback[formatter.identifier] = formatter(*self.args)
81
82 message['traceback'] = traceback
83
This diff has been collapsed as it changes many lines, (1018 lines changed) Show them Hide them
@@ -0,0 +1,1018 b''
1 # encoding: utf-8
2
3 """
4 ultraTB.py -- Spice up your tracebacks!
5
6 * ColorTB
7 I've always found it a bit hard to visually parse tracebacks in Python. The
8 ColorTB class is a solution to that problem. It colors the different parts of a
9 traceback in a manner similar to what you would expect from a syntax-highlighting
10 text editor.
11
12 Installation instructions for ColorTB:
13 import sys,ultraTB
14 sys.excepthook = ultraTB.ColorTB()
15
16 * VerboseTB
17 I've also included a port of Ka-Ping Yee's "cgitb.py" that produces all kinds
18 of useful info when a traceback occurs. Ping originally had it spit out HTML
19 and intended it for CGI programmers, but why should they have all the fun? I
20 altered it to spit out colored text to the terminal. It's a bit overwhelming,
21 but kind of neat, and maybe useful for long-running programs that you believe
22 are bug-free. If a crash *does* occur in that type of program you want details.
23 Give it a shot--you'll love it or you'll hate it.
24
25 Note:
26
27 The Verbose mode prints the variables currently visible where the exception
28 happened (shortening their strings if too long). This can potentially be
29 very slow, if you happen to have a huge data structure whose string
30 representation is complex to compute. Your computer may appear to freeze for
31 a while with cpu usage at 100%. If this occurs, you can cancel the traceback
32 with Ctrl-C (maybe hitting it more than once).
33
34 If you encounter this kind of situation often, you may want to use the
35 Verbose_novars mode instead of the regular Verbose, which avoids formatting
36 variables (but otherwise includes the information and context given by
37 Verbose).
38
39
40 Installation instructions for ColorTB:
41 import sys,ultraTB
42 sys.excepthook = ultraTB.VerboseTB()
43
44 Note: Much of the code in this module was lifted verbatim from the standard
45 library module 'traceback.py' and Ka-Ping Yee's 'cgitb.py'.
46
47 * Color schemes
48 The colors are defined in the class TBTools through the use of the
49 ColorSchemeTable class. Currently the following exist:
50
51 - NoColor: allows all of this module to be used in any terminal (the color
52 escapes are just dummy blank strings).
53
54 - Linux: is meant to look good in a terminal like the Linux console (black
55 or very dark background).
56
57 - LightBG: similar to Linux but swaps dark/light colors to be more readable
58 in light background terminals.
59
60 You can implement other color schemes easily, the syntax is fairly
61 self-explanatory. Please send back new schemes you develop to the author for
62 possible inclusion in future releases.
63
64 $Id: ultraTB.py 2480 2007-07-06 19:33:43Z fperez $"""
65
66 __docformat__ = "restructuredtext en"
67
68 #-------------------------------------------------------------------------------
69 # Copyright (C) 2008 The IPython Development Team
70 #
71 # Distributed under the terms of the BSD License. The full license is in
72 # the file COPYING, distributed as part of this software.
73 #-------------------------------------------------------------------------------
74
75 #-------------------------------------------------------------------------------
76 # Imports
77 #-------------------------------------------------------------------------------
78
79 from IPython import Release
80 __author__ = '%s <%s>\n%s <%s>' % (Release.authors['Nathan']+
81 Release.authors['Fernando'])
82 __license__ = Release.license
83
84 # Required modules
85 import inspect
86 import keyword
87 import linecache
88 import os
89 import pydoc
90 import re
91 import string
92 import sys
93 import time
94 import tokenize
95 import traceback
96 import types
97
98 # For purposes of monkeypatching inspect to fix a bug in it.
99 from inspect import getsourcefile, getfile, getmodule,\
100 ismodule, isclass, ismethod, isfunction, istraceback, isframe, iscode
101
102
103 # IPython's own modules
104 # Modified pdb which doesn't damage IPython's readline handling
105 from IPython import Debugger, PyColorize
106 from IPython.ipstruct import Struct
107 from IPython.excolors import ExceptionColors
108 from IPython.genutils import Term,uniq_stable,error,info
109
110 # Globals
111 # amount of space to put line numbers before verbose tracebacks
112 INDENT_SIZE = 8
113
114 # Default color scheme. This is used, for example, by the traceback
115 # formatter. When running in an actual IPython instance, the user's rc.colors
116 # value is used, but havinga module global makes this functionality available
117 # to users of ultraTB who are NOT running inside ipython.
118 DEFAULT_SCHEME = 'NoColor'
119
120 #---------------------------------------------------------------------------
121 # Code begins
122
123 # Utility functions
124 def inspect_error():
125 """Print a message about internal inspect errors.
126
127 These are unfortunately quite common."""
128
129 error('Internal Python error in the inspect module.\n'
130 'Below is the traceback from this internal error.\n')
131
132
133 def findsource(object):
134 """Return the entire source file and starting line number for an object.
135
136 The argument may be a module, class, method, function, traceback, frame,
137 or code object. The source code is returned as a list of all the lines
138 in the file and the line number indexes a line in that list. An IOError
139 is raised if the source code cannot be retrieved.
140
141 FIXED version with which we monkeypatch the stdlib to work around a bug."""
142
143 file = getsourcefile(object) or getfile(object)
144 module = getmodule(object, file)
145 if module:
146 lines = linecache.getlines(file, module.__dict__)
147 else:
148 lines = linecache.getlines(file)
149 if not lines:
150 raise IOError('could not get source code')
151
152 if ismodule(object):
153 return lines, 0
154
155 if isclass(object):
156 name = object.__name__
157 pat = re.compile(r'^(\s*)class\s*' + name + r'\b')
158 # make some effort to find the best matching class definition:
159 # use the one with the least indentation, which is the one
160 # that's most probably not inside a function definition.
161 candidates = []
162 for i in range(len(lines)):
163 match = pat.match(lines[i])
164 if match:
165 # if it's at toplevel, it's already the best one
166 if lines[i][0] == 'c':
167 return lines, i
168 # else add whitespace to candidate list
169 candidates.append((match.group(1), i))
170 if candidates:
171 # this will sort by whitespace, and by line number,
172 # less whitespace first
173 candidates.sort()
174 return lines, candidates[0][1]
175 else:
176 raise IOError('could not find class definition')
177
178 if ismethod(object):
179 object = object.im_func
180 if isfunction(object):
181 object = object.func_code
182 if istraceback(object):
183 object = object.tb_frame
184 if isframe(object):
185 object = object.f_code
186 if iscode(object):
187 if not hasattr(object, 'co_firstlineno'):
188 raise IOError('could not find function definition')
189 pat = re.compile(r'^(\s*def\s)|(.*(?<!\w)lambda(:|\s))|^(\s*@)')
190 pmatch = pat.match
191 # fperez - fix: sometimes, co_firstlineno can give a number larger than
192 # the length of lines, which causes an error. Safeguard against that.
193 lnum = min(object.co_firstlineno,len(lines))-1
194 while lnum > 0:
195 if pmatch(lines[lnum]): break
196 lnum -= 1
197
198 return lines, lnum
199 raise IOError('could not find code object')
200
201 # Monkeypatch inspect to apply our bugfix. This code only works with py25
202 if sys.version_info[:2] >= (2,5):
203 inspect.findsource = findsource
204
205 def _fixed_getinnerframes(etb, context=1,tb_offset=0):
206 import linecache
207 LNUM_POS, LINES_POS, INDEX_POS = 2, 4, 5
208
209 records = inspect.getinnerframes(etb, context)
210
211 # If the error is at the console, don't build any context, since it would
212 # otherwise produce 5 blank lines printed out (there is no file at the
213 # console)
214 rec_check = records[tb_offset:]
215 try:
216 rname = rec_check[0][1]
217 if rname == '<ipython console>' or rname.endswith('<string>'):
218 return rec_check
219 except IndexError:
220 pass
221
222 aux = traceback.extract_tb(etb)
223 assert len(records) == len(aux)
224 for i, (file, lnum, _, _) in zip(range(len(records)), aux):
225 maybeStart = lnum-1 - context//2
226 start = max(maybeStart, 0)
227 end = start + context
228 lines = linecache.getlines(file)[start:end]
229 # pad with empty lines if necessary
230 if maybeStart < 0:
231 lines = (['\n'] * -maybeStart) + lines
232 if len(lines) < context:
233 lines += ['\n'] * (context - len(lines))
234 buf = list(records[i])
235 buf[LNUM_POS] = lnum
236 buf[INDEX_POS] = lnum - 1 - start
237 buf[LINES_POS] = lines
238 records[i] = tuple(buf)
239 return records[tb_offset:]
240
241 # Helper function -- largely belongs to VerboseTB, but we need the same
242 # functionality to produce a pseudo verbose TB for SyntaxErrors, so that they
243 # can be recognized properly by ipython.el's py-traceback-line-re
244 # (SyntaxErrors have to be treated specially because they have no traceback)
245
246 _parser = PyColorize.Parser()
247
248 def _formatTracebackLines(lnum, index, lines, Colors, lvals=None,scheme=None):
249 numbers_width = INDENT_SIZE - 1
250 res = []
251 i = lnum - index
252
253 # This lets us get fully syntax-highlighted tracebacks.
254 if scheme is None:
255 try:
256 scheme = __IPYTHON__.rc.colors
257 except:
258 scheme = DEFAULT_SCHEME
259 _line_format = _parser.format2
260
261 for line in lines:
262 new_line, err = _line_format(line,'str',scheme)
263 if not err: line = new_line
264
265 if i == lnum:
266 # This is the line with the error
267 pad = numbers_width - len(str(i))
268 if pad >= 3:
269 marker = '-'*(pad-3) + '-> '
270 elif pad == 2:
271 marker = '> '
272 elif pad == 1:
273 marker = '>'
274 else:
275 marker = ''
276 num = marker + str(i)
277 line = '%s%s%s %s%s' %(Colors.linenoEm, num,
278 Colors.line, line, Colors.Normal)
279 else:
280 num = '%*s' % (numbers_width,i)
281 line = '%s%s%s %s' %(Colors.lineno, num,
282 Colors.Normal, line)
283
284 res.append(line)
285 if lvals and i == lnum:
286 res.append(lvals + '\n')
287 i = i + 1
288 return res
289
290
291 #---------------------------------------------------------------------------
292 # Module classes
293 class TBTools:
294 """Basic tools used by all traceback printer classes."""
295
296 def __init__(self,color_scheme = 'NoColor',call_pdb=False):
297 # Whether to call the interactive pdb debugger after printing
298 # tracebacks or not
299 self.call_pdb = call_pdb
300
301 # Create color table
302 self.color_scheme_table = ExceptionColors
303
304 self.set_colors(color_scheme)
305 self.old_scheme = color_scheme # save initial value for toggles
306
307 if call_pdb:
308 self.pdb = Debugger.Pdb(self.color_scheme_table.active_scheme_name)
309 else:
310 self.pdb = None
311
312 def set_colors(self,*args,**kw):
313 """Shorthand access to the color table scheme selector method."""
314
315 # Set own color table
316 self.color_scheme_table.set_active_scheme(*args,**kw)
317 # for convenience, set Colors to the active scheme
318 self.Colors = self.color_scheme_table.active_colors
319 # Also set colors of debugger
320 if hasattr(self,'pdb') and self.pdb is not None:
321 self.pdb.set_colors(*args,**kw)
322
323 def color_toggle(self):
324 """Toggle between the currently active color scheme and NoColor."""
325
326 if self.color_scheme_table.active_scheme_name == 'NoColor':
327 self.color_scheme_table.set_active_scheme(self.old_scheme)
328 self.Colors = self.color_scheme_table.active_colors
329 else:
330 self.old_scheme = self.color_scheme_table.active_scheme_name
331 self.color_scheme_table.set_active_scheme('NoColor')
332 self.Colors = self.color_scheme_table.active_colors
333
334 #---------------------------------------------------------------------------
335 class ListTB(TBTools):
336 """Print traceback information from a traceback list, with optional color.
337
338 Calling: requires 3 arguments:
339 (etype, evalue, elist)
340 as would be obtained by:
341 etype, evalue, tb = sys.exc_info()
342 if tb:
343 elist = traceback.extract_tb(tb)
344 else:
345 elist = None
346
347 It can thus be used by programs which need to process the traceback before
348 printing (such as console replacements based on the code module from the
349 standard library).
350
351 Because they are meant to be called without a full traceback (only a
352 list), instances of this class can't call the interactive pdb debugger."""
353
354 def __init__(self,color_scheme = 'NoColor'):
355 TBTools.__init__(self,color_scheme = color_scheme,call_pdb=0)
356
357 def __call__(self, etype, value, elist):
358 Term.cout.flush()
359 Term.cerr.flush()
360 print >> Term.cerr, self.text(etype,value,elist)
361
362 def text(self,etype, value, elist,context=5):
363 """Return a color formatted string with the traceback info."""
364
365 Colors = self.Colors
366 out_string = ['%s%s%s\n' % (Colors.topline,'-'*60,Colors.Normal)]
367 if elist:
368 out_string.append('Traceback %s(most recent call last)%s:' % \
369 (Colors.normalEm, Colors.Normal) + '\n')
370 out_string.extend(self._format_list(elist))
371 lines = self._format_exception_only(etype, value)
372 for line in lines[:-1]:
373 out_string.append(" "+line)
374 out_string.append(lines[-1])
375 return ''.join(out_string)
376
377 def _format_list(self, extracted_list):
378 """Format a list of traceback entry tuples for printing.
379
380 Given a list of tuples as returned by extract_tb() or
381 extract_stack(), return a list of strings ready for printing.
382 Each string in the resulting list corresponds to the item with the
383 same index in the argument list. Each string ends in a newline;
384 the strings may contain internal newlines as well, for those items
385 whose source text line is not None.
386
387 Lifted almost verbatim from traceback.py
388 """
389
390 Colors = self.Colors
391 list = []
392 for filename, lineno, name, line in extracted_list[:-1]:
393 item = ' File %s"%s"%s, line %s%d%s, in %s%s%s\n' % \
394 (Colors.filename, filename, Colors.Normal,
395 Colors.lineno, lineno, Colors.Normal,
396 Colors.name, name, Colors.Normal)
397 if line:
398 item = item + ' %s\n' % line.strip()
399 list.append(item)
400 # Emphasize the last entry
401 filename, lineno, name, line = extracted_list[-1]
402 item = '%s File %s"%s"%s, line %s%d%s, in %s%s%s%s\n' % \
403 (Colors.normalEm,
404 Colors.filenameEm, filename, Colors.normalEm,
405 Colors.linenoEm, lineno, Colors.normalEm,
406 Colors.nameEm, name, Colors.normalEm,
407 Colors.Normal)
408 if line:
409 item = item + '%s %s%s\n' % (Colors.line, line.strip(),
410 Colors.Normal)
411 list.append(item)
412 return list
413
414 def _format_exception_only(self, etype, value):
415 """Format the exception part of a traceback.
416
417 The arguments are the exception type and value such as given by
418 sys.exc_info()[:2]. The return value is a list of strings, each ending
419 in a newline. Normally, the list contains a single string; however,
420 for SyntaxError exceptions, it contains several lines that (when
421 printed) display detailed information about where the syntax error
422 occurred. The message indicating which exception occurred is the
423 always last string in the list.
424
425 Also lifted nearly verbatim from traceback.py
426 """
427
428 Colors = self.Colors
429 list = []
430 try:
431 stype = Colors.excName + etype.__name__ + Colors.Normal
432 except AttributeError:
433 stype = etype # String exceptions don't get special coloring
434 if value is None:
435 list.append( str(stype) + '\n')
436 else:
437 if etype is SyntaxError:
438 try:
439 msg, (filename, lineno, offset, line) = value
440 except:
441 pass
442 else:
443 #print 'filename is',filename # dbg
444 if not filename: filename = "<string>"
445 list.append('%s File %s"%s"%s, line %s%d%s\n' % \
446 (Colors.normalEm,
447 Colors.filenameEm, filename, Colors.normalEm,
448 Colors.linenoEm, lineno, Colors.Normal ))
449 if line is not None:
450 i = 0
451 while i < len(line) and line[i].isspace():
452 i = i+1
453 list.append('%s %s%s\n' % (Colors.line,
454 line.strip(),
455 Colors.Normal))
456 if offset is not None:
457 s = ' '
458 for c in line[i:offset-1]:
459 if c.isspace():
460 s = s + c
461 else:
462 s = s + ' '
463 list.append('%s%s^%s\n' % (Colors.caret, s,
464 Colors.Normal) )
465 value = msg
466 s = self._some_str(value)
467 if s:
468 list.append('%s%s:%s %s\n' % (str(stype), Colors.excName,
469 Colors.Normal, s))
470 else:
471 list.append('%s\n' % str(stype))
472 return list
473
474 def _some_str(self, value):
475 # Lifted from traceback.py
476 try:
477 return str(value)
478 except:
479 return '<unprintable %s object>' % type(value).__name__
480
481 #----------------------------------------------------------------------------
482 class VerboseTB(TBTools):
483 """A port of Ka-Ping Yee's cgitb.py module that outputs color text instead
484 of HTML. Requires inspect and pydoc. Crazy, man.
485
486 Modified version which optionally strips the topmost entries from the
487 traceback, to be used with alternate interpreters (because their own code
488 would appear in the traceback)."""
489
490 def __init__(self,color_scheme = 'Linux',tb_offset=0,long_header=0,
491 call_pdb = 0, include_vars=1):
492 """Specify traceback offset, headers and color scheme.
493
494 Define how many frames to drop from the tracebacks. Calling it with
495 tb_offset=1 allows use of this handler in interpreters which will have
496 their own code at the top of the traceback (VerboseTB will first
497 remove that frame before printing the traceback info)."""
498 TBTools.__init__(self,color_scheme=color_scheme,call_pdb=call_pdb)
499 self.tb_offset = tb_offset
500 self.long_header = long_header
501 self.include_vars = include_vars
502
503 def text(self, etype, evalue, etb, context=5):
504 """Return a nice text document describing the traceback."""
505
506 # some locals
507 try:
508 etype = etype.__name__
509 except AttributeError:
510 pass
511 Colors = self.Colors # just a shorthand + quicker name lookup
512 ColorsNormal = Colors.Normal # used a lot
513 col_scheme = self.color_scheme_table.active_scheme_name
514 indent = ' '*INDENT_SIZE
515 em_normal = '%s\n%s%s' % (Colors.valEm, indent,ColorsNormal)
516 undefined = '%sundefined%s' % (Colors.em, ColorsNormal)
517 exc = '%s%s%s' % (Colors.excName,etype,ColorsNormal)
518
519 # some internal-use functions
520 def text_repr(value):
521 """Hopefully pretty robust repr equivalent."""
522 # this is pretty horrible but should always return *something*
523 try:
524 return pydoc.text.repr(value)
525 except KeyboardInterrupt:
526 raise
527 except:
528 try:
529 return repr(value)
530 except KeyboardInterrupt:
531 raise
532 except:
533 try:
534 # all still in an except block so we catch
535 # getattr raising
536 name = getattr(value, '__name__', None)
537 if name:
538 # ick, recursion
539 return text_repr(name)
540 klass = getattr(value, '__class__', None)
541 if klass:
542 return '%s instance' % text_repr(klass)
543 except KeyboardInterrupt:
544 raise
545 except:
546 return 'UNRECOVERABLE REPR FAILURE'
547 def eqrepr(value, repr=text_repr): return '=%s' % repr(value)
548 def nullrepr(value, repr=text_repr): return ''
549
550 # meat of the code begins
551 try:
552 etype = etype.__name__
553 except AttributeError:
554 pass
555
556 if self.long_header:
557 # Header with the exception type, python version, and date
558 pyver = 'Python ' + string.split(sys.version)[0] + ': ' + sys.executable
559 date = time.ctime(time.time())
560
561 head = '%s%s%s\n%s%s%s\n%s' % (Colors.topline, '-'*75, ColorsNormal,
562 exc, ' '*(75-len(str(etype))-len(pyver)),
563 pyver, string.rjust(date, 75) )
564 head += "\nA problem occured executing Python code. Here is the sequence of function"\
565 "\ncalls leading up to the error, with the most recent (innermost) call last."
566 else:
567 # Simplified header
568 head = '%s%s%s\n%s%s' % (Colors.topline, '-'*75, ColorsNormal,exc,
569 string.rjust('Traceback (most recent call last)',
570 75 - len(str(etype)) ) )
571 frames = []
572 # Flush cache before calling inspect. This helps alleviate some of the
573 # problems with python 2.3's inspect.py.
574 linecache.checkcache()
575 # Drop topmost frames if requested
576 try:
577 # Try the default getinnerframes and Alex's: Alex's fixes some
578 # problems, but it generates empty tracebacks for console errors
579 # (5 blanks lines) where none should be returned.
580 #records = inspect.getinnerframes(etb, context)[self.tb_offset:]
581 #print 'python records:', records # dbg
582 records = _fixed_getinnerframes(etb, context,self.tb_offset)
583 #print 'alex records:', records # dbg
584 except:
585
586 # FIXME: I've been getting many crash reports from python 2.3
587 # users, traceable to inspect.py. If I can find a small test-case
588 # to reproduce this, I should either write a better workaround or
589 # file a bug report against inspect (if that's the real problem).
590 # So far, I haven't been able to find an isolated example to
591 # reproduce the problem.
592 inspect_error()
593 traceback.print_exc(file=Term.cerr)
594 info('\nUnfortunately, your original traceback can not be constructed.\n')
595 return ''
596
597 # build some color string templates outside these nested loops
598 tpl_link = '%s%%s%s' % (Colors.filenameEm,ColorsNormal)
599 tpl_call = 'in %s%%s%s%%s%s' % (Colors.vName, Colors.valEm,
600 ColorsNormal)
601 tpl_call_fail = 'in %s%%s%s(***failed resolving arguments***)%s' % \
602 (Colors.vName, Colors.valEm, ColorsNormal)
603 tpl_local_var = '%s%%s%s' % (Colors.vName, ColorsNormal)
604 tpl_global_var = '%sglobal%s %s%%s%s' % (Colors.em, ColorsNormal,
605 Colors.vName, ColorsNormal)
606 tpl_name_val = '%%s %s= %%s%s' % (Colors.valEm, ColorsNormal)
607 tpl_line = '%s%%s%s %%s' % (Colors.lineno, ColorsNormal)
608 tpl_line_em = '%s%%s%s %%s%s' % (Colors.linenoEm,Colors.line,
609 ColorsNormal)
610
611 # now, loop over all records printing context and info
612 abspath = os.path.abspath
613 for frame, file, lnum, func, lines, index in records:
614 #print '*** record:',file,lnum,func,lines,index # dbg
615 try:
616 file = file and abspath(file) or '?'
617 except OSError:
618 # if file is '<console>' or something not in the filesystem,
619 # the abspath call will throw an OSError. Just ignore it and
620 # keep the original file string.
621 pass
622 link = tpl_link % file
623 try:
624 args, varargs, varkw, locals = inspect.getargvalues(frame)
625 except:
626 # This can happen due to a bug in python2.3. We should be
627 # able to remove this try/except when 2.4 becomes a
628 # requirement. Bug details at http://python.org/sf/1005466
629 inspect_error()
630 traceback.print_exc(file=Term.cerr)
631 info("\nIPython's exception reporting continues...\n")
632
633 if func == '?':
634 call = ''
635 else:
636 # Decide whether to include variable details or not
637 var_repr = self.include_vars and eqrepr or nullrepr
638 try:
639 call = tpl_call % (func,inspect.formatargvalues(args,
640 varargs, varkw,
641 locals,formatvalue=var_repr))
642 except KeyError:
643 # Very odd crash from inspect.formatargvalues(). The
644 # scenario under which it appeared was a call to
645 # view(array,scale) in NumTut.view.view(), where scale had
646 # been defined as a scalar (it should be a tuple). Somehow
647 # inspect messes up resolving the argument list of view()
648 # and barfs out. At some point I should dig into this one
649 # and file a bug report about it.
650 inspect_error()
651 traceback.print_exc(file=Term.cerr)
652 info("\nIPython's exception reporting continues...\n")
653 call = tpl_call_fail % func
654
655 # Initialize a list of names on the current line, which the
656 # tokenizer below will populate.
657 names = []
658
659 def tokeneater(token_type, token, start, end, line):
660 """Stateful tokeneater which builds dotted names.
661
662 The list of names it appends to (from the enclosing scope) can
663 contain repeated composite names. This is unavoidable, since
664 there is no way to disambguate partial dotted structures until
665 the full list is known. The caller is responsible for pruning
666 the final list of duplicates before using it."""
667
668 # build composite names
669 if token == '.':
670 try:
671 names[-1] += '.'
672 # store state so the next token is added for x.y.z names
673 tokeneater.name_cont = True
674 return
675 except IndexError:
676 pass
677 if token_type == tokenize.NAME and token not in keyword.kwlist:
678 if tokeneater.name_cont:
679 # Dotted names
680 names[-1] += token
681 tokeneater.name_cont = False
682 else:
683 # Regular new names. We append everything, the caller
684 # will be responsible for pruning the list later. It's
685 # very tricky to try to prune as we go, b/c composite
686 # names can fool us. The pruning at the end is easy
687 # to do (or the caller can print a list with repeated
688 # names if so desired.
689 names.append(token)
690 elif token_type == tokenize.NEWLINE:
691 raise IndexError
692 # we need to store a bit of state in the tokenizer to build
693 # dotted names
694 tokeneater.name_cont = False
695
696 def linereader(file=file, lnum=[lnum], getline=linecache.getline):
697 line = getline(file, lnum[0])
698 lnum[0] += 1
699 return line
700
701 # Build the list of names on this line of code where the exception
702 # occurred.
703 try:
704 # This builds the names list in-place by capturing it from the
705 # enclosing scope.
706 tokenize.tokenize(linereader, tokeneater)
707 except IndexError:
708 # signals exit of tokenizer
709 pass
710 except tokenize.TokenError,msg:
711 _m = ("An unexpected error occurred while tokenizing input\n"
712 "The following traceback may be corrupted or invalid\n"
713 "The error message is: %s\n" % msg)
714 error(_m)
715
716 # prune names list of duplicates, but keep the right order
717 unique_names = uniq_stable(names)
718
719 # Start loop over vars
720 lvals = []
721 if self.include_vars:
722 for name_full in unique_names:
723 name_base = name_full.split('.',1)[0]
724 if name_base in frame.f_code.co_varnames:
725 if locals.has_key(name_base):
726 try:
727 value = repr(eval(name_full,locals))
728 except:
729 value = undefined
730 else:
731 value = undefined
732 name = tpl_local_var % name_full
733 else:
734 if frame.f_globals.has_key(name_base):
735 try:
736 value = repr(eval(name_full,frame.f_globals))
737 except:
738 value = undefined
739 else:
740 value = undefined
741 name = tpl_global_var % name_full
742 lvals.append(tpl_name_val % (name,value))
743 if lvals:
744 lvals = '%s%s' % (indent,em_normal.join(lvals))
745 else:
746 lvals = ''
747
748 level = '%s %s\n' % (link,call)
749
750 if index is None:
751 frames.append(level)
752 else:
753 frames.append('%s%s' % (level,''.join(
754 _formatTracebackLines(lnum,index,lines,Colors,lvals,
755 col_scheme))))
756
757 # Get (safely) a string form of the exception info
758 try:
759 etype_str,evalue_str = map(str,(etype,evalue))
760 except:
761 # User exception is improperly defined.
762 etype,evalue = str,sys.exc_info()[:2]
763 etype_str,evalue_str = map(str,(etype,evalue))
764 # ... and format it
765 exception = ['%s%s%s: %s' % (Colors.excName, etype_str,
766 ColorsNormal, evalue_str)]
767 if type(evalue) is types.InstanceType:
768 try:
769 names = [w for w in dir(evalue) if isinstance(w, basestring)]
770 except:
771 # Every now and then, an object with funny inernals blows up
772 # when dir() is called on it. We do the best we can to report
773 # the problem and continue
774 _m = '%sException reporting error (object with broken dir())%s:'
775 exception.append(_m % (Colors.excName,ColorsNormal))
776 etype_str,evalue_str = map(str,sys.exc_info()[:2])
777 exception.append('%s%s%s: %s' % (Colors.excName,etype_str,
778 ColorsNormal, evalue_str))
779 names = []
780 for name in names:
781 value = text_repr(getattr(evalue, name))
782 exception.append('\n%s%s = %s' % (indent, name, value))
783 # return all our info assembled as a single string
784 return '%s\n\n%s\n%s' % (head,'\n'.join(frames),''.join(exception[0]) )
785
786 def debugger(self,force=False):
787 """Call up the pdb debugger if desired, always clean up the tb
788 reference.
789
790 Keywords:
791
792 - force(False): by default, this routine checks the instance call_pdb
793 flag and does not actually invoke the debugger if the flag is false.
794 The 'force' option forces the debugger to activate even if the flag
795 is false.
796
797 If the call_pdb flag is set, the pdb interactive debugger is
798 invoked. In all cases, the self.tb reference to the current traceback
799 is deleted to prevent lingering references which hamper memory
800 management.
801
802 Note that each call to pdb() does an 'import readline', so if your app
803 requires a special setup for the readline completers, you'll have to
804 fix that by hand after invoking the exception handler."""
805
806 if force or self.call_pdb:
807 if self.pdb is None:
808 self.pdb = Debugger.Pdb(
809 self.color_scheme_table.active_scheme_name)
810 # the system displayhook may have changed, restore the original
811 # for pdb
812 dhook = sys.displayhook
813 sys.displayhook = sys.__displayhook__
814 self.pdb.reset()
815 # Find the right frame so we don't pop up inside ipython itself
816 if hasattr(self,'tb'):
817 etb = self.tb
818 else:
819 etb = self.tb = sys.last_traceback
820 while self.tb.tb_next is not None:
821 self.tb = self.tb.tb_next
822 try:
823 if etb and etb.tb_next:
824 etb = etb.tb_next
825 self.pdb.botframe = etb.tb_frame
826 self.pdb.interaction(self.tb.tb_frame, self.tb)
827 finally:
828 sys.displayhook = dhook
829
830 if hasattr(self,'tb'):
831 del self.tb
832
833 def handler(self, info=None):
834 (etype, evalue, etb) = info or sys.exc_info()
835 self.tb = etb
836 Term.cout.flush()
837 Term.cerr.flush()
838 print >> Term.cerr, self.text(etype, evalue, etb)
839
840 # Changed so an instance can just be called as VerboseTB_inst() and print
841 # out the right info on its own.
842 def __call__(self, etype=None, evalue=None, etb=None):
843 """This hook can replace sys.excepthook (for Python 2.1 or higher)."""
844 if etb is None:
845 self.handler()
846 else:
847 self.handler((etype, evalue, etb))
848 self.debugger()
849
850 #----------------------------------------------------------------------------
851 class FormattedTB(VerboseTB,ListTB):
852 """Subclass ListTB but allow calling with a traceback.
853
854 It can thus be used as a sys.excepthook for Python > 2.1.
855
856 Also adds 'Context' and 'Verbose' modes, not available in ListTB.
857
858 Allows a tb_offset to be specified. This is useful for situations where
859 one needs to remove a number of topmost frames from the traceback (such as
860 occurs with python programs that themselves execute other python code,
861 like Python shells). """
862
863 def __init__(self, mode = 'Plain', color_scheme='Linux',
864 tb_offset = 0,long_header=0,call_pdb=0,include_vars=0):
865
866 # NEVER change the order of this list. Put new modes at the end:
867 self.valid_modes = ['Plain','Context','Verbose']
868 self.verbose_modes = self.valid_modes[1:3]
869
870 VerboseTB.__init__(self,color_scheme,tb_offset,long_header,
871 call_pdb=call_pdb,include_vars=include_vars)
872 self.set_mode(mode)
873
874 def _extract_tb(self,tb):
875 if tb:
876 return traceback.extract_tb(tb)
877 else:
878 return None
879
880 def text(self, etype, value, tb,context=5,mode=None):
881 """Return formatted traceback.
882
883 If the optional mode parameter is given, it overrides the current
884 mode."""
885
886 if mode is None:
887 mode = self.mode
888 if mode in self.verbose_modes:
889 # verbose modes need a full traceback
890 return VerboseTB.text(self,etype, value, tb,context=5)
891 else:
892 # We must check the source cache because otherwise we can print
893 # out-of-date source code.
894 linecache.checkcache()
895 # Now we can extract and format the exception
896 elist = self._extract_tb(tb)
897 if len(elist) > self.tb_offset:
898 del elist[:self.tb_offset]
899 return ListTB.text(self,etype,value,elist)
900
901 def set_mode(self,mode=None):
902 """Switch to the desired mode.
903
904 If mode is not specified, cycles through the available modes."""
905
906 if not mode:
907 new_idx = ( self.valid_modes.index(self.mode) + 1 ) % \
908 len(self.valid_modes)
909 self.mode = self.valid_modes[new_idx]
910 elif mode not in self.valid_modes:
911 raise ValueError, 'Unrecognized mode in FormattedTB: <'+mode+'>\n'\
912 'Valid modes: '+str(self.valid_modes)
913 else:
914 self.mode = mode
915 # include variable details only in 'Verbose' mode
916 self.include_vars = (self.mode == self.valid_modes[2])
917
918 # some convenient shorcuts
919 def plain(self):
920 self.set_mode(self.valid_modes[0])
921
922 def context(self):
923 self.set_mode(self.valid_modes[1])
924
925 def verbose(self):
926 self.set_mode(self.valid_modes[2])
927
928 #----------------------------------------------------------------------------
929 class AutoFormattedTB(FormattedTB):
930 """A traceback printer which can be called on the fly.
931
932 It will find out about exceptions by itself.
933
934 A brief example:
935
936 AutoTB = AutoFormattedTB(mode = 'Verbose',color_scheme='Linux')
937 try:
938 ...
939 except:
940 AutoTB() # or AutoTB(out=logfile) where logfile is an open file object
941 """
942 def __call__(self,etype=None,evalue=None,etb=None,
943 out=None,tb_offset=None):
944 """Print out a formatted exception traceback.
945
946 Optional arguments:
947 - out: an open file-like object to direct output to.
948
949 - tb_offset: the number of frames to skip over in the stack, on a
950 per-call basis (this overrides temporarily the instance's tb_offset
951 given at initialization time. """
952
953 if out is None:
954 out = Term.cerr
955 Term.cout.flush()
956 out.flush()
957 if tb_offset is not None:
958 tb_offset, self.tb_offset = self.tb_offset, tb_offset
959 print >> out, self.text(etype, evalue, etb)
960 self.tb_offset = tb_offset
961 else:
962 print >> out, self.text(etype, evalue, etb)
963 self.debugger()
964
965 def text(self,etype=None,value=None,tb=None,context=5,mode=None):
966 if etype is None:
967 etype,value,tb = sys.exc_info()
968 self.tb = tb
969 return FormattedTB.text(self,etype,value,tb,context=5,mode=mode)
970
971 #---------------------------------------------------------------------------
972 # A simple class to preserve Nathan's original functionality.
973 class ColorTB(FormattedTB):
974 """Shorthand to initialize a FormattedTB in Linux colors mode."""
975 def __init__(self,color_scheme='Linux',call_pdb=0):
976 FormattedTB.__init__(self,color_scheme=color_scheme,
977 call_pdb=call_pdb)
978
979 #----------------------------------------------------------------------------
980 # module testing (minimal)
981 if __name__ == "__main__":
982 def spam(c, (d, e)):
983 x = c + d
984 y = c * d
985 foo(x, y)
986
987 def foo(a, b, bar=1):
988 eggs(a, b + bar)
989
990 def eggs(f, g, z=globals()):
991 h = f + g
992 i = f - g
993 return h / i
994
995 print ''
996 print '*** Before ***'
997 try:
998 print spam(1, (2, 3))
999 except:
1000 traceback.print_exc()
1001 print ''
1002
1003 handler = ColorTB()
1004 print '*** ColorTB ***'
1005 try:
1006 print spam(1, (2, 3))
1007 except:
1008 apply(handler, sys.exc_info() )
1009 print ''
1010
1011 handler = VerboseTB()
1012 print '*** VerboseTB ***'
1013 try:
1014 print spam(1, (2, 3))
1015 except:
1016 apply(handler, sys.exc_info() )
1017 print ''
1018
@@ -0,0 +1,197 b''
1 # encoding: utf-8
2
3 __docformat__ = "restructuredtext en"
4
5 #-------------------------------------------------------------------------------
6 # Copyright (C) 2008 The IPython Development Team
7 #
8 # Distributed under the terms of the BSD License. The full license is in
9 # the file COPYING, distributed as part of this software.
10 #-------------------------------------------------------------------------------
11
12 #-------------------------------------------------------------------------------
13 # Imports
14 #-------------------------------------------------------------------------------
15
16 import os
17 import sys
18
19
20 # This class is mostly taken from IPython.
21 class InputList(list):
22 """ Class to store user input.
23
24 It's basically a list, but slices return a string instead of a list, thus
25 allowing things like (assuming 'In' is an instance):
26
27 exec In[4:7]
28
29 or
30
31 exec In[5:9] + In[14] + In[21:25]
32 """
33
34 def __getslice__(self, i, j):
35 return ''.join(list.__getslice__(self, i, j))
36
37 def add(self, index, command):
38 """ Add a command to the list with the appropriate index.
39
40 If the index is greater than the current length of the list, empty
41 strings are added in between.
42 """
43
44 length = len(self)
45 if length == index:
46 self.append(command)
47 elif length > index:
48 self[index] = command
49 else:
50 extras = index - length
51 self.extend([''] * extras)
52 self.append(command)
53
54
55 class Bunch(dict):
56 """ A dictionary that exposes its keys as attributes.
57 """
58
59 def __init__(self, *args, **kwds):
60 dict.__init__(self, *args, **kwds)
61 self.__dict__ = self
62
63
64 def esc_quotes(strng):
65 """ Return the input string with single and double quotes escaped out.
66 """
67
68 return strng.replace('"', '\\"').replace("'", "\\'")
69
70 def make_quoted_expr(s):
71 """Return string s in appropriate quotes, using raw string if possible.
72
73 Effectively this turns string: cd \ao\ao\
74 to: r"cd \ao\ao\_"[:-1]
75
76 Note the use of raw string and padding at the end to allow trailing
77 backslash.
78 """
79
80 tail = ''
81 tailpadding = ''
82 raw = ''
83 if "\\" in s:
84 raw = 'r'
85 if s.endswith('\\'):
86 tail = '[:-1]'
87 tailpadding = '_'
88 if '"' not in s:
89 quote = '"'
90 elif "'" not in s:
91 quote = "'"
92 elif '"""' not in s and not s.endswith('"'):
93 quote = '"""'
94 elif "'''" not in s and not s.endswith("'"):
95 quote = "'''"
96 else:
97 # Give up, backslash-escaped string will do
98 return '"%s"' % esc_quotes(s)
99 res = ''.join([raw, quote, s, tailpadding, quote, tail])
100 return res
101
102 # This function is used by ipython in a lot of places to make system calls.
103 # We need it to be slightly different under win32, due to the vagaries of
104 # 'network shares'. A win32 override is below.
105
106 def system_shell(cmd, verbose=False, debug=False, header=''):
107 """ Execute a command in the system shell; always return None.
108
109 Parameters
110 ----------
111 cmd : str
112 The command to execute.
113 verbose : bool
114 If True, print the command to be executed.
115 debug : bool
116 Only print, do not actually execute.
117 header : str
118 Header to print to screen prior to the executed command. No extra
119 newlines are added.
120
121 Description
122 -----------
123 This returns None so it can be conveniently used in interactive loops
124 without getting the return value (typically 0) printed many times.
125 """
126
127 if verbose or debug:
128 print header + cmd
129
130 # Flush stdout so we don't mangle python's buffering.
131 sys.stdout.flush()
132 if not debug:
133 os.system(cmd)
134
135 # Override shell() for win32 to deal with network shares.
136 if os.name in ('nt', 'dos'):
137
138 system_shell_ori = system_shell
139
140 def system_shell(cmd, verbose=False, debug=False, header=''):
141 if os.getcwd().startswith(r"\\"):
142 path = os.getcwd()
143 # Change to c drive (cannot be on UNC-share when issuing os.system,
144 # as cmd.exe cannot handle UNC addresses).
145 os.chdir("c:")
146 # Issue pushd to the UNC-share and then run the command.
147 try:
148 system_shell_ori('"pushd %s&&"'%path+cmd,verbose,debug,header)
149 finally:
150 os.chdir(path)
151 else:
152 system_shell_ori(cmd,verbose,debug,header)
153
154 system_shell.__doc__ = system_shell_ori.__doc__
155
156 def getoutputerror(cmd, verbose=False, debug=False, header='', split=False):
157 """ Executes a command and returns the output.
158
159 Parameters
160 ----------
161 cmd : str
162 The command to execute.
163 verbose : bool
164 If True, print the command to be executed.
165 debug : bool
166 Only print, do not actually execute.
167 header : str
168 Header to print to screen prior to the executed command. No extra
169 newlines are added.
170 split : bool
171 If True, return the output as a list split on newlines.
172
173 """
174
175 if verbose or debug:
176 print header+cmd
177
178 if not cmd:
179 # Return empty lists or strings.
180 if split:
181 return [], []
182 else:
183 return '', ''
184
185 if not debug:
186 # fixme: use subprocess.
187 pin,pout,perr = os.popen3(cmd)
188 tout = pout.read().rstrip()
189 terr = perr.read().rstrip()
190 pin.close()
191 pout.close()
192 perr.close()
193 if split:
194 return tout.split('\n'), terr.split('\n')
195 else:
196 return tout, terr
197
@@ -0,0 +1,87 b''
1 # encoding: utf-8
2
3 """A class that manages the engines connection to the controller."""
4
5 __docformat__ = "restructuredtext en"
6
7 #-------------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
13
14 #-------------------------------------------------------------------------------
15 # Imports
16 #-------------------------------------------------------------------------------
17
18 import os
19 import cPickle as pickle
20
21 from twisted.python import log
22
23 from IPython.kernel.fcutil import find_furl
24 from IPython.kernel.enginefc import IFCEngine
25
26 #-------------------------------------------------------------------------------
27 # The ClientConnector class
28 #-------------------------------------------------------------------------------
29
30 class EngineConnector(object):
31 """Manage an engines connection to a controller.
32
33 This class takes a foolscap `Tub` and provides a `connect_to_controller`
34 method that will use the `Tub` to connect to a controller and register
35 the engine with the controller.
36 """
37
38 def __init__(self, tub):
39 self.tub = tub
40
41 def connect_to_controller(self, engine_service, furl_or_file):
42 """
43 Make a connection to a controller specified by a furl.
44
45 This method takes an `IEngineBase` instance and a foolcap URL and uses
46 the `tub` attribute to make a connection to the controller. The
47 foolscap URL contains all the information needed to connect to the
48 controller, including the ip and port as well as any encryption and
49 authentication information needed for the connection.
50
51 After getting a reference to the controller, this method calls the
52 `register_engine` method of the controller to actually register the
53 engine.
54
55 :Parameters:
56 engine_service : IEngineBase
57 An instance of an `IEngineBase` implementer
58 furl_or_file : str
59 A furl or a filename containing a furl
60 """
61 if not self.tub.running:
62 self.tub.startService()
63 self.engine_service = engine_service
64 self.engine_reference = IFCEngine(self.engine_service)
65 self.furl = find_furl(furl_or_file)
66 d = self.tub.getReference(self.furl)
67 d.addCallbacks(self._register, self._log_failure)
68 return d
69
70 def _log_failure(self, reason):
71 log.err('engine registration failed:')
72 log.err(reason)
73 return reason
74
75 def _register(self, rr):
76 self.remote_ref = rr
77 # Now register myself with the controller
78 desired_id = self.engine_service.id
79 d = self.remote_ref.callRemote('register_engine', self.engine_reference,
80 desired_id, os.getpid(), pickle.dumps(self.engine_service.properties,2))
81 return d.addCallbacks(self._reference_sent, self._log_failure)
82
83 def _reference_sent(self, registration_dict):
84 self.engine_service.id = registration_dict['id']
85 log.msg("engine registration succeeded, got id: %r" % self.engine_service.id)
86 return self.engine_service.id
87
This diff has been collapsed as it changes many lines, (548 lines changed) Show them Hide them
@@ -0,0 +1,548 b''
1 # encoding: utf-8
2 # -*- test-case-name: IPython.kernel.test.test_enginepb -*-
3
4 """
5 Expose the IPython EngineService using the Foolscap network protocol.
6
7 Foolscap is a high-performance and secure network protocol.
8 """
9 __docformat__ = "restructuredtext en"
10
11 #-------------------------------------------------------------------------------
12 # Copyright (C) 2008 The IPython Development Team
13 #
14 # Distributed under the terms of the BSD License. The full license is in
15 # the file COPYING, distributed as part of this software.
16 #-------------------------------------------------------------------------------
17
18 #-------------------------------------------------------------------------------
19 # Imports
20 #-------------------------------------------------------------------------------
21
22 import os, time
23 import cPickle as pickle
24
25 from twisted.python import components, log, failure
26 from twisted.python.failure import Failure
27 from twisted.internet import defer, reactor, threads
28 from twisted.internet.interfaces import IProtocolFactory
29 from zope.interface import Interface, implements, Attribute
30
31 from twisted.internet.base import DelayedCall
32 DelayedCall.debug = True
33
34 from foolscap import Referenceable, DeadReferenceError
35 from foolscap.referenceable import RemoteReference
36
37 from IPython.kernel.pbutil import packageFailure, unpackageFailure
38 from IPython.kernel.util import printer
39 from IPython.kernel.twistedutil import gatherBoth
40 from IPython.kernel import newserialized
41 from IPython.kernel.error import ProtocolError
42 from IPython.kernel import controllerservice
43 from IPython.kernel.controllerservice import IControllerBase
44 from IPython.kernel.engineservice import \
45 IEngineBase, \
46 IEngineQueued, \
47 EngineService, \
48 StrictDict
49 from IPython.kernel.pickleutil import \
50 can, \
51 canDict, \
52 canSequence, \
53 uncan, \
54 uncanDict, \
55 uncanSequence
56
57
58 #-------------------------------------------------------------------------------
59 # The client (Engine) side of things
60 #-------------------------------------------------------------------------------
61
62 # Expose a FC interface to the EngineService
63
64 class IFCEngine(Interface):
65 """An interface that exposes an EngineService over Foolscap.
66
67 The methods in this interface are similar to those from IEngine,
68 but their arguments and return values slightly different to reflect
69 that FC cannot send arbitrary objects. We handle this by pickling/
70 unpickling that the two endpoints.
71
72 If a remote or local exception is raised, the appropriate Failure
73 will be returned instead.
74 """
75 pass
76
77
78 class FCEngineReferenceFromService(Referenceable, object):
79 """Adapt an `IEngineBase` to an `IFCEngine` implementer.
80
81 This exposes an `IEngineBase` to foolscap by adapting it to a
82 `foolscap.Referenceable`.
83
84 See the documentation of the `IEngineBase` methods for more details.
85 """
86
87 implements(IFCEngine)
88
89 def __init__(self, service):
90 assert IEngineBase.providedBy(service), \
91 "IEngineBase is not provided by" + repr(service)
92 self.service = service
93 self.collectors = {}
94
95 def remote_get_id(self):
96 return self.service.id
97
98 def remote_set_id(self, id):
99 self.service.id = id
100
101 def _checkProperties(self, result):
102 dosync = self.service.properties.modified
103 self.service.properties.modified = False
104 return (dosync and pickle.dumps(self.service.properties, 2)), result
105
106 def remote_execute(self, lines):
107 d = self.service.execute(lines)
108 d.addErrback(packageFailure)
109 d.addCallback(self._checkProperties)
110 d.addErrback(packageFailure)
111 #d.addCallback(lambda r: log.msg("Got result: " + str(r)))
112 return d
113
114 #---------------------------------------------------------------------------
115 # Old version of push
116 #---------------------------------------------------------------------------
117
118 def remote_push(self, pNamespace):
119 try:
120 namespace = pickle.loads(pNamespace)
121 except:
122 return defer.fail(failure.Failure()).addErrback(packageFailure)
123 else:
124 return self.service.push(namespace).addErrback(packageFailure)
125
126 #---------------------------------------------------------------------------
127 # pull
128 #---------------------------------------------------------------------------
129
130 def remote_pull(self, keys):
131 d = self.service.pull(keys)
132 d.addCallback(pickle.dumps, 2)
133 d.addErrback(packageFailure)
134 return d
135
136 #---------------------------------------------------------------------------
137 # push/pullFuction
138 #---------------------------------------------------------------------------
139
140 def remote_push_function(self, pNamespace):
141 try:
142 namespace = pickle.loads(pNamespace)
143 except:
144 return defer.fail(failure.Failure()).addErrback(packageFailure)
145 else:
146 # The usage of globals() here is an attempt to bind any pickled functions
147 # to the globals of this module. What we really want is to have it bound
148 # to the globals of the callers module. This will require walking the
149 # stack. BG 10/3/07.
150 namespace = uncanDict(namespace, globals())
151 return self.service.push_function(namespace).addErrback(packageFailure)
152
153 def remote_pull_function(self, keys):
154 d = self.service.pull_function(keys)
155 if len(keys)>1:
156 d.addCallback(canSequence)
157 elif len(keys)==1:
158 d.addCallback(can)
159 d.addCallback(pickle.dumps, 2)
160 d.addErrback(packageFailure)
161 return d
162
163 #---------------------------------------------------------------------------
164 # Other methods
165 #---------------------------------------------------------------------------
166
167 def remote_get_result(self, i=None):
168 return self.service.get_result(i).addErrback(packageFailure)
169
170 def remote_reset(self):
171 return self.service.reset().addErrback(packageFailure)
172
173 def remote_kill(self):
174 return self.service.kill().addErrback(packageFailure)
175
176 def remote_keys(self):
177 return self.service.keys().addErrback(packageFailure)
178
179 #---------------------------------------------------------------------------
180 # push/pull_serialized
181 #---------------------------------------------------------------------------
182
183 def remote_push_serialized(self, pNamespace):
184 try:
185 namespace = pickle.loads(pNamespace)
186 except:
187 return defer.fail(failure.Failure()).addErrback(packageFailure)
188 else:
189 d = self.service.push_serialized(namespace)
190 return d.addErrback(packageFailure)
191
192 def remote_pull_serialized(self, keys):
193 d = self.service.pull_serialized(keys)
194 d.addCallback(pickle.dumps, 2)
195 d.addErrback(packageFailure)
196 return d
197
198 #---------------------------------------------------------------------------
199 # Properties interface
200 #---------------------------------------------------------------------------
201
202 def remote_set_properties(self, pNamespace):
203 try:
204 namespace = pickle.loads(pNamespace)
205 except:
206 return defer.fail(failure.Failure()).addErrback(packageFailure)
207 else:
208 return self.service.set_properties(namespace).addErrback(packageFailure)
209
210 def remote_get_properties(self, keys=None):
211 d = self.service.get_properties(keys)
212 d.addCallback(pickle.dumps, 2)
213 d.addErrback(packageFailure)
214 return d
215
216 def remote_has_properties(self, keys):
217 d = self.service.has_properties(keys)
218 d.addCallback(pickle.dumps, 2)
219 d.addErrback(packageFailure)
220 return d
221
222 def remote_del_properties(self, keys):
223 d = self.service.del_properties(keys)
224 d.addErrback(packageFailure)
225 return d
226
227 def remote_clear_properties(self):
228 d = self.service.clear_properties()
229 d.addErrback(packageFailure)
230 return d
231
232
233 components.registerAdapter(FCEngineReferenceFromService,
234 IEngineBase,
235 IFCEngine)
236
237
238 #-------------------------------------------------------------------------------
239 # Now the server (Controller) side of things
240 #-------------------------------------------------------------------------------
241
242 class EngineFromReference(object):
243 """Adapt a `RemoteReference` to an `IEngineBase` implementing object.
244
245 When an engine connects to a controller, it calls the `register_engine`
246 method of the controller and passes the controller a `RemoteReference` to
247 itself. This class is used to adapt this `RemoteReference` to an object
248 that implements the full `IEngineBase` interface.
249
250 See the documentation of `IEngineBase` for details on the methods.
251 """
252
253 implements(IEngineBase)
254
255 def __init__(self, reference):
256 self.reference = reference
257 self._id = None
258 self._properties = StrictDict()
259 self.currentCommand = None
260
261 def callRemote(self, *args, **kwargs):
262 try:
263 return self.reference.callRemote(*args, **kwargs)
264 except DeadReferenceError:
265 self.notifier()
266 self.stopNotifying(self.notifier)
267 return defer.fail()
268
269 def get_id(self):
270 """Return the Engines id."""
271 return self._id
272
273 def set_id(self, id):
274 """Set the Engines id."""
275 self._id = id
276 return self.callRemote('set_id', id)
277
278 id = property(get_id, set_id)
279
280 def syncProperties(self, r):
281 try:
282 psync, result = r
283 except (ValueError, TypeError):
284 return r
285 else:
286 if psync:
287 log.msg("sync properties")
288 pick = self.checkReturnForFailure(psync)
289 if isinstance(pick, failure.Failure):
290 self.properties = pick
291 return pick
292 else:
293 self.properties = pickle.loads(pick)
294 return result
295
296 def _set_properties(self, dikt):
297 self._properties.clear()
298 self._properties.update(dikt)
299
300 def _get_properties(self):
301 if isinstance(self._properties, failure.Failure):
302 self._properties.raiseException()
303 return self._properties
304
305 properties = property(_get_properties, _set_properties)
306
307 #---------------------------------------------------------------------------
308 # Methods from IEngine
309 #---------------------------------------------------------------------------
310
311 #---------------------------------------------------------------------------
312 # execute
313 #---------------------------------------------------------------------------
314
315 def execute(self, lines):
316 # self._needProperties = True
317 d = self.callRemote('execute', lines)
318 d.addCallback(self.syncProperties)
319 return d.addCallback(self.checkReturnForFailure)
320
321 #---------------------------------------------------------------------------
322 # push
323 #---------------------------------------------------------------------------
324
325 def push(self, namespace):
326 try:
327 package = pickle.dumps(namespace, 2)
328 except:
329 return defer.fail(failure.Failure())
330 else:
331 if isinstance(package, failure.Failure):
332 return defer.fail(package)
333 else:
334 d = self.callRemote('push', package)
335 return d.addCallback(self.checkReturnForFailure)
336
337 #---------------------------------------------------------------------------
338 # pull
339 #---------------------------------------------------------------------------
340
341 def pull(self, keys):
342 d = self.callRemote('pull', keys)
343 d.addCallback(self.checkReturnForFailure)
344 d.addCallback(pickle.loads)
345 return d
346
347 #---------------------------------------------------------------------------
348 # push/pull_function
349 #---------------------------------------------------------------------------
350
351 def push_function(self, namespace):
352 try:
353 package = pickle.dumps(canDict(namespace), 2)
354 except:
355 return defer.fail(failure.Failure())
356 else:
357 if isinstance(package, failure.Failure):
358 return defer.fail(package)
359 else:
360 d = self.callRemote('push_function', package)
361 return d.addCallback(self.checkReturnForFailure)
362
363 def pull_function(self, keys):
364 d = self.callRemote('pull_function', keys)
365 d.addCallback(self.checkReturnForFailure)
366 d.addCallback(pickle.loads)
367 # The usage of globals() here is an attempt to bind any pickled functions
368 # to the globals of this module. What we really want is to have it bound
369 # to the globals of the callers module. This will require walking the
370 # stack. BG 10/3/07.
371 if len(keys)==1:
372 d.addCallback(uncan, globals())
373 elif len(keys)>1:
374 d.addCallback(uncanSequence, globals())
375 return d
376
377 #---------------------------------------------------------------------------
378 # Other methods
379 #---------------------------------------------------------------------------
380
381 def get_result(self, i=None):
382 return self.callRemote('get_result', i).addCallback(self.checkReturnForFailure)
383
384 def reset(self):
385 self._refreshProperties = True
386 d = self.callRemote('reset')
387 d.addCallback(self.syncProperties)
388 return d.addCallback(self.checkReturnForFailure)
389
390 def kill(self):
391 #this will raise pb.PBConnectionLost on success
392 d = self.callRemote('kill')
393 d.addCallback(self.syncProperties)
394 d.addCallback(self.checkReturnForFailure)
395 d.addErrback(self.killBack)
396 return d
397
398 def killBack(self, f):
399 log.msg('filling engine: %s' % f)
400 return None
401
402 def keys(self):
403 return self.callRemote('keys').addCallback(self.checkReturnForFailure)
404
405 #---------------------------------------------------------------------------
406 # Properties methods
407 #---------------------------------------------------------------------------
408
409 def set_properties(self, properties):
410 try:
411 package = pickle.dumps(properties, 2)
412 except:
413 return defer.fail(failure.Failure())
414 else:
415 if isinstance(package, failure.Failure):
416 return defer.fail(package)
417 else:
418 d = self.callRemote('set_properties', package)
419 return d.addCallback(self.checkReturnForFailure)
420 return d
421
422 def get_properties(self, keys=None):
423 d = self.callRemote('get_properties', keys)
424 d.addCallback(self.checkReturnForFailure)
425 d.addCallback(pickle.loads)
426 return d
427
428 def has_properties(self, keys):
429 d = self.callRemote('has_properties', keys)
430 d.addCallback(self.checkReturnForFailure)
431 d.addCallback(pickle.loads)
432 return d
433
434 def del_properties(self, keys):
435 d = self.callRemote('del_properties', keys)
436 d.addCallback(self.checkReturnForFailure)
437 # d.addCallback(pickle.loads)
438 return d
439
440 def clear_properties(self):
441 d = self.callRemote('clear_properties')
442 d.addCallback(self.checkReturnForFailure)
443 return d
444
445 #---------------------------------------------------------------------------
446 # push/pull_serialized
447 #---------------------------------------------------------------------------
448
449 def push_serialized(self, namespace):
450 """Older version of pushSerialize."""
451 try:
452 package = pickle.dumps(namespace, 2)
453 except:
454 return defer.fail(failure.Failure())
455 else:
456 if isinstance(package, failure.Failure):
457 return defer.fail(package)
458 else:
459 d = self.callRemote('push_serialized', package)
460 return d.addCallback(self.checkReturnForFailure)
461
462 def pull_serialized(self, keys):
463 d = self.callRemote('pull_serialized', keys)
464 d.addCallback(self.checkReturnForFailure)
465 d.addCallback(pickle.loads)
466 return d
467
468 #---------------------------------------------------------------------------
469 # Misc
470 #---------------------------------------------------------------------------
471
472 def checkReturnForFailure(self, r):
473 """See if a returned value is a pickled Failure object.
474
475 To distinguish between general pickled objects and pickled Failures, the
476 other side should prepend the string FAILURE: to any pickled Failure.
477 """
478 return unpackageFailure(r)
479
480
481 components.registerAdapter(EngineFromReference,
482 RemoteReference,
483 IEngineBase)
484
485
486 #-------------------------------------------------------------------------------
487 # Now adapt an IControllerBase to incoming FC connections
488 #-------------------------------------------------------------------------------
489
490
491 class IFCControllerBase(Interface):
492 """
493 Interface that tells how an Engine sees a Controller.
494
495 In our architecture, the Controller listens for Engines to connect
496 and register. This interface defines that registration method as it is
497 exposed over the Foolscap network protocol
498 """
499
500 def remote_register_engine(self, engineReference, id=None, pid=None, pproperties=None):
501 """
502 Register new engine on the controller.
503
504 Engines must call this upon connecting to the controller if they
505 want to do work for the controller.
506
507 See the documentation of `IControllerCore` for more details.
508 """
509
510
511 class FCRemoteEngineRefFromService(Referenceable):
512 """
513 Adapt an `IControllerBase` to an `IFCControllerBase`.
514 """
515
516 implements(IFCControllerBase)
517
518 def __init__(self, service):
519 assert IControllerBase.providedBy(service), \
520 "IControllerBase is not provided by " + repr(service)
521 self.service = service
522
523 def remote_register_engine(self, engine_reference, id=None, pid=None, pproperties=None):
524 # First adapt the engine_reference to a basic non-queued engine
525 engine = IEngineBase(engine_reference)
526 if pproperties:
527 engine.properties = pickle.loads(pproperties)
528 # Make it an IQueuedEngine before registration
529 remote_engine = IEngineQueued(engine)
530 # Get the ip/port of the remote side
531 peer_address = engine_reference.tracker.broker.transport.getPeer()
532 ip = peer_address.host
533 port = peer_address.port
534 reg_dict = self.service.register_engine(remote_engine, id, ip, port, pid)
535 # Now setup callback for disconnect and unregistering the engine
536 def notify(*args):
537 return self.service.unregister_engine(reg_dict['id'])
538 engine_reference.tracker.broker.notifyOnDisconnect(notify)
539
540 engine.notifier = notify
541 engine.stopNotifying = engine_reference.tracker.broker.dontNotifyOnDisconnect
542
543 return reg_dict
544
545
546 components.registerAdapter(FCRemoteEngineRefFromService,
547 IControllerBase,
548 IFCControllerBase)
This diff has been collapsed as it changes many lines, (864 lines changed) Show them Hide them
@@ -0,0 +1,864 b''
1 # encoding: utf-8
2 # -*- test-case-name: IPython.kernel.tests.test_engineservice -*-
3
4 """A Twisted Service Representation of the IPython core.
5
6 The IPython Core exposed to the network is called the Engine. Its
7 representation in Twisted in the EngineService. Interfaces and adapters
8 are used to abstract out the details of the actual network protocol used.
9 The EngineService is an Engine that knows nothing about the actual protocol
10 used.
11
12 The EngineService is exposed with various network protocols in modules like:
13
14 enginepb.py
15 enginevanilla.py
16
17 As of 12/12/06 the classes in this module have been simplified greatly. It was
18 felt that we had over-engineered things. To improve the maintainability of the
19 code we have taken out the ICompleteEngine interface and the completeEngine
20 method that automatically added methods to engines.
21
22 """
23
24 __docformat__ = "restructuredtext en"
25
26 #-------------------------------------------------------------------------------
27 # Copyright (C) 2008 The IPython Development Team
28 #
29 # Distributed under the terms of the BSD License. The full license is in
30 # the file COPYING, distributed as part of this software.
31 #-------------------------------------------------------------------------------
32
33 #-------------------------------------------------------------------------------
34 # Imports
35 #-------------------------------------------------------------------------------
36
37 import os, sys, copy
38 import cPickle as pickle
39 from new import instancemethod
40
41 from twisted.application import service
42 from twisted.internet import defer, reactor
43 from twisted.python import log, failure, components
44 import zope.interface as zi
45
46 from IPython.kernel.core.interpreter import Interpreter
47 from IPython.kernel import newserialized, error, util
48 from IPython.kernel.util import printer
49 from IPython.kernel.twistedutil import gatherBoth, DeferredList
50 from IPython.kernel import codeutil
51
52
53 #-------------------------------------------------------------------------------
54 # Interface specification for the Engine
55 #-------------------------------------------------------------------------------
56
57 class IEngineCore(zi.Interface):
58 """The minimal required interface for the IPython Engine.
59
60 This interface provides a formal specification of the IPython core.
61 All these methods should return deferreds regardless of what side of a
62 network connection they are on.
63
64 In general, this class simply wraps a shell class and wraps its return
65 values as Deferred objects. If the underlying shell class method raises
66 an exception, this class should convert it to a twisted.failure.Failure
67 that will be propagated along the Deferred's errback chain.
68
69 In addition, Failures are aggressive. By this, we mean that if a method
70 is performing multiple actions (like pulling multiple object) if any
71 single one fails, the entire method will fail with that Failure. It is
72 all or nothing.
73 """
74
75 id = zi.interface.Attribute("the id of the Engine object")
76 properties = zi.interface.Attribute("A dict of properties of the Engine")
77
78 def execute(lines):
79 """Execute lines of Python code.
80
81 Returns a dictionary with keys (id, number, stdin, stdout, stderr)
82 upon success.
83
84 Returns a failure object if the execution of lines raises an exception.
85 """
86
87 def push(namespace):
88 """Push dict namespace into the user's namespace.
89
90 Returns a deferred to None or a failure.
91 """
92
93 def pull(keys):
94 """Pulls values out of the user's namespace by keys.
95
96 Returns a deferred to a tuple objects or a single object.
97
98 Raises NameError if any one of objects doess not exist.
99 """
100
101 def push_function(namespace):
102 """Push a dict of key, function pairs into the user's namespace.
103
104 Returns a deferred to None or a failure."""
105
106 def pull_function(keys):
107 """Pulls functions out of the user's namespace by keys.
108
109 Returns a deferred to a tuple of functions or a single function.
110
111 Raises NameError if any one of the functions does not exist.
112 """
113
114 def get_result(i=None):
115 """Get the stdin/stdout/stderr of command i.
116
117 Returns a deferred to a dict with keys
118 (id, number, stdin, stdout, stderr).
119
120 Raises IndexError if command i does not exist.
121 Raises TypeError if i in not an int.
122 """
123
124 def reset():
125 """Reset the shell.
126
127 This clears the users namespace. Won't cause modules to be
128 reloaded. Should also re-initialize certain variables like id.
129 """
130
131 def kill():
132 """Kill the engine by stopping the reactor."""
133
134 def keys():
135 """Return the top level variables in the users namspace.
136
137 Returns a deferred to a dict."""
138
139
140 class IEngineSerialized(zi.Interface):
141 """Push/Pull methods that take Serialized objects.
142
143 All methods should return deferreds.
144 """
145
146 def push_serialized(namespace):
147 """Push a dict of keys and Serialized objects into the user's namespace."""
148
149 def pull_serialized(keys):
150 """Pull objects by key from the user's namespace as Serialized.
151
152 Returns a list of or one Serialized.
153
154 Raises NameError is any one of the objects does not exist.
155 """
156
157
158 class IEngineProperties(zi.Interface):
159 """Methods for access to the properties object of an Engine"""
160
161 properties = zi.Attribute("A StrictDict object, containing the properties")
162
163 def set_properties(properties):
164 """set properties by key and value"""
165
166 def get_properties(keys=None):
167 """get a list of properties by `keys`, if no keys specified, get all"""
168
169 def del_properties(keys):
170 """delete properties by `keys`"""
171
172 def has_properties(keys):
173 """get a list of bool values for whether `properties` has `keys`"""
174
175 def clear_properties():
176 """clear the properties dict"""
177
178 class IEngineBase(IEngineCore, IEngineSerialized, IEngineProperties):
179 """The basic engine interface that EngineService will implement.
180
181 This exists so it is easy to specify adapters that adapt to and from the
182 API that the basic EngineService implements.
183 """
184 pass
185
186 class IEngineQueued(IEngineBase):
187 """Interface for adding a queue to an IEngineBase.
188
189 This interface extends the IEngineBase interface to add methods for managing
190 the engine's queue. The implicit details of this interface are that the
191 execution of all methods declared in IEngineBase should appropriately be
192 put through a queue before execution.
193
194 All methods should return deferreds.
195 """
196
197 def clear_queue():
198 """Clear the queue."""
199
200 def queue_status():
201 """Get the queued and pending commands in the queue."""
202
203 def register_failure_observer(obs):
204 """Register an observer of pending Failures.
205
206 The observer must implement IFailureObserver.
207 """
208
209 def unregister_failure_observer(obs):
210 """Unregister an observer of pending Failures."""
211
212
213 class IEngineThreaded(zi.Interface):
214 """A place holder for threaded commands.
215
216 All methods should return deferreds.
217 """
218 pass
219
220
221 #-------------------------------------------------------------------------------
222 # Functions and classes to implement the EngineService
223 #-------------------------------------------------------------------------------
224
225
226 class StrictDict(dict):
227 """This is a strict copying dictionary for use as the interface to the
228 properties of an Engine.
229 :IMPORTANT:
230 This object copies the values you set to it, and returns copies to you
231 when you request them. The only way to change properties os explicitly
232 through the setitem and getitem of the dictionary interface.
233 Example:
234 >>> e = kernel.get_engine(id)
235 >>> L = someList
236 >>> e.properties['L'] = L
237 >>> L == e.properties['L']
238 ... True
239 >>> L.append(something Else)
240 >>> L == e.properties['L']
241 ... False
242
243 getitem copies, so calls to methods of objects do not affect the
244 properties, as in the following example:
245 >>> e.properties[1] = range(2)
246 >>> print e.properties[1]
247 ... [0, 1]
248 >>> e.properties[1].append(2)
249 >>> print e.properties[1]
250 ... [0, 1]
251
252 """
253 def __init__(self, *args, **kwargs):
254 dict.__init__(self, *args, **kwargs)
255 self.modified = True
256
257 def __getitem__(self, key):
258 return copy.deepcopy(dict.__getitem__(self, key))
259
260 def __setitem__(self, key, value):
261 # check if this entry is valid for transport around the network
262 # and copying
263 try:
264 pickle.dumps(key, 2)
265 pickle.dumps(value, 2)
266 newvalue = copy.deepcopy(value)
267 except:
268 raise error.InvalidProperty(value)
269 dict.__setitem__(self, key, newvalue)
270 self.modified = True
271
272 def __delitem__(self, key):
273 dict.__delitem__(self, key)
274 self.modified = True
275
276 def update(self, dikt):
277 for k,v in dikt.iteritems():
278 self[k] = v
279
280 def pop(self, key):
281 self.modified = True
282 return dict.pop(self, key)
283
284 def popitem(self):
285 self.modified = True
286 return dict.popitem(self)
287
288 def clear(self):
289 self.modified = True
290 dict.clear(self)
291
292 def subDict(self, *keys):
293 d = {}
294 for key in keys:
295 d[key] = self[key]
296 return d
297
298
299
300 class EngineAPI(object):
301 """This is the object through which the user can edit the `properties`
302 attribute of an Engine.
303 The Engine Properties object copies all object in and out of itself.
304 See the EngineProperties object for details.
305 """
306 _fix=False
307 def __init__(self, id):
308 self.id = id
309 self.properties = StrictDict()
310 self._fix=True
311
312 def __setattr__(self, k,v):
313 if self._fix:
314 raise error.KernelError("I am protected!")
315 else:
316 object.__setattr__(self, k, v)
317
318 def __delattr__(self, key):
319 raise error.KernelError("I am protected!")
320
321
322 _apiDict = {}
323
324 def get_engine(id):
325 """Get the Engine API object, whcih currently just provides the properties
326 object, by ID"""
327 global _apiDict
328 if not _apiDict.get(id):
329 _apiDict[id] = EngineAPI(id)
330 return _apiDict[id]
331
332 def drop_engine(id):
333 """remove an engine"""
334 global _apiDict
335 if _apiDict.has_key(id):
336 del _apiDict[id]
337
338 class EngineService(object, service.Service):
339 """Adapt a IPython shell into a IEngine implementing Twisted Service."""
340
341 zi.implements(IEngineBase)
342 name = 'EngineService'
343
344 def __init__(self, shellClass=Interpreter, mpi=None):
345 """Create an EngineService.
346
347 shellClass: something that implements IInterpreter or core1
348 mpi: an mpi module that has rank and size attributes
349 """
350 self.shellClass = shellClass
351 self.shell = self.shellClass()
352 self.mpi = mpi
353 self.id = None
354 self.properties = get_engine(self.id).properties
355 if self.mpi is not None:
356 log.msg("MPI started with rank = %i and size = %i" %
357 (self.mpi.rank, self.mpi.size))
358 self.id = self.mpi.rank
359 self._seedNamespace()
360
361 # Make id a property so that the shell can get the updated id
362
363 def _setID(self, id):
364 self._id = id
365 self.properties = get_engine(id).properties
366 self.shell.push({'id': id})
367
368 def _getID(self):
369 return self._id
370
371 id = property(_getID, _setID)
372
373 def _seedNamespace(self):
374 self.shell.push({'mpi': self.mpi, 'id' : self.id})
375
376 def executeAndRaise(self, msg, callable, *args, **kwargs):
377 """Call a method of self.shell and wrap any exception."""
378 d = defer.Deferred()
379 try:
380 result = callable(*args, **kwargs)
381 except:
382 # This gives the following:
383 # et=exception class
384 # ev=exception class instance
385 # tb=traceback object
386 et,ev,tb = sys.exc_info()
387 # This call adds attributes to the exception value
388 et,ev,tb = self.shell.formatTraceback(et,ev,tb,msg)
389 # Add another attribute
390 ev._ipython_engine_info = msg
391 f = failure.Failure(ev,et,None)
392 d.errback(f)
393 else:
394 d.callback(result)
395
396 return d
397
398 # The IEngine methods. See the interface for documentation.
399
400 def execute(self, lines):
401 msg = {'engineid':self.id,
402 'method':'execute',
403 'args':[lines]}
404 d = self.executeAndRaise(msg, self.shell.execute, lines)
405 d.addCallback(self.addIDToResult)
406 return d
407
408 def addIDToResult(self, result):
409 result['id'] = self.id
410 return result
411
412 def push(self, namespace):
413 msg = {'engineid':self.id,
414 'method':'push',
415 'args':[repr(namespace.keys())]}
416 d = self.executeAndRaise(msg, self.shell.push, namespace)
417 return d
418
419 def pull(self, keys):
420 msg = {'engineid':self.id,
421 'method':'pull',
422 'args':[repr(keys)]}
423 d = self.executeAndRaise(msg, self.shell.pull, keys)
424 return d
425
426 def push_function(self, namespace):
427 msg = {'engineid':self.id,
428 'method':'push_function',
429 'args':[repr(namespace.keys())]}
430 d = self.executeAndRaise(msg, self.shell.push_function, namespace)
431 return d
432
433 def pull_function(self, keys):
434 msg = {'engineid':self.id,
435 'method':'pull_function',
436 'args':[repr(keys)]}
437 d = self.executeAndRaise(msg, self.shell.pull_function, keys)
438 return d
439
440 def get_result(self, i=None):
441 msg = {'engineid':self.id,
442 'method':'get_result',
443 'args':[repr(i)]}
444 d = self.executeAndRaise(msg, self.shell.getCommand, i)
445 d.addCallback(self.addIDToResult)
446 return d
447
448 def reset(self):
449 msg = {'engineid':self.id,
450 'method':'reset',
451 'args':[]}
452 del self.shell
453 self.shell = self.shellClass()
454 self.properties.clear()
455 d = self.executeAndRaise(msg, self._seedNamespace)
456 return d
457
458 def kill(self):
459 drop_engine(self.id)
460 try:
461 reactor.stop()
462 except RuntimeError:
463 log.msg('The reactor was not running apparently.')
464 return defer.fail()
465 else:
466 return defer.succeed(None)
467
468 def keys(self):
469 """Return a list of variables names in the users top level namespace.
470
471 This used to return a dict of all the keys/repr(values) in the
472 user's namespace. This was too much info for the ControllerService
473 to handle so it is now just a list of keys.
474 """
475
476 remotes = []
477 for k in self.shell.user_ns.iterkeys():
478 if k not in ['__name__', '_ih', '_oh', '__builtins__',
479 'In', 'Out', '_', '__', '___', '__IP', 'input', 'raw_input']:
480 remotes.append(k)
481 return defer.succeed(remotes)
482
483 def set_properties(self, properties):
484 msg = {'engineid':self.id,
485 'method':'set_properties',
486 'args':[repr(properties.keys())]}
487 return self.executeAndRaise(msg, self.properties.update, properties)
488
489 def get_properties(self, keys=None):
490 msg = {'engineid':self.id,
491 'method':'get_properties',
492 'args':[repr(keys)]}
493 if keys is None:
494 keys = self.properties.keys()
495 return self.executeAndRaise(msg, self.properties.subDict, *keys)
496
497 def _doDel(self, keys):
498 for key in keys:
499 del self.properties[key]
500
501 def del_properties(self, keys):
502 msg = {'engineid':self.id,
503 'method':'del_properties',
504 'args':[repr(keys)]}
505 return self.executeAndRaise(msg, self._doDel, keys)
506
507 def _doHas(self, keys):
508 return [self.properties.has_key(key) for key in keys]
509
510 def has_properties(self, keys):
511 msg = {'engineid':self.id,
512 'method':'has_properties',
513 'args':[repr(keys)]}
514 return self.executeAndRaise(msg, self._doHas, keys)
515
516 def clear_properties(self):
517 msg = {'engineid':self.id,
518 'method':'clear_properties',
519 'args':[]}
520 return self.executeAndRaise(msg, self.properties.clear)
521
522 def push_serialized(self, sNamespace):
523 msg = {'engineid':self.id,
524 'method':'push_serialized',
525 'args':[repr(sNamespace.keys())]}
526 ns = {}
527 for k,v in sNamespace.iteritems():
528 try:
529 unserialized = newserialized.IUnSerialized(v)
530 ns[k] = unserialized.getObject()
531 except:
532 return defer.fail()
533 return self.executeAndRaise(msg, self.shell.push, ns)
534
535 def pull_serialized(self, keys):
536 msg = {'engineid':self.id,
537 'method':'pull_serialized',
538 'args':[repr(keys)]}
539 if isinstance(keys, str):
540 keys = [keys]
541 if len(keys)==1:
542 d = self.executeAndRaise(msg, self.shell.pull, keys)
543 d.addCallback(newserialized.serialize)
544 return d
545 elif len(keys)>1:
546 d = self.executeAndRaise(msg, self.shell.pull, keys)
547 @d.addCallback
548 def packThemUp(values):
549 serials = []
550 for v in values:
551 try:
552 serials.append(newserialized.serialize(v))
553 except:
554 return defer.fail(failure.Failure())
555 return serials
556 return packThemUp
557
558
559 def queue(methodToQueue):
560 def queuedMethod(this, *args, **kwargs):
561 name = methodToQueue.__name__
562 return this.submitCommand(Command(name, *args, **kwargs))
563 return queuedMethod
564
565 class QueuedEngine(object):
566 """Adapt an IEngineBase to an IEngineQueued by wrapping it.
567
568 The resulting object will implement IEngineQueued which extends
569 IEngineCore which extends (IEngineBase, IEngineSerialized).
570
571 This seems like the best way of handling it, but I am not sure. The
572 other option is to have the various base interfaces be used like
573 mix-in intefaces. The problem I have with this is adpatation is
574 more difficult and complicated because there can be can multiple
575 original and final Interfaces.
576 """
577
578 zi.implements(IEngineQueued)
579
580 def __init__(self, engine):
581 """Create a QueuedEngine object from an engine
582
583 engine: An implementor of IEngineCore and IEngineSerialized
584 keepUpToDate: whether to update the remote status when the
585 queue is empty. Defaults to False.
586 """
587
588 # This is the right way to do these tests rather than
589 # IEngineCore in list(zi.providedBy(engine)) which will only
590 # picks of the interfaces that are directly declared by engine.
591 assert IEngineBase.providedBy(engine), \
592 "engine passed to QueuedEngine doesn't provide IEngineBase"
593
594 self.engine = engine
595 self.id = engine.id
596 self.queued = []
597 self.history = {}
598 self.engineStatus = {}
599 self.currentCommand = None
600 self.failureObservers = []
601
602 def _get_properties(self):
603 return self.engine.properties
604
605 properties = property(_get_properties, lambda self, _: None)
606 # Queue management methods. You should not call these directly
607
608 def submitCommand(self, cmd):
609 """Submit command to queue."""
610
611 d = defer.Deferred()
612 cmd.setDeferred(d)
613 if self.currentCommand is not None:
614 if self.currentCommand.finished:
615 # log.msg("Running command immediately: %r" % cmd)
616 self.currentCommand = cmd
617 self.runCurrentCommand()
618 else: # command is still running
619 # log.msg("Command is running: %r" % self.currentCommand)
620 # log.msg("Queueing: %r" % cmd)
621 self.queued.append(cmd)
622 else:
623 # log.msg("No current commands, running: %r" % cmd)
624 self.currentCommand = cmd
625 self.runCurrentCommand()
626 return d
627
628 def runCurrentCommand(self):
629 """Run current command."""
630
631 cmd = self.currentCommand
632 f = getattr(self.engine, cmd.remoteMethod, None)
633 if f:
634 d = f(*cmd.args, **cmd.kwargs)
635 if cmd.remoteMethod is 'execute':
636 d.addCallback(self.saveResult)
637 d.addCallback(self.finishCommand)
638 d.addErrback(self.abortCommand)
639 else:
640 return defer.fail(AttributeError(cmd.remoteMethod))
641
642 def _flushQueue(self):
643 """Pop next command in queue and run it."""
644
645 if len(self.queued) > 0:
646 self.currentCommand = self.queued.pop(0)
647 self.runCurrentCommand()
648
649 def saveResult(self, result):
650 """Put the result in the history."""
651 self.history[result['number']] = result
652 return result
653
654 def finishCommand(self, result):
655 """Finish currrent command."""
656
657 # The order of these commands is absolutely critical.
658 self.currentCommand.handleResult(result)
659 self.currentCommand.finished = True
660 self._flushQueue()
661 return result
662
663 def abortCommand(self, reason):
664 """Abort current command.
665
666 This eats the Failure but first passes it onto the Deferred that the
667 user has.
668
669 It also clear out the queue so subsequence commands don't run.
670 """
671
672 # The order of these 3 commands is absolutely critical. The currentCommand
673 # must first be marked as finished BEFORE the queue is cleared and before
674 # the current command is sent the failure.
675 # Also, the queue must be cleared BEFORE the current command is sent the Failure
676 # otherwise the errback chain could trigger new commands to be added to the
677 # queue before we clear it. We should clear ONLY the commands that were in
678 # the queue when the error occured.
679 self.currentCommand.finished = True
680 s = "%r %r %r" % (self.currentCommand.remoteMethod, self.currentCommand.args, self.currentCommand.kwargs)
681 self.clear_queue(msg=s)
682 self.currentCommand.handleError(reason)
683
684 return None
685
686 #---------------------------------------------------------------------------
687 # IEngineCore methods
688 #---------------------------------------------------------------------------
689
690 @queue
691 def execute(self, lines):
692 pass
693
694 @queue
695 def push(self, namespace):
696 pass
697
698 @queue
699 def pull(self, keys):
700 pass
701
702 @queue
703 def push_function(self, namespace):
704 pass
705
706 @queue
707 def pull_function(self, keys):
708 pass
709
710 def get_result(self, i=None):
711 if i is None:
712 i = max(self.history.keys()+[None])
713
714 cmd = self.history.get(i, None)
715 # Uncomment this line to disable chaching of results
716 #cmd = None
717 if cmd is None:
718 return self.submitCommand(Command('get_result', i))
719 else:
720 return defer.succeed(cmd)
721
722 def reset(self):
723 self.clear_queue()
724 self.history = {} # reset the cache - I am not sure we should do this
725 return self.submitCommand(Command('reset'))
726
727 def kill(self):
728 self.clear_queue()
729 return self.submitCommand(Command('kill'))
730
731 @queue
732 def keys(self):
733 pass
734
735 #---------------------------------------------------------------------------
736 # IEngineSerialized methods
737 #---------------------------------------------------------------------------
738
739 @queue
740 def push_serialized(self, namespace):
741 pass
742
743 @queue
744 def pull_serialized(self, keys):
745 pass
746
747 #---------------------------------------------------------------------------
748 # IEngineProperties methods
749 #---------------------------------------------------------------------------
750
751 @queue
752 def set_properties(self, namespace):
753 pass
754
755 @queue
756 def get_properties(self, keys=None):
757 pass
758
759 @queue
760 def del_properties(self, keys):
761 pass
762
763 @queue
764 def has_properties(self, keys):
765 pass
766
767 @queue
768 def clear_properties(self):
769 pass
770
771 #---------------------------------------------------------------------------
772 # IQueuedEngine methods
773 #---------------------------------------------------------------------------
774
775 def clear_queue(self, msg=''):
776 """Clear the queue, but doesn't cancel the currently running commmand."""
777
778 for cmd in self.queued:
779 cmd.deferred.errback(failure.Failure(error.QueueCleared(msg)))
780 self.queued = []
781 return defer.succeed(None)
782
783 def queue_status(self):
784 if self.currentCommand is not None:
785 if self.currentCommand.finished:
786 pending = repr(None)
787 else:
788 pending = repr(self.currentCommand)
789 else:
790 pending = repr(None)
791 dikt = {'queue':map(repr,self.queued), 'pending':pending}
792 return defer.succeed(dikt)
793
794 def register_failure_observer(self, obs):
795 self.failureObservers.append(obs)
796
797 def unregister_failure_observer(self, obs):
798 self.failureObservers.remove(obs)
799
800
801 # Now register QueuedEngine as an adpater class that makes an IEngineBase into a
802 # IEngineQueued.
803 components.registerAdapter(QueuedEngine, IEngineBase, IEngineQueued)
804
805
806 class Command(object):
807 """A command object that encapslates queued commands.
808
809 This class basically keeps track of a command that has been queued
810 in a QueuedEngine. It manages the deferreds and hold the method to be called
811 and the arguments to that method.
812 """
813
814
815 def __init__(self, remoteMethod, *args, **kwargs):
816 """Build a new Command object."""
817
818 self.remoteMethod = remoteMethod
819 self.args = args
820 self.kwargs = kwargs
821 self.finished = False
822
823 def setDeferred(self, d):
824 """Sets the deferred attribute of the Command."""
825
826 self.deferred = d
827
828 def __repr__(self):
829 if not self.args:
830 args = ''
831 else:
832 args = str(self.args)[1:-2] #cut off (...,)
833 for k,v in self.kwargs.iteritems():
834 if args:
835 args += ', '
836 args += '%s=%r' %(k,v)
837 return "%s(%s)" %(self.remoteMethod, args)
838
839 def handleResult(self, result):
840 """When the result is ready, relay it to self.deferred."""
841
842 self.deferred.callback(result)
843
844 def handleError(self, reason):
845 """When an error has occured, relay it to self.deferred."""
846
847 self.deferred.errback(reason)
848
849 class ThreadedEngineService(EngineService):
850
851 zi.implements(IEngineBase)
852
853 def __init__(self, shellClass=Interpreter, mpi=None):
854 EngineService.__init__(self, shellClass, mpi)
855 # Only import this if we are going to use this class
856 from twisted.internet import threads
857
858 def execute(self, lines):
859 msg = """engine: %r
860 method: execute(lines)
861 lines = %s""" % (self.id, lines)
862 d = threads.deferToThread(self.executeAndRaise, msg, self.shell.execute, lines)
863 d.addCallback(self.addIDToResult)
864 return d
@@ -0,0 +1,185 b''
1 # encoding: utf-8
2
3 """Classes and functions for kernel related errors and exceptions."""
4
5 __docformat__ = "restructuredtext en"
6
7 #-------------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
13
14 #-------------------------------------------------------------------------------
15 # Imports
16 #-------------------------------------------------------------------------------
17
18 from IPython.kernel.core import error
19 from twisted.python import failure
20
21 #-------------------------------------------------------------------------------
22 # Error classes
23 #-------------------------------------------------------------------------------
24
25 class KernelError(error.IPythonError):
26 pass
27
28 class NotDefined(KernelError):
29 def __init__(self, name):
30 self.name = name
31 self.args = (name,)
32
33 def __repr__(self):
34 return '<NotDefined: %s>' % self.name
35
36 __str__ = __repr__
37
38 class QueueCleared(KernelError):
39 pass
40
41 class IdInUse(KernelError):
42 pass
43
44 class ProtocolError(KernelError):
45 pass
46
47 class ConnectionError(KernelError):
48 pass
49
50 class InvalidEngineID(KernelError):
51 pass
52
53 class NoEnginesRegistered(KernelError):
54 pass
55
56 class InvalidClientID(KernelError):
57 pass
58
59 class InvalidDeferredID(KernelError):
60 pass
61
62 class SerializationError(KernelError):
63 pass
64
65 class MessageSizeError(KernelError):
66 pass
67
68 class PBMessageSizeError(MessageSizeError):
69 pass
70
71 class ResultNotCompleted(KernelError):
72 pass
73
74 class ResultAlreadyRetrieved(KernelError):
75 pass
76
77 class ClientError(KernelError):
78 pass
79
80 class TaskAborted(KernelError):
81 pass
82
83 class TaskTimeout(KernelError):
84 pass
85
86 class NotAPendingResult(KernelError):
87 pass
88
89 class UnpickleableException(KernelError):
90 pass
91
92 class AbortedPendingDeferredError(KernelError):
93 pass
94
95 class InvalidProperty(KernelError):
96 pass
97
98 class MissingBlockArgument(KernelError):
99 pass
100
101 class StopLocalExecution(KernelError):
102 pass
103
104 class SecurityError(KernelError):
105 pass
106
107 class CompositeError(KernelError):
108 def __init__(self, message, elist):
109 Exception.__init__(self, *(message, elist))
110 self.message = message
111 self.elist = elist
112
113 def _get_engine_str(self, ev):
114 try:
115 ei = ev._ipython_engine_info
116 except AttributeError:
117 return '[Engine Exception]'
118 else:
119 return '[%i:%s]: ' % (ei['engineid'], ei['method'])
120
121 def _get_traceback(self, ev):
122 try:
123 tb = ev._ipython_traceback_text
124 except AttributeError:
125 return 'No traceback available'
126 else:
127 return tb
128
129 def __str__(self):
130 s = str(self.message)
131 for et, ev, etb in self.elist:
132 engine_str = self._get_engine_str(ev)
133 s = s + '\n' + engine_str + str(et.__name__) + ': ' + str(ev)
134 return s
135
136 def print_tracebacks(self, excid=None):
137 if excid is None:
138 for (et,ev,etb) in self.elist:
139 print self._get_engine_str(ev)
140 print self._get_traceback(ev)
141 print
142 else:
143 try:
144 et,ev,etb = self.elist[excid]
145 except:
146 raise IndexError("an exception with index %i does not exist"%excid)
147 else:
148 print self._get_engine_str(ev)
149 print self._get_traceback(ev)
150
151 def raise_exception(self, excid=0):
152 try:
153 et,ev,etb = self.elist[excid]
154 except:
155 raise IndexError("an exception with index %i does not exist"%excid)
156 else:
157 raise et, ev, etb
158
159 def collect_exceptions(rlist, method):
160 elist = []
161 for r in rlist:
162 if isinstance(r, failure.Failure):
163 r.cleanFailure()
164 et, ev, etb = r.type, r.value, r.tb
165 # Sometimes we could have CompositeError in our list. Just take
166 # the errors out of them and put them in our new list. This
167 # has the effect of flattening lists of CompositeErrors into one
168 # CompositeError
169 if et==CompositeError:
170 for e in ev.elist:
171 elist.append(e)
172 else:
173 elist.append((et, ev, etb))
174 if len(elist)==0:
175 return rlist
176 else:
177 msg = "one or more exceptions from call to method: %s" % (method)
178 # This silliness is needed so the debugger has access to the exception
179 # instance (e in this case)
180 try:
181 raise CompositeError(msg, elist)
182 except CompositeError, e:
183 raise e
184
185
@@ -0,0 +1,69 b''
1 # encoding: utf-8
2
3 """Foolscap related utilities."""
4
5 __docformat__ = "restructuredtext en"
6
7 #-------------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
13
14 #-------------------------------------------------------------------------------
15 # Imports
16 #-------------------------------------------------------------------------------
17
18 import os
19
20 from foolscap import Tub, UnauthenticatedTub
21
22 def check_furl_file_security(furl_file, secure):
23 """Remove the old furl_file if changing security modes."""
24
25 if os.path.isfile(furl_file):
26 f = open(furl_file, 'r')
27 oldfurl = f.read().strip()
28 f.close()
29 if (oldfurl.startswith('pb://') and not secure) or (oldfurl.startswith('pbu://') and secure):
30 os.remove(furl_file)
31
32 def is_secure(furl):
33 if is_valid(furl):
34 if furl.startswith("pb://"):
35 return True
36 elif furl.startswith("pbu://"):
37 return False
38 else:
39 raise ValueError("invalid furl: %s" % furl)
40
41 def is_valid(furl):
42 if isinstance(furl, str):
43 if furl.startswith("pb://") or furl.startswith("pbu://"):
44 return True
45 else:
46 return False
47
48 def find_furl(furl_or_file):
49 if isinstance(furl_or_file, str):
50 if is_valid(furl_or_file):
51 return furl_or_file
52 if os.path.isfile(furl_or_file):
53 furl = open(furl_or_file, 'r').read().strip()
54 if is_valid(furl):
55 return furl
56 raise ValueError("not a furl or a file containing a furl: %s" % furl_or_file)
57
58 # We do this so if a user doesn't have OpenSSL installed, it will try to use
59 # an UnauthenticatedTub. But, they will still run into problems if they
60 # try to use encrypted furls.
61 try:
62 import OpenSSL
63 except:
64 Tub = UnauthenticatedTub
65 have_crypto = False
66 else:
67 have_crypto = True
68
69
@@ -0,0 +1,171 b''
1 # encoding: utf-8
2
3 """Magic command interface for interactive parallel work."""
4
5 __docformat__ = "restructuredtext en"
6
7 #-------------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
13
14 #-------------------------------------------------------------------------------
15 # Imports
16 #-------------------------------------------------------------------------------
17
18 import new
19
20 from IPython.iplib import InteractiveShell
21 from IPython.Shell import MTInteractiveShell
22
23 from twisted.internet.defer import Deferred
24
25
26 #-------------------------------------------------------------------------------
27 # Definitions of magic functions for use with IPython
28 #-------------------------------------------------------------------------------
29
30 NO_ACTIVE_CONTROLLER = """
31 Error: No Controller is activated
32 Use activate() on a RemoteController object to activate it for magics.
33 """
34
35 def magic_result(self,parameter_s=''):
36 """Print the result of command i on all engines of the active controller.
37
38 To activate a controller in IPython, first create it and then call
39 the activate() method.
40
41 Then you can do the following:
42
43 >>> result # Print the latest result
44 Printing result...
45 [127.0.0.1:0] In [1]: b = 10
46 [127.0.0.1:1] In [1]: b = 10
47
48 >>> result 0 # Print result 0
49 In [14]: result 0
50 Printing result...
51 [127.0.0.1:0] In [0]: a = 5
52 [127.0.0.1:1] In [0]: a = 5
53 """
54 try:
55 activeController = __IPYTHON__.activeController
56 except AttributeError:
57 print NO_ACTIVE_CONTROLLER
58 else:
59 try:
60 index = int(parameter_s)
61 except:
62 index = None
63 result = activeController.get_result(index)
64 return result
65
66 def magic_px(self,parameter_s=''):
67 """Executes the given python command on the active IPython Controller.
68
69 To activate a Controller in IPython, first create it and then call
70 the activate() method.
71
72 Then you can do the following:
73
74 >>> %px a = 5 # Runs a = 5 on all nodes
75 """
76
77 try:
78 activeController = __IPYTHON__.activeController
79 except AttributeError:
80 print NO_ACTIVE_CONTROLLER
81 else:
82 print "Executing command on Controller"
83 result = activeController.execute(parameter_s)
84 return result
85
86 def pxrunsource(self, source, filename="<input>", symbol="single"):
87
88 try:
89 code = self.compile(source, filename, symbol)
90 except (OverflowError, SyntaxError, ValueError):
91 # Case 1
92 self.showsyntaxerror(filename)
93 return None
94
95 if code is None:
96 # Case 2
97 return True
98
99 # Case 3
100 # Because autopx is enabled, we now call executeAll or disable autopx if
101 # %autopx or autopx has been called
102 if '_ip.magic("%autopx' in source or '_ip.magic("autopx' in source:
103 _disable_autopx(self)
104 return False
105 else:
106 try:
107 result = self.activeController.execute(source)
108 except:
109 self.showtraceback()
110 else:
111 print result.__repr__()
112 return False
113
114 def magic_autopx(self, parameter_s=''):
115 """Toggles auto parallel mode for the active IPython Controller.
116
117 To activate a Controller in IPython, first create it and then call
118 the activate() method.
119
120 Then you can do the following:
121
122 >>> %autopx # Now all commands are executed in parallel
123 Auto Parallel Enabled
124 Type %autopx to disable
125 ...
126 >>> %autopx # Now all commands are locally executed
127 Auto Parallel Disabled
128 """
129
130 if hasattr(self, 'autopx'):
131 if self.autopx == True:
132 _disable_autopx(self)
133 else:
134 _enable_autopx(self)
135 else:
136 _enable_autopx(self)
137
138 def _enable_autopx(self):
139 """Enable %autopx mode by saving the original runsource and installing
140 pxrunsource.
141 """
142 try:
143 activeController = __IPYTHON__.activeController
144 except AttributeError:
145 print "No active RemoteController found, use RemoteController.activate()."
146 else:
147 self._original_runsource = self.runsource
148 self.runsource = new.instancemethod(pxrunsource, self, self.__class__)
149 self.autopx = True
150 print "Auto Parallel Enabled\nType %autopx to disable"
151
152 def _disable_autopx(self):
153 """Disable %autopx by restoring the original runsource."""
154 if hasattr(self, 'autopx'):
155 if self.autopx == True:
156 self.runsource = self._original_runsource
157 self.autopx = False
158 print "Auto Parallel Disabled"
159
160 # Add the new magic function to the class dict:
161
162 InteractiveShell.magic_result = magic_result
163 InteractiveShell.magic_px = magic_px
164 InteractiveShell.magic_autopx = magic_autopx
165
166 # And remove the global name to keep global namespace clean. Don't worry, the
167 # copy bound to IPython stays, we're just removing the global name.
168 del magic_result
169 del magic_px
170 del magic_autopx
171
@@ -0,0 +1,121 b''
1 # encoding: utf-8
2
3 """Classes used in scattering and gathering sequences.
4
5 Scattering consists of partitioning a sequence and sending the various
6 pieces to individual nodes in a cluster.
7 """
8
9 __docformat__ = "restructuredtext en"
10
11 #-------------------------------------------------------------------------------
12 # Copyright (C) 2008 The IPython Development Team
13 #
14 # Distributed under the terms of the BSD License. The full license is in
15 # the file COPYING, distributed as part of this software.
16 #-------------------------------------------------------------------------------
17
18 #-------------------------------------------------------------------------------
19 # Imports
20 #-------------------------------------------------------------------------------
21
22 import types
23
24 from IPython.genutils import flatten as genutil_flatten
25
26 #-------------------------------------------------------------------------------
27 # Figure out which array packages are present and their array types
28 #-------------------------------------------------------------------------------
29
30 arrayModules = []
31 try:
32 import Numeric
33 except ImportError:
34 pass
35 else:
36 arrayModules.append({'module':Numeric, 'type':Numeric.arraytype})
37 try:
38 import numpy
39 except ImportError:
40 pass
41 else:
42 arrayModules.append({'module':numpy, 'type':numpy.ndarray})
43 try:
44 import numarray
45 except ImportError:
46 pass
47 else:
48 arrayModules.append({'module':numarray,
49 'type':numarray.numarraycore.NumArray})
50
51 class Map:
52 """A class for partitioning a sequence using a map."""
53
54 def getPartition(self, seq, p, q):
55 """Returns the pth partition of q partitions of seq."""
56
57 # Test for error conditions here
58 if p<0 or p>=q:
59 print "No partition exists."
60 return
61
62 remainder = len(seq)%q
63 basesize = len(seq)/q
64 hi = []
65 lo = []
66 for n in range(q):
67 if n < remainder:
68 lo.append(n * (basesize + 1))
69 hi.append(lo[-1] + basesize + 1)
70 else:
71 lo.append(n*basesize + remainder)
72 hi.append(lo[-1] + basesize)
73
74
75 result = seq[lo[p]:hi[p]]
76 return result
77
78 def joinPartitions(self, listOfPartitions):
79 return self.concatenate(listOfPartitions)
80
81 def concatenate(self, listOfPartitions):
82 testObject = listOfPartitions[0]
83 # First see if we have a known array type
84 for m in arrayModules:
85 #print m
86 if isinstance(testObject, m['type']):
87 return m['module'].concatenate(listOfPartitions)
88 # Next try for Python sequence types
89 if isinstance(testObject, (types.ListType, types.TupleType)):
90 return genutil_flatten(listOfPartitions)
91 # If we have scalars, just return listOfPartitions
92 return listOfPartitions
93
94 class RoundRobinMap(Map):
95 """Partitions a sequence in a roun robin fashion.
96
97 This currently does not work!
98 """
99
100 def getPartition(self, seq, p, q):
101 return seq[p:len(seq):q]
102 #result = []
103 #for i in range(p,len(seq),q):
104 # result.append(seq[i])
105 #return result
106
107 def joinPartitions(self, listOfPartitions):
108 #lengths = [len(x) for x in listOfPartitions]
109 #maxPartitionLength = len(listOfPartitions[0])
110 #numberOfPartitions = len(listOfPartitions)
111 #concat = self.concatenate(listOfPartitions)
112 #totalLength = len(concat)
113 #result = []
114 #for i in range(maxPartitionLength):
115 # result.append(concat[i:totalLength:maxPartitionLength])
116 return self.concatenate(listOfPartitions)
117
118 styles = {'basic':Map}
119
120
121
This diff has been collapsed as it changes many lines, (780 lines changed) Show them Hide them
@@ -0,0 +1,780 b''
1 # encoding: utf-8
2 # -*- test-case-name: IPython.kernel.test.test_multiengine -*-
3
4 """Adapt the IPython ControllerServer to IMultiEngine.
5
6 This module provides classes that adapt a ControllerService to the
7 IMultiEngine interface. This interface is a basic interactive interface
8 for working with a set of engines where it is desired to have explicit
9 access to each registered engine.
10
11 The classes here are exposed to the network in files like:
12
13 * multienginevanilla.py
14 * multienginepb.py
15 """
16
17 __docformat__ = "restructuredtext en"
18
19 #-------------------------------------------------------------------------------
20 # Copyright (C) 2008 The IPython Development Team
21 #
22 # Distributed under the terms of the BSD License. The full license is in
23 # the file COPYING, distributed as part of this software.
24 #-------------------------------------------------------------------------------
25
26 #-------------------------------------------------------------------------------
27 # Imports
28 #-------------------------------------------------------------------------------
29
30 from new import instancemethod
31 from types import FunctionType
32
33 from twisted.application import service
34 from twisted.internet import defer, reactor
35 from twisted.python import log, components, failure
36 from zope.interface import Interface, implements, Attribute
37
38 from IPython.tools import growl
39 from IPython.kernel.util import printer
40 from IPython.kernel.twistedutil import gatherBoth
41 from IPython.kernel import map as Map
42 from IPython.kernel import error
43 from IPython.kernel.pendingdeferred import PendingDeferredManager, two_phase
44 from IPython.kernel.controllerservice import \
45 ControllerAdapterBase, \
46 ControllerService, \
47 IControllerBase
48
49
50 #-------------------------------------------------------------------------------
51 # Interfaces for the MultiEngine representation of a controller
52 #-------------------------------------------------------------------------------
53
54 class IEngineMultiplexer(Interface):
55 """Interface to multiple engines implementing IEngineCore/Serialized/Queued.
56
57 This class simply acts as a multiplexer of methods that are in the
58 various IEngines* interfaces. Thus the methods here are jut like those
59 in the IEngine* interfaces, but with an extra first argument, targets.
60 The targets argument can have the following forms:
61
62 * targets = 10 # Engines are indexed by ints
63 * targets = [0,1,2,3] # A list of ints
64 * targets = 'all' # A string to indicate all targets
65
66 If targets is bad in any way, an InvalidEngineID will be raised. This
67 includes engines not being registered.
68
69 All IEngineMultiplexer multiplexer methods must return a Deferred to a list
70 with length equal to the number of targets. The elements of the list will
71 correspond to the return of the corresponding IEngine method.
72
73 Failures are aggressive, meaning that if an action fails for any target,
74 the overall action will fail immediately with that Failure.
75
76 :Parameters:
77 targets : int, list of ints, or 'all'
78 Engine ids the action will apply to.
79
80 :Returns: Deferred to a list of results for each engine.
81
82 :Exception:
83 InvalidEngineID
84 If the targets argument is bad or engines aren't registered.
85 NoEnginesRegistered
86 If there are no engines registered and targets='all'
87 """
88
89 #---------------------------------------------------------------------------
90 # Mutiplexed methods
91 #---------------------------------------------------------------------------
92
93 def execute(lines, targets='all'):
94 """Execute lines of Python code on targets.
95
96 See the class docstring for information about targets and possible
97 exceptions this method can raise.
98
99 :Parameters:
100 lines : str
101 String of python code to be executed on targets.
102 """
103
104 def push(namespace, targets='all'):
105 """Push dict namespace into the user's namespace on targets.
106
107 See the class docstring for information about targets and possible
108 exceptions this method can raise.
109
110 :Parameters:
111 namspace : dict
112 Dict of key value pairs to be put into the users namspace.
113 """
114
115 def pull(keys, targets='all'):
116 """Pull values out of the user's namespace on targets by keys.
117
118 See the class docstring for information about targets and possible
119 exceptions this method can raise.
120
121 :Parameters:
122 keys : tuple of strings
123 Sequence of keys to be pulled from user's namespace.
124 """
125
126 def push_function(namespace, targets='all'):
127 """"""
128
129 def pull_function(keys, targets='all'):
130 """"""
131
132 def get_result(i=None, targets='all'):
133 """Get the result for command i from targets.
134
135 See the class docstring for information about targets and possible
136 exceptions this method can raise.
137
138 :Parameters:
139 i : int or None
140 Command index or None to indicate most recent command.
141 """
142
143 def reset(targets='all'):
144 """Reset targets.
145
146 This clears the users namespace of the Engines, but won't cause
147 modules to be reloaded.
148 """
149
150 def keys(targets='all'):
151 """Get variable names defined in user's namespace on targets."""
152
153 def kill(controller=False, targets='all'):
154 """Kill the targets Engines and possibly the controller.
155
156 :Parameters:
157 controller : boolean
158 Should the controller be killed as well. If so all the
159 engines will be killed first no matter what targets is.
160 """
161
162 def push_serialized(namespace, targets='all'):
163 """Push a namespace of Serialized objects to targets.
164
165 :Parameters:
166 namespace : dict
167 A dict whose keys are the variable names and whose values
168 are serialized version of the objects.
169 """
170
171 def pull_serialized(keys, targets='all'):
172 """Pull Serialized objects by keys from targets.
173
174 :Parameters:
175 keys : tuple of strings
176 Sequence of variable names to pull as serialized objects.
177 """
178
179 def clear_queue(targets='all'):
180 """Clear the queue of pending command for targets."""
181
182 def queue_status(targets='all'):
183 """Get the status of the queue on the targets."""
184
185 def set_properties(properties, targets='all'):
186 """set properties by key and value"""
187
188 def get_properties(keys=None, targets='all'):
189 """get a list of properties by `keys`, if no keys specified, get all"""
190
191 def del_properties(keys, targets='all'):
192 """delete properties by `keys`"""
193
194 def has_properties(keys, targets='all'):
195 """get a list of bool values for whether `properties` has `keys`"""
196
197 def clear_properties(targets='all'):
198 """clear the properties dict"""
199
200
201 class IMultiEngine(IEngineMultiplexer):
202 """A controller that exposes an explicit interface to all of its engines.
203
204 This is the primary inteface for interactive usage.
205 """
206
207 def get_ids():
208 """Return list of currently registered ids.
209
210 :Returns: A Deferred to a list of registered engine ids.
211 """
212
213
214
215 #-------------------------------------------------------------------------------
216 # Implementation of the core MultiEngine classes
217 #-------------------------------------------------------------------------------
218
219 class MultiEngine(ControllerAdapterBase):
220 """The representation of a ControllerService as a IMultiEngine.
221
222 Although it is not implemented currently, this class would be where a
223 client/notification API is implemented. It could inherit from something
224 like results.NotifierParent and then use the notify method to send
225 notifications.
226 """
227
228 implements(IMultiEngine)
229
230 def __init(self, controller):
231 ControllerAdapterBase.__init__(self, controller)
232
233 #---------------------------------------------------------------------------
234 # Helper methods
235 #---------------------------------------------------------------------------
236
237 def engineList(self, targets):
238 """Parse the targets argument into a list of valid engine objects.
239
240 :Parameters:
241 targets : int, list of ints or 'all'
242 The targets argument to be parsed.
243
244 :Returns: List of engine objects.
245
246 :Exception:
247 InvalidEngineID
248 If targets is not valid or if an engine is not registered.
249 """
250 if isinstance(targets, int):
251 if targets not in self.engines.keys():
252 log.msg("Engine with id %i is not registered" % targets)
253 raise error.InvalidEngineID("Engine with id %i is not registered" % targets)
254 else:
255 return [self.engines[targets]]
256 elif isinstance(targets, (list, tuple)):
257 for id in targets:
258 if id not in self.engines.keys():
259 log.msg("Engine with id %r is not registered" % id)
260 raise error.InvalidEngineID("Engine with id %r is not registered" % id)
261 return map(self.engines.get, targets)
262 elif targets == 'all':
263 eList = self.engines.values()
264 if len(eList) == 0:
265 msg = """There are no engines registered.
266 Check the logs in ~/.ipython/log if you think there should have been."""
267 raise error.NoEnginesRegistered(msg)
268 else:
269 return eList
270 else:
271 raise error.InvalidEngineID("targets argument is not an int, list of ints or 'all': %r"%targets)
272
273 def _performOnEngines(self, methodName, *args, **kwargs):
274 """Calls a method on engines and returns deferred to list of results.
275
276 :Parameters:
277 methodName : str
278 Name of the method to be called.
279 targets : int, list of ints, 'all'
280 The targets argument to be parsed into a list of engine objects.
281 args
282 The positional keyword arguments to be passed to the engines.
283 kwargs
284 The keyword arguments passed to the method
285
286 :Returns: List of deferreds to the results on each engine
287
288 :Exception:
289 InvalidEngineID
290 If the targets argument is bad in any way.
291 AttributeError
292 If the method doesn't exist on one of the engines.
293 """
294 targets = kwargs.pop('targets')
295 log.msg("Performing %s on %r" % (methodName, targets))
296 # log.msg("Performing %s(%r, %r) on %r" % (methodName, args, kwargs, targets))
297 # This will and should raise if targets is not valid!
298 engines = self.engineList(targets)
299 dList = []
300 for e in engines:
301 meth = getattr(e, methodName, None)
302 if meth is not None:
303 dList.append(meth(*args, **kwargs))
304 else:
305 raise AttributeError("Engine %i does not have method %s" % (e.id, methodName))
306 return dList
307
308 def _performOnEnginesAndGatherBoth(self, methodName, *args, **kwargs):
309 """Called _performOnEngines and wraps result/exception into deferred."""
310 try:
311 dList = self._performOnEngines(methodName, *args, **kwargs)
312 except (error.InvalidEngineID, AttributeError, KeyError, error.NoEnginesRegistered):
313 return defer.fail(failure.Failure())
314 else:
315 # Having fireOnOneErrback is causing problems with the determinacy
316 # of the system. Basically, once a single engine has errbacked, this
317 # method returns. In some cases, this will cause client to submit
318 # another command. Because the previous command is still running
319 # on some engines, this command will be queued. When those commands
320 # then errback, the second command will raise QueueCleared. Ahhh!
321 d = gatherBoth(dList,
322 fireOnOneErrback=0,
323 consumeErrors=1,
324 logErrors=0)
325 d.addCallback(error.collect_exceptions, methodName)
326 return d
327
328 #---------------------------------------------------------------------------
329 # General IMultiEngine methods
330 #---------------------------------------------------------------------------
331
332 def get_ids(self):
333 return defer.succeed(self.engines.keys())
334
335 #---------------------------------------------------------------------------
336 # IEngineMultiplexer methods
337 #---------------------------------------------------------------------------
338
339 def execute(self, lines, targets='all'):
340 return self._performOnEnginesAndGatherBoth('execute', lines, targets=targets)
341
342 def push(self, ns, targets='all'):
343 return self._performOnEnginesAndGatherBoth('push', ns, targets=targets)
344
345 def pull(self, keys, targets='all'):
346 return self._performOnEnginesAndGatherBoth('pull', keys, targets=targets)
347
348 def push_function(self, ns, targets='all'):
349 return self._performOnEnginesAndGatherBoth('push_function', ns, targets=targets)
350
351 def pull_function(self, keys, targets='all'):
352 return self._performOnEnginesAndGatherBoth('pull_function', keys, targets=targets)
353
354 def get_result(self, i=None, targets='all'):
355 return self._performOnEnginesAndGatherBoth('get_result', i, targets=targets)
356
357 def reset(self, targets='all'):
358 return self._performOnEnginesAndGatherBoth('reset', targets=targets)
359
360 def keys(self, targets='all'):
361 return self._performOnEnginesAndGatherBoth('keys', targets=targets)
362
363 def kill(self, controller=False, targets='all'):
364 if controller:
365 targets = 'all'
366 d = self._performOnEnginesAndGatherBoth('kill', targets=targets)
367 if controller:
368 log.msg("Killing controller")
369 d.addCallback(lambda _: reactor.callLater(2.0, reactor.stop))
370 # Consume any weird stuff coming back
371 d.addBoth(lambda _: None)
372 return d
373
374 def push_serialized(self, namespace, targets='all'):
375 for k, v in namespace.iteritems():
376 log.msg("Pushed object %s is %f MB" % (k, v.getDataSize()))
377 d = self._performOnEnginesAndGatherBoth('push_serialized', namespace, targets=targets)
378 return d
379
380 def pull_serialized(self, keys, targets='all'):
381 try:
382 dList = self._performOnEngines('pull_serialized', keys, targets=targets)
383 except (error.InvalidEngineID, AttributeError, error.NoEnginesRegistered):
384 return defer.fail(failure.Failure())
385 else:
386 for d in dList:
387 d.addCallback(self._logSizes)
388 d = gatherBoth(dList,
389 fireOnOneErrback=0,
390 consumeErrors=1,
391 logErrors=0)
392 d.addCallback(error.collect_exceptions, 'pull_serialized')
393 return d
394
395 def _logSizes(self, listOfSerialized):
396 if isinstance(listOfSerialized, (list, tuple)):
397 for s in listOfSerialized:
398 log.msg("Pulled object is %f MB" % s.getDataSize())
399 else:
400 log.msg("Pulled object is %f MB" % listOfSerialized.getDataSize())
401 return listOfSerialized
402
403 def clear_queue(self, targets='all'):
404 return self._performOnEnginesAndGatherBoth('clear_queue', targets=targets)
405
406 def queue_status(self, targets='all'):
407 log.msg("Getting queue status on %r" % targets)
408 try:
409 engines = self.engineList(targets)
410 except (error.InvalidEngineID, AttributeError, error.NoEnginesRegistered):
411 return defer.fail(failure.Failure())
412 else:
413 dList = []
414 for e in engines:
415 dList.append(e.queue_status().addCallback(lambda s:(e.id, s)))
416 d = gatherBoth(dList,
417 fireOnOneErrback=0,
418 consumeErrors=1,
419 logErrors=0)
420 d.addCallback(error.collect_exceptions, 'queue_status')
421 return d
422
423 def get_properties(self, keys=None, targets='all'):
424 log.msg("Getting properties on %r" % targets)
425 try:
426 engines = self.engineList(targets)
427 except (error.InvalidEngineID, AttributeError, error.NoEnginesRegistered):
428 return defer.fail(failure.Failure())
429 else:
430 dList = [e.get_properties(keys) for e in engines]
431 d = gatherBoth(dList,
432 fireOnOneErrback=0,
433 consumeErrors=1,
434 logErrors=0)
435 d.addCallback(error.collect_exceptions, 'get_properties')
436 return d
437
438 def set_properties(self, properties, targets='all'):
439 log.msg("Setting properties on %r" % targets)
440 try:
441 engines = self.engineList(targets)
442 except (error.InvalidEngineID, AttributeError, error.NoEnginesRegistered):
443 return defer.fail(failure.Failure())
444 else:
445 dList = [e.set_properties(properties) for e in engines]
446 d = gatherBoth(dList,
447 fireOnOneErrback=0,
448 consumeErrors=1,
449 logErrors=0)
450 d.addCallback(error.collect_exceptions, 'set_properties')
451 return d
452
453 def has_properties(self, keys, targets='all'):
454 log.msg("Checking properties on %r" % targets)
455 try:
456 engines = self.engineList(targets)
457 except (error.InvalidEngineID, AttributeError, error.NoEnginesRegistered):
458 return defer.fail(failure.Failure())
459 else:
460 dList = [e.has_properties(keys) for e in engines]
461 d = gatherBoth(dList,
462 fireOnOneErrback=0,
463 consumeErrors=1,
464 logErrors=0)
465 d.addCallback(error.collect_exceptions, 'has_properties')
466 return d
467
468 def del_properties(self, keys, targets='all'):
469 log.msg("Deleting properties on %r" % targets)
470 try:
471 engines = self.engineList(targets)
472 except (error.InvalidEngineID, AttributeError, error.NoEnginesRegistered):
473 return defer.fail(failure.Failure())
474 else:
475 dList = [e.del_properties(keys) for e in engines]
476 d = gatherBoth(dList,
477 fireOnOneErrback=0,
478 consumeErrors=1,
479 logErrors=0)
480 d.addCallback(error.collect_exceptions, 'del_properties')
481 return d
482
483 def clear_properties(self, targets='all'):
484 log.msg("Clearing properties on %r" % targets)
485 try:
486 engines = self.engineList(targets)
487 except (error.InvalidEngineID, AttributeError, error.NoEnginesRegistered):
488 return defer.fail(failure.Failure())
489 else:
490 dList = [e.clear_properties() for e in engines]
491 d = gatherBoth(dList,
492 fireOnOneErrback=0,
493 consumeErrors=1,
494 logErrors=0)
495 d.addCallback(error.collect_exceptions, 'clear_properties')
496 return d
497
498
499 components.registerAdapter(MultiEngine,
500 IControllerBase,
501 IMultiEngine)
502
503
504 #-------------------------------------------------------------------------------
505 # Interfaces for the Synchronous MultiEngine
506 #-------------------------------------------------------------------------------
507
508 class ISynchronousEngineMultiplexer(Interface):
509 pass
510
511
512 class ISynchronousMultiEngine(ISynchronousEngineMultiplexer):
513 """Synchronous, two-phase version of IMultiEngine.
514
515 Methods in this interface are identical to those of IMultiEngine, but they
516 take one additional argument:
517
518 execute(lines, targets='all') -> execute(lines, targets='all, block=True)
519
520 :Parameters:
521 block : boolean
522 Should the method return a deferred to a deferredID or the
523 actual result. If block=False a deferred to a deferredID is
524 returned and the user must call `get_pending_deferred` at a later
525 point. If block=True, a deferred to the actual result comes back.
526 """
527 def get_pending_deferred(deferredID, block=True):
528 """"""
529
530 def clear_pending_deferreds():
531 """"""
532
533
534 #-------------------------------------------------------------------------------
535 # Implementation of the Synchronous MultiEngine
536 #-------------------------------------------------------------------------------
537
538 class SynchronousMultiEngine(PendingDeferredManager):
539 """Adapt an `IMultiEngine` -> `ISynchronousMultiEngine`
540
541 Warning, this class uses a decorator that currently uses **kwargs.
542 Because of this block must be passed as a kwarg, not positionally.
543 """
544
545 implements(ISynchronousMultiEngine)
546
547 def __init__(self, multiengine):
548 self.multiengine = multiengine
549 PendingDeferredManager.__init__(self)
550
551 #---------------------------------------------------------------------------
552 # Decorated pending deferred methods
553 #---------------------------------------------------------------------------
554
555 @two_phase
556 def execute(self, lines, targets='all'):
557 d = self.multiengine.execute(lines, targets)
558 return d
559
560 @two_phase
561 def push(self, namespace, targets='all'):
562 return self.multiengine.push(namespace, targets)
563
564 @two_phase
565 def pull(self, keys, targets='all'):
566 d = self.multiengine.pull(keys, targets)
567 return d
568
569 @two_phase
570 def push_function(self, namespace, targets='all'):
571 return self.multiengine.push_function(namespace, targets)
572
573 @two_phase
574 def pull_function(self, keys, targets='all'):
575 d = self.multiengine.pull_function(keys, targets)
576 return d
577
578 @two_phase
579 def get_result(self, i=None, targets='all'):
580 return self.multiengine.get_result(i, targets='all')
581
582 @two_phase
583 def reset(self, targets='all'):
584 return self.multiengine.reset(targets)
585
586 @two_phase
587 def keys(self, targets='all'):
588 return self.multiengine.keys(targets)
589
590 @two_phase
591 def kill(self, controller=False, targets='all'):
592 return self.multiengine.kill(controller, targets)
593
594 @two_phase
595 def push_serialized(self, namespace, targets='all'):
596 return self.multiengine.push_serialized(namespace, targets)
597
598 @two_phase
599 def pull_serialized(self, keys, targets='all'):
600 return self.multiengine.pull_serialized(keys, targets)
601
602 @two_phase
603 def clear_queue(self, targets='all'):
604 return self.multiengine.clear_queue(targets)
605
606 @two_phase
607 def queue_status(self, targets='all'):
608 return self.multiengine.queue_status(targets)
609
610 @two_phase
611 def set_properties(self, properties, targets='all'):
612 return self.multiengine.set_properties(properties, targets)
613
614 @two_phase
615 def get_properties(self, keys=None, targets='all'):
616 return self.multiengine.get_properties(keys, targets)
617
618 @two_phase
619 def has_properties(self, keys, targets='all'):
620 return self.multiengine.has_properties(keys, targets)
621
622 @two_phase
623 def del_properties(self, keys, targets='all'):
624 return self.multiengine.del_properties(keys, targets)
625
626 @two_phase
627 def clear_properties(self, targets='all'):
628 return self.multiengine.clear_properties(targets)
629
630 #---------------------------------------------------------------------------
631 # IMultiEngine methods
632 #---------------------------------------------------------------------------
633
634 def get_ids(self):
635 """Return a list of registered engine ids.
636
637 Never use the two phase block/non-block stuff for this.
638 """
639 return self.multiengine.get_ids()
640
641
642 components.registerAdapter(SynchronousMultiEngine, IMultiEngine, ISynchronousMultiEngine)
643
644
645 #-------------------------------------------------------------------------------
646 # Various high-level interfaces that can be used as MultiEngine mix-ins
647 #-------------------------------------------------------------------------------
648
649 #-------------------------------------------------------------------------------
650 # IMultiEngineCoordinator
651 #-------------------------------------------------------------------------------
652
653 class IMultiEngineCoordinator(Interface):
654 """Methods that work on multiple engines explicitly."""
655
656 def scatter(key, seq, style='basic', flatten=False, targets='all'):
657 """Partition and distribute a sequence to targets.
658
659 :Parameters:
660 key : str
661 The variable name to call the scattered sequence.
662 seq : list, tuple, array
663 The sequence to scatter. The type should be preserved.
664 style : string
665 A specification of how the sequence is partitioned. Currently
666 only 'basic' is implemented.
667 flatten : boolean
668 Should single element sequences be converted to scalars.
669 """
670
671 def gather(key, style='basic', targets='all'):
672 """Gather object key from targets.
673
674 :Parameters:
675 key : string
676 The name of a sequence on the targets to gather.
677 style : string
678 A specification of how the sequence is partitioned. Currently
679 only 'basic' is implemented.
680 """
681
682 def map(func, seq, style='basic', targets='all'):
683 """A parallelized version of Python's builtin map.
684
685 This function implements the following pattern:
686
687 1. The sequence seq is scattered to the given targets.
688 2. map(functionSource, seq) is called on each engine.
689 3. The resulting sequences are gathered back to the local machine.
690
691 :Parameters:
692 targets : int, list or 'all'
693 The engine ids the action will apply to. Call `get_ids` to see
694 a list of currently available engines.
695 func : str, function
696 An actual function object or a Python string that names a
697 callable defined on the engines.
698 seq : list, tuple or numpy array
699 The local sequence to be scattered.
700 style : str
701 Only 'basic' is supported for now.
702
703 :Returns: A list of len(seq) with functionSource called on each element
704 of seq.
705
706 Example
707 =======
708
709 >>> rc.mapAll('lambda x: x*x', range(10000))
710 [0,2,4,9,25,36,...]
711 """
712
713
714 class ISynchronousMultiEngineCoordinator(IMultiEngineCoordinator):
715 """Methods that work on multiple engines explicitly."""
716 pass
717
718
719 #-------------------------------------------------------------------------------
720 # IMultiEngineExtras
721 #-------------------------------------------------------------------------------
722
723 class IMultiEngineExtras(Interface):
724
725 def zip_pull(targets, *keys):
726 """Pull, but return results in a different format from `pull`.
727
728 This method basically returns zip(pull(targets, *keys)), with a few
729 edge cases handled differently. Users of chainsaw will find this format
730 familiar.
731
732 :Parameters:
733 targets : int, list or 'all'
734 The engine ids the action will apply to. Call `get_ids` to see
735 a list of currently available engines.
736 keys: list or tuple of str
737 A list of variable names as string of the Python objects to be pulled
738 back to the client.
739
740 :Returns: A list of pulled Python objects for each target.
741 """
742
743 def run(targets, fname):
744 """Run a .py file on targets.
745
746 :Parameters:
747 targets : int, list or 'all'
748 The engine ids the action will apply to. Call `get_ids` to see
749 a list of currently available engines.
750 fname : str
751 The filename of a .py file on the local system to be sent to and run
752 on the engines.
753 block : boolean
754 Should I block or not. If block=True, wait for the action to
755 complete and return the result. If block=False, return a
756 `PendingResult` object that can be used to later get the
757 result. If block is not specified, the block attribute
758 will be used instead.
759 """
760
761
762 class ISynchronousMultiEngineExtras(IMultiEngineExtras):
763 pass
764
765
766 #-------------------------------------------------------------------------------
767 # The full MultiEngine interface
768 #-------------------------------------------------------------------------------
769
770 class IFullMultiEngine(IMultiEngine,
771 IMultiEngineCoordinator,
772 IMultiEngineExtras):
773 pass
774
775
776 class IFullSynchronousMultiEngine(ISynchronousMultiEngine,
777 ISynchronousMultiEngineCoordinator,
778 ISynchronousMultiEngineExtras):
779 pass
780
This diff has been collapsed as it changes many lines, (840 lines changed) Show them Hide them
@@ -0,0 +1,840 b''
1 # encoding: utf-8
2 # -*- test-case-name: IPython.kernel.test.test_multiengineclient -*-
3
4 """General Classes for IMultiEngine clients."""
5
6 __docformat__ = "restructuredtext en"
7
8 #-------------------------------------------------------------------------------
9 # Copyright (C) 2008 The IPython Development Team
10 #
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
13 #-------------------------------------------------------------------------------
14
15 #-------------------------------------------------------------------------------
16 # Imports
17 #-------------------------------------------------------------------------------
18
19 import sys
20 import cPickle as pickle
21 from types import FunctionType
22 import linecache
23
24 from twisted.internet import reactor
25 from twisted.python import components, log
26 from twisted.python.failure import Failure
27 from zope.interface import Interface, implements, Attribute
28
29 from IPython.ColorANSI import TermColors
30
31 from IPython.kernel.twistedutil import blockingCallFromThread
32 from IPython.kernel import error
33 from IPython.kernel.parallelfunction import ParallelFunction
34 from IPython.kernel import map as Map
35 from IPython.kernel import multiengine as me
36 from IPython.kernel.multiengine import (IFullMultiEngine,
37 IFullSynchronousMultiEngine)
38
39
40 #-------------------------------------------------------------------------------
41 # Pending Result things
42 #-------------------------------------------------------------------------------
43
44 class IPendingResult(Interface):
45 """A representation of a result that is pending.
46
47 This class is similar to Twisted's `Deferred` object, but is designed to be
48 used in a synchronous context.
49 """
50
51 result_id=Attribute("ID of the deferred on the other side")
52 client=Attribute("A client that I came from")
53 r=Attribute("An attribute that is a property that calls and returns get_result")
54
55 def get_result(default=None, block=True):
56 """
57 Get a result that is pending.
58
59 :Parameters:
60 default
61 The value to return if the result is not ready.
62 block : boolean
63 Should I block for the result.
64
65 :Returns: The actual result or the default value.
66 """
67
68 def add_callback(f, *args, **kwargs):
69 """
70 Add a callback that is called with the result.
71
72 If the original result is foo, adding a callback will cause
73 f(foo, *args, **kwargs) to be returned instead. If multiple
74 callbacks are registered, they are chained together: the result of
75 one is passed to the next and so on.
76
77 Unlike Twisted's Deferred object, there is no errback chain. Thus
78 any exception raised will not be caught and handled. User must
79 catch these by hand when calling `get_result`.
80 """
81
82
83 class PendingResult(object):
84 """A representation of a result that is not yet ready.
85
86 A user should not create a `PendingResult` instance by hand.
87
88 Methods
89 =======
90
91 * `get_result`
92 * `add_callback`
93
94 Properties
95 ==========
96 * `r`
97 """
98
99 def __init__(self, client, result_id):
100 """Create a PendingResult with a result_id and a client instance.
101
102 The client should implement `_getPendingResult(result_id, block)`.
103 """
104 self.client = client
105 self.result_id = result_id
106 self.called = False
107 self.raised = False
108 self.callbacks = []
109
110 def get_result(self, default=None, block=True):
111 """Get a result that is pending.
112
113 This method will connect to an IMultiEngine adapted controller
114 and see if the result is ready. If the action triggers an exception
115 raise it and record it. This method records the result/exception once it is
116 retrieved. Calling `get_result` again will get this cached result or will
117 re-raise the exception. The .r attribute is a property that calls
118 `get_result` with block=True.
119
120 :Parameters:
121 default
122 The value to return if the result is not ready.
123 block : boolean
124 Should I block for the result.
125
126 :Returns: The actual result or the default value.
127 """
128
129 if self.called:
130 if self.raised:
131 raise self.result[0], self.result[1], self.result[2]
132 else:
133 return self.result
134 try:
135 result = self.client.get_pending_deferred(self.result_id, block)
136 except error.ResultNotCompleted:
137 return default
138 except:
139 # Reraise other error, but first record them so they can be reraised
140 # later if .r or get_result is called again.
141 self.result = sys.exc_info()
142 self.called = True
143 self.raised = True
144 raise
145 else:
146 for cb in self.callbacks:
147 result = cb[0](result, *cb[1], **cb[2])
148 self.result = result
149 self.called = True
150 return result
151
152 def add_callback(self, f, *args, **kwargs):
153 """Add a callback that is called with the result.
154
155 If the original result is result, adding a callback will cause
156 f(result, *args, **kwargs) to be returned instead. If multiple
157 callbacks are registered, they are chained together: the result of
158 one is passed to the next and so on.
159
160 Unlike Twisted's Deferred object, there is no errback chain. Thus
161 any exception raised will not be caught and handled. User must
162 catch these by hand when calling `get_result`.
163 """
164 assert callable(f)
165 self.callbacks.append((f, args, kwargs))
166
167 def __cmp__(self, other):
168 if self.result_id < other.result_id:
169 return -1
170 else:
171 return 1
172
173 def _get_r(self):
174 return self.get_result(block=True)
175
176 r = property(_get_r)
177 """This property is a shortcut to a `get_result(block=True)`."""
178
179
180 #-------------------------------------------------------------------------------
181 # Pretty printing wrappers for certain lists
182 #-------------------------------------------------------------------------------
183
184 class ResultList(list):
185 """A subclass of list that pretty prints the output of `execute`/`get_result`."""
186
187 def __repr__(self):
188 output = []
189 blue = TermColors.Blue
190 normal = TermColors.Normal
191 red = TermColors.Red
192 green = TermColors.Green
193 output.append("<Results List>\n")
194 for cmd in self:
195 if isinstance(cmd, Failure):
196 output.append(cmd)
197 else:
198 target = cmd.get('id',None)
199 cmd_num = cmd.get('number',None)
200 cmd_stdin = cmd.get('input',{}).get('translated','No Input')
201 cmd_stdout = cmd.get('stdout', None)
202 cmd_stderr = cmd.get('stderr', None)
203 output.append("%s[%i]%s In [%i]:%s %s\n" % \
204 (green, target,
205 blue, cmd_num, normal, cmd_stdin))
206 if cmd_stdout:
207 output.append("%s[%i]%s Out[%i]:%s %s\n" % \
208 (green, target,
209 red, cmd_num, normal, cmd_stdout))
210 if cmd_stderr:
211 output.append("%s[%i]%s Err[%i]:\n%s %s" % \
212 (green, target,
213 red, cmd_num, normal, cmd_stderr))
214 return ''.join(output)
215
216
217 def wrapResultList(result):
218 """A function that wraps the output of `execute`/`get_result` -> `ResultList`."""
219 if len(result) == 0:
220 result = [result]
221 return ResultList(result)
222
223
224 class QueueStatusList(list):
225 """A subclass of list that pretty prints the output of `queue_status`."""
226
227 def __repr__(self):
228 output = []
229 output.append("<Queue Status List>\n")
230 for e in self:
231 output.append("Engine: %s\n" % repr(e[0]))
232 output.append(" Pending: %s\n" % repr(e[1]['pending']))
233 for q in e[1]['queue']:
234 output.append(" Command: %s\n" % repr(q))
235 return ''.join(output)
236
237
238 #-------------------------------------------------------------------------------
239 # InteractiveMultiEngineClient
240 #-------------------------------------------------------------------------------
241
242 class InteractiveMultiEngineClient(object):
243 """A mixin class that add a few methods to a multiengine client.
244
245 The methods in this mixin class are designed for interactive usage.
246 """
247
248 def activate(self):
249 """Make this `MultiEngineClient` active for parallel magic commands.
250
251 IPython has a magic command syntax to work with `MultiEngineClient` objects.
252 In a given IPython session there is a single active one. While
253 there can be many `MultiEngineClient` created and used by the user,
254 there is only one active one. The active `MultiEngineClient` is used whenever
255 the magic commands %px and %autopx are used.
256
257 The activate() method is called on a given `MultiEngineClient` to make it
258 active. Once this has been done, the magic commands can be used.
259 """
260
261 try:
262 __IPYTHON__.activeController = self
263 except NameError:
264 print "The IPython Controller magics only work within IPython."
265
266 def __setitem__(self, key, value):
267 """Add a dictionary interface for pushing/pulling.
268
269 This functions as a shorthand for `push`.
270
271 :Parameters:
272 key : str
273 What to call the remote object.
274 value : object
275 The local Python object to push.
276 """
277 targets, block = self._findTargetsAndBlock()
278 return self.push({key:value}, targets=targets, block=block)
279
280 def __getitem__(self, key):
281 """Add a dictionary interface for pushing/pulling.
282
283 This functions as a shorthand to `pull`.
284
285 :Parameters:
286 - `key`: A string representing the key.
287 """
288 if isinstance(key, str):
289 targets, block = self._findTargetsAndBlock()
290 return self.pull(key, targets=targets, block=block)
291 else:
292 raise TypeError("__getitem__ only takes strs")
293
294 def __len__(self):
295 """Return the number of available engines."""
296 return len(self.get_ids())
297
298 def parallelize(self, func, targets=None, block=None):
299 """Build a `ParallelFunction` object for functionName on engines.
300
301 The returned object will implement a parallel version of functionName
302 that takes a local sequence as its only argument and calls (in
303 parallel) functionName on each element of that sequence. The
304 `ParallelFunction` object has a `targets` attribute that controls
305 which engines the function is run on.
306
307 :Parameters:
308 targets : int, list or 'all'
309 The engine ids the action will apply to. Call `get_ids` to see
310 a list of currently available engines.
311 functionName : str
312 A Python string that names a callable defined on the engines.
313
314 :Returns: A `ParallelFunction` object.
315
316 Examples
317 ========
318
319 >>> psin = rc.parallelize('all','lambda x:sin(x)')
320 >>> psin(range(10000))
321 [0,2,4,9,25,36,...]
322 """
323 targets, block = self._findTargetsAndBlock(targets, block)
324 return ParallelFunction(func, self, targets, block)
325
326 #---------------------------------------------------------------------------
327 # Make this a context manager for with
328 #---------------------------------------------------------------------------
329
330 def findsource_file(self,f):
331 linecache.checkcache()
332 s = findsource(f.f_code)
333 lnum = f.f_lineno
334 wsource = s[0][f.f_lineno:]
335 return strip_whitespace(wsource)
336
337 def findsource_ipython(self,f):
338 from IPython import ipapi
339 self.ip = ipapi.get()
340 wsource = [l+'\n' for l in
341 self.ip.IP.input_hist_raw[-1].splitlines()[1:]]
342 return strip_whitespace(wsource)
343
344 def __enter__(self):
345 f = sys._getframe(1)
346 local_ns = f.f_locals
347 global_ns = f.f_globals
348 if f.f_code.co_filename == '<ipython console>':
349 s = self.findsource_ipython(f)
350 else:
351 s = self.findsource_file(f)
352
353 self._with_context_result = self.execute(s)
354
355 def __exit__ (self, etype, value, tb):
356 if issubclass(etype,error.StopLocalExecution):
357 return True
358
359
360 def remote():
361 m = 'Special exception to stop local execution of parallel code.'
362 raise error.StopLocalExecution(m)
363
364 def strip_whitespace(source):
365 # Expand tabs to avoid any confusion.
366 wsource = [l.expandtabs(4) for l in source]
367 # Detect the indentation level
368 done = False
369 for line in wsource:
370 if line.isspace():
371 continue
372 for col,char in enumerate(line):
373 if char != ' ':
374 done = True
375 break
376 if done:
377 break
378 # Now we know how much leading space there is in the code. Next, we
379 # extract up to the first line that has less indentation.
380 # WARNINGS: we skip comments that may be misindented, but we do NOT yet
381 # detect triple quoted strings that may have flush left text.
382 for lno,line in enumerate(wsource):
383 lead = line[:col]
384 if lead.isspace():
385 continue
386 else:
387 if not lead.lstrip().startswith('#'):
388 break
389 # The real 'with' source is up to lno
390 src_lines = [l[col:] for l in wsource[:lno+1]]
391
392 # Finally, check that the source's first non-comment line begins with the
393 # special call 'remote()'
394 for nline,line in enumerate(src_lines):
395 if line.isspace() or line.startswith('#'):
396 continue
397 if 'remote()' in line:
398 break
399 else:
400 raise ValueError('remote() call missing at the start of code')
401 src = ''.join(src_lines[nline+1:])
402 #print 'SRC:\n<<<<<<<>>>>>>>\n%s<<<<<>>>>>>' % src # dbg
403 return src
404
405
406 #-------------------------------------------------------------------------------
407 # The top-level MultiEngine client adaptor
408 #-------------------------------------------------------------------------------
409
410
411 class IFullBlockingMultiEngineClient(Interface):
412 pass
413
414
415 class FullBlockingMultiEngineClient(InteractiveMultiEngineClient):
416 """
417 A blocking client to the `IMultiEngine` controller interface.
418
419 This class allows users to use a set of engines for a parallel
420 computation through the `IMultiEngine` interface. In this interface,
421 each engine has a specific id (an int) that is used to refer to the
422 engine, run code on it, etc.
423 """
424
425 implements(IFullBlockingMultiEngineClient)
426
427 def __init__(self, smultiengine):
428 self.smultiengine = smultiengine
429 self.block = True
430 self.targets = 'all'
431
432 def _findBlock(self, block=None):
433 if block is None:
434 return self.block
435 else:
436 if block in (True, False):
437 return block
438 else:
439 raise ValueError("block must be True or False")
440
441 def _findTargets(self, targets=None):
442 if targets is None:
443 return self.targets
444 else:
445 if not isinstance(targets, (str,list,tuple,int)):
446 raise ValueError("targets must be a str, list, tuple or int")
447 return targets
448
449 def _findTargetsAndBlock(self, targets=None, block=None):
450 return self._findTargets(targets), self._findBlock(block)
451
452 def _blockFromThread(self, function, *args, **kwargs):
453 block = kwargs.get('block', None)
454 if block is None:
455 raise error.MissingBlockArgument("'block' keyword argument is missing")
456 result = blockingCallFromThread(function, *args, **kwargs)
457 if not block:
458 result = PendingResult(self, result)
459 return result
460
461 def get_pending_deferred(self, deferredID, block):
462 return blockingCallFromThread(self.smultiengine.get_pending_deferred, deferredID, block)
463
464 def barrier(self, pendingResults):
465 """Synchronize a set of `PendingResults`.
466
467 This method is a synchronization primitive that waits for a set of
468 `PendingResult` objects to complete. More specifically, barier does
469 the following.
470
471 * The `PendingResult`s are sorted by result_id.
472 * The `get_result` method is called for each `PendingResult` sequentially
473 with block=True.
474 * If a `PendingResult` gets a result that is an exception, it is
475 trapped and can be re-raised later by calling `get_result` again.
476 * The `PendingResult`s are flushed from the controller.
477
478 After barrier has been called on a `PendingResult`, its results can
479 be retrieved by calling `get_result` again or accesing the `r` attribute
480 of the instance.
481 """
482
483 # Convert to list for sorting and check class type
484 prList = list(pendingResults)
485 for pr in prList:
486 if not isinstance(pr, PendingResult):
487 raise error.NotAPendingResult("Objects passed to barrier must be PendingResult instances")
488
489 # Sort the PendingResults so they are in order
490 prList.sort()
491 # Block on each PendingResult object
492 for pr in prList:
493 try:
494 result = pr.get_result(block=True)
495 except Exception:
496 pass
497
498 def flush(self):
499 """
500 Clear all pending deferreds/results from the controller.
501
502 For each `PendingResult` that is created by this client, the controller
503 holds on to the result for that `PendingResult`. This can be a problem
504 if there are a large number of `PendingResult` objects that are created.
505
506 Once the result of the `PendingResult` has been retrieved, the result
507 is removed from the controller, but if a user doesn't get a result (
508 they just ignore the `PendingResult`) the result is kept forever on the
509 controller. This method allows the user to clear out all un-retrieved
510 results on the controller.
511 """
512 r = blockingCallFromThread(self.smultiengine.clear_pending_deferreds)
513 return r
514
515 clear_pending_results = flush
516
517 #---------------------------------------------------------------------------
518 # IEngineMultiplexer related methods
519 #---------------------------------------------------------------------------
520
521 def execute(self, lines, targets=None, block=None):
522 """
523 Execute code on a set of engines.
524
525 :Parameters:
526 lines : str
527 The Python code to execute as a string
528 targets : id or list of ids
529 The engine to use for the execution
530 block : boolean
531 If False, this method will return the actual result. If False,
532 a `PendingResult` is returned which can be used to get the result
533 at a later time.
534 """
535 targets, block = self._findTargetsAndBlock(targets, block)
536 result = blockingCallFromThread(self.smultiengine.execute, lines,
537 targets=targets, block=block)
538 if block:
539 result = ResultList(result)
540 else:
541 result = PendingResult(self, result)
542 result.add_callback(wrapResultList)
543 return result
544
545 def push(self, namespace, targets=None, block=None):
546 """
547 Push a dictionary of keys and values to engines namespace.
548
549 Each engine has a persistent namespace. This method is used to push
550 Python objects into that namespace.
551
552 The objects in the namespace must be pickleable.
553
554 :Parameters:
555 namespace : dict
556 A dict that contains Python objects to be injected into
557 the engine persistent namespace.
558 targets : id or list of ids
559 The engine to use for the execution
560 block : boolean
561 If False, this method will return the actual result. If False,
562 a `PendingResult` is returned which can be used to get the result
563 at a later time.
564 """
565 targets, block = self._findTargetsAndBlock(targets, block)
566 return self._blockFromThread(self.smultiengine.push, namespace,
567 targets=targets, block=block)
568
569 def pull(self, keys, targets=None, block=None):
570 """
571 Pull Python objects by key out of engines namespaces.
572
573 :Parameters:
574 keys : str or list of str
575 The names of the variables to be pulled
576 targets : id or list of ids
577 The engine to use for the execution
578 block : boolean
579 If False, this method will return the actual result. If False,
580 a `PendingResult` is returned which can be used to get the result
581 at a later time.
582 """
583 targets, block = self._findTargetsAndBlock(targets, block)
584 return self._blockFromThread(self.smultiengine.pull, keys, targets=targets, block=block)
585
586 def push_function(self, namespace, targets=None, block=None):
587 """
588 Push a Python function to an engine.
589
590 This method is used to push a Python function to an engine. This
591 method can then be used in code on the engines. Closures are not supported.
592
593 :Parameters:
594 namespace : dict
595 A dict whose values are the functions to be pushed. The keys give
596 that names that the function will appear as in the engines
597 namespace.
598 targets : id or list of ids
599 The engine to use for the execution
600 block : boolean
601 If False, this method will return the actual result. If False,
602 a `PendingResult` is returned which can be used to get the result
603 at a later time.
604 """
605 targets, block = self._findTargetsAndBlock(targets, block)
606 return self._blockFromThread(self.smultiengine.push_function, namespace, targets=targets, block=block)
607
608 def pull_function(self, keys, targets=None, block=None):
609 """
610 Pull a Python function from an engine.
611
612 This method is used to pull a Python function from an engine.
613 Closures are not supported.
614
615 :Parameters:
616 keys : str or list of str
617 The names of the functions to be pulled
618 targets : id or list of ids
619 The engine to use for the execution
620 block : boolean
621 If False, this method will return the actual result. If False,
622 a `PendingResult` is returned which can be used to get the result
623 at a later time.
624 """
625 targets, block = self._findTargetsAndBlock(targets, block)
626 return self._blockFromThread(self.smultiengine.pull_function, keys, targets=targets, block=block)
627
628 def push_serialized(self, namespace, targets=None, block=None):
629 targets, block = self._findTargetsAndBlock(targets, block)
630 return self._blockFromThread(self.smultiengine.push_serialized, namespace, targets=targets, block=block)
631
632 def pull_serialized(self, keys, targets=None, block=None):
633 targets, block = self._findTargetsAndBlock(targets, block)
634 return self._blockFromThread(self.smultiengine.pull_serialized, keys, targets=targets, block=block)
635
636 def get_result(self, i=None, targets=None, block=None):
637 """
638 Get a previous result.
639
640 When code is executed in an engine, a dict is created and returned. This
641 method retrieves that dict for previous commands.
642
643 :Parameters:
644 i : int
645 The number of the result to get
646 targets : id or list of ids
647 The engine to use for the execution
648 block : boolean
649 If False, this method will return the actual result. If False,
650 a `PendingResult` is returned which can be used to get the result
651 at a later time.
652 """
653 targets, block = self._findTargetsAndBlock(targets, block)
654 result = blockingCallFromThread(self.smultiengine.get_result, i, targets=targets, block=block)
655 if block:
656 result = ResultList(result)
657 else:
658 result = PendingResult(self, result)
659 result.add_callback(wrapResultList)
660 return result
661
662 def reset(self, targets=None, block=None):
663 """
664 Reset an engine.
665
666 This method clears out the namespace of an engine.
667
668 :Parameters:
669 targets : id or list of ids
670 The engine to use for the execution
671 block : boolean
672 If False, this method will return the actual result. If False,
673 a `PendingResult` is returned which can be used to get the result
674 at a later time.
675 """
676 targets, block = self._findTargetsAndBlock(targets, block)
677 return self._blockFromThread(self.smultiengine.reset, targets=targets, block=block)
678
679 def keys(self, targets=None, block=None):
680 """
681 Get a list of all the variables in an engine's namespace.
682
683 :Parameters:
684 targets : id or list of ids
685 The engine to use for the execution
686 block : boolean
687 If False, this method will return the actual result. If False,
688 a `PendingResult` is returned which can be used to get the result
689 at a later time.
690 """
691 targets, block = self._findTargetsAndBlock(targets, block)
692 return self._blockFromThread(self.smultiengine.keys, targets=targets, block=block)
693
694 def kill(self, controller=False, targets=None, block=None):
695 """
696 Kill the engines and controller.
697
698 This method is used to stop the engine and controller by calling
699 `reactor.stop`.
700
701 :Parameters:
702 controller : boolean
703 If True, kill the engines and controller. If False, just the
704 engines
705 targets : id or list of ids
706 The engine to use for the execution
707 block : boolean
708 If False, this method will return the actual result. If False,
709 a `PendingResult` is returned which can be used to get the result
710 at a later time.
711 """
712 targets, block = self._findTargetsAndBlock(targets, block)
713 return self._blockFromThread(self.smultiengine.kill, controller, targets=targets, block=block)
714
715 def clear_queue(self, targets=None, block=None):
716 """
717 Clear out the controller's queue for an engine.
718
719 The controller maintains a queue for each engine. This clear it out.
720
721 :Parameters:
722 targets : id or list of ids
723 The engine to use for the execution
724 block : boolean
725 If False, this method will return the actual result. If False,
726 a `PendingResult` is returned which can be used to get the result
727 at a later time.
728 """
729 targets, block = self._findTargetsAndBlock(targets, block)
730 return self._blockFromThread(self.smultiengine.clear_queue, targets=targets, block=block)
731
732 def queue_status(self, targets=None, block=None):
733 """
734 Get the status of an engines queue.
735
736 :Parameters:
737 targets : id or list of ids
738 The engine to use for the execution
739 block : boolean
740 If False, this method will return the actual result. If False,
741 a `PendingResult` is returned which can be used to get the result
742 at a later time.
743 """
744 targets, block = self._findTargetsAndBlock(targets, block)
745 return self._blockFromThread(self.smultiengine.queue_status, targets=targets, block=block)
746
747 def set_properties(self, properties, targets=None, block=None):
748 targets, block = self._findTargetsAndBlock(targets, block)
749 return self._blockFromThread(self.smultiengine.set_properties, properties, targets=targets, block=block)
750
751 def get_properties(self, keys=None, targets=None, block=None):
752 targets, block = self._findTargetsAndBlock(targets, block)
753 return self._blockFromThread(self.smultiengine.get_properties, keys, targets=targets, block=block)
754
755 def has_properties(self, keys, targets=None, block=None):
756 targets, block = self._findTargetsAndBlock(targets, block)
757 return self._blockFromThread(self.smultiengine.has_properties, keys, targets=targets, block=block)
758
759 def del_properties(self, keys, targets=None, block=None):
760 targets, block = self._findTargetsAndBlock(targets, block)
761 return self._blockFromThread(self.smultiengine.del_properties, keys, targets=targets, block=block)
762
763 def clear_properties(self, targets=None, block=None):
764 targets, block = self._findTargetsAndBlock(targets, block)
765 return self._blockFromThread(self.smultiengine.clear_properties, targets=targets, block=block)
766
767 #---------------------------------------------------------------------------
768 # IMultiEngine related methods
769 #---------------------------------------------------------------------------
770
771 def get_ids(self):
772 """
773 Returns the ids of currently registered engines.
774 """
775 result = blockingCallFromThread(self.smultiengine.get_ids)
776 return result
777
778 #---------------------------------------------------------------------------
779 # IMultiEngineCoordinator
780 #---------------------------------------------------------------------------
781
782 def scatter(self, key, seq, style='basic', flatten=False, targets=None, block=None):
783 """
784 Partition a Python sequence and send the partitions to a set of engines.
785 """
786 targets, block = self._findTargetsAndBlock(targets, block)
787 return self._blockFromThread(self.smultiengine.scatter, key, seq,
788 style, flatten, targets=targets, block=block)
789
790 def gather(self, key, style='basic', targets=None, block=None):
791 """
792 Gather a partitioned sequence on a set of engines as a single local seq.
793 """
794 targets, block = self._findTargetsAndBlock(targets, block)
795 return self._blockFromThread(self.smultiengine.gather, key, style,
796 targets=targets, block=block)
797
798 def map(self, func, seq, style='basic', targets=None, block=None):
799 """
800 A parallelized version of Python's builtin map
801 """
802 targets, block = self._findTargetsAndBlock(targets, block)
803 return self._blockFromThread(self.smultiengine.map, func, seq,
804 style, targets=targets, block=block)
805
806 #---------------------------------------------------------------------------
807 # IMultiEngineExtras
808 #---------------------------------------------------------------------------
809
810 def zip_pull(self, keys, targets=None, block=None):
811 targets, block = self._findTargetsAndBlock(targets, block)
812 return self._blockFromThread(self.smultiengine.zip_pull, keys,
813 targets=targets, block=block)
814
815 def run(self, filename, targets=None, block=None):
816 """
817 Run a Python code in a file on the engines.
818
819 :Parameters:
820 filename : str
821 The name of the local file to run
822 targets : id or list of ids
823 The engine to use for the execution
824 block : boolean
825 If False, this method will return the actual result. If False,
826 a `PendingResult` is returned which can be used to get the result
827 at a later time.
828 """
829 targets, block = self._findTargetsAndBlock(targets, block)
830 return self._blockFromThread(self.smultiengine.run, filename,
831 targets=targets, block=block)
832
833
834
835 components.registerAdapter(FullBlockingMultiEngineClient,
836 IFullSynchronousMultiEngine, IFullBlockingMultiEngineClient)
837
838
839
840
This diff has been collapsed as it changes many lines, (668 lines changed) Show them Hide them
@@ -0,0 +1,668 b''
1 # encoding: utf-8
2
3 """
4 Expose the multiengine controller over the Foolscap network protocol.
5 """
6
7 __docformat__ = "restructuredtext en"
8
9 #-------------------------------------------------------------------------------
10 # Copyright (C) 2008 The IPython Development Team
11 #
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
14 #-------------------------------------------------------------------------------
15
16 #-------------------------------------------------------------------------------
17 # Imports
18 #-------------------------------------------------------------------------------
19
20 import cPickle as pickle
21 from types import FunctionType
22
23 from zope.interface import Interface, implements
24 from twisted.internet import defer
25 from twisted.python import components, failure, log
26
27 from foolscap import Referenceable
28
29 from IPython.kernel import error
30 from IPython.kernel.util import printer
31 from IPython.kernel import map as Map
32 from IPython.kernel.twistedutil import gatherBoth
33 from IPython.kernel.multiengine import (MultiEngine,
34 IMultiEngine,
35 IFullSynchronousMultiEngine,
36 ISynchronousMultiEngine)
37 from IPython.kernel.multiengineclient import wrapResultList
38 from IPython.kernel.pendingdeferred import PendingDeferredManager
39 from IPython.kernel.pickleutil import (can, canDict,
40 canSequence, uncan, uncanDict, uncanSequence)
41
42 from IPython.kernel.clientinterfaces import (
43 IFCClientInterfaceProvider,
44 IBlockingClientAdaptor
45 )
46
47 # Needed to access the true globals from __main__.__dict__
48 import __main__
49
50 #-------------------------------------------------------------------------------
51 # The Controller side of things
52 #-------------------------------------------------------------------------------
53
54 def packageResult(wrappedMethod):
55
56 def wrappedPackageResult(self, *args, **kwargs):
57 d = wrappedMethod(self, *args, **kwargs)
58 d.addCallback(self.packageSuccess)
59 d.addErrback(self.packageFailure)
60 return d
61 return wrappedPackageResult
62
63
64 class IFCSynchronousMultiEngine(Interface):
65 """Foolscap interface to `ISynchronousMultiEngine`.
66
67 The methods in this interface are similar to those of
68 `ISynchronousMultiEngine`, but their arguments and return values are pickled
69 if they are not already simple Python types that can be send over XML-RPC.
70
71 See the documentation of `ISynchronousMultiEngine` and `IMultiEngine` for
72 documentation about the methods.
73
74 Most methods in this interface act like the `ISynchronousMultiEngine`
75 versions and can be called in blocking or non-blocking mode.
76 """
77 pass
78
79
80 class FCSynchronousMultiEngineFromMultiEngine(Referenceable):
81 """Adapt `IMultiEngine` -> `ISynchronousMultiEngine` -> `IFCSynchronousMultiEngine`.
82 """
83
84 implements(IFCSynchronousMultiEngine, IFCClientInterfaceProvider)
85
86 addSlash = True
87
88 def __init__(self, multiengine):
89 # Adapt the raw multiengine to `ISynchronousMultiEngine` before saving
90 # it. This allow this class to do two adaptation steps.
91 self.smultiengine = ISynchronousMultiEngine(multiengine)
92 self._deferredIDCallbacks = {}
93
94 #---------------------------------------------------------------------------
95 # Non interface methods
96 #---------------------------------------------------------------------------
97
98 def packageFailure(self, f):
99 f.cleanFailure()
100 return self.packageSuccess(f)
101
102 def packageSuccess(self, obj):
103 serial = pickle.dumps(obj, 2)
104 return serial
105
106 #---------------------------------------------------------------------------
107 # Things related to PendingDeferredManager
108 #---------------------------------------------------------------------------
109
110 @packageResult
111 def remote_get_pending_deferred(self, deferredID, block):
112 d = self.smultiengine.get_pending_deferred(deferredID, block)
113 try:
114 callback = self._deferredIDCallbacks.pop(deferredID)
115 except KeyError:
116 callback = None
117 if callback is not None:
118 d.addCallback(callback[0], *callback[1], **callback[2])
119 return d
120
121 @packageResult
122 def remote_clear_pending_deferreds(self):
123 return defer.maybeDeferred(self.smultiengine.clear_pending_deferreds)
124
125 def _addDeferredIDCallback(self, did, callback, *args, **kwargs):
126 self._deferredIDCallbacks[did] = (callback, args, kwargs)
127 return did
128
129 #---------------------------------------------------------------------------
130 # IEngineMultiplexer related methods
131 #---------------------------------------------------------------------------
132
133 @packageResult
134 def remote_execute(self, lines, targets, block):
135 return self.smultiengine.execute(lines, targets=targets, block=block)
136
137 @packageResult
138 def remote_push(self, binaryNS, targets, block):
139 try:
140 namespace = pickle.loads(binaryNS)
141 except:
142 d = defer.fail(failure.Failure())
143 else:
144 d = self.smultiengine.push(namespace, targets=targets, block=block)
145 return d
146
147 @packageResult
148 def remote_pull(self, keys, targets, block):
149 d = self.smultiengine.pull(keys, targets=targets, block=block)
150 return d
151
152 @packageResult
153 def remote_push_function(self, binaryNS, targets, block):
154 try:
155 namespace = pickle.loads(binaryNS)
156 except:
157 d = defer.fail(failure.Failure())
158 else:
159 namespace = uncanDict(namespace)
160 d = self.smultiengine.push_function(namespace, targets=targets, block=block)
161 return d
162
163 def _canMultipleKeys(self, result):
164 return [canSequence(r) for r in result]
165
166 @packageResult
167 def remote_pull_function(self, keys, targets, block):
168 def can_functions(r, keys):
169 if len(keys)==1 or isinstance(keys, str):
170 result = canSequence(r)
171 elif len(keys)>1:
172 result = [canSequence(s) for s in r]
173 return result
174 d = self.smultiengine.pull_function(keys, targets=targets, block=block)
175 if block:
176 d.addCallback(can_functions, keys)
177 else:
178 d.addCallback(lambda did: self._addDeferredIDCallback(did, can_functions, keys))
179 return d
180
181 @packageResult
182 def remote_push_serialized(self, binaryNS, targets, block):
183 try:
184 namespace = pickle.loads(binaryNS)
185 except:
186 d = defer.fail(failure.Failure())
187 else:
188 d = self.smultiengine.push_serialized(namespace, targets=targets, block=block)
189 return d
190
191 @packageResult
192 def remote_pull_serialized(self, keys, targets, block):
193 d = self.smultiengine.pull_serialized(keys, targets=targets, block=block)
194 return d
195
196 @packageResult
197 def remote_get_result(self, i, targets, block):
198 if i == 'None':
199 i = None
200 return self.smultiengine.get_result(i, targets=targets, block=block)
201
202 @packageResult
203 def remote_reset(self, targets, block):
204 return self.smultiengine.reset(targets=targets, block=block)
205
206 @packageResult
207 def remote_keys(self, targets, block):
208 return self.smultiengine.keys(targets=targets, block=block)
209
210 @packageResult
211 def remote_kill(self, controller, targets, block):
212 return self.smultiengine.kill(controller, targets=targets, block=block)
213
214 @packageResult
215 def remote_clear_queue(self, targets, block):
216 return self.smultiengine.clear_queue(targets=targets, block=block)
217
218 @packageResult
219 def remote_queue_status(self, targets, block):
220 return self.smultiengine.queue_status(targets=targets, block=block)
221
222 @packageResult
223 def remote_set_properties(self, binaryNS, targets, block):
224 try:
225 ns = pickle.loads(binaryNS)
226 except:
227 d = defer.fail(failure.Failure())
228 else:
229 d = self.smultiengine.set_properties(ns, targets=targets, block=block)
230 return d
231
232 @packageResult
233 def remote_get_properties(self, keys, targets, block):
234 if keys=='None':
235 keys=None
236 return self.smultiengine.get_properties(keys, targets=targets, block=block)
237
238 @packageResult
239 def remote_has_properties(self, keys, targets, block):
240 return self.smultiengine.has_properties(keys, targets=targets, block=block)
241
242 @packageResult
243 def remote_del_properties(self, keys, targets, block):
244 return self.smultiengine.del_properties(keys, targets=targets, block=block)
245
246 @packageResult
247 def remote_clear_properties(self, targets, block):
248 return self.smultiengine.clear_properties(targets=targets, block=block)
249
250 #---------------------------------------------------------------------------
251 # IMultiEngine related methods
252 #---------------------------------------------------------------------------
253
254 def remote_get_ids(self):
255 """Get the ids of the registered engines.
256
257 This method always blocks.
258 """
259 return self.smultiengine.get_ids()
260
261 #---------------------------------------------------------------------------
262 # IFCClientInterfaceProvider related methods
263 #---------------------------------------------------------------------------
264
265 def remote_get_client_name(self):
266 return 'IPython.kernel.multienginefc.FCFullSynchronousMultiEngineClient'
267
268
269 # The __init__ method of `FCMultiEngineFromMultiEngine` first adapts the
270 # `IMultiEngine` to `ISynchronousMultiEngine` so this is actually doing a
271 # two phase adaptation.
272 components.registerAdapter(FCSynchronousMultiEngineFromMultiEngine,
273 IMultiEngine, IFCSynchronousMultiEngine)
274
275
276 #-------------------------------------------------------------------------------
277 # The Client side of things
278 #-------------------------------------------------------------------------------
279
280
281 class FCFullSynchronousMultiEngineClient(object):
282
283 implements(IFullSynchronousMultiEngine, IBlockingClientAdaptor)
284
285 def __init__(self, remote_reference):
286 self.remote_reference = remote_reference
287 self._deferredIDCallbacks = {}
288 # This class manages some pending deferreds through this instance. This
289 # is required for methods like gather/scatter as it enables us to
290 # create our own pending deferreds for composite operations.
291 self.pdm = PendingDeferredManager()
292
293 #---------------------------------------------------------------------------
294 # Non interface methods
295 #---------------------------------------------------------------------------
296
297 def unpackage(self, r):
298 return pickle.loads(r)
299
300 #---------------------------------------------------------------------------
301 # Things related to PendingDeferredManager
302 #---------------------------------------------------------------------------
303
304 def get_pending_deferred(self, deferredID, block=True):
305
306 # Because we are managing some pending deferreds locally (through
307 # self.pdm) and some remotely (on the controller), we first try the
308 # local one and then the remote one.
309 if self.pdm.quick_has_id(deferredID):
310 d = self.pdm.get_pending_deferred(deferredID, block)
311 return d
312 else:
313 d = self.remote_reference.callRemote('get_pending_deferred', deferredID, block)
314 d.addCallback(self.unpackage)
315 try:
316 callback = self._deferredIDCallbacks.pop(deferredID)
317 except KeyError:
318 callback = None
319 if callback is not None:
320 d.addCallback(callback[0], *callback[1], **callback[2])
321 return d
322
323 def clear_pending_deferreds(self):
324
325 # This clear both the local (self.pdm) and remote pending deferreds
326 self.pdm.clear_pending_deferreds()
327 d2 = self.remote_reference.callRemote('clear_pending_deferreds')
328 d2.addCallback(self.unpackage)
329 return d2
330
331 def _addDeferredIDCallback(self, did, callback, *args, **kwargs):
332 self._deferredIDCallbacks[did] = (callback, args, kwargs)
333 return did
334
335 #---------------------------------------------------------------------------
336 # IEngineMultiplexer related methods
337 #---------------------------------------------------------------------------
338
339 def execute(self, lines, targets='all', block=True):
340 d = self.remote_reference.callRemote('execute', lines, targets, block)
341 d.addCallback(self.unpackage)
342 return d
343
344 def push(self, namespace, targets='all', block=True):
345 serial = pickle.dumps(namespace, 2)
346 d = self.remote_reference.callRemote('push', serial, targets, block)
347 d.addCallback(self.unpackage)
348 return d
349
350 def pull(self, keys, targets='all', block=True):
351 d = self.remote_reference.callRemote('pull', keys, targets, block)
352 d.addCallback(self.unpackage)
353 return d
354
355 def push_function(self, namespace, targets='all', block=True):
356 cannedNamespace = canDict(namespace)
357 serial = pickle.dumps(cannedNamespace, 2)
358 d = self.remote_reference.callRemote('push_function', serial, targets, block)
359 d.addCallback(self.unpackage)
360 return d
361
362 def pull_function(self, keys, targets='all', block=True):
363 def uncan_functions(r, keys):
364 if len(keys)==1 or isinstance(keys, str):
365 return uncanSequence(r)
366 elif len(keys)>1:
367 return [uncanSequence(s) for s in r]
368 d = self.remote_reference.callRemote('pull_function', keys, targets, block)
369 if block:
370 d.addCallback(self.unpackage)
371 d.addCallback(uncan_functions, keys)
372 else:
373 d.addCallback(self.unpackage)
374 d.addCallback(lambda did: self._addDeferredIDCallback(did, uncan_functions, keys))
375 return d
376
377 def push_serialized(self, namespace, targets='all', block=True):
378 cannedNamespace = canDict(namespace)
379 serial = pickle.dumps(cannedNamespace, 2)
380 d = self.remote_reference.callRemote('push_serialized', serial, targets, block)
381 d.addCallback(self.unpackage)
382 return d
383
384 def pull_serialized(self, keys, targets='all', block=True):
385 d = self.remote_reference.callRemote('pull_serialized', keys, targets, block)
386 d.addCallback(self.unpackage)
387 return d
388
389 def get_result(self, i=None, targets='all', block=True):
390 if i is None: # This is because None cannot be marshalled by xml-rpc
391 i = 'None'
392 d = self.remote_reference.callRemote('get_result', i, targets, block)
393 d.addCallback(self.unpackage)
394 return d
395
396 def reset(self, targets='all', block=True):
397 d = self.remote_reference.callRemote('reset', targets, block)
398 d.addCallback(self.unpackage)
399 return d
400
401 def keys(self, targets='all', block=True):
402 d = self.remote_reference.callRemote('keys', targets, block)
403 d.addCallback(self.unpackage)
404 return d
405
406 def kill(self, controller=False, targets='all', block=True):
407 d = self.remote_reference.callRemote('kill', controller, targets, block)
408 d.addCallback(self.unpackage)
409 return d
410
411 def clear_queue(self, targets='all', block=True):
412 d = self.remote_reference.callRemote('clear_queue', targets, block)
413 d.addCallback(self.unpackage)
414 return d
415
416 def queue_status(self, targets='all', block=True):
417 d = self.remote_reference.callRemote('queue_status', targets, block)
418 d.addCallback(self.unpackage)
419 return d
420
421 def set_properties(self, properties, targets='all', block=True):
422 serial = pickle.dumps(properties, 2)
423 d = self.remote_reference.callRemote('set_properties', serial, targets, block)
424 d.addCallback(self.unpackage)
425 return d
426
427 def get_properties(self, keys=None, targets='all', block=True):
428 if keys==None:
429 keys='None'
430 d = self.remote_reference.callRemote('get_properties', keys, targets, block)
431 d.addCallback(self.unpackage)
432 return d
433
434 def has_properties(self, keys, targets='all', block=True):
435 d = self.remote_reference.callRemote('has_properties', keys, targets, block)
436 d.addCallback(self.unpackage)
437 return d
438
439 def del_properties(self, keys, targets='all', block=True):
440 d = self.remote_reference.callRemote('del_properties', keys, targets, block)
441 d.addCallback(self.unpackage)
442 return d
443
444 def clear_properties(self, targets='all', block=True):
445 d = self.remote_reference.callRemote('clear_properties', targets, block)
446 d.addCallback(self.unpackage)
447 return d
448
449 #---------------------------------------------------------------------------
450 # IMultiEngine related methods
451 #---------------------------------------------------------------------------
452
453 def get_ids(self):
454 d = self.remote_reference.callRemote('get_ids')
455 return d
456
457 #---------------------------------------------------------------------------
458 # ISynchronousMultiEngineCoordinator related methods
459 #---------------------------------------------------------------------------
460
461 def _process_targets(self, targets):
462 def create_targets(ids):
463 if isinstance(targets, int):
464 engines = [targets]
465 elif targets=='all':
466 engines = ids
467 elif isinstance(targets, (list, tuple)):
468 engines = targets
469 for t in engines:
470 if not t in ids:
471 raise error.InvalidEngineID("engine with id %r does not exist"%t)
472 return engines
473
474 d = self.get_ids()
475 d.addCallback(create_targets)
476 return d
477
478 def scatter(self, key, seq, style='basic', flatten=False, targets='all', block=True):
479
480 # Note: scatter and gather handle pending deferreds locally through self.pdm.
481 # This enables us to collect a bunch fo deferred ids and make a secondary
482 # deferred id that corresponds to the entire group. This logic is extremely
483 # difficult to get right though.
484 def do_scatter(engines):
485 nEngines = len(engines)
486 mapClass = Map.styles[style]
487 mapObject = mapClass()
488 d_list = []
489 # Loop through and push to each engine in non-blocking mode.
490 # This returns a set of deferreds to deferred_ids
491 for index, engineid in enumerate(engines):
492 partition = mapObject.getPartition(seq, index, nEngines)
493 if flatten and len(partition) == 1:
494 d = self.push({key: partition[0]}, targets=engineid, block=False)
495 else:
496 d = self.push({key: partition}, targets=engineid, block=False)
497 d_list.append(d)
498 # Collect the deferred to deferred_ids
499 d = gatherBoth(d_list,
500 fireOnOneErrback=0,
501 consumeErrors=1,
502 logErrors=0)
503 # Now d has a list of deferred_ids or Failures coming
504 d.addCallback(error.collect_exceptions, 'scatter')
505 def process_did_list(did_list):
506 """Turn a list of deferred_ids into a final result or failure."""
507 new_d_list = [self.get_pending_deferred(did, True) for did in did_list]
508 final_d = gatherBoth(new_d_list,
509 fireOnOneErrback=0,
510 consumeErrors=1,
511 logErrors=0)
512 final_d.addCallback(error.collect_exceptions, 'scatter')
513 final_d.addCallback(lambda lop: [i[0] for i in lop])
514 return final_d
515 # Now, depending on block, we need to handle the list deferred_ids
516 # coming down the pipe diferently.
517 if block:
518 # If we are blocking register a callback that will transform the
519 # list of deferred_ids into the final result.
520 d.addCallback(process_did_list)
521 return d
522 else:
523 # Here we are going to use a _local_ PendingDeferredManager.
524 deferred_id = self.pdm.get_deferred_id()
525 # This is the deferred we will return to the user that will fire
526 # with the local deferred_id AFTER we have received the list of
527 # primary deferred_ids
528 d_to_return = defer.Deferred()
529 def do_it(did_list):
530 """Produce a deferred to the final result, but first fire the
531 deferred we will return to the user that has the local
532 deferred id."""
533 d_to_return.callback(deferred_id)
534 return process_did_list(did_list)
535 d.addCallback(do_it)
536 # Now save the deferred to the final result
537 self.pdm.save_pending_deferred(d, deferred_id)
538 return d_to_return
539
540 d = self._process_targets(targets)
541 d.addCallback(do_scatter)
542 return d
543
544 def gather(self, key, style='basic', targets='all', block=True):
545
546 # Note: scatter and gather handle pending deferreds locally through self.pdm.
547 # This enables us to collect a bunch fo deferred ids and make a secondary
548 # deferred id that corresponds to the entire group. This logic is extremely
549 # difficult to get right though.
550 def do_gather(engines):
551 nEngines = len(engines)
552 mapClass = Map.styles[style]
553 mapObject = mapClass()
554 d_list = []
555 # Loop through and push to each engine in non-blocking mode.
556 # This returns a set of deferreds to deferred_ids
557 for index, engineid in enumerate(engines):
558 d = self.pull(key, targets=engineid, block=False)
559 d_list.append(d)
560 # Collect the deferred to deferred_ids
561 d = gatherBoth(d_list,
562 fireOnOneErrback=0,
563 consumeErrors=1,
564 logErrors=0)
565 # Now d has a list of deferred_ids or Failures coming
566 d.addCallback(error.collect_exceptions, 'scatter')
567 def process_did_list(did_list):
568 """Turn a list of deferred_ids into a final result or failure."""
569 new_d_list = [self.get_pending_deferred(did, True) for did in did_list]
570 final_d = gatherBoth(new_d_list,
571 fireOnOneErrback=0,
572 consumeErrors=1,
573 logErrors=0)
574 final_d.addCallback(error.collect_exceptions, 'gather')
575 final_d.addCallback(lambda lop: [i[0] for i in lop])
576 final_d.addCallback(mapObject.joinPartitions)
577 return final_d
578 # Now, depending on block, we need to handle the list deferred_ids
579 # coming down the pipe diferently.
580 if block:
581 # If we are blocking register a callback that will transform the
582 # list of deferred_ids into the final result.
583 d.addCallback(process_did_list)
584 return d
585 else:
586 # Here we are going to use a _local_ PendingDeferredManager.
587 deferred_id = self.pdm.get_deferred_id()
588 # This is the deferred we will return to the user that will fire
589 # with the local deferred_id AFTER we have received the list of
590 # primary deferred_ids
591 d_to_return = defer.Deferred()
592 def do_it(did_list):
593 """Produce a deferred to the final result, but first fire the
594 deferred we will return to the user that has the local
595 deferred id."""
596 d_to_return.callback(deferred_id)
597 return process_did_list(did_list)
598 d.addCallback(do_it)
599 # Now save the deferred to the final result
600 self.pdm.save_pending_deferred(d, deferred_id)
601 return d_to_return
602
603 d = self._process_targets(targets)
604 d.addCallback(do_gather)
605 return d
606
607 def map(self, func, seq, style='basic', targets='all', block=True):
608 d_list = []
609 if isinstance(func, FunctionType):
610 d = self.push_function(dict(_ipython_map_func=func), targets=targets, block=False)
611 d.addCallback(lambda did: self.get_pending_deferred(did, True))
612 sourceToRun = '_ipython_map_seq_result = map(_ipython_map_func, _ipython_map_seq)'
613 elif isinstance(func, str):
614 d = defer.succeed(None)
615 sourceToRun = \
616 '_ipython_map_seq_result = map(%s, _ipython_map_seq)' % func
617 else:
618 raise TypeError("func must be a function or str")
619
620 d.addCallback(lambda _: self.scatter('_ipython_map_seq', seq, style, targets=targets))
621 d.addCallback(lambda _: self.execute(sourceToRun, targets=targets, block=False))
622 d.addCallback(lambda did: self.get_pending_deferred(did, True))
623 d.addCallback(lambda _: self.gather('_ipython_map_seq_result', style, targets=targets, block=block))
624 return d
625
626 #---------------------------------------------------------------------------
627 # ISynchronousMultiEngineExtras related methods
628 #---------------------------------------------------------------------------
629
630 def _transformPullResult(self, pushResult, multitargets, lenKeys):
631 if not multitargets:
632 result = pushResult[0]
633 elif lenKeys > 1:
634 result = zip(*pushResult)
635 elif lenKeys is 1:
636 result = list(pushResult)
637 return result
638
639 def zip_pull(self, keys, targets='all', block=True):
640 multitargets = not isinstance(targets, int) and len(targets) > 1
641 lenKeys = len(keys)
642 d = self.pull(keys, targets=targets, block=block)
643 if block:
644 d.addCallback(self._transformPullResult, multitargets, lenKeys)
645 else:
646 d.addCallback(lambda did: self._addDeferredIDCallback(did, self._transformPullResult, multitargets, lenKeys))
647 return d
648
649 def run(self, fname, targets='all', block=True):
650 fileobj = open(fname,'r')
651 source = fileobj.read()
652 fileobj.close()
653 # if the compilation blows, we get a local error right away
654 try:
655 code = compile(source,fname,'exec')
656 except:
657 return defer.fail(failure.Failure())
658 # Now run the code
659 d = self.execute(source, targets=targets, block=block)
660 return d
661
662 #---------------------------------------------------------------------------
663 # IBlockingClientAdaptor related methods
664 #---------------------------------------------------------------------------
665
666 def adapt_to_blocking_client(self):
667 from IPython.kernel.multiengineclient import IFullBlockingMultiEngineClient
668 return IFullBlockingMultiEngineClient(self)
@@ -0,0 +1,163 b''
1 # encoding: utf-8
2 # -*- test-case-name: IPython.kernel.test.test_newserialized -*-
3
4 """Refactored serialization classes and interfaces."""
5
6 __docformat__ = "restructuredtext en"
7
8 #-------------------------------------------------------------------------------
9 # Copyright (C) 2008 The IPython Development Team
10 #
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
13 #-------------------------------------------------------------------------------
14
15 #-------------------------------------------------------------------------------
16 # Imports
17 #-------------------------------------------------------------------------------
18
19 import cPickle as pickle
20
21 from zope.interface import Interface, implements
22 from twisted.python import components
23
24 try:
25 import numpy
26 except ImportError:
27 pass
28
29 from IPython.kernel.error import SerializationError
30
31 class ISerialized(Interface):
32
33 def getData():
34 """"""
35
36 def getDataSize(units=10.0**6):
37 """"""
38
39 def getTypeDescriptor():
40 """"""
41
42 def getMetadata():
43 """"""
44
45
46 class IUnSerialized(Interface):
47
48 def getObject():
49 """"""
50
51 class Serialized(object):
52
53 implements(ISerialized)
54
55 def __init__(self, data, typeDescriptor, metadata={}):
56 self.data = data
57 self.typeDescriptor = typeDescriptor
58 self.metadata = metadata
59
60 def getData(self):
61 return self.data
62
63 def getDataSize(self, units=10.0**6):
64 return len(self.data)/units
65
66 def getTypeDescriptor(self):
67 return self.typeDescriptor
68
69 def getMetadata(self):
70 return self.metadata
71
72
73 class UnSerialized(object):
74
75 implements(IUnSerialized)
76
77 def __init__(self, obj):
78 self.obj = obj
79
80 def getObject(self):
81 return self.obj
82
83
84 class SerializeIt(object):
85
86 implements(ISerialized)
87
88 def __init__(self, unSerialized):
89 self.data = None
90 self.obj = unSerialized.getObject()
91 if globals().has_key('numpy'):
92 if isinstance(self.obj, numpy.ndarray):
93 if len(self.obj) == 0: # length 0 arrays can't be reconstructed
94 raise SerializationError("You cannot send a length 0 array")
95 self.obj = numpy.ascontiguousarray(self.obj, dtype=None)
96 self.typeDescriptor = 'ndarray'
97 self.metadata = {'shape':self.obj.shape,
98 'dtype':self.obj.dtype.str}
99 else:
100 self.typeDescriptor = 'pickle'
101 self.metadata = {}
102 else:
103 self.typeDescriptor = 'pickle'
104 self.metadata = {}
105 self._generateData()
106
107 def _generateData(self):
108 if self.typeDescriptor == 'ndarray':
109 self.data = numpy.getbuffer(self.obj)
110 elif self.typeDescriptor == 'pickle':
111 self.data = pickle.dumps(self.obj, 2)
112 else:
113 raise SerializationError("Really wierd serialization error.")
114 del self.obj
115
116 def getData(self):
117 return self.data
118
119 def getDataSize(self, units=10.0**6):
120 return len(self.data)/units
121
122 def getTypeDescriptor(self):
123 return self.typeDescriptor
124
125 def getMetadata(self):
126 return self.metadata
127
128
129 class UnSerializeIt(UnSerialized):
130
131 implements(IUnSerialized)
132
133 def __init__(self, serialized):
134 self.serialized = serialized
135
136 def getObject(self):
137 typeDescriptor = self.serialized.getTypeDescriptor()
138 if globals().has_key('numpy'):
139 if typeDescriptor == 'ndarray':
140 result = numpy.frombuffer(self.serialized.getData(), dtype = self.serialized.metadata['dtype'])
141 result.shape = self.serialized.metadata['shape']
142 # This is a hack to make the array writable. We are working with
143 # the numpy folks to address this issue.
144 result = result.copy()
145 elif typeDescriptor == 'pickle':
146 result = pickle.loads(self.serialized.getData())
147 else:
148 raise SerializationError("Really wierd serialization error.")
149 elif typeDescriptor == 'pickle':
150 result = pickle.loads(self.serialized.getData())
151 else:
152 raise SerializationError("Really wierd serialization error.")
153 return result
154
155 components.registerAdapter(UnSerializeIt, ISerialized, IUnSerialized)
156
157 components.registerAdapter(SerializeIt, IUnSerialized, ISerialized)
158
159 def serialize(obj):
160 return ISerialized(UnSerialized(obj))
161
162 def unserialize(serialized):
163 return IUnSerialized(serialized).getObject()
@@ -0,0 +1,32 b''
1 # encoding: utf-8
2
3 """A parallelized function that does scatter/execute/gather."""
4
5 __docformat__ = "restructuredtext en"
6
7 #-------------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
13
14 #-------------------------------------------------------------------------------
15 # Imports
16 #-------------------------------------------------------------------------------
17
18 from types import FunctionType
19
20 class ParallelFunction:
21 """A function that operates in parallel on sequences."""
22 def __init__(self, func, multiengine, targets, block):
23 """Create a `ParallelFunction`.
24 """
25 assert isinstance(func, (str, FunctionType)), "func must be a fuction or str"
26 self.func = func
27 self.multiengine = multiengine
28 self.targets = targets
29 self.block = block
30
31 def __call__(self, sequence):
32 return self.multiengine.map(self.func, sequence, targets=self.targets, block=self.block) No newline at end of file
@@ -0,0 +1,34 b''
1 # encoding: utf-8
2
3 """Low level configuration for Twisted's Perspective Broker protocol."""
4
5 __docformat__ = "restructuredtext en"
6
7 #-------------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
13
14 #-------------------------------------------------------------------------------
15 # Imports
16 #-------------------------------------------------------------------------------
17
18 from twisted.spread import banana
19
20
21 #-------------------------------------------------------------------------------
22 # This is where you configure the size limit of the banana protocol that
23 # PB uses. WARNING, this only works if you are NOT using cBanana, which is
24 # faster than banana.py.
25 #-------------------------------------------------------------------------------
26
27
28
29 #banana.SIZE_LIMIT = 640*1024 # The default of 640 kB
30 banana.SIZE_LIMIT = 10*1024*1024 # 10 MB
31 #banana.SIZE_LIMIT = 50*1024*1024 # 50 MB
32
33 # This sets the size of chunks used when paging is used.
34 CHUNK_SIZE = 64*1024
@@ -0,0 +1,93 b''
1 # encoding: utf-8
2
3 """Utilities for PB using modules."""
4
5 __docformat__ = "restructuredtext en"
6
7 #-------------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
13
14 #-------------------------------------------------------------------------------
15 # Imports
16 #-------------------------------------------------------------------------------
17
18 import cPickle as pickle
19
20 from twisted.python.failure import Failure
21 from twisted.python import failure
22 import threading, sys
23
24 from IPython.kernel import pbconfig
25 from IPython.kernel.error import PBMessageSizeError, UnpickleableException
26
27
28 #-------------------------------------------------------------------------------
29 # The actual utilities
30 #-------------------------------------------------------------------------------
31
32 def packageFailure(f):
33 """Clean and pickle a failure preappending the string FAILURE:"""
34
35 f.cleanFailure()
36 # This is sometimes helpful in debugging
37 #f.raiseException()
38 try:
39 pString = pickle.dumps(f, 2)
40 except pickle.PicklingError:
41 # Certain types of exceptions are not pickleable, for instance ones
42 # from Boost.Python. We try to wrap them in something that is
43 f.type = UnpickleableException
44 f.value = UnpickleableException(str(f.type) + ": " + str(f.value))
45 pString = pickle.dumps(f, 2)
46 return 'FAILURE:' + pString
47
48 def unpackageFailure(r):
49 """
50 See if a returned value is a pickled Failure object.
51
52 To distinguish between general pickled objects and pickled Failures, the
53 other side should prepend the string FAILURE: to any pickled Failure.
54 """
55 if isinstance(r, str):
56 if r.startswith('FAILURE:'):
57 try:
58 result = pickle.loads(r[8:])
59 except pickle.PickleError:
60 return failure.Failure( \
61 FailureUnpickleable("Could not unpickle failure."))
62 else:
63 return result
64 return r
65
66 def checkMessageSize(m, info):
67 """Check string m to see if it violates banana.SIZE_LIMIT.
68
69 This should be used on the client side of things for push, scatter
70 and push_serialized and on the other end for pull, gather and pull_serialized.
71
72 :Parameters:
73 `m` : string
74 Message whose size will be checked.
75 `info` : string
76 String describing what object the message refers to.
77
78 :Exceptions:
79 - `PBMessageSizeError`: Raised in the message is > banana.SIZE_LIMIT
80
81 :returns: The original message or a Failure wrapping a PBMessageSizeError
82 """
83
84 if len(m) > pbconfig.banana.SIZE_LIMIT:
85 s = """Objects too big to transfer:
86 Names: %s
87 Actual Size (kB): %d
88 SIZE_LIMIT (kB): %d
89 * SIZE_LIMIT can be set in kernel.pbconfig""" \
90 % (info, len(m)/1024, pbconfig.banana.SIZE_LIMIT/1024)
91 return Failure(PBMessageSizeError(s))
92 else:
93 return m No newline at end of file
@@ -0,0 +1,178 b''
1 # encoding: utf-8
2 # -*- test-case-name: IPython.kernel.test.test_pendingdeferred -*-
3
4 """Classes to manage pending Deferreds.
5
6 A pending deferred is a deferred that may or may not have fired. This module
7 is useful for taking a class whose methods return deferreds and wrapping it to
8 provide API that keeps track of those deferreds for later retrieval. See the
9 tests for examples of its usage.
10 """
11
12 __docformat__ = "restructuredtext en"
13
14 #-------------------------------------------------------------------------------
15 # Copyright (C) 2008 The IPython Development Team
16 #
17 # Distributed under the terms of the BSD License. The full license is in
18 # the file COPYING, distributed as part of this software.
19 #-------------------------------------------------------------------------------
20
21 #-------------------------------------------------------------------------------
22 # Imports
23 #-------------------------------------------------------------------------------
24
25 from twisted.application import service
26 from twisted.internet import defer, reactor
27 from twisted.python import log, components, failure
28 from zope.interface import Interface, implements, Attribute
29
30 from IPython.kernel.twistedutil import gatherBoth
31 from IPython.kernel import error
32 from IPython.external import guid
33 from IPython.tools import growl
34
35 class PendingDeferredManager(object):
36 """A class to track pending deferreds.
37
38 To track a pending deferred, the user of this class must first
39 get a deferredID by calling `get_next_deferred_id`. Then the user
40 calls `save_pending_deferred` passing that id and the deferred to
41 be tracked. To later retrieve it, the user calls
42 `get_pending_deferred` passing the id.
43 """
44
45 def __init__(self):
46 """Manage pending deferreds."""
47
48 self.results = {} # Populated when results are ready
49 self.deferred_ids = [] # List of deferred ids I am managing
50 self.deferreds_to_callback = {} # dict of lists of deferreds to callback
51
52 def get_deferred_id(self):
53 return guid.generate()
54
55 def quick_has_id(self, deferred_id):
56 return deferred_id in self.deferred_ids
57
58 def _save_result(self, result, deferred_id):
59 if self.quick_has_id(deferred_id):
60 self.results[deferred_id] = result
61 self._trigger_callbacks(deferred_id)
62
63 def _trigger_callbacks(self, deferred_id):
64 # Go through and call the waiting callbacks
65 result = self.results.get(deferred_id)
66 if result is not None: # Only trigger if there is a result
67 try:
68 d = self.deferreds_to_callback.pop(deferred_id)
69 except KeyError:
70 d = None
71 if d is not None:
72 if isinstance(result, failure.Failure):
73 d.errback(result)
74 else:
75 d.callback(result)
76 self.delete_pending_deferred(deferred_id)
77
78 def save_pending_deferred(self, d, deferred_id=None):
79 """Save the result of a deferred for later retrieval.
80
81 This works even if the deferred has not fired.
82
83 Only callbacks and errbacks applied to d before this method
84 is called will be called no the final result.
85 """
86 if deferred_id is None:
87 deferred_id = self.get_deferred_id()
88 self.deferred_ids.append(deferred_id)
89 d.addBoth(self._save_result, deferred_id)
90 return deferred_id
91
92 def _protected_del(self, key, container):
93 try:
94 del container[key]
95 except Exception:
96 pass
97
98 def delete_pending_deferred(self, deferred_id):
99 """Remove a deferred I am tracking and add a null Errback.
100
101 :Parameters:
102 deferredID : str
103 The id of a deferred that I am tracking.
104 """
105 if self.quick_has_id(deferred_id):
106 # First go through a errback any deferreds that are still waiting
107 d = self.deferreds_to_callback.get(deferred_id)
108 if d is not None:
109 d.errback(failure.Failure(error.AbortedPendingDeferredError("pending deferred has been deleted: %r"%deferred_id)))
110 # Now delete all references to this deferred_id
111 ind = self.deferred_ids.index(deferred_id)
112 self._protected_del(ind, self.deferred_ids)
113 self._protected_del(deferred_id, self.deferreds_to_callback)
114 self._protected_del(deferred_id, self.results)
115 else:
116 raise error.InvalidDeferredID('invalid deferred_id: %r' % deferred_id)
117
118 def clear_pending_deferreds(self):
119 """Remove all the deferreds I am tracking."""
120 for did in self.deferred_ids:
121 self.delete_pending_deferred(did)
122
123 def _delete_and_pass_through(self, r, deferred_id):
124 self.delete_pending_deferred(deferred_id)
125 return r
126
127 def get_pending_deferred(self, deferred_id, block):
128 if not self.quick_has_id(deferred_id) or self.deferreds_to_callback.get(deferred_id) is not None:
129 return defer.fail(failure.Failure(error.InvalidDeferredID('invalid deferred_id: %r' + deferred_id)))
130 result = self.results.get(deferred_id)
131 if result is not None:
132 self.delete_pending_deferred(deferred_id)
133 if isinstance(result, failure.Failure):
134 return defer.fail(result)
135 else:
136 return defer.succeed(result)
137 else: # Result is not ready
138 if block:
139 d = defer.Deferred()
140 self.deferreds_to_callback[deferred_id] = d
141 return d
142 else:
143 return defer.fail(failure.Failure(error.ResultNotCompleted("result not completed: %r" % deferred_id)))
144
145 def two_phase(wrapped_method):
146 """Wrap methods that return a deferred into a two phase process.
147
148 This transforms::
149
150 foo(arg1, arg2, ...) -> foo(arg1, arg2,...,block=True).
151
152 The wrapped method will then return a deferred to a deferred id. This will
153 only work on method of classes that inherit from `PendingDeferredManager`,
154 as that class provides an API for
155
156 block is a boolean to determine if we should use the two phase process or
157 just simply call the wrapped method. At this point block does not have a
158 default and it probably won't.
159 """
160
161 def wrapper_two_phase(pdm, *args, **kwargs):
162 try:
163 block = kwargs.pop('block')
164 except KeyError:
165 block = True # The default if not specified
166 if block:
167 return wrapped_method(pdm, *args, **kwargs)
168 else:
169 d = wrapped_method(pdm, *args, **kwargs)
170 deferred_id=pdm.save_pending_deferred(d)
171 return defer.succeed(deferred_id)
172
173 return wrapper_two_phase
174
175
176
177
178
@@ -0,0 +1,83 b''
1 # encoding: utf-8
2
3 """Pickle related utilities."""
4
5 __docformat__ = "restructuredtext en"
6
7 #-------------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
13
14 #-------------------------------------------------------------------------------
15 # Imports
16 #-------------------------------------------------------------------------------
17
18 from types import FunctionType
19 from twisted.python import log
20
21 class CannedObject(object):
22 pass
23
24 class CannedFunction(CannedObject):
25
26 def __init__(self, f):
27 self._checkType(f)
28 self.code = f.func_code
29
30 def _checkType(self, obj):
31 assert isinstance(obj, FunctionType), "Not a function type"
32
33 def getFunction(self, g=None):
34 if g is None:
35 g = globals()
36 newFunc = FunctionType(self.code, g)
37 return newFunc
38
39 def can(obj):
40 if isinstance(obj, FunctionType):
41 return CannedFunction(obj)
42 else:
43 return obj
44
45 def canDict(obj):
46 if isinstance(obj, dict):
47 for k, v in obj.iteritems():
48 obj[k] = can(v)
49 return obj
50 else:
51 return obj
52
53 def canSequence(obj):
54 if isinstance(obj, (list, tuple)):
55 t = type(obj)
56 return t([can(i) for i in obj])
57 else:
58 return obj
59
60 def uncan(obj, g=None):
61 if isinstance(obj, CannedFunction):
62 return obj.getFunction(g)
63 else:
64 return obj
65
66 def uncanDict(obj, g=None):
67 if isinstance(obj, dict):
68 for k, v in obj.iteritems():
69 obj[k] = uncan(v,g)
70 return obj
71 else:
72 return obj
73
74 def uncanSequence(obj, g=None):
75 if isinstance(obj, (list, tuple)):
76 t = type(obj)
77 return t([uncan(i,g) for i in obj])
78 else:
79 return obj
80
81
82 def rebindFunctionGlobals(f, glbls):
83 return FunctionType(f.func_code, glbls)
@@ -0,0 +1,16 b''
1 # encoding: utf-8
2
3 """"""
4
5 __docformat__ = "restructuredtext en"
6
7 #-------------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
13
14 #-------------------------------------------------------------------------------
15 # Imports
16 #------------------------------------------------------------------------------- No newline at end of file
@@ -0,0 +1,22 b''
1 #!/usr/bin/env python
2 # encoding: utf-8
3
4 """ipcluster script"""
5
6 __docformat__ = "restructuredtext en"
7
8 #-------------------------------------------------------------------------------
9 # Copyright (C) 2008 The IPython Development Team
10 #
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
13 #-------------------------------------------------------------------------------
14
15 #-------------------------------------------------------------------------------
16 # Imports
17 #-------------------------------------------------------------------------------
18
19 if __name__ == '__main__':
20 from IPython.kernel.scripts import ipcluster
21 ipcluster.main()
22
@@ -0,0 +1,323 b''
1 #!/usr/bin/env python
2 # encoding: utf-8
3
4 """Start an IPython cluster conveniently, either locally or remotely.
5
6 Basic usage
7 -----------
8
9 For local operation, the simplest mode of usage is:
10
11 %prog -n N
12
13 where N is the number of engines you want started.
14
15 For remote operation, you must call it with a cluster description file:
16
17 %prog -f clusterfile.py
18
19 The cluster file is a normal Python script which gets run via execfile(). You
20 can have arbitrary logic in it, but all that matters is that at the end of the
21 execution, it declares the variables 'controller', 'engines', and optionally
22 'sshx'. See the accompanying examples for details on what these variables must
23 contain.
24
25
26 Notes
27 -----
28
29 WARNING: this code is still UNFINISHED and EXPERIMENTAL! It is incomplete,
30 some listed options are not really implemented, and all of its interfaces are
31 subject to change.
32
33 When operating over SSH for a remote cluster, this program relies on the
34 existence of a particular script called 'sshx'. This script must live in the
35 target systems where you'll be running your controller and engines, and is
36 needed to configure your PATH and PYTHONPATH variables for further execution of
37 python code at the other end of an SSH connection. The script can be as simple
38 as:
39
40 #!/bin/sh
41 . $HOME/.bashrc
42 "$@"
43
44 which is the default one provided by IPython. You can modify this or provide
45 your own. Since it's quite likely that for different clusters you may need
46 this script to configure things differently or that it may live in different
47 locations, its full path can be set in the same file where you define the
48 cluster setup. IPython's order of evaluation for this variable is the
49 following:
50
51 a) Internal default: 'sshx'. This only works if it is in the default system
52 path which SSH sets up in non-interactive mode.
53
54 b) Environment variable: if $IPYTHON_SSHX is defined, this overrides the
55 internal default.
56
57 c) Variable 'sshx' in the cluster configuration file: finally, this will
58 override the previous two values.
59
60 This code is Unix-only, with precious little hope of any of this ever working
61 under Windows, since we need SSH from the ground up, we background processes,
62 etc. Ports of this functionality to Windows are welcome.
63
64
65 Call summary
66 ------------
67
68 %prog [options]
69 """
70
71 __docformat__ = "restructuredtext en"
72
73 #-------------------------------------------------------------------------------
74 # Copyright (C) 2008 The IPython Development Team
75 #
76 # Distributed under the terms of the BSD License. The full license is in
77 # the file COPYING, distributed as part of this software.
78 #-------------------------------------------------------------------------------
79
80 #-------------------------------------------------------------------------------
81 # Stdlib imports
82 #-------------------------------------------------------------------------------
83
84 import os
85 import signal
86 import sys
87 import time
88
89 from optparse import OptionParser
90 from subprocess import Popen,call
91
92 #---------------------------------------------------------------------------
93 # IPython imports
94 #---------------------------------------------------------------------------
95 from IPython.tools import utils
96 from IPython.config import cutils
97
98 #---------------------------------------------------------------------------
99 # Normal code begins
100 #---------------------------------------------------------------------------
101
102 def parse_args():
103 """Parse command line and return opts,args."""
104
105 parser = OptionParser(usage=__doc__)
106 newopt = parser.add_option # shorthand
107
108 newopt("--controller-port", type="int", dest="controllerport",
109 help="the TCP port the controller is listening on")
110
111 newopt("--controller-ip", type="string", dest="controllerip",
112 help="the TCP ip address of the controller")
113
114 newopt("-n", "--num", type="int", dest="n",default=2,
115 help="the number of engines to start")
116
117 newopt("--engine-port", type="int", dest="engineport",
118 help="the TCP port the controller will listen on for engine "
119 "connections")
120
121 newopt("--engine-ip", type="string", dest="engineip",
122 help="the TCP ip address the controller will listen on "
123 "for engine connections")
124
125 newopt("--mpi", type="string", dest="mpi",
126 help="use mpi with package: for instance --mpi=mpi4py")
127
128 newopt("-l", "--logfile", type="string", dest="logfile",
129 help="log file name")
130
131 newopt('-f','--cluster-file',dest='clusterfile',
132 help='file describing a remote cluster')
133
134 return parser.parse_args()
135
136 def numAlive(controller,engines):
137 """Return the number of processes still alive."""
138 retcodes = [controller.poll()] + \
139 [e.poll() for e in engines]
140 return retcodes.count(None)
141
142 stop = lambda pid: os.kill(pid,signal.SIGINT)
143 kill = lambda pid: os.kill(pid,signal.SIGTERM)
144
145 def cleanup(clean,controller,engines):
146 """Stop the controller and engines with the given cleanup method."""
147
148 for e in engines:
149 if e.poll() is None:
150 print 'Stopping engine, pid',e.pid
151 clean(e.pid)
152 if controller.poll() is None:
153 print 'Stopping controller, pid',controller.pid
154 clean(controller.pid)
155
156
157 def ensureDir(path):
158 """Ensure a directory exists or raise an exception."""
159 if not os.path.isdir(path):
160 os.makedirs(path)
161
162
163 def startMsg(control_host,control_port=10105):
164 """Print a startup message"""
165 print
166 print 'Your cluster is up and running.'
167 print
168 print 'For interactive use, you can make a MultiEngineClient with:'
169 print
170 print 'from IPython.kernel import client'
171 print "mec = client.MultiEngineClient((%r,%s))" % \
172 (control_host,control_port)
173 print
174 print 'You can then cleanly stop the cluster from IPython using:'
175 print
176 print 'mec.kill(controller=True)'
177 print
178
179
180 def clusterLocal(opt,arg):
181 """Start a cluster on the local machine."""
182
183 # Store all logs inside the ipython directory
184 ipdir = cutils.get_ipython_dir()
185 pjoin = os.path.join
186
187 logfile = opt.logfile
188 if logfile is None:
189 logdir_base = pjoin(ipdir,'log')
190 ensureDir(logdir_base)
191 logfile = pjoin(logdir_base,'ipcluster-')
192
193 print 'Starting controller:',
194 controller = Popen(['ipcontroller','--logfile',logfile])
195 print 'Controller PID:',controller.pid
196
197 print 'Starting engines: ',
198 time.sleep(3)
199
200 englogfile = '%s%s-' % (logfile,controller.pid)
201 mpi = opt.mpi
202 if mpi: # start with mpi - killing the engines with sigterm will not work if you do this
203 engines = [Popen(['mpirun', '-np', str(opt.n), 'ipengine', '--mpi', mpi, '--logfile',englogfile])]
204 else: # do what we would normally do
205 engines = [ Popen(['ipengine','--logfile',englogfile])
206 for i in range(opt.n) ]
207 eids = [e.pid for e in engines]
208 print 'Engines PIDs: ',eids
209 print 'Log files: %s*' % englogfile
210
211 proc_ids = eids + [controller.pid]
212 procs = engines + [controller]
213
214 grpid = os.getpgrp()
215 try:
216 startMsg('127.0.0.1')
217 print 'You can also hit Ctrl-C to stop it, or use from the cmd line:'
218 print
219 print 'kill -INT',grpid
220 print
221 try:
222 while True:
223 time.sleep(5)
224 except:
225 pass
226 finally:
227 print 'Stopping cluster. Cleaning up...'
228 cleanup(stop,controller,engines)
229 for i in range(4):
230 time.sleep(i+2)
231 nZombies = numAlive(controller,engines)
232 if nZombies== 0:
233 print 'OK: All processes cleaned up.'
234 break
235 print 'Trying again, %d processes did not stop...' % nZombies
236 cleanup(kill,controller,engines)
237 if numAlive(controller,engines) == 0:
238 print 'OK: All processes cleaned up.'
239 break
240 else:
241 print '*'*75
242 print 'ERROR: could not kill some processes, try to do it',
243 print 'manually.'
244 zombies = []
245 if controller.returncode is None:
246 print 'Controller is alive: pid =',controller.pid
247 zombies.append(controller.pid)
248 liveEngines = [ e for e in engines if e.returncode is None ]
249 for e in liveEngines:
250 print 'Engine is alive: pid =',e.pid
251 zombies.append(e.pid)
252 print
253 print 'Zombie summary:',' '.join(map(str,zombies))
254
255 def clusterRemote(opt,arg):
256 """Start a remote cluster over SSH"""
257
258 # Load the remote cluster configuration
259 clConfig = {}
260 execfile(opt.clusterfile,clConfig)
261 contConfig = clConfig['controller']
262 engConfig = clConfig['engines']
263 # Determine where to find sshx:
264 sshx = clConfig.get('sshx',os.environ.get('IPYTHON_SSHX','sshx'))
265
266 # Store all logs inside the ipython directory
267 ipdir = cutils.get_ipython_dir()
268 pjoin = os.path.join
269
270 logfile = opt.logfile
271 if logfile is None:
272 logdir_base = pjoin(ipdir,'log')
273 ensureDir(logdir_base)
274 logfile = pjoin(logdir_base,'ipcluster')
275
276 # Append this script's PID to the logfile name always
277 logfile = '%s-%s' % (logfile,os.getpid())
278
279 print 'Starting controller:'
280 # Controller data:
281 xsys = os.system
282
283 contHost = contConfig['host']
284 contLog = '%s-con-%s-' % (logfile,contHost)
285 cmd = "ssh %s '%s' 'ipcontroller --logfile %s' &" % \
286 (contHost,sshx,contLog)
287 #print 'cmd:<%s>' % cmd # dbg
288 xsys(cmd)
289 time.sleep(2)
290
291 print 'Starting engines: '
292 for engineHost,engineData in engConfig.iteritems():
293 if isinstance(engineData,int):
294 numEngines = engineData
295 else:
296 raise NotImplementedError('port configuration not finished for engines')
297
298 print 'Sarting %d engines on %s' % (numEngines,engineHost)
299 engLog = '%s-eng-%s-' % (logfile,engineHost)
300 for i in range(numEngines):
301 cmd = "ssh %s '%s' 'ipengine --controller-ip %s --logfile %s' &" % \
302 (engineHost,sshx,contHost,engLog)
303 #print 'cmd:<%s>' % cmd # dbg
304 xsys(cmd)
305 # Wait after each host a little bit
306 time.sleep(1)
307
308 startMsg(contConfig['host'])
309
310 def main():
311 """Main driver for the two big options: local or remote cluster."""
312
313 opt,arg = parse_args()
314
315 clusterfile = opt.clusterfile
316 if clusterfile:
317 clusterRemote(opt,arg)
318 else:
319 clusterLocal(opt,arg)
320
321
322 if __name__=='__main__':
323 main()
@@ -0,0 +1,20 b''
1 #!/usr/bin/env python
2 # encoding: utf-8
3
4 __docformat__ = "restructuredtext en"
5
6 #-------------------------------------------------------------------------------
7 # Copyright (C) 2008 The IPython Development Team
8 #
9 # Distributed under the terms of the BSD License. The full license is in
10 # the file COPYING, distributed as part of this software.
11 #-------------------------------------------------------------------------------
12
13 #-------------------------------------------------------------------------------
14 # Imports
15 #-------------------------------------------------------------------------------
16
17 if __name__ == '__main__':
18 from IPython.kernel.scripts import ipcontroller
19 ipcontroller.main()
20
@@ -0,0 +1,366 b''
1 #!/usr/bin/env python
2 # encoding: utf-8
3
4 """The IPython controller."""
5
6 __docformat__ = "restructuredtext en"
7
8 #-------------------------------------------------------------------------------
9 # Copyright (C) 2008 The IPython Development Team
10 #
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
13 #-------------------------------------------------------------------------------
14
15 #-------------------------------------------------------------------------------
16 # Imports
17 #-------------------------------------------------------------------------------
18
19 # Python looks for an empty string at the beginning of sys.path to enable
20 # importing from the cwd.
21 import sys
22 sys.path.insert(0, '')
23
24 import sys, time, os
25 from optparse import OptionParser
26
27 from twisted.application import internet, service
28 from twisted.internet import reactor, error, defer
29 from twisted.python import log
30
31 from IPython.kernel.fcutil import Tub, UnauthenticatedTub, have_crypto
32
33 # from IPython.tools import growl
34 # growl.start("IPython1 Controller")
35
36 from IPython.kernel.error import SecurityError
37 from IPython.kernel import controllerservice
38 from IPython.kernel.fcutil import check_furl_file_security
39
40 from IPython.kernel.config import config_manager as kernel_config_manager
41 from IPython.config.cutils import import_item
42
43
44 #-------------------------------------------------------------------------------
45 # Code
46 #-------------------------------------------------------------------------------
47
48 def make_tub(ip, port, secure, cert_file):
49 """
50 Create a listening tub given an ip, port, and cert_file location.
51
52 :Parameters:
53 ip : str
54 The ip address that the tub should listen on. Empty means all
55 port : int
56 The port that the tub should listen on. A value of 0 means
57 pick a random port
58 secure: boolean
59 Will the connection be secure (in the foolscap sense)
60 cert_file:
61 A filename of a file to be used for theSSL certificate
62 """
63 if secure:
64 if have_crypto:
65 tub = Tub(certFile=cert_file)
66 else:
67 raise SecurityError("OpenSSL is not available, so we can't run in secure mode, aborting")
68 else:
69 tub = UnauthenticatedTub()
70
71 # Set the strport based on the ip and port and start listening
72 if ip == '':
73 strport = "tcp:%i" % port
74 else:
75 strport = "tcp:%i:interface=%s" % (port, ip)
76 listener = tub.listenOn(strport)
77
78 return tub, listener
79
80 def make_client_service(controller_service, config):
81 """
82 Create a service that will listen for clients.
83
84 This service is simply a `foolscap.Tub` instance that has a set of Referenceables
85 registered with it.
86 """
87
88 # Now create the foolscap tub
89 ip = config['controller']['client_tub']['ip']
90 port = config['controller']['client_tub'].as_int('port')
91 location = config['controller']['client_tub']['location']
92 secure = config['controller']['client_tub']['secure']
93 cert_file = config['controller']['client_tub']['cert_file']
94 client_tub, client_listener = make_tub(ip, port, secure, cert_file)
95
96 # Set the location in the trivial case of localhost
97 if ip == 'localhost' or ip == '127.0.0.1':
98 location = "127.0.0.1"
99
100 if not secure:
101 log.msg("WARNING: you are running the controller with no client security")
102
103 def set_location_and_register():
104 """Set the location for the tub and return a deferred."""
105
106 def register(empty, ref, furl_file):
107 client_tub.registerReference(ref, furlFile=furl_file)
108
109 if location == '':
110 d = client_tub.setLocationAutomatically()
111 else:
112 d = defer.maybeDeferred(client_tub.setLocation, "%s:%i" % (location, client_listener.getPortnum()))
113
114 for ciname, ci in config['controller']['controller_interfaces'].iteritems():
115 log.msg("Adapting Controller to interface: %s" % ciname)
116 furl_file = ci['furl_file']
117 log.msg("Saving furl for interface [%s] to file: %s" % (ciname, furl_file))
118 check_furl_file_security(furl_file, secure)
119 adapted_controller = import_item(ci['controller_interface'])(controller_service)
120 d.addCallback(register, import_item(ci['fc_interface'])(adapted_controller),
121 furl_file=ci['furl_file'])
122
123 reactor.callWhenRunning(set_location_and_register)
124 return client_tub
125
126
127 def make_engine_service(controller_service, config):
128 """
129 Create a service that will listen for engines.
130
131 This service is simply a `foolscap.Tub` instance that has a set of Referenceables
132 registered with it.
133 """
134
135 # Now create the foolscap tub
136 ip = config['controller']['engine_tub']['ip']
137 port = config['controller']['engine_tub'].as_int('port')
138 location = config['controller']['engine_tub']['location']
139 secure = config['controller']['engine_tub']['secure']
140 cert_file = config['controller']['engine_tub']['cert_file']
141 engine_tub, engine_listener = make_tub(ip, port, secure, cert_file)
142
143 # Set the location in the trivial case of localhost
144 if ip == 'localhost' or ip == '127.0.0.1':
145 location = "127.0.0.1"
146
147 if not secure:
148 log.msg("WARNING: you are running the controller with no engine security")
149
150 def set_location_and_register():
151 """Set the location for the tub and return a deferred."""
152
153 def register(empty, ref, furl_file):
154 engine_tub.registerReference(ref, furlFile=furl_file)
155
156 if location == '':
157 d = engine_tub.setLocationAutomatically()
158 else:
159 d = defer.maybeDeferred(engine_tub.setLocation, "%s:%i" % (location, engine_listener.getPortnum()))
160
161 furl_file = config['controller']['engine_furl_file']
162 engine_fc_interface = import_item(config['controller']['engine_fc_interface'])
163 log.msg("Saving furl for the engine to file: %s" % furl_file)
164 check_furl_file_security(furl_file, secure)
165 fc_controller = engine_fc_interface(controller_service)
166 d.addCallback(register, fc_controller, furl_file=furl_file)
167
168 reactor.callWhenRunning(set_location_and_register)
169 return engine_tub
170
171 def start_controller():
172 """
173 Start the controller by creating the service hierarchy and starting the reactor.
174
175 This method does the following:
176
177 * It starts the controller logging
178 * In execute an import statement for the controller
179 * It creates 2 `foolscap.Tub` instances for the client and the engines
180 and registers `foolscap.Referenceables` with the tubs to expose the
181 controller to engines and clients.
182 """
183 config = kernel_config_manager.get_config_obj()
184
185 # Start logging
186 logfile = config['controller']['logfile']
187 if logfile:
188 logfile = logfile + str(os.getpid()) + '.log'
189 try:
190 openLogFile = open(logfile, 'w')
191 except:
192 openLogFile = sys.stdout
193 else:
194 openLogFile = sys.stdout
195 log.startLogging(openLogFile)
196
197 # Execute any user defined import statements
198 cis = config['controller']['import_statement']
199 if cis:
200 try:
201 exec cis in globals(), locals()
202 except:
203 log.msg("Error running import_statement: %s" % cis)
204
205 # Create the service hierarchy
206 main_service = service.MultiService()
207 # The controller service
208 controller_service = controllerservice.ControllerService()
209 controller_service.setServiceParent(main_service)
210 # The client tub and all its refereceables
211 client_service = make_client_service(controller_service, config)
212 client_service.setServiceParent(main_service)
213 # The engine tub
214 engine_service = make_engine_service(controller_service, config)
215 engine_service.setServiceParent(main_service)
216 # Start the controller service and set things running
217 main_service.startService()
218 reactor.run()
219
220 def init_config():
221 """
222 Initialize the configuration using default and command line options.
223 """
224
225 parser = OptionParser()
226
227 # Client related options
228 parser.add_option(
229 "--client-ip",
230 type="string",
231 dest="client_ip",
232 help="the IP address or hostname the controller will listen on for client connections"
233 )
234 parser.add_option(
235 "--client-port",
236 type="int",
237 dest="client_port",
238 help="the port the controller will listen on for client connections"
239 )
240 parser.add_option(
241 '--client-location',
242 type="string",
243 dest="client_location",
244 help="hostname or ip for clients to connect to"
245 )
246 parser.add_option(
247 "-x",
248 action="store_false",
249 dest="client_secure",
250 help="turn off all client security"
251 )
252 parser.add_option(
253 '--client-cert-file',
254 type="string",
255 dest="client_cert_file",
256 help="file to store the client SSL certificate"
257 )
258 parser.add_option(
259 '--task-furl-file',
260 type="string",
261 dest="task_furl_file",
262 help="file to store the FURL for task clients to connect with"
263 )
264 parser.add_option(
265 '--multiengine-furl-file',
266 type="string",
267 dest="multiengine_furl_file",
268 help="file to store the FURL for multiengine clients to connect with"
269 )
270 # Engine related options
271 parser.add_option(
272 "--engine-ip",
273 type="string",
274 dest="engine_ip",
275 help="the IP address or hostname the controller will listen on for engine connections"
276 )
277 parser.add_option(
278 "--engine-port",
279 type="int",
280 dest="engine_port",
281 help="the port the controller will listen on for engine connections"
282 )
283 parser.add_option(
284 '--engine-location',
285 type="string",
286 dest="engine_location",
287 help="hostname or ip for engines to connect to"
288 )
289 parser.add_option(
290 "-y",
291 action="store_false",
292 dest="engine_secure",
293 help="turn off all engine security"
294 )
295 parser.add_option(
296 '--engine-cert-file',
297 type="string",
298 dest="engine_cert_file",
299 help="file to store the engine SSL certificate"
300 )
301 parser.add_option(
302 '--engine-furl-file',
303 type="string",
304 dest="engine_furl_file",
305 help="file to store the FURL for engines to connect with"
306 )
307 parser.add_option(
308 "-l", "--logfile",
309 type="string",
310 dest="logfile",
311 help="log file name (default is stdout)"
312 )
313 parser.add_option(
314 "--ipythondir",
315 type="string",
316 dest="ipythondir",
317 help="look for config files and profiles in this directory"
318 )
319
320 (options, args) = parser.parse_args()
321
322 kernel_config_manager.update_config_obj_from_default_file(options.ipythondir)
323 config = kernel_config_manager.get_config_obj()
324
325 # Update with command line options
326 if options.client_ip is not None:
327 config['controller']['client_tub']['ip'] = options.client_ip
328 if options.client_port is not None:
329 config['controller']['client_tub']['port'] = options.client_port
330 if options.client_location is not None:
331 config['controller']['client_tub']['location'] = options.client_location
332 if options.client_secure is not None:
333 config['controller']['client_tub']['secure'] = options.client_secure
334 if options.client_cert_file is not None:
335 config['controller']['client_tub']['cert_file'] = options.client_cert_file
336 if options.task_furl_file is not None:
337 config['controller']['controller_interfaces']['task']['furl_file'] = options.task_furl_file
338 if options.multiengine_furl_file is not None:
339 config['controller']['controller_interfaces']['multiengine']['furl_file'] = options.multiengine_furl_file
340 if options.engine_ip is not None:
341 config['controller']['engine_tub']['ip'] = options.engine_ip
342 if options.engine_port is not None:
343 config['controller']['engine_tub']['port'] = options.engine_port
344 if options.engine_location is not None:
345 config['controller']['engine_tub']['location'] = options.engine_location
346 if options.engine_secure is not None:
347 config['controller']['engine_tub']['secure'] = options.engine_secure
348 if options.engine_cert_file is not None:
349 config['controller']['engine_tub']['cert_file'] = options.engine_cert_file
350 if options.engine_furl_file is not None:
351 config['controller']['engine_furl_file'] = options.engine_furl_file
352
353 if options.logfile is not None:
354 config['controller']['logfile'] = options.logfile
355
356 kernel_config_manager.update_config_obj(config)
357
358 def main():
359 """
360 After creating the configuration information, start the controller.
361 """
362 init_config()
363 start_controller()
364
365 if __name__ == "__main__":
366 main()
@@ -0,0 +1,20 b''
1 #!/usr/bin/env python
2 # encoding: utf-8
3
4 __docformat__ = "restructuredtext en"
5
6 #-------------------------------------------------------------------------------
7 # Copyright (C) 2008 The IPython Development Team
8 #
9 # Distributed under the terms of the BSD License. The full license is in
10 # the file COPYING, distributed as part of this software.
11 #-------------------------------------------------------------------------------
12
13 #-------------------------------------------------------------------------------
14 # Imports
15 #-------------------------------------------------------------------------------
16
17 if __name__ == '__main__':
18 from IPython.kernel.scripts import ipengine
19 ipengine.main()
20
@@ -0,0 +1,169 b''
1 #!/usr/bin/env python
2 # encoding: utf-8
3
4 """Start the IPython Engine."""
5
6 __docformat__ = "restructuredtext en"
7
8 #-------------------------------------------------------------------------------
9 # Copyright (C) 2008 The IPython Development Team
10 #
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
13 #-------------------------------------------------------------------------------
14
15 #-------------------------------------------------------------------------------
16 # Imports
17 #-------------------------------------------------------------------------------
18
19 # Python looks for an empty string at the beginning of sys.path to enable
20 # importing from the cwd.
21 import sys
22 sys.path.insert(0, '')
23
24 import sys, os
25 from optparse import OptionParser
26
27 from twisted.application import service
28 from twisted.internet import reactor
29 from twisted.python import log
30
31 from IPython.kernel.fcutil import Tub, UnauthenticatedTub
32
33 from IPython.kernel.core.config import config_manager as core_config_manager
34 from IPython.config.cutils import import_item
35 from IPython.kernel.engineservice import EngineService
36 from IPython.kernel.config import config_manager as kernel_config_manager
37 from IPython.kernel.engineconnector import EngineConnector
38
39
40 #-------------------------------------------------------------------------------
41 # Code
42 #-------------------------------------------------------------------------------
43
44 def start_engine():
45 """
46 Start the engine, by creating it and starting the Twisted reactor.
47
48 This method does:
49
50 * If it exists, runs the `mpi_import_statement` to call `MPI_Init`
51 * Starts the engine logging
52 * Creates an IPython shell and wraps it in an `EngineService`
53 * Creates a `foolscap.Tub` to use in connecting to a controller.
54 * Uses the tub and the `EngineService` along with a Foolscap URL
55 (or FURL) to connect to the controller and register the engine
56 with the controller
57 """
58 kernel_config = kernel_config_manager.get_config_obj()
59 core_config = core_config_manager.get_config_obj()
60
61 # Execute the mpi import statement that needs to call MPI_Init
62 mpikey = kernel_config['mpi']['default']
63 mpi_import_statement = kernel_config['mpi'].get(mpikey, None)
64 if mpi_import_statement is not None:
65 try:
66 exec mpi_import_statement in locals(), globals()
67 except:
68 mpi = None
69 else:
70 mpi = None
71
72 # Start logging
73 logfile = kernel_config['engine']['logfile']
74 if logfile:
75 logfile = logfile + str(os.getpid()) + '.log'
76 try:
77 openLogFile = open(logfile, 'w')
78 except:
79 openLogFile = sys.stdout
80 else:
81 openLogFile = sys.stdout
82 log.startLogging(openLogFile)
83
84 # Create the underlying shell class and EngineService
85 shell_class = import_item(core_config['shell']['shell_class'])
86 engine_service = EngineService(shell_class, mpi=mpi)
87 shell_import_statement = core_config['shell']['import_statement']
88 if shell_import_statement:
89 try:
90 engine_service.execute(shell_import_statement)
91 except:
92 log.msg("Error running import_statement: %s" % sis)
93
94 # Create the service hierarchy
95 main_service = service.MultiService()
96 engine_service.setServiceParent(main_service)
97 tub_service = Tub()
98 tub_service.setServiceParent(main_service)
99 # This needs to be called before the connection is initiated
100 main_service.startService()
101
102 # This initiates the connection to the controller and calls
103 # register_engine to tell the controller we are ready to do work
104 engine_connector = EngineConnector(tub_service)
105 furl_file = kernel_config['engine']['furl_file']
106 d = engine_connector.connect_to_controller(engine_service, furl_file)
107 d.addErrback(lambda _: reactor.stop())
108
109 reactor.run()
110
111
112 def init_config():
113 """
114 Initialize the configuration using default and command line options.
115 """
116
117 parser = OptionParser()
118
119 parser.add_option(
120 "--furl-file",
121 type="string",
122 dest="furl_file",
123 help="The filename containing the FURL of the controller"
124 )
125 parser.add_option(
126 "--mpi",
127 type="string",
128 dest="mpi",
129 help="How to enable MPI (mpi4py, pytrilinos, or empty string to disable)"
130 )
131 parser.add_option(
132 "-l",
133 "--logfile",
134 type="string",
135 dest="logfile",
136 help="log file name (default is stdout)"
137 )
138 parser.add_option(
139 "--ipythondir",
140 type="string",
141 dest="ipythondir",
142 help="look for config files and profiles in this directory"
143 )
144
145 (options, args) = parser.parse_args()
146
147 kernel_config_manager.update_config_obj_from_default_file(options.ipythondir)
148 core_config_manager.update_config_obj_from_default_file(options.ipythondir)
149
150 kernel_config = kernel_config_manager.get_config_obj()
151 # Now override with command line options
152 if options.furl_file is not None:
153 kernel_config['engine']['furl_file'] = options.furl_file
154 if options.logfile is not None:
155 kernel_config['engine']['logfile'] = options.logfile
156 if options.mpi is not None:
157 kernel_config['mpi']['default'] = options.mpi
158
159
160 def main():
161 """
162 After creating the configuration information, start the engine.
163 """
164 init_config()
165 start_engine()
166
167
168 if __name__ == "__main__":
169 main() No newline at end of file
This diff has been collapsed as it changes many lines, (799 lines changed) Show them Hide them
@@ -0,0 +1,799 b''
1 # encoding: utf-8
2 # -*- test-case-name: IPython.kernel.tests.test_task -*-
3
4 """Task farming representation of the ControllerService."""
5
6 __docformat__ = "restructuredtext en"
7
8 #-------------------------------------------------------------------------------
9 # Copyright (C) 2008 The IPython Development Team
10 #
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
13 #-------------------------------------------------------------------------------
14
15 #-------------------------------------------------------------------------------
16 # Imports
17 #-------------------------------------------------------------------------------
18
19 import copy, time
20 from types import FunctionType as function
21
22 import zope.interface as zi, string
23 from twisted.internet import defer, reactor
24 from twisted.python import components, log, failure
25
26 # from IPython.genutils import time
27
28 from IPython.kernel import engineservice as es, error
29 from IPython.kernel import controllerservice as cs
30 from IPython.kernel.twistedutil import gatherBoth, DeferredList
31
32 from IPython.kernel.pickleutil import can,uncan, CannedFunction
33
34 def canTask(task):
35 t = copy.copy(task)
36 t.depend = can(t.depend)
37 if t.recovery_task:
38 t.recovery_task = canTask(t.recovery_task)
39 return t
40
41 def uncanTask(task):
42 t = copy.copy(task)
43 t.depend = uncan(t.depend)
44 if t.recovery_task and t.recovery_task is not task:
45 t.recovery_task = uncanTask(t.recovery_task)
46 return t
47
48 time_format = '%Y/%m/%d %H:%M:%S'
49
50 class Task(object):
51 """Our representation of a task for the `TaskController` interface.
52
53 The user should create instances of this class to represent a task that
54 needs to be done.
55
56 :Parameters:
57 expression : str
58 A str that is valid python code that is the task.
59 pull : str or list of str
60 The names of objects to be pulled as results. If not specified,
61 will return {'result', None}
62 push : dict
63 A dict of objects to be pushed into the engines namespace before
64 execution of the expression.
65 clear_before : boolean
66 Should the engine's namespace be cleared before the task is run.
67 Default=False.
68 clear_after : boolean
69 Should the engine's namespace be cleared after the task is run.
70 Default=False.
71 retries : int
72 The number of times to resumbit the task if it fails. Default=0.
73 recovery_task : Task
74 This is the Task to be run when the task has exhausted its retries
75 Default=None.
76 depend : bool function(properties)
77 This is the dependency function for the Task, which determines
78 whether a task can be run on a Worker. `depend` is called with
79 one argument, the worker's properties dict, and should return
80 True if the worker meets the dependencies or False if it does
81 not.
82 Default=None - run on any worker
83 options : dict
84 Any other keyword options for more elaborate uses of tasks
85
86 Examples
87 --------
88
89 >>> t = Task('dostuff(args)')
90 >>> t = Task('a=5', pull='a')
91 >>> t = Task('a=5\nb=4', pull=['a','b'])
92 >>> t = Task('os.kill(os.getpid(),9)', retries=100) # this is a bad idea
93 # A dependency case:
94 >>> def hasMPI(props):
95 ... return props.get('mpi') is not None
96 >>> t = Task('mpi.send(blah,blah)', depend = hasMPI)
97 """
98
99 def __init__(self, expression, pull=None, push=None,
100 clear_before=False, clear_after=False, retries=0,
101 recovery_task=None, depend=None, **options):
102 self.expression = expression
103 if isinstance(pull, str):
104 self.pull = [pull]
105 else:
106 self.pull = pull
107 self.push = push
108 self.clear_before = clear_before
109 self.clear_after = clear_after
110 self.retries=retries
111 self.recovery_task = recovery_task
112 self.depend = depend
113 self.options = options
114 self.taskid = None
115
116 class ResultNS:
117 """The result namespace object for use in TaskResult objects as tr.ns.
118 It builds an object from a dictionary, such that it has attributes
119 according to the key,value pairs of the dictionary.
120
121 This works by calling setattr on ALL key,value pairs in the dict. If a user
122 chooses to overwrite the `__repr__` or `__getattr__` attributes, they can.
123 This can be a bad idea, as it may corrupt standard behavior of the
124 ns object.
125
126 Example
127 --------
128
129 >>> ns = ResultNS({'a':17,'foo':range(3)})
130 >>> print ns
131 NS{'a':17,'foo':range(3)}
132 >>> ns.a
133 17
134 >>> ns['foo']
135 [0,1,2]
136 """
137 def __init__(self, dikt):
138 for k,v in dikt.iteritems():
139 setattr(self,k,v)
140
141 def __repr__(self):
142 l = dir(self)
143 d = {}
144 for k in l:
145 # do not print private objects
146 if k[:2] != '__' and k[-2:] != '__':
147 d[k] = getattr(self, k)
148 return "NS"+repr(d)
149
150 def __getitem__(self, key):
151 return getattr(self, key)
152
153 class TaskResult(object):
154 """
155 An object for returning task results.
156
157 This object encapsulates the results of a task. On task
158 success it will have a keys attribute that will have a list
159 of the variables that have been pulled back. These variables
160 are accessible as attributes of this class as well. On
161 success the failure attribute will be None.
162
163 In task failure, keys will be empty, but failure will contain
164 the failure object that encapsulates the remote exception.
165 One can also simply call the raiseException() method of
166 this class to re-raise any remote exception in the local
167 session.
168
169 The TaskResult has a .ns member, which is a property for access
170 to the results. If the Task had pull=['a', 'b'], then the
171 Task Result will have attributes tr.ns.a, tr.ns.b for those values.
172 Accessing tr.ns will raise the remote failure if the task failed.
173
174 The engineid attribute should have the engineid of the engine
175 that ran the task. But, because engines can come and go in
176 the ipython task system, the engineid may not continue to be
177 valid or accurate.
178
179 The taskid attribute simply gives the taskid that the task
180 is tracked under.
181 """
182 taskid = None
183
184 def _getNS(self):
185 if isinstance(self.failure, failure.Failure):
186 return self.failure.raiseException()
187 else:
188 return self._ns
189
190 def _setNS(self, v):
191 raise Exception("I am protected!")
192
193 ns = property(_getNS, _setNS)
194
195 def __init__(self, results, engineid):
196 self.engineid = engineid
197 if isinstance(results, failure.Failure):
198 self.failure = results
199 self.results = {}
200 else:
201 self.results = results
202 self.failure = None
203
204 self._ns = ResultNS(self.results)
205
206 self.keys = self.results.keys()
207
208 def __repr__(self):
209 if self.failure is not None:
210 contents = self.failure
211 else:
212 contents = self.results
213 return "TaskResult[ID:%r]:%r"%(self.taskid, contents)
214
215 def __getitem__(self, key):
216 if self.failure is not None:
217 self.raiseException()
218 return self.results[key]
219
220 def raiseException(self):
221 """Re-raise any remote exceptions in the local python session."""
222 if self.failure is not None:
223 self.failure.raiseException()
224
225
226 class IWorker(zi.Interface):
227 """The Basic Worker Interface.
228
229 A worked is a representation of an Engine that is ready to run tasks.
230 """
231
232 zi.Attribute("workerid", "the id of the worker")
233
234 def run(task):
235 """Run task in worker's namespace.
236
237 :Parameters:
238 task : a `Task` object
239
240 :Returns: `Deferred` to a `TaskResult` object.
241 """
242
243
244 class WorkerFromQueuedEngine(object):
245 """Adapt an `IQueuedEngine` to an `IWorker` object"""
246 zi.implements(IWorker)
247
248 def __init__(self, qe):
249 self.queuedEngine = qe
250 self.workerid = None
251
252 def _get_properties(self):
253 return self.queuedEngine.properties
254
255 properties = property(_get_properties, lambda self, _:None)
256
257 def run(self, task):
258 """Run task in worker's namespace.
259
260 :Parameters:
261 task : a `Task` object
262
263 :Returns: `Deferred` to a `TaskResult` object.
264 """
265 if task.clear_before:
266 d = self.queuedEngine.reset()
267 else:
268 d = defer.succeed(None)
269
270 if task.push is not None:
271 d.addCallback(lambda r: self.queuedEngine.push(task.push))
272
273 d.addCallback(lambda r: self.queuedEngine.execute(task.expression))
274
275 if task.pull is not None:
276 d.addCallback(lambda r: self.queuedEngine.pull(task.pull))
277 else:
278 d.addCallback(lambda r: None)
279
280 def reseter(result):
281 self.queuedEngine.reset()
282 return result
283
284 if task.clear_after:
285 d.addBoth(reseter)
286
287 return d.addBoth(self._zipResults, task.pull, time.time(), time.localtime())
288
289 def _zipResults(self, result, names, start, start_struct):
290 """Callback for construting the TaskResult object."""
291 if isinstance(result, failure.Failure):
292 tr = TaskResult(result, self.queuedEngine.id)
293 else:
294 if names is None:
295 resultDict = {}
296 elif len(names) == 1:
297 resultDict = {names[0]:result}
298 else:
299 resultDict = dict(zip(names, result))
300 tr = TaskResult(resultDict, self.queuedEngine.id)
301 # the time info
302 tr.submitted = time.strftime(time_format, start_struct)
303 tr.completed = time.strftime(time_format)
304 tr.duration = time.time()-start
305 return tr
306
307
308 components.registerAdapter(WorkerFromQueuedEngine, es.IEngineQueued, IWorker)
309
310 class IScheduler(zi.Interface):
311 """The interface for a Scheduler.
312 """
313 zi.Attribute("nworkers", "the number of unassigned workers")
314 zi.Attribute("ntasks", "the number of unscheduled tasks")
315 zi.Attribute("workerids", "a list of the worker ids")
316 zi.Attribute("taskids", "a list of the task ids")
317
318 def add_task(task, **flags):
319 """Add a task to the queue of the Scheduler.
320
321 :Parameters:
322 task : a `Task` object
323 The task to be queued.
324 flags : dict
325 General keywords for more sophisticated scheduling
326 """
327
328 def pop_task(id=None):
329 """Pops a Task object.
330
331 This gets the next task to be run. If no `id` is requested, the highest priority
332 task is returned.
333
334 :Parameters:
335 id
336 The id of the task to be popped. The default (None) is to return
337 the highest priority task.
338
339 :Returns: a `Task` object
340
341 :Exceptions:
342 IndexError : raised if no taskid in queue
343 """
344
345 def add_worker(worker, **flags):
346 """Add a worker to the worker queue.
347
348 :Parameters:
349 worker : an IWorker implementing object
350 flags : General keywords for more sophisticated scheduling
351 """
352
353 def pop_worker(id=None):
354 """Pops an IWorker object that is ready to do work.
355
356 This gets the next IWorker that is ready to do work.
357
358 :Parameters:
359 id : if specified, will pop worker with workerid=id, else pops
360 highest priority worker. Defaults to None.
361
362 :Returns:
363 an IWorker object
364
365 :Exceptions:
366 IndexError : raised if no workerid in queue
367 """
368
369 def ready():
370 """Returns True if there is something to do, False otherwise"""
371
372 def schedule():
373 """Returns a tuple of the worker and task pair for the next
374 task to be run.
375 """
376
377
378 class FIFOScheduler(object):
379 """A basic First-In-First-Out (Queue) Scheduler.
380 This is the default Scheduler for the TaskController.
381 See the docstrings for IScheduler for interface details.
382 """
383
384 zi.implements(IScheduler)
385
386 def __init__(self):
387 self.tasks = []
388 self.workers = []
389
390 def _ntasks(self):
391 return len(self.tasks)
392
393 def _nworkers(self):
394 return len(self.workers)
395
396 ntasks = property(_ntasks, lambda self, _:None)
397 nworkers = property(_nworkers, lambda self, _:None)
398
399 def _taskids(self):
400 return [t.taskid for t in self.tasks]
401
402 def _workerids(self):
403 return [w.workerid for w in self.workers]
404
405 taskids = property(_taskids, lambda self,_:None)
406 workerids = property(_workerids, lambda self,_:None)
407
408 def add_task(self, task, **flags):
409 self.tasks.append(task)
410
411 def pop_task(self, id=None):
412 if id is None:
413 return self.tasks.pop(0)
414 else:
415 for i in range(len(self.tasks)):
416 taskid = self.tasks[i].taskid
417 if id == taskid:
418 return self.tasks.pop(i)
419 raise IndexError("No task #%i"%id)
420
421 def add_worker(self, worker, **flags):
422 self.workers.append(worker)
423
424 def pop_worker(self, id=None):
425 if id is None:
426 return self.workers.pop(0)
427 else:
428 for i in range(len(self.workers)):
429 workerid = self.workers[i].workerid
430 if id == workerid:
431 return self.workers.pop(i)
432 raise IndexError("No worker #%i"%id)
433
434 def schedule(self):
435 for t in self.tasks:
436 for w in self.workers:
437 try:# do not allow exceptions to break this
438 cando = t.depend is None or t.depend(w.properties)
439 except:
440 cando = False
441 if cando:
442 return self.pop_worker(w.workerid), self.pop_task(t.taskid)
443 return None, None
444
445
446
447 class LIFOScheduler(FIFOScheduler):
448 """A Last-In-First-Out (Stack) Scheduler. This scheduler should naively
449 reward fast engines by giving them more jobs. This risks starvation, but
450 only in cases with low load, where starvation does not really matter.
451 """
452
453 def add_task(self, task, **flags):
454 # self.tasks.reverse()
455 self.tasks.insert(0, task)
456 # self.tasks.reverse()
457
458 def add_worker(self, worker, **flags):
459 # self.workers.reverse()
460 self.workers.insert(0, worker)
461 # self.workers.reverse()
462
463
464 class ITaskController(cs.IControllerBase):
465 """The Task based interface to a `ControllerService` object
466
467 This adapts a `ControllerService` to the ITaskController interface.
468 """
469
470 def run(task):
471 """Run a task.
472
473 :Parameters:
474 task : an IPython `Task` object
475
476 :Returns: the integer ID of the task
477 """
478
479 def get_task_result(taskid, block=False):
480 """Get the result of a task by its ID.
481
482 :Parameters:
483 taskid : int
484 the id of the task whose result is requested
485
486 :Returns: `Deferred` to (taskid, actualResult) if the task is done, and None
487 if not.
488
489 :Exceptions:
490 actualResult will be an `IndexError` if no such task has been submitted
491 """
492
493 def abort(taskid):
494 """Remove task from queue if task is has not been submitted.
495
496 If the task has already been submitted, wait for it to finish and discard
497 results and prevent resubmission.
498
499 :Parameters:
500 taskid : the id of the task to be aborted
501
502 :Returns:
503 `Deferred` to abort attempt completion. Will be None on success.
504
505 :Exceptions:
506 deferred will fail with `IndexError` if no such task has been submitted
507 or the task has already completed.
508 """
509
510 def barrier(taskids):
511 """Block until the list of taskids are completed.
512
513 Returns None on success.
514 """
515
516 def spin():
517 """touch the scheduler, to resume scheduling without submitting
518 a task.
519 """
520
521 def queue_status(self, verbose=False):
522 """Get a dictionary with the current state of the task queue.
523
524 If verbose is True, then return lists of taskids, otherwise,
525 return the number of tasks with each status.
526 """
527
528
529 class TaskController(cs.ControllerAdapterBase):
530 """The Task based interface to a Controller object.
531
532 If you want to use a different scheduler, just subclass this and set
533 the `SchedulerClass` member to the *class* of your chosen scheduler.
534 """
535
536 zi.implements(ITaskController)
537 SchedulerClass = FIFOScheduler
538
539 timeout = 30
540
541 def __init__(self, controller):
542 self.controller = controller
543 self.controller.on_register_engine_do(self.registerWorker, True)
544 self.controller.on_unregister_engine_do(self.unregisterWorker, True)
545 self.taskid = 0
546 self.failurePenalty = 1 # the time in seconds to penalize
547 # a worker for failing a task
548 self.pendingTasks = {} # dict of {workerid:(taskid, task)}
549 self.deferredResults = {} # dict of {taskid:deferred}
550 self.finishedResults = {} # dict of {taskid:actualResult}
551 self.workers = {} # dict of {workerid:worker}
552 self.abortPending = [] # dict of {taskid:abortDeferred}
553 self.idleLater = None # delayed call object for timeout
554 self.scheduler = self.SchedulerClass()
555
556 for id in self.controller.engines.keys():
557 self.workers[id] = IWorker(self.controller.engines[id])
558 self.workers[id].workerid = id
559 self.schedule.add_worker(self.workers[id])
560
561 def registerWorker(self, id):
562 """Called by controller.register_engine."""
563 if self.workers.get(id):
564 raise "We already have one! This should not happen."
565 self.workers[id] = IWorker(self.controller.engines[id])
566 self.workers[id].workerid = id
567 if not self.pendingTasks.has_key(id):# if not working
568 self.scheduler.add_worker(self.workers[id])
569 self.distributeTasks()
570
571 def unregisterWorker(self, id):
572 """Called by controller.unregister_engine"""
573
574 if self.workers.has_key(id):
575 try:
576 self.scheduler.pop_worker(id)
577 except IndexError:
578 pass
579 self.workers.pop(id)
580
581 def _pendingTaskIDs(self):
582 return [t.taskid for t in self.pendingTasks.values()]
583
584 #---------------------------------------------------------------------------
585 # Interface methods
586 #---------------------------------------------------------------------------
587
588 def run(self, task):
589 """Run a task and return `Deferred` to its taskid."""
590 task.taskid = self.taskid
591 task.start = time.localtime()
592 self.taskid += 1
593 d = defer.Deferred()
594 self.scheduler.add_task(task)
595 # log.msg('Queuing task: %i' % task.taskid)
596
597 self.deferredResults[task.taskid] = []
598 self.distributeTasks()
599 return defer.succeed(task.taskid)
600
601 def get_task_result(self, taskid, block=False):
602 """Returns a `Deferred` to a TaskResult tuple or None."""
603 # log.msg("Getting task result: %i" % taskid)
604 if self.finishedResults.has_key(taskid):
605 tr = self.finishedResults[taskid]
606 return defer.succeed(tr)
607 elif self.deferredResults.has_key(taskid):
608 if block:
609 d = defer.Deferred()
610 self.deferredResults[taskid].append(d)
611 return d
612 else:
613 return defer.succeed(None)
614 else:
615 return defer.fail(IndexError("task ID not registered: %r" % taskid))
616
617 def abort(self, taskid):
618 """Remove a task from the queue if it has not been run already."""
619 if not isinstance(taskid, int):
620 return defer.fail(failure.Failure(TypeError("an integer task id expected: %r" % taskid)))
621 try:
622 self.scheduler.pop_task(taskid)
623 except IndexError, e:
624 if taskid in self.finishedResults.keys():
625 d = defer.fail(IndexError("Task Already Completed"))
626 elif taskid in self.abortPending:
627 d = defer.fail(IndexError("Task Already Aborted"))
628 elif taskid in self._pendingTaskIDs():# task is pending
629 self.abortPending.append(taskid)
630 d = defer.succeed(None)
631 else:
632 d = defer.fail(e)
633 else:
634 d = defer.execute(self._doAbort, taskid)
635
636 return d
637
638 def barrier(self, taskids):
639 dList = []
640 if isinstance(taskids, int):
641 taskids = [taskids]
642 for id in taskids:
643 d = self.get_task_result(id, block=True)
644 dList.append(d)
645 d = DeferredList(dList, consumeErrors=1)
646 d.addCallbacks(lambda r: None)
647 return d
648
649 def spin(self):
650 return defer.succeed(self.distributeTasks())
651
652 def queue_status(self, verbose=False):
653 pending = self._pendingTaskIDs()
654 failed = []
655 succeeded = []
656 for k,v in self.finishedResults.iteritems():
657 if not isinstance(v, failure.Failure):
658 if hasattr(v,'failure'):
659 if v.failure is None:
660 succeeded.append(k)
661 else:
662 failed.append(k)
663 scheduled = self.scheduler.taskids
664 if verbose:
665 result = dict(pending=pending, failed=failed,
666 succeeded=succeeded, scheduled=scheduled)
667 else:
668 result = dict(pending=len(pending),failed=len(failed),
669 succeeded=len(succeeded),scheduled=len(scheduled))
670 return defer.succeed(result)
671
672 #---------------------------------------------------------------------------
673 # Queue methods
674 #---------------------------------------------------------------------------
675
676 def _doAbort(self, taskid):
677 """Helper function for aborting a pending task."""
678 # log.msg("Task aborted: %i" % taskid)
679 result = failure.Failure(error.TaskAborted())
680 self._finishTask(taskid, result)
681 if taskid in self.abortPending:
682 self.abortPending.remove(taskid)
683
684 def _finishTask(self, taskid, result):
685 dlist = self.deferredResults.pop(taskid)
686 result.taskid = taskid # The TaskResult should save the taskid
687 self.finishedResults[taskid] = result
688 for d in dlist:
689 d.callback(result)
690
691 def distributeTasks(self):
692 """Distribute tasks while self.scheduler has things to do."""
693 # log.msg("distributing Tasks")
694 worker, task = self.scheduler.schedule()
695 if not worker and not task:
696 if self.idleLater and self.idleLater.called:# we are inside failIdle
697 self.idleLater = None
698 else:
699 self.checkIdle()
700 return False
701 # else something to do:
702 while worker and task:
703 # get worker and task
704 # add to pending
705 self.pendingTasks[worker.workerid] = task
706 # run/link callbacks
707 d = worker.run(task)
708 # log.msg("Running task %i on worker %i" %(task.taskid, worker.workerid))
709 d.addBoth(self.taskCompleted, task.taskid, worker.workerid)
710 worker, task = self.scheduler.schedule()
711 # check for idle timeout:
712 self.checkIdle()
713 return True
714
715 def checkIdle(self):
716 if self.idleLater and not self.idleLater.called:
717 self.idleLater.cancel()
718 if self.scheduler.ntasks and self.workers and \
719 self.scheduler.nworkers == len(self.workers):
720 self.idleLater = reactor.callLater(self.timeout, self.failIdle)
721 else:
722 self.idleLater = None
723
724 def failIdle(self):
725 if not self.distributeTasks():
726 while self.scheduler.ntasks:
727 t = self.scheduler.pop_task()
728 msg = "task %i failed to execute due to unmet dependencies"%t.taskid
729 msg += " for %i seconds"%self.timeout
730 # log.msg("Task aborted by timeout: %i" % t.taskid)
731 f = failure.Failure(error.TaskTimeout(msg))
732 self._finishTask(t.taskid, f)
733 self.idleLater = None
734
735
736 def taskCompleted(self, result, taskid, workerid):
737 """This is the err/callback for a completed task."""
738 try:
739 task = self.pendingTasks.pop(workerid)
740 except:
741 # this should not happen
742 log.msg("Tried to pop bad pending task %i from worker %i"%(taskid, workerid))
743 log.msg("Result: %r"%result)
744 log.msg("Pending tasks: %s"%self.pendingTasks)
745 return
746
747 # Check if aborted while pending
748 aborted = False
749 if taskid in self.abortPending:
750 self._doAbort(taskid)
751 aborted = True
752
753 if not aborted:
754 if result.failure is not None and isinstance(result.failure, failure.Failure): # we failed
755 log.msg("Task %i failed on worker %i"% (taskid, workerid))
756 if task.retries > 0: # resubmit
757 task.retries -= 1
758 self.scheduler.add_task(task)
759 s = "Resubmitting task %i, %i retries remaining" %(taskid, task.retries)
760 log.msg(s)
761 self.distributeTasks()
762 elif isinstance(task.recovery_task, Task) and \
763 task.recovery_task.retries > -1:
764 # retries = -1 is to prevent infinite recovery_task loop
765 task.retries = -1
766 task.recovery_task.taskid = taskid
767 task = task.recovery_task
768 self.scheduler.add_task(task)
769 s = "Recovering task %i, %i retries remaining" %(taskid, task.retries)
770 log.msg(s)
771 self.distributeTasks()
772 else: # done trying
773 self._finishTask(taskid, result)
774 # wait a second before readmitting a worker that failed
775 # it may have died, and not yet been unregistered
776 reactor.callLater(self.failurePenalty, self.readmitWorker, workerid)
777 else: # we succeeded
778 # log.msg("Task completed: %i"% taskid)
779 self._finishTask(taskid, result)
780 self.readmitWorker(workerid)
781 else:# we aborted the task
782 if result.failure is not None and isinstance(result.failure, failure.Failure): # it failed, penalize worker
783 reactor.callLater(self.failurePenalty, self.readmitWorker, workerid)
784 else:
785 self.readmitWorker(workerid)
786
787 def readmitWorker(self, workerid):
788 """Readmit a worker to the scheduler.
789
790 This is outside `taskCompleted` because of the `failurePenalty` being
791 implemented through `reactor.callLater`.
792 """
793
794 if workerid in self.workers.keys() and workerid not in self.pendingTasks.keys():
795 self.scheduler.add_worker(self.workers[workerid])
796 self.distributeTasks()
797
798
799 components.registerAdapter(TaskController, cs.IControllerBase, ITaskController)
@@ -0,0 +1,161 b''
1 # encoding: utf-8
2 # -*- test-case-name: IPython.kernel.tests.test_taskcontrollerxmlrpc -*-
3
4 """The Generic Task Client object.
5
6 This must be subclassed based on your connection method.
7 """
8
9 __docformat__ = "restructuredtext en"
10
11 #-------------------------------------------------------------------------------
12 # Copyright (C) 2008 The IPython Development Team
13 #
14 # Distributed under the terms of the BSD License. The full license is in
15 # the file COPYING, distributed as part of this software.
16 #-------------------------------------------------------------------------------
17
18 #-------------------------------------------------------------------------------
19 # Imports
20 #-------------------------------------------------------------------------------
21
22 from zope.interface import Interface, implements
23 from twisted.python import components, log
24
25 from IPython.kernel.twistedutil import blockingCallFromThread
26 from IPython.kernel import task, error
27
28 #-------------------------------------------------------------------------------
29 # Connecting Task Client
30 #-------------------------------------------------------------------------------
31
32 class InteractiveTaskClient(object):
33
34 def irun(self, *args, **kwargs):
35 """Run a task on the `TaskController`.
36
37 This method is a shorthand for run(task) and its arguments are simply
38 passed onto a `Task` object:
39
40 irun(*args, **kwargs) -> run(Task(*args, **kwargs))
41
42 :Parameters:
43 expression : str
44 A str that is valid python code that is the task.
45 pull : str or list of str
46 The names of objects to be pulled as results.
47 push : dict
48 A dict of objects to be pushed into the engines namespace before
49 execution of the expression.
50 clear_before : boolean
51 Should the engine's namespace be cleared before the task is run.
52 Default=False.
53 clear_after : boolean
54 Should the engine's namespace be cleared after the task is run.
55 Default=False.
56 retries : int
57 The number of times to resumbit the task if it fails. Default=0.
58 options : dict
59 Any other keyword options for more elaborate uses of tasks
60
61 :Returns: A `TaskResult` object.
62 """
63 block = kwargs.pop('block', False)
64 if len(args) == 1 and isinstance(args[0], task.Task):
65 t = args[0]
66 else:
67 t = task.Task(*args, **kwargs)
68 taskid = self.run(t)
69 print "TaskID = %i"%taskid
70 if block:
71 return self.get_task_result(taskid, block)
72 else:
73 return taskid
74
75 class IBlockingTaskClient(Interface):
76 """
77 An interface for blocking task clients.
78 """
79 pass
80
81
82 class BlockingTaskClient(InteractiveTaskClient):
83 """
84 This class provides a blocking task client.
85 """
86
87 implements(IBlockingTaskClient)
88
89 def __init__(self, task_controller):
90 self.task_controller = task_controller
91 self.block = True
92
93 def run(self, task):
94 """
95 Run a task and return a task id that can be used to get the task result.
96
97 :Parameters:
98 task : `Task`
99 The `Task` object to run
100 """
101 return blockingCallFromThread(self.task_controller.run, task)
102
103 def get_task_result(self, taskid, block=False):
104 """
105 Get or poll for a task result.
106
107 :Parameters:
108 taskid : int
109 The id of the task whose result to get
110 block : boolean
111 If True, wait until the task is done and then result the
112 `TaskResult` object. If False, just poll for the result and
113 return None if the task is not done.
114 """
115 return blockingCallFromThread(self.task_controller.get_task_result,
116 taskid, block)
117
118 def abort(self, taskid):
119 """
120 Abort a task by task id if it has not been started.
121 """
122 return blockingCallFromThread(self.task_controller.abort, taskid)
123
124 def barrier(self, taskids):
125 """
126 Wait for a set of tasks to finish.
127
128 :Parameters:
129 taskids : list of ints
130 A list of task ids to wait for.
131 """
132 return blockingCallFromThread(self.task_controller.barrier, taskids)
133
134 def spin(self):
135 """
136 Cause the scheduler to schedule tasks.
137
138 This method only needs to be called in unusual situations where the
139 scheduler is idle for some reason.
140 """
141 return blockingCallFromThread(self.task_controller.spin)
142
143 def queue_status(self, verbose=False):
144 """
145 Get a dictionary with the current state of the task queue.
146
147 :Parameters:
148 verbose : boolean
149 If True, return a list of taskids. If False, simply give
150 the number of tasks with each status.
151
152 :Returns:
153 A dict with the queue status.
154 """
155 return blockingCallFromThread(self.task_controller.queue_status, verbose)
156
157
158 components.registerAdapter(BlockingTaskClient,
159 task.ITaskController, IBlockingTaskClient)
160
161
@@ -0,0 +1,267 b''
1 # encoding: utf-8
2 # -*- test-case-name: IPython.kernel.tests.test_taskxmlrpc -*-
3 """A Foolscap interface to a TaskController.
4
5 This class lets Foolscap clients talk to a TaskController.
6 """
7
8 __docformat__ = "restructuredtext en"
9
10 #-------------------------------------------------------------------------------
11 # Copyright (C) 2008 The IPython Development Team
12 #
13 # Distributed under the terms of the BSD License. The full license is in
14 # the file COPYING, distributed as part of this software.
15 #-------------------------------------------------------------------------------
16
17 #-------------------------------------------------------------------------------
18 # Imports
19 #-------------------------------------------------------------------------------
20
21 import cPickle as pickle
22 import xmlrpclib, copy
23
24 from zope.interface import Interface, implements
25 from twisted.internet import defer
26 from twisted.python import components, failure
27
28 from foolscap import Referenceable
29
30 from IPython.kernel.twistedutil import blockingCallFromThread
31 from IPython.kernel import error, task as taskmodule, taskclient
32 from IPython.kernel.pickleutil import can, uncan
33 from IPython.kernel.clientinterfaces import (
34 IFCClientInterfaceProvider,
35 IBlockingClientAdaptor
36 )
37
38 #-------------------------------------------------------------------------------
39 # The Controller side of things
40 #-------------------------------------------------------------------------------
41
42
43 class IFCTaskController(Interface):
44 """Foolscap interface to task controller.
45
46 See the documentation of ITaskController for documentation about the methods.
47 """
48 def remote_run(request, binTask):
49 """"""
50
51 def remote_abort(request, taskid):
52 """"""
53
54 def remote_get_task_result(request, taskid, block=False):
55 """"""
56
57 def remote_barrier(request, taskids):
58 """"""
59
60 def remote_spin(request):
61 """"""
62
63 def remote_queue_status(request, verbose):
64 """"""
65
66
67 class FCTaskControllerFromTaskController(Referenceable):
68 """XML-RPC attachmeot for controller.
69
70 See IXMLRPCTaskController and ITaskController (and its children) for documentation.
71 """
72 implements(IFCTaskController, IFCClientInterfaceProvider)
73
74 def __init__(self, taskController):
75 self.taskController = taskController
76
77 #---------------------------------------------------------------------------
78 # Non interface methods
79 #---------------------------------------------------------------------------
80
81 def packageFailure(self, f):
82 f.cleanFailure()
83 return self.packageSuccess(f)
84
85 def packageSuccess(self, obj):
86 serial = pickle.dumps(obj, 2)
87 return serial
88
89 #---------------------------------------------------------------------------
90 # ITaskController related methods
91 #---------------------------------------------------------------------------
92
93 def remote_run(self, ptask):
94 try:
95 ctask = pickle.loads(ptask)
96 task = taskmodule.uncanTask(ctask)
97 except:
98 d = defer.fail(pickle.UnpickleableError("Could not unmarshal task"))
99 else:
100 d = self.taskController.run(task)
101 d.addCallback(self.packageSuccess)
102 d.addErrback(self.packageFailure)
103 return d
104
105 def remote_abort(self, taskid):
106 d = self.taskController.abort(taskid)
107 d.addCallback(self.packageSuccess)
108 d.addErrback(self.packageFailure)
109 return d
110
111 def remote_get_task_result(self, taskid, block=False):
112 d = self.taskController.get_task_result(taskid, block)
113 d.addCallback(self.packageSuccess)
114 d.addErrback(self.packageFailure)
115 return d
116
117 def remote_barrier(self, taskids):
118 d = self.taskController.barrier(taskids)
119 d.addCallback(self.packageSuccess)
120 d.addErrback(self.packageFailure)
121 return d
122
123 def remote_spin(self):
124 d = self.taskController.spin()
125 d.addCallback(self.packageSuccess)
126 d.addErrback(self.packageFailure)
127 return d
128
129 def remote_queue_status(self, verbose):
130 d = self.taskController.queue_status(verbose)
131 d.addCallback(self.packageSuccess)
132 d.addErrback(self.packageFailure)
133 return d
134
135 def remote_get_client_name(self):
136 return 'IPython.kernel.taskfc.FCTaskClient'
137
138 components.registerAdapter(FCTaskControllerFromTaskController,
139 taskmodule.ITaskController, IFCTaskController)
140
141
142 #-------------------------------------------------------------------------------
143 # The Client side of things
144 #-------------------------------------------------------------------------------
145
146 class FCTaskClient(object):
147 """XML-RPC based TaskController client that implements ITaskController.
148
149 :Parameters:
150 addr : (ip, port)
151 The ip (str) and port (int) tuple of the `TaskController`.
152 """
153 implements(taskmodule.ITaskController, IBlockingClientAdaptor)
154
155 def __init__(self, remote_reference):
156 self.remote_reference = remote_reference
157
158 #---------------------------------------------------------------------------
159 # Non interface methods
160 #---------------------------------------------------------------------------
161
162 def unpackage(self, r):
163 return pickle.loads(r)
164
165 #---------------------------------------------------------------------------
166 # ITaskController related methods
167 #---------------------------------------------------------------------------
168 def run(self, task):
169 """Run a task on the `TaskController`.
170
171 :Parameters:
172 task : a `Task` object
173
174 The Task object is created using the following signature:
175
176 Task(expression, pull=None, push={}, clear_before=False,
177 clear_after=False, retries=0, **options):)
178
179 The meaning of the arguments is as follows:
180
181 :Task Parameters:
182 expression : str
183 A str that is valid python code that is the task.
184 pull : str or list of str
185 The names of objects to be pulled as results.
186 push : dict
187 A dict of objects to be pushed into the engines namespace before
188 execution of the expression.
189 clear_before : boolean
190 Should the engine's namespace be cleared before the task is run.
191 Default=False.
192 clear_after : boolean
193 Should the engine's namespace be cleared after the task is run.
194 Default=False.
195 retries : int
196 The number of times to resumbit the task if it fails. Default=0.
197 options : dict
198 Any other keyword options for more elaborate uses of tasks
199
200 :Returns: The int taskid of the submitted task. Pass this to
201 `get_task_result` to get the `TaskResult` object.
202 """
203 assert isinstance(task, taskmodule.Task), "task must be a Task object!"
204 ctask = taskmodule.canTask(task) # handles arbitrary function in .depend
205 # as well as arbitrary recovery_task chains
206 ptask = pickle.dumps(ctask, 2)
207 d = self.remote_reference.callRemote('run', ptask)
208 d.addCallback(self.unpackage)
209 return d
210
211 def get_task_result(self, taskid, block=False):
212 """The task result by taskid.
213
214 :Parameters:
215 taskid : int
216 The taskid of the task to be retrieved.
217 block : boolean
218 Should I block until the task is done?
219
220 :Returns: A `TaskResult` object that encapsulates the task result.
221 """
222 d = self.remote_reference.callRemote('get_task_result', taskid, block)
223 d.addCallback(self.unpackage)
224 return d
225
226 def abort(self, taskid):
227 """Abort a task by taskid.
228
229 :Parameters:
230 taskid : int
231 The taskid of the task to be aborted.
232 block : boolean
233 Should I block until the task is aborted.
234 """
235 d = self.remote_reference.callRemote('abort', taskid)
236 d.addCallback(self.unpackage)
237 return d
238
239 def barrier(self, taskids):
240 """Block until all tasks are completed.
241
242 :Parameters:
243 taskids : list, tuple
244 A sequence of taskids to block on.
245 """
246 d = self.remote_reference.callRemote('barrier', taskids)
247 d.addCallback(self.unpackage)
248 return d
249
250 def spin(self):
251 """touch the scheduler, to resume scheduling without submitting
252 a task.
253 """
254 d = self.remote_reference.callRemote('spin')
255 d.addCallback(self.unpackage)
256 return d
257
258 def queue_status(self, verbose=False):
259 """Return a dict with the status of the task queue."""
260 d = self.remote_reference.callRemote('queue_status', verbose)
261 d.addCallback(self.unpackage)
262 return d
263
264 def adapt_to_blocking_client(self):
265 from IPython.kernel.taskclient import IBlockingTaskClient
266 return IBlockingTaskClient(self)
267
@@ -0,0 +1,10 b''
1 # encoding: utf-8
2
3 __docformat__ = "restructuredtext en"
4
5 #-------------------------------------------------------------------------------
6 # Copyright (C) 2008 The IPython Development Team
7 #
8 # Distributed under the terms of the BSD License. The full license is in
9 # the file COPYING, distributed as part of this software.
10 #-------------------------------------------------------------------------------
@@ -0,0 +1,102 b''
1 # encoding: utf-8
2
3 """This file contains unittests for the kernel.engineservice.py module.
4
5 Things that should be tested:
6
7 - Should the EngineService return Deferred objects?
8 - Run the same tests that are run in shell.py.
9 - Make sure that the Interface is really implemented.
10 - The startService and stopService methods.
11 """
12
13 __docformat__ = "restructuredtext en"
14
15 #-------------------------------------------------------------------------------
16 # Copyright (C) 2008 The IPython Development Team
17 #
18 # Distributed under the terms of the BSD License. The full license is in
19 # the file COPYING, distributed as part of this software.
20 #-------------------------------------------------------------------------------
21
22 #-------------------------------------------------------------------------------
23 # Imports
24 #-------------------------------------------------------------------------------
25
26 from twisted.internet import defer
27 import zope.interface as zi
28
29 from IPython.kernel import engineservice as es
30 from IPython.kernel import error
31 from IPython.testutils.util import DeferredTestCase
32 from IPython.kernel.controllerservice import \
33 IControllerCore
34
35
36 class IControllerCoreTestCase(object):
37 """Tests for objects that implement IControllerCore.
38
39 This test assumes that self.controller is defined and implements
40 IControllerCore.
41 """
42
43 def testIControllerCoreInterface(self):
44 """Does self.engine claim to implement IEngineCore?"""
45 self.assert_(IControllerCore.providedBy(self.controller))
46
47 def testIControllerCoreInterfaceMethods(self):
48 """Does self.engine have the methods and attributes in IEngireCore."""
49 for m in list(IControllerCore):
50 self.assert_(hasattr(self.controller, m))
51
52 def testRegisterUnregisterEngine(self):
53 engine = es.EngineService()
54 qengine = es.QueuedEngine(engine)
55 regDict = self.controller.register_engine(qengine, 0)
56 self.assert_(isinstance(regDict, dict))
57 self.assert_(regDict.has_key('id'))
58 self.assert_(regDict['id']==0)
59 self.controller.unregister_engine(0)
60 self.assert_(self.controller.engines.get(0, None) == None)
61
62 def testRegisterUnregisterMultipleEngines(self):
63 e1 = es.EngineService()
64 qe1 = es.QueuedEngine(e1)
65 e2 = es.EngineService()
66 qe2 = es.QueuedEngine(e2)
67 rd1 = self.controller.register_engine(qe1, 0)
68 self.assertEquals(rd1['id'], 0)
69 rd2 = self.controller.register_engine(qe2, 1)
70 self.assertEquals(rd2['id'], 1)
71 self.controller.unregister_engine(0)
72 rd1 = self.controller.register_engine(qe1, 0)
73 self.assertEquals(rd1['id'], 0)
74 self.controller.unregister_engine(1)
75 rd2 = self.controller.register_engine(qe2, 0)
76 self.assertEquals(rd2['id'], 1)
77 self.controller.unregister_engine(0)
78 self.controller.unregister_engine(1)
79 self.assertEquals(self.controller.engines,{})
80
81 def testRegisterCallables(self):
82 e1 = es.EngineService()
83 qe1 = es.QueuedEngine(e1)
84 self.registerCallableCalled = ';lkj'
85 self.unregisterCallableCalled = ';lkj'
86 self.controller.on_register_engine_do(self._registerCallable, False)
87 self.controller.on_unregister_engine_do(self._unregisterCallable, False)
88 self.controller.register_engine(qe1, 0)
89 self.assertEquals(self.registerCallableCalled, 'asdf')
90 self.controller.unregister_engine(0)
91 self.assertEquals(self.unregisterCallableCalled, 'asdf')
92 self.controller.on_register_engine_do_not(self._registerCallable)
93 self.controller.on_unregister_engine_do_not(self._unregisterCallable)
94
95 def _registerCallable(self):
96 self.registerCallableCalled = 'asdf'
97
98 def _unregisterCallable(self):
99 self.unregisterCallableCalled = 'asdf'
100
101 def testBadUnregister(self):
102 self.assertRaises(AssertionError, self.controller.unregister_engine, 'foo') No newline at end of file
@@ -0,0 +1,373 b''
1 # encoding: utf-8
2
3 """Test template for complete engine object"""
4
5 __docformat__ = "restructuredtext en"
6
7 #-------------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
13
14 #-------------------------------------------------------------------------------
15 # Imports
16 #-------------------------------------------------------------------------------
17
18 import cPickle as pickle
19
20 from twisted.internet import defer, reactor
21 from twisted.python import failure
22 from twisted.application import service
23 import zope.interface as zi
24
25 from IPython.kernel import newserialized
26 from IPython.kernel import error
27 from IPython.kernel.pickleutil import can, uncan
28 import IPython.kernel.engineservice as es
29 from IPython.kernel.core.interpreter import Interpreter
30 from IPython.testutils.parametric import Parametric, parametric
31
32 #-------------------------------------------------------------------------------
33 # Tests
34 #-------------------------------------------------------------------------------
35
36
37 # A sequence of valid commands run through execute
38 validCommands = ['a=5',
39 'b=10',
40 'a=5; b=10; c=a+b',
41 'import math; 2.0*math.pi',
42 """def f():
43 result = 0.0
44 for i in range(10):
45 result += i
46 """,
47 'if 1<2: a=5',
48 """import time
49 time.sleep(0.1)""",
50 """from math import cos;
51 x = 1.0*cos(0.5)""", # Semicolons lead to Discard ast nodes that should be discarded
52 """from sets import Set
53 s = Set()
54 """, # Trailing whitespace should be allowed.
55 """import math
56 math.cos(1.0)""", # Test a method call with a discarded return value
57 """x=1.0234
58 a=5; b=10""", # Test an embedded semicolon
59 """x=1.0234
60 a=5; b=10;""" # Test both an embedded and trailing semicolon
61 ]
62
63 # A sequence of commands that raise various exceptions
64 invalidCommands = [('a=1/0',ZeroDivisionError),
65 ('print v',NameError),
66 ('l=[];l[0]',IndexError),
67 ("d={};d['a']",KeyError),
68 ("assert 1==0",AssertionError),
69 ("import abababsdbfsbaljasdlja",ImportError),
70 ("raise Exception()",Exception)]
71
72 def testf(x):
73 return 2.0*x
74
75 globala = 99
76
77 def testg(x):
78 return globala*x
79
80 class IEngineCoreTestCase(object):
81 """Test an IEngineCore implementer."""
82
83 def createShell(self):
84 return Interpreter()
85
86 def catchQueueCleared(self, f):
87 try:
88 f.raiseException()
89 except error.QueueCleared:
90 pass
91
92 def testIEngineCoreInterface(self):
93 """Does self.engine claim to implement IEngineCore?"""
94 self.assert_(es.IEngineCore.providedBy(self.engine))
95
96 def testIEngineCoreInterfaceMethods(self):
97 """Does self.engine have the methods and attributes in IEngineCore."""
98 for m in list(es.IEngineCore):
99 self.assert_(hasattr(self.engine, m))
100
101 def testIEngineCoreDeferreds(self):
102 d = self.engine.execute('a=5')
103 d.addCallback(lambda _: self.engine.pull('a'))
104 d.addCallback(lambda _: self.engine.get_result())
105 d.addCallback(lambda _: self.engine.keys())
106 d.addCallback(lambda _: self.engine.push(dict(a=10)))
107 return d
108
109 def runTestExecute(self, cmd):
110 self.shell = Interpreter()
111 actual = self.shell.execute(cmd)
112 def compare(computed):
113 actual['id'] = computed['id']
114 self.assertEquals(actual, computed)
115 d = self.engine.execute(cmd)
116 d.addCallback(compare)
117 return d
118
119 @parametric
120 def testExecute(cls):
121 return [(cls.runTestExecute, cmd) for cmd in validCommands]
122
123 def runTestExecuteFailures(self, cmd, exc):
124 def compare(f):
125 self.assertRaises(exc, f.raiseException)
126 d = self.engine.execute(cmd)
127 d.addErrback(compare)
128 return d
129
130 @parametric
131 def testExecuteFailures(cls):
132 return [(cls.runTestExecuteFailures, cmd, exc) for cmd, exc in invalidCommands]
133
134 def runTestPushPull(self, o):
135 d = self.engine.push(dict(a=o))
136 d.addCallback(lambda r: self.engine.pull('a'))
137 d.addCallback(lambda r: self.assertEquals(o,r))
138 return d
139
140 @parametric
141 def testPushPull(cls):
142 objs = [10,"hi there",1.2342354,{"p":(1,2)},None]
143 return [(cls.runTestPushPull, o) for o in objs]
144
145 def testPullNameError(self):
146 d = self.engine.push(dict(a=5))
147 d.addCallback(lambda _:self.engine.reset())
148 d.addCallback(lambda _: self.engine.pull("a"))
149 d.addErrback(lambda f: self.assertRaises(NameError, f.raiseException))
150 return d
151
152 def testPushPullFailures(self):
153 d = self.engine.pull('a')
154 d.addErrback(lambda f: self.assertRaises(NameError, f.raiseException))
155 d.addCallback(lambda _: self.engine.execute('l = lambda x: x'))
156 d.addCallback(lambda _: self.engine.pull('l'))
157 d.addErrback(lambda f: self.assertRaises(pickle.PicklingError, f.raiseException))
158 d.addCallback(lambda _: self.engine.push(dict(l=lambda x: x)))
159 d.addErrback(lambda f: self.assertRaises(pickle.PicklingError, f.raiseException))
160 return d
161
162 def testPushPullArray(self):
163 try:
164 import numpy
165 except:
166 print 'no numpy, ',
167 return
168 a = numpy.random.random(1000)
169 d = self.engine.push(dict(a=a))
170 d.addCallback(lambda _: self.engine.pull('a'))
171 d.addCallback(lambda b: b==a)
172 d.addCallback(lambda c: c.all())
173 return self.assertDeferredEquals(d, True)
174
175 def testPushFunction(self):
176
177 d = self.engine.push_function(dict(f=testf))
178 d.addCallback(lambda _: self.engine.execute('result = f(10)'))
179 d.addCallback(lambda _: self.engine.pull('result'))
180 d.addCallback(lambda r: self.assertEquals(r, testf(10)))
181 return d
182
183 def testPullFunction(self):
184 d = self.engine.push_function(dict(f=testf, g=testg))
185 d.addCallback(lambda _: self.engine.pull_function(('f','g')))
186 d.addCallback(lambda r: self.assertEquals(r[0](10), testf(10)))
187 return d
188
189 def testPushFunctionGlobal(self):
190 """Make sure that pushed functions pick up the user's namespace for globals."""
191 d = self.engine.push(dict(globala=globala))
192 d.addCallback(lambda _: self.engine.push_function(dict(g=testg)))
193 d.addCallback(lambda _: self.engine.execute('result = g(10)'))
194 d.addCallback(lambda _: self.engine.pull('result'))
195 d.addCallback(lambda r: self.assertEquals(r, testg(10)))
196 return d
197
198 def testGetResultFailure(self):
199 d = self.engine.get_result(None)
200 d.addErrback(lambda f: self.assertRaises(IndexError, f.raiseException))
201 d.addCallback(lambda _: self.engine.get_result(10))
202 d.addErrback(lambda f: self.assertRaises(IndexError, f.raiseException))
203 return d
204
205 def runTestGetResult(self, cmd):
206 self.shell = Interpreter()
207 actual = self.shell.execute(cmd)
208 def compare(computed):
209 actual['id'] = computed['id']
210 self.assertEquals(actual, computed)
211 d = self.engine.execute(cmd)
212 d.addCallback(lambda r: self.engine.get_result(r['number']))
213 d.addCallback(compare)
214 return d
215
216 @parametric
217 def testGetResult(cls):
218 return [(cls.runTestGetResult, cmd) for cmd in validCommands]
219
220 def testGetResultDefault(self):
221 cmd = 'a=5'
222 shell = self.createShell()
223 shellResult = shell.execute(cmd)
224 def popit(dikt, key):
225 dikt.pop(key)
226 return dikt
227 d = self.engine.execute(cmd)
228 d.addCallback(lambda _: self.engine.get_result())
229 d.addCallback(lambda r: self.assertEquals(shellResult, popit(r,'id')))
230 return d
231
232 def testKeys(self):
233 d = self.engine.keys()
234 d.addCallback(lambda s: isinstance(s, list))
235 d.addCallback(lambda r: self.assertEquals(r, True))
236 return d
237
238 Parametric(IEngineCoreTestCase)
239
240 class IEngineSerializedTestCase(object):
241 """Test an IEngineCore implementer."""
242
243 def testIEngineSerializedInterface(self):
244 """Does self.engine claim to implement IEngineCore?"""
245 self.assert_(es.IEngineSerialized.providedBy(self.engine))
246
247 def testIEngineSerializedInterfaceMethods(self):
248 """Does self.engine have the methods and attributes in IEngireCore."""
249 for m in list(es.IEngineSerialized):
250 self.assert_(hasattr(self.engine, m))
251
252 def testIEngineSerializedDeferreds(self):
253 dList = []
254 d = self.engine.push_serialized(dict(key=newserialized.serialize(12345)))
255 self.assert_(isinstance(d, defer.Deferred))
256 dList.append(d)
257 d = self.engine.pull_serialized('key')
258 self.assert_(isinstance(d, defer.Deferred))
259 dList.append(d)
260 D = defer.DeferredList(dList)
261 return D
262
263 def testPushPullSerialized(self):
264 objs = [10,"hi there",1.2342354,{"p":(1,2)}]
265 d = defer.succeed(None)
266 for o in objs:
267 self.engine.push_serialized(dict(key=newserialized.serialize(o)))
268 value = self.engine.pull_serialized('key')
269 value.addCallback(lambda serial: newserialized.IUnSerialized(serial).getObject())
270 d = self.assertDeferredEquals(value,o,d)
271 return d
272
273 def testPullSerializedFailures(self):
274 d = self.engine.pull_serialized('a')
275 d.addErrback(lambda f: self.assertRaises(NameError, f.raiseException))
276 d.addCallback(lambda _: self.engine.execute('l = lambda x: x'))
277 d.addCallback(lambda _: self.engine.pull_serialized('l'))
278 d.addErrback(lambda f: self.assertRaises(pickle.PicklingError, f.raiseException))
279 return d
280
281 Parametric(IEngineSerializedTestCase)
282
283 class IEngineQueuedTestCase(object):
284 """Test an IEngineQueued implementer."""
285
286 def testIEngineQueuedInterface(self):
287 """Does self.engine claim to implement IEngineQueued?"""
288 self.assert_(es.IEngineQueued.providedBy(self.engine))
289
290 def testIEngineQueuedInterfaceMethods(self):
291 """Does self.engine have the methods and attributes in IEngireQueued."""
292 for m in list(es.IEngineQueued):
293 self.assert_(hasattr(self.engine, m))
294
295 def testIEngineQueuedDeferreds(self):
296 dList = []
297 d = self.engine.clear_queue()
298 self.assert_(isinstance(d, defer.Deferred))
299 dList.append(d)
300 d = self.engine.queue_status()
301 self.assert_(isinstance(d, defer.Deferred))
302 dList.append(d)
303 D = defer.DeferredList(dList)
304 return D
305
306 def testClearQueue(self):
307 result = self.engine.clear_queue()
308 d1 = self.assertDeferredEquals(result, None)
309 d1.addCallback(lambda _: self.engine.queue_status())
310 d2 = self.assertDeferredEquals(d1, {'queue':[], 'pending':'None'})
311 return d2
312
313 def testQueueStatus(self):
314 result = self.engine.queue_status()
315 result.addCallback(lambda r: 'queue' in r and 'pending' in r)
316 d = self.assertDeferredEquals(result, True)
317 return d
318
319 Parametric(IEngineQueuedTestCase)
320
321 class IEnginePropertiesTestCase(object):
322 """Test an IEngineProperties implementor."""
323
324 def testIEnginePropertiesInterface(self):
325 """Does self.engine claim to implement IEngineProperties?"""
326 self.assert_(es.IEngineProperties.providedBy(self.engine))
327
328 def testIEnginePropertiesInterfaceMethods(self):
329 """Does self.engine have the methods and attributes in IEngireProperties."""
330 for m in list(es.IEngineProperties):
331 self.assert_(hasattr(self.engine, m))
332
333 def testGetSetProperties(self):
334 dikt = dict(a=5, b='asdf', c=True, d=None, e=range(5))
335 d = self.engine.set_properties(dikt)
336 d.addCallback(lambda r: self.engine.get_properties())
337 d = self.assertDeferredEquals(d, dikt)
338 d.addCallback(lambda r: self.engine.get_properties(('c',)))
339 d = self.assertDeferredEquals(d, {'c': dikt['c']})
340 d.addCallback(lambda r: self.engine.set_properties(dict(c=False)))
341 d.addCallback(lambda r: self.engine.get_properties(('c', 'd')))
342 d = self.assertDeferredEquals(d, dict(c=False, d=None))
343 return d
344
345 def testClearProperties(self):
346 dikt = dict(a=5, b='asdf', c=True, d=None, e=range(5))
347 d = self.engine.set_properties(dikt)
348 d.addCallback(lambda r: self.engine.clear_properties())
349 d.addCallback(lambda r: self.engine.get_properties())
350 d = self.assertDeferredEquals(d, {})
351 return d
352
353 def testDelHasProperties(self):
354 dikt = dict(a=5, b='asdf', c=True, d=None, e=range(5))
355 d = self.engine.set_properties(dikt)
356 d.addCallback(lambda r: self.engine.del_properties(('b','e')))
357 d.addCallback(lambda r: self.engine.has_properties(('a','b','c','d','e')))
358 d = self.assertDeferredEquals(d, [True, False, True, True, False])
359 return d
360
361 def testStrictDict(self):
362 s = """from IPython.kernel.engineservice import get_engine
363 p = get_engine(%s).properties"""%self.engine.id
364 d = self.engine.execute(s)
365 d.addCallback(lambda r: self.engine.execute("p['a'] = lambda _:None"))
366 d = self.assertDeferredRaises(d, error.InvalidProperty)
367 d.addCallback(lambda r: self.engine.execute("p['a'] = range(5)"))
368 d.addCallback(lambda r: self.engine.execute("p['a'].append(5)"))
369 d.addCallback(lambda r: self.engine.get_properties('a'))
370 d = self.assertDeferredEquals(d, dict(a=range(5)))
371 return d
372
373 Parametric(IEnginePropertiesTestCase)
This diff has been collapsed as it changes many lines, (838 lines changed) Show them Hide them
@@ -0,0 +1,838 b''
1 # encoding: utf-8
2
3 """"""
4
5 __docformat__ = "restructuredtext en"
6
7 #-------------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
13
14 #-------------------------------------------------------------------------------
15 # Imports
16 #-------------------------------------------------------------------------------
17
18 from twisted.internet import defer
19
20 from IPython.kernel import engineservice as es
21 from IPython.kernel import multiengine as me
22 from IPython.kernel import newserialized
23 from IPython.kernel.error import NotDefined
24 from IPython.testutils import util
25 from IPython.testutils.parametric import parametric, Parametric
26 from IPython.kernel import newserialized
27 from IPython.kernel.util import printer
28 from IPython.kernel.error import (InvalidEngineID,
29 NoEnginesRegistered,
30 CompositeError,
31 InvalidDeferredID)
32 from IPython.kernel.tests.engineservicetest import validCommands, invalidCommands
33 from IPython.kernel.core.interpreter import Interpreter
34
35
36 #-------------------------------------------------------------------------------
37 # Base classes and utilities
38 #-------------------------------------------------------------------------------
39
40 class IMultiEngineBaseTestCase(object):
41 """Basic utilities for working with multiengine tests.
42
43 Some subclass should define:
44
45 * self.multiengine
46 * self.engines to keep track of engines for clean up"""
47
48 def createShell(self):
49 return Interpreter()
50
51 def addEngine(self, n=1):
52 for i in range(n):
53 e = es.EngineService()
54 e.startService()
55 regDict = self.controller.register_engine(es.QueuedEngine(e), None)
56 e.id = regDict['id']
57 self.engines.append(e)
58
59
60 def testf(x):
61 return 2.0*x
62
63
64 globala = 99
65
66
67 def testg(x):
68 return globala*x
69
70
71 def isdid(did):
72 if not isinstance(did, str):
73 return False
74 if not len(did)==40:
75 return False
76 return True
77
78
79 def _raise_it(f):
80 try:
81 f.raiseException()
82 except CompositeError, e:
83 e.raise_exception()
84
85 #-------------------------------------------------------------------------------
86 # IMultiEngineTestCase
87 #-------------------------------------------------------------------------------
88
89 class IMultiEngineTestCase(IMultiEngineBaseTestCase):
90 """A test for any object that implements IEngineMultiplexer.
91
92 self.multiengine must be defined and implement IEngineMultiplexer.
93 """
94
95 def testIMultiEngineInterface(self):
96 """Does self.engine claim to implement IEngineCore?"""
97 self.assert_(me.IEngineMultiplexer.providedBy(self.multiengine))
98 self.assert_(me.IMultiEngine.providedBy(self.multiengine))
99
100 def testIEngineMultiplexerInterfaceMethods(self):
101 """Does self.engine have the methods and attributes in IEngineCore."""
102 for m in list(me.IEngineMultiplexer):
103 self.assert_(hasattr(self.multiengine, m))
104
105 def testIEngineMultiplexerDeferreds(self):
106 self.addEngine(1)
107 d= self.multiengine.execute('a=5', targets=0)
108 d.addCallback(lambda _: self.multiengine.push(dict(a=5),targets=0))
109 d.addCallback(lambda _: self.multiengine.push(dict(a=5, b='asdf', c=[1,2,3]),targets=0))
110 d.addCallback(lambda _: self.multiengine.pull(('a','b','c'),targets=0))
111 d.addCallback(lambda _: self.multiengine.get_result(targets=0))
112 d.addCallback(lambda _: self.multiengine.reset(targets=0))
113 d.addCallback(lambda _: self.multiengine.keys(targets=0))
114 d.addCallback(lambda _: self.multiengine.push_serialized(dict(a=newserialized.serialize(10)),targets=0))
115 d.addCallback(lambda _: self.multiengine.pull_serialized('a',targets=0))
116 d.addCallback(lambda _: self.multiengine.clear_queue(targets=0))
117 d.addCallback(lambda _: self.multiengine.queue_status(targets=0))
118 return d
119
120 def testInvalidEngineID(self):
121 self.addEngine(1)
122 badID = 100
123 d = self.multiengine.execute('a=5', targets=badID)
124 d.addErrback(lambda f: self.assertRaises(InvalidEngineID, f.raiseException))
125 d.addCallback(lambda _: self.multiengine.push(dict(a=5), targets=badID))
126 d.addErrback(lambda f: self.assertRaises(InvalidEngineID, f.raiseException))
127 d.addCallback(lambda _: self.multiengine.pull('a', targets=badID))
128 d.addErrback(lambda f: self.assertRaises(InvalidEngineID, f.raiseException))
129 d.addCallback(lambda _: self.multiengine.reset(targets=badID))
130 d.addErrback(lambda f: self.assertRaises(InvalidEngineID, f.raiseException))
131 d.addCallback(lambda _: self.multiengine.keys(targets=badID))
132 d.addErrback(lambda f: self.assertRaises(InvalidEngineID, f.raiseException))
133 d.addCallback(lambda _: self.multiengine.push_serialized(dict(a=newserialized.serialize(10)), targets=badID))
134 d.addErrback(lambda f: self.assertRaises(InvalidEngineID, f.raiseException))
135 d.addCallback(lambda _: self.multiengine.pull_serialized('a', targets=badID))
136 d.addErrback(lambda f: self.assertRaises(InvalidEngineID, f.raiseException))
137 d.addCallback(lambda _: self.multiengine.queue_status(targets=badID))
138 d.addErrback(lambda f: self.assertRaises(InvalidEngineID, f.raiseException))
139 return d
140
141 def testNoEnginesRegistered(self):
142 badID = 'all'
143 d= self.multiengine.execute('a=5', targets=badID)
144 d.addErrback(lambda f: self.assertRaises(NoEnginesRegistered, f.raiseException))
145 d.addCallback(lambda _: self.multiengine.push(dict(a=5), targets=badID))
146 d.addErrback(lambda f: self.assertRaises(NoEnginesRegistered, f.raiseException))
147 d.addCallback(lambda _: self.multiengine.pull('a', targets=badID))
148 d.addErrback(lambda f: self.assertRaises(NoEnginesRegistered, f.raiseException))
149 d.addCallback(lambda _: self.multiengine.get_result(targets=badID))
150 d.addErrback(lambda f: self.assertRaises(NoEnginesRegistered, f.raiseException))
151 d.addCallback(lambda _: self.multiengine.reset(targets=badID))
152 d.addErrback(lambda f: self.assertRaises(NoEnginesRegistered, f.raiseException))
153 d.addCallback(lambda _: self.multiengine.keys(targets=badID))
154 d.addErrback(lambda f: self.assertRaises(NoEnginesRegistered, f.raiseException))
155 d.addCallback(lambda _: self.multiengine.push_serialized(dict(a=newserialized.serialize(10)), targets=badID))
156 d.addErrback(lambda f: self.assertRaises(NoEnginesRegistered, f.raiseException))
157 d.addCallback(lambda _: self.multiengine.pull_serialized('a', targets=badID))
158 d.addErrback(lambda f: self.assertRaises(NoEnginesRegistered, f.raiseException))
159 d.addCallback(lambda _: self.multiengine.queue_status(targets=badID))
160 d.addErrback(lambda f: self.assertRaises(NoEnginesRegistered, f.raiseException))
161 return d
162
163 def runExecuteAll(self, d, cmd, shell):
164 actual = shell.execute(cmd)
165 d.addCallback(lambda _: self.multiengine.execute(cmd))
166 def compare(result):
167 for r in result:
168 actual['id'] = r['id']
169 self.assertEquals(r, actual)
170 d.addCallback(compare)
171
172 def testExecuteAll(self):
173 self.addEngine(4)
174 d= defer.Deferred()
175 shell = Interpreter()
176 for cmd in validCommands:
177 self.runExecuteAll(d, cmd, shell)
178 d.callback(None)
179 return d
180
181 # The following two methods show how to do parametrized
182 # tests. This is really slick! Same is used above.
183 def runExecuteFailures(self, cmd, exc):
184 self.addEngine(4)
185 d= self.multiengine.execute(cmd)
186 d.addErrback(lambda f: self.assertRaises(exc, _raise_it, f))
187 return d
188
189 @parametric
190 def testExecuteFailures(cls):
191 return [(cls.runExecuteFailures,cmd,exc) for
192 cmd,exc in invalidCommands]
193
194 def testPushPull(self):
195 self.addEngine(1)
196 objs = [10,"hi there",1.2342354,{"p":(1,2)}]
197 d= self.multiengine.push(dict(key=objs[0]), targets=0)
198 d.addCallback(lambda _: self.multiengine.pull('key', targets=0))
199 d.addCallback(lambda r: self.assertEquals(r, [objs[0]]))
200 d.addCallback(lambda _: self.multiengine.push(dict(key=objs[1]), targets=0))
201 d.addCallback(lambda _: self.multiengine.pull('key', targets=0))
202 d.addCallback(lambda r: self.assertEquals(r, [objs[1]]))
203 d.addCallback(lambda _: self.multiengine.push(dict(key=objs[2]), targets=0))
204 d.addCallback(lambda _: self.multiengine.pull('key', targets=0))
205 d.addCallback(lambda r: self.assertEquals(r, [objs[2]]))
206 d.addCallback(lambda _: self.multiengine.push(dict(key=objs[3]), targets=0))
207 d.addCallback(lambda _: self.multiengine.pull('key', targets=0))
208 d.addCallback(lambda r: self.assertEquals(r, [objs[3]]))
209 d.addCallback(lambda _: self.multiengine.reset(targets=0))
210 d.addCallback(lambda _: self.multiengine.pull('a', targets=0))
211 d.addErrback(lambda f: self.assertRaises(NameError, _raise_it, f))
212 d.addCallback(lambda _: self.multiengine.push(dict(a=10,b=20)))
213 d.addCallback(lambda _: self.multiengine.pull(('a','b')))
214 d.addCallback(lambda r: self.assertEquals(r, [[10,20]]))
215 return d
216
217 def testPushPullAll(self):
218 self.addEngine(4)
219 d= self.multiengine.push(dict(a=10))
220 d.addCallback(lambda _: self.multiengine.pull('a'))
221 d.addCallback(lambda r: self.assert_(r==[10,10,10,10]))
222 d.addCallback(lambda _: self.multiengine.push(dict(a=10, b=20)))
223 d.addCallback(lambda _: self.multiengine.pull(('a','b')))
224 d.addCallback(lambda r: self.assert_(r==4*[[10,20]]))
225 d.addCallback(lambda _: self.multiengine.push(dict(a=10, b=20), targets=0))
226 d.addCallback(lambda _: self.multiengine.pull(('a','b'), targets=0))
227 d.addCallback(lambda r: self.assert_(r==[[10,20]]))
228 d.addCallback(lambda _: self.multiengine.push(dict(a=None, b=None), targets=0))
229 d.addCallback(lambda _: self.multiengine.pull(('a','b'), targets=0))
230 d.addCallback(lambda r: self.assert_(r==[[None,None]]))
231 return d
232
233 def testPushPullSerialized(self):
234 self.addEngine(1)
235 objs = [10,"hi there",1.2342354,{"p":(1,2)}]
236 d= self.multiengine.push_serialized(dict(key=newserialized.serialize(objs[0])), targets=0)
237 d.addCallback(lambda _: self.multiengine.pull_serialized('key', targets=0))
238 d.addCallback(lambda serial: newserialized.IUnSerialized(serial[0]).getObject())
239 d.addCallback(lambda r: self.assertEquals(r, objs[0]))
240 d.addCallback(lambda _: self.multiengine.push_serialized(dict(key=newserialized.serialize(objs[1])), targets=0))
241 d.addCallback(lambda _: self.multiengine.pull_serialized('key', targets=0))
242 d.addCallback(lambda serial: newserialized.IUnSerialized(serial[0]).getObject())
243 d.addCallback(lambda r: self.assertEquals(r, objs[1]))
244 d.addCallback(lambda _: self.multiengine.push_serialized(dict(key=newserialized.serialize(objs[2])), targets=0))
245 d.addCallback(lambda _: self.multiengine.pull_serialized('key', targets=0))
246 d.addCallback(lambda serial: newserialized.IUnSerialized(serial[0]).getObject())
247 d.addCallback(lambda r: self.assertEquals(r, objs[2]))
248 d.addCallback(lambda _: self.multiengine.push_serialized(dict(key=newserialized.serialize(objs[3])), targets=0))
249 d.addCallback(lambda _: self.multiengine.pull_serialized('key', targets=0))
250 d.addCallback(lambda serial: newserialized.IUnSerialized(serial[0]).getObject())
251 d.addCallback(lambda r: self.assertEquals(r, objs[3]))
252 d.addCallback(lambda _: self.multiengine.push(dict(a=10,b=range(5)), targets=0))
253 d.addCallback(lambda _: self.multiengine.pull_serialized(('a','b'), targets=0))
254 d.addCallback(lambda serial: [newserialized.IUnSerialized(s).getObject() for s in serial[0]])
255 d.addCallback(lambda r: self.assertEquals(r, [10, range(5)]))
256 d.addCallback(lambda _: self.multiengine.reset(targets=0))
257 d.addCallback(lambda _: self.multiengine.pull_serialized('a', targets=0))
258 d.addErrback(lambda f: self.assertRaises(NameError, _raise_it, f))
259 return d
260
261 objs = [10,"hi there",1.2342354,{"p":(1,2)}]
262 d= defer.succeed(None)
263 for o in objs:
264 self.multiengine.push_serialized(0, key=newserialized.serialize(o))
265 value = self.multiengine.pull_serialized(0, 'key')
266 value.addCallback(lambda serial: newserialized.IUnSerialized(serial[0]).getObject())
267 d = self.assertDeferredEquals(value,o,d)
268 return d
269
270 def runGetResultAll(self, d, cmd, shell):
271 actual = shell.execute(cmd)
272 d.addCallback(lambda _: self.multiengine.execute(cmd))
273 d.addCallback(lambda _: self.multiengine.get_result())
274 def compare(result):
275 for r in result:
276 actual['id'] = r['id']
277 self.assertEquals(r, actual)
278 d.addCallback(compare)
279
280 def testGetResultAll(self):
281 self.addEngine(4)
282 d= defer.Deferred()
283 shell = Interpreter()
284 for cmd in validCommands:
285 self.runGetResultAll(d, cmd, shell)
286 d.callback(None)
287 return d
288
289 def testGetResultDefault(self):
290 self.addEngine(1)
291 target = 0
292 cmd = 'a=5'
293 shell = self.createShell()
294 shellResult = shell.execute(cmd)
295 def popit(dikt, key):
296 dikt.pop(key)
297 return dikt
298 d= self.multiengine.execute(cmd, targets=target)
299 d.addCallback(lambda _: self.multiengine.get_result(targets=target))
300 d.addCallback(lambda r: self.assertEquals(shellResult, popit(r[0],'id')))
301 return d
302
303 def testGetResultFailure(self):
304 self.addEngine(1)
305 d= self.multiengine.get_result(None, targets=0)
306 d.addErrback(lambda f: self.assertRaises(IndexError, _raise_it, f))
307 d.addCallback(lambda _: self.multiengine.get_result(10, targets=0))
308 d.addErrback(lambda f: self.assertRaises(IndexError, _raise_it, f))
309 return d
310
311 def testPushFunction(self):
312 self.addEngine(1)
313 d= self.multiengine.push_function(dict(f=testf), targets=0)
314 d.addCallback(lambda _: self.multiengine.execute('result = f(10)', targets=0))
315 d.addCallback(lambda _: self.multiengine.pull('result', targets=0))
316 d.addCallback(lambda r: self.assertEquals(r[0], testf(10)))
317 d.addCallback(lambda _: self.multiengine.push(dict(globala=globala), targets=0))
318 d.addCallback(lambda _: self.multiengine.push_function(dict(g=testg), targets=0))
319 d.addCallback(lambda _: self.multiengine.execute('result = g(10)', targets=0))
320 d.addCallback(lambda _: self.multiengine.pull('result', targets=0))
321 d.addCallback(lambda r: self.assertEquals(r[0], testg(10)))
322 return d
323
324 def testPullFunction(self):
325 self.addEngine(1)
326 d= self.multiengine.push(dict(a=globala), targets=0)
327 d.addCallback(lambda _: self.multiengine.push_function(dict(f=testf), targets=0))
328 d.addCallback(lambda _: self.multiengine.pull_function('f', targets=0))
329 d.addCallback(lambda r: self.assertEquals(r[0](10), testf(10)))
330 d.addCallback(lambda _: self.multiengine.execute("def g(x): return x*x", targets=0))
331 d.addCallback(lambda _: self.multiengine.pull_function(('f','g'),targets=0))
332 d.addCallback(lambda r: self.assertEquals((r[0][0](10),r[0][1](10)), (testf(10), 100)))
333 return d
334
335 def testPushFunctionAll(self):
336 self.addEngine(4)
337 d= self.multiengine.push_function(dict(f=testf))
338 d.addCallback(lambda _: self.multiengine.execute('result = f(10)'))
339 d.addCallback(lambda _: self.multiengine.pull('result'))
340 d.addCallback(lambda r: self.assertEquals(r, 4*[testf(10)]))
341 d.addCallback(lambda _: self.multiengine.push(dict(globala=globala)))
342 d.addCallback(lambda _: self.multiengine.push_function(dict(testg=testg)))
343 d.addCallback(lambda _: self.multiengine.execute('result = testg(10)'))
344 d.addCallback(lambda _: self.multiengine.pull('result'))
345 d.addCallback(lambda r: self.assertEquals(r, 4*[testg(10)]))
346 return d
347
348 def testPullFunctionAll(self):
349 self.addEngine(4)
350 d= self.multiengine.push_function(dict(f=testf))
351 d.addCallback(lambda _: self.multiengine.pull_function('f'))
352 d.addCallback(lambda r: self.assertEquals([func(10) for func in r], 4*[testf(10)]))
353 return d
354
355 def testGetIDs(self):
356 self.addEngine(1)
357 d= self.multiengine.get_ids()
358 d.addCallback(lambda r: self.assertEquals(r, [0]))
359 d.addCallback(lambda _: self.addEngine(3))
360 d.addCallback(lambda _: self.multiengine.get_ids())
361 d.addCallback(lambda r: self.assertEquals(r, [0,1,2,3]))
362 return d
363
364 def testClearQueue(self):
365 self.addEngine(4)
366 d= self.multiengine.clear_queue()
367 d.addCallback(lambda r: self.assertEquals(r,4*[None]))
368 return d
369
370 def testQueueStatus(self):
371 self.addEngine(4)
372 d= self.multiengine.queue_status(targets=0)
373 d.addCallback(lambda r: self.assert_(isinstance(r[0],tuple)))
374 return d
375
376 def testGetSetProperties(self):
377 self.addEngine(4)
378 dikt = dict(a=5, b='asdf', c=True, d=None, e=range(5))
379 d= self.multiengine.set_properties(dikt)
380 d.addCallback(lambda r: self.multiengine.get_properties())
381 d.addCallback(lambda r: self.assertEquals(r, 4*[dikt]))
382 d.addCallback(lambda r: self.multiengine.get_properties(('c',)))
383 d.addCallback(lambda r: self.assertEquals(r, 4*[{'c': dikt['c']}]))
384 d.addCallback(lambda r: self.multiengine.set_properties(dict(c=False)))
385 d.addCallback(lambda r: self.multiengine.get_properties(('c', 'd')))
386 d.addCallback(lambda r: self.assertEquals(r, 4*[dict(c=False, d=None)]))
387 return d
388
389 def testClearProperties(self):
390 self.addEngine(4)
391 dikt = dict(a=5, b='asdf', c=True, d=None, e=range(5))
392 d= self.multiengine.set_properties(dikt)
393 d.addCallback(lambda r: self.multiengine.clear_properties())
394 d.addCallback(lambda r: self.multiengine.get_properties())
395 d.addCallback(lambda r: self.assertEquals(r, 4*[{}]))
396 return d
397
398 def testDelHasProperties(self):
399 self.addEngine(4)
400 dikt = dict(a=5, b='asdf', c=True, d=None, e=range(5))
401 d= self.multiengine.set_properties(dikt)
402 d.addCallback(lambda r: self.multiengine.del_properties(('b','e')))
403 d.addCallback(lambda r: self.multiengine.has_properties(('a','b','c','d','e')))
404 d.addCallback(lambda r: self.assertEquals(r, 4*[[True, False, True, True, False]]))
405 return d
406
407 Parametric(IMultiEngineTestCase)
408
409 #-------------------------------------------------------------------------------
410 # ISynchronousMultiEngineTestCase
411 #-------------------------------------------------------------------------------
412
413 class ISynchronousMultiEngineTestCase(IMultiEngineBaseTestCase):
414
415 def testISynchronousMultiEngineInterface(self):
416 """Does self.engine claim to implement IEngineCore?"""
417 self.assert_(me.ISynchronousEngineMultiplexer.providedBy(self.multiengine))
418 self.assert_(me.ISynchronousMultiEngine.providedBy(self.multiengine))
419
420 def testExecute(self):
421 self.addEngine(4)
422 execute = self.multiengine.execute
423 d= execute('a=5', targets=0, block=True)
424 d.addCallback(lambda r: self.assert_(len(r)==1))
425 d.addCallback(lambda _: execute('b=10'))
426 d.addCallback(lambda r: self.assert_(len(r)==4))
427 d.addCallback(lambda _: execute('c=30', block=False))
428 d.addCallback(lambda did: self.assert_(isdid(did)))
429 d.addCallback(lambda _: execute('d=[0,1,2]', block=False))
430 d.addCallback(lambda did: self.multiengine.get_pending_deferred(did, True))
431 d.addCallback(lambda r: self.assert_(len(r)==4))
432 return d
433
434 def testPushPull(self):
435 data = dict(a=10, b=1.05, c=range(10), d={'e':(1,2),'f':'hi'})
436 self.addEngine(4)
437 push = self.multiengine.push
438 pull = self.multiengine.pull
439 d= push({'data':data}, targets=0)
440 d.addCallback(lambda r: pull('data', targets=0))
441 d.addCallback(lambda r: self.assertEqual(r,[data]))
442 d.addCallback(lambda _: push({'data':data}))
443 d.addCallback(lambda r: pull('data'))
444 d.addCallback(lambda r: self.assertEqual(r,4*[data]))
445 d.addCallback(lambda _: push({'data':data}, block=False))
446 d.addCallback(lambda did: self.multiengine.get_pending_deferred(did, True))
447 d.addCallback(lambda _: pull('data', block=False))
448 d.addCallback(lambda did: self.multiengine.get_pending_deferred(did, True))
449 d.addCallback(lambda r: self.assertEqual(r,4*[data]))
450 d.addCallback(lambda _: push(dict(a=10,b=20)))
451 d.addCallback(lambda _: pull(('a','b')))
452 d.addCallback(lambda r: self.assertEquals(r, 4*[[10,20]]))
453 return d
454
455 def testPushPullFunction(self):
456 self.addEngine(4)
457 pushf = self.multiengine.push_function
458 pullf = self.multiengine.pull_function
459 push = self.multiengine.push
460 pull = self.multiengine.pull
461 execute = self.multiengine.execute
462 d= pushf({'testf':testf}, targets=0)
463 d.addCallback(lambda r: pullf('testf', targets=0))
464 d.addCallback(lambda r: self.assertEqual(r[0](1.0), testf(1.0)))
465 d.addCallback(lambda _: execute('r = testf(10)', targets=0))
466 d.addCallback(lambda _: pull('r', targets=0))
467 d.addCallback(lambda r: self.assertEquals(r[0], testf(10)))
468 d.addCallback(lambda _: pushf({'testf':testf}, block=False))
469 d.addCallback(lambda did: self.multiengine.get_pending_deferred(did, True))
470 d.addCallback(lambda _: pullf('testf', block=False))
471 d.addCallback(lambda did: self.multiengine.get_pending_deferred(did, True))
472 d.addCallback(lambda r: self.assertEqual(r[0](1.0), testf(1.0)))
473 d.addCallback(lambda _: execute("def g(x): return x*x", targets=0))
474 d.addCallback(lambda _: pullf(('testf','g'),targets=0))
475 d.addCallback(lambda r: self.assertEquals((r[0][0](10),r[0][1](10)), (testf(10), 100)))
476 return d
477
478 def testGetResult(self):
479 shell = Interpreter()
480 result1 = shell.execute('a=10')
481 result1['id'] = 0
482 result2 = shell.execute('b=20')
483 result2['id'] = 0
484 execute= self.multiengine.execute
485 get_result = self.multiengine.get_result
486 self.addEngine(1)
487 d= execute('a=10')
488 d.addCallback(lambda _: get_result())
489 d.addCallback(lambda r: self.assertEquals(r[0], result1))
490 d.addCallback(lambda _: execute('b=20'))
491 d.addCallback(lambda _: get_result(1))
492 d.addCallback(lambda r: self.assertEquals(r[0], result1))
493 d.addCallback(lambda _: get_result(2, block=False))
494 d.addCallback(lambda did: self.multiengine.get_pending_deferred(did, True))
495 d.addCallback(lambda r: self.assertEquals(r[0], result2))
496 return d
497
498 def testResetAndKeys(self):
499 self.addEngine(1)
500
501 #Blocking mode
502 d= self.multiengine.push(dict(a=10, b=20, c=range(10)), targets=0)
503 d.addCallback(lambda _: self.multiengine.keys(targets=0))
504 def keys_found(keys):
505 self.assert_('a' in keys[0])
506 self.assert_('b' in keys[0])
507 self.assert_('b' in keys[0])
508 d.addCallback(keys_found)
509 d.addCallback(lambda _: self.multiengine.reset(targets=0))
510 d.addCallback(lambda _: self.multiengine.keys(targets=0))
511 def keys_not_found(keys):
512 self.assert_('a' not in keys[0])
513 self.assert_('b' not in keys[0])
514 self.assert_('b' not in keys[0])
515 d.addCallback(keys_not_found)
516
517 #Non-blocking mode
518 d.addCallback(lambda _: self.multiengine.push(dict(a=10, b=20, c=range(10)), targets=0))
519 d.addCallback(lambda _: self.multiengine.keys(targets=0, block=False))
520 d.addCallback(lambda did: self.multiengine.get_pending_deferred(did, True))
521 def keys_found(keys):
522 self.assert_('a' in keys[0])
523 self.assert_('b' in keys[0])
524 self.assert_('b' in keys[0])
525 d.addCallback(keys_found)
526 d.addCallback(lambda _: self.multiengine.reset(targets=0, block=False))
527 d.addCallback(lambda did: self.multiengine.get_pending_deferred(did, True))
528 d.addCallback(lambda _: self.multiengine.keys(targets=0, block=False))
529 d.addCallback(lambda did: self.multiengine.get_pending_deferred(did, True))
530 def keys_not_found(keys):
531 self.assert_('a' not in keys[0])
532 self.assert_('b' not in keys[0])
533 self.assert_('b' not in keys[0])
534 d.addCallback(keys_not_found)
535
536 return d
537
538 def testPushPullSerialized(self):
539 self.addEngine(1)
540 dikt = dict(a=10,b='hi there',c=1.2345,d={'p':(1,2)})
541 sdikt = {}
542 for k,v in dikt.iteritems():
543 sdikt[k] = newserialized.serialize(v)
544 d= self.multiengine.push_serialized(dict(a=sdikt['a']), targets=0)
545 d.addCallback(lambda _: self.multiengine.pull('a',targets=0))
546 d.addCallback(lambda r: self.assertEquals(r[0], dikt['a']))
547 d.addCallback(lambda _: self.multiengine.pull_serialized('a', targets=0))
548 d.addCallback(lambda serial: newserialized.IUnSerialized(serial[0]).getObject())
549 d.addCallback(lambda r: self.assertEquals(r, dikt['a']))
550 d.addCallback(lambda _: self.multiengine.push_serialized(sdikt, targets=0))
551 d.addCallback(lambda _: self.multiengine.pull_serialized(sdikt.keys(), targets=0))
552 d.addCallback(lambda serial: [newserialized.IUnSerialized(s).getObject() for s in serial[0]])
553 d.addCallback(lambda r: self.assertEquals(r, dikt.values()))
554 d.addCallback(lambda _: self.multiengine.reset(targets=0))
555 d.addCallback(lambda _: self.multiengine.pull_serialized('a', targets=0))
556 d.addErrback(lambda f: self.assertRaises(NameError, _raise_it, f))
557
558 #Non-blocking mode
559 d.addCallback(lambda r: self.multiengine.push_serialized(dict(a=sdikt['a']), targets=0, block=False))
560 d.addCallback(lambda did: self.multiengine.get_pending_deferred(did, True))
561 d.addCallback(lambda _: self.multiengine.pull('a',targets=0))
562 d.addCallback(lambda r: self.assertEquals(r[0], dikt['a']))
563 d.addCallback(lambda _: self.multiengine.pull_serialized('a', targets=0, block=False))
564 d.addCallback(lambda did: self.multiengine.get_pending_deferred(did, True))
565 d.addCallback(lambda serial: newserialized.IUnSerialized(serial[0]).getObject())
566 d.addCallback(lambda r: self.assertEquals(r, dikt['a']))
567 d.addCallback(lambda _: self.multiengine.push_serialized(sdikt, targets=0, block=False))
568 d.addCallback(lambda did: self.multiengine.get_pending_deferred(did, True))
569 d.addCallback(lambda _: self.multiengine.pull_serialized(sdikt.keys(), targets=0, block=False))
570 d.addCallback(lambda did: self.multiengine.get_pending_deferred(did, True))
571 d.addCallback(lambda serial: [newserialized.IUnSerialized(s).getObject() for s in serial[0]])
572 d.addCallback(lambda r: self.assertEquals(r, dikt.values()))
573 d.addCallback(lambda _: self.multiengine.reset(targets=0))
574 d.addCallback(lambda _: self.multiengine.pull_serialized('a', targets=0, block=False))
575 d.addCallback(lambda did: self.multiengine.get_pending_deferred(did, True))
576 d.addErrback(lambda f: self.assertRaises(NameError, _raise_it, f))
577 return d
578
579 def testClearQueue(self):
580 self.addEngine(4)
581 d= self.multiengine.clear_queue()
582 d.addCallback(lambda r: self.multiengine.clear_queue(block=False))
583 d.addCallback(lambda did: self.multiengine.get_pending_deferred(did, True))
584 d.addCallback(lambda r: self.assertEquals(r,4*[None]))
585 return d
586
587 def testQueueStatus(self):
588 self.addEngine(4)
589 d= self.multiengine.queue_status(targets=0)
590 d.addCallback(lambda r: self.assert_(isinstance(r[0],tuple)))
591 d.addCallback(lambda r: self.multiengine.queue_status(targets=0, block=False))
592 d.addCallback(lambda did: self.multiengine.get_pending_deferred(did, True))
593 d.addCallback(lambda r: self.assert_(isinstance(r[0],tuple)))
594 return d
595
596 def testGetIDs(self):
597 self.addEngine(1)
598 d= self.multiengine.get_ids()
599 d.addCallback(lambda r: self.assertEquals(r, [0]))
600 d.addCallback(lambda _: self.addEngine(3))
601 d.addCallback(lambda _: self.multiengine.get_ids())
602 d.addCallback(lambda r: self.assertEquals(r, [0,1,2,3]))
603 return d
604
605 def testGetSetProperties(self):
606 self.addEngine(4)
607 dikt = dict(a=5, b='asdf', c=True, d=None, e=range(5))
608 d= self.multiengine.set_properties(dikt)
609 d.addCallback(lambda r: self.multiengine.get_properties())
610 d.addCallback(lambda r: self.assertEquals(r, 4*[dikt]))
611 d.addCallback(lambda r: self.multiengine.get_properties(('c',)))
612 d.addCallback(lambda r: self.assertEquals(r, 4*[{'c': dikt['c']}]))
613 d.addCallback(lambda r: self.multiengine.set_properties(dict(c=False)))
614 d.addCallback(lambda r: self.multiengine.get_properties(('c', 'd')))
615 d.addCallback(lambda r: self.assertEquals(r, 4*[dict(c=False, d=None)]))
616
617 #Non-blocking
618 d.addCallback(lambda r: self.multiengine.set_properties(dikt, block=False))
619 d.addCallback(lambda did: self.multiengine.get_pending_deferred(did, True))
620 d.addCallback(lambda r: self.multiengine.get_properties(block=False))
621 d.addCallback(lambda did: self.multiengine.get_pending_deferred(did, True))
622 d.addCallback(lambda r: self.assertEquals(r, 4*[dikt]))
623 d.addCallback(lambda r: self.multiengine.get_properties(('c',), block=False))
624 d.addCallback(lambda did: self.multiengine.get_pending_deferred(did, True))
625 d.addCallback(lambda r: self.assertEquals(r, 4*[{'c': dikt['c']}]))
626 d.addCallback(lambda r: self.multiengine.set_properties(dict(c=False), block=False))
627 d.addCallback(lambda did: self.multiengine.get_pending_deferred(did, True))
628 d.addCallback(lambda r: self.multiengine.get_properties(('c', 'd'), block=False))
629 d.addCallback(lambda did: self.multiengine.get_pending_deferred(did, True))
630 d.addCallback(lambda r: self.assertEquals(r, 4*[dict(c=False, d=None)]))
631 return d
632
633 def testClearProperties(self):
634 self.addEngine(4)
635 dikt = dict(a=5, b='asdf', c=True, d=None, e=range(5))
636 d= self.multiengine.set_properties(dikt)
637 d.addCallback(lambda r: self.multiengine.clear_properties())
638 d.addCallback(lambda r: self.multiengine.get_properties())
639 d.addCallback(lambda r: self.assertEquals(r, 4*[{}]))
640
641 #Non-blocking
642 d.addCallback(lambda r: self.multiengine.set_properties(dikt, block=False))
643 d.addCallback(lambda did: self.multiengine.get_pending_deferred(did, True))
644 d.addCallback(lambda r: self.multiengine.clear_properties(block=False))
645 d.addCallback(lambda did: self.multiengine.get_pending_deferred(did, True))
646 d.addCallback(lambda r: self.multiengine.get_properties(block=False))
647 d.addCallback(lambda did: self.multiengine.get_pending_deferred(did, True))
648 d.addCallback(lambda r: self.assertEquals(r, 4*[{}]))
649 return d
650
651 def testDelHasProperties(self):
652 self.addEngine(4)
653 dikt = dict(a=5, b='asdf', c=True, d=None, e=range(5))
654 d= self.multiengine.set_properties(dikt)
655 d.addCallback(lambda r: self.multiengine.del_properties(('b','e')))
656 d.addCallback(lambda r: self.multiengine.has_properties(('a','b','c','d','e')))
657 d.addCallback(lambda r: self.assertEquals(r, 4*[[True, False, True, True, False]]))
658
659 #Non-blocking
660 d.addCallback(lambda r: self.multiengine.set_properties(dikt, block=False))
661 d.addCallback(lambda did: self.multiengine.get_pending_deferred(did, True))
662 d.addCallback(lambda r: self.multiengine.del_properties(('b','e'), block=False))
663 d.addCallback(lambda did: self.multiengine.get_pending_deferred(did, True))
664 d.addCallback(lambda r: self.multiengine.has_properties(('a','b','c','d','e'), block=False))
665 d.addCallback(lambda did: self.multiengine.get_pending_deferred(did, True))
666 d.addCallback(lambda r: self.assertEquals(r, 4*[[True, False, True, True, False]]))
667 return d
668
669 def test_clear_pending_deferreds(self):
670 self.addEngine(4)
671 did_list = []
672 d= self.multiengine.execute('a=10',block=False)
673 d.addCallback(lambda did: did_list.append(did))
674 d.addCallback(lambda _: self.multiengine.push(dict(b=10),block=False))
675 d.addCallback(lambda did: did_list.append(did))
676 d.addCallback(lambda _: self.multiengine.pull(('a','b'),block=False))
677 d.addCallback(lambda did: did_list.append(did))
678 d.addCallback(lambda _: self.multiengine.clear_pending_deferreds())
679 d.addCallback(lambda _: self.multiengine.get_pending_deferred(did_list[0],True))
680 d.addErrback(lambda f: self.assertRaises(InvalidDeferredID, f.raiseException))
681 d.addCallback(lambda _: self.multiengine.get_pending_deferred(did_list[1],True))
682 d.addErrback(lambda f: self.assertRaises(InvalidDeferredID, f.raiseException))
683 d.addCallback(lambda _: self.multiengine.get_pending_deferred(did_list[2],True))
684 d.addErrback(lambda f: self.assertRaises(InvalidDeferredID, f.raiseException))
685 return d
686
687 #-------------------------------------------------------------------------------
688 # Coordinator test cases
689 #-------------------------------------------------------------------------------
690
691 class IMultiEngineCoordinatorTestCase(object):
692
693 def testScatterGather(self):
694 self.addEngine(4)
695 d= self.multiengine.scatter('a', range(16))
696 d.addCallback(lambda r: self.multiengine.gather('a'))
697 d.addCallback(lambda r: self.assertEquals(r, range(16)))
698 d.addCallback(lambda _: self.multiengine.gather('asdf'))
699 d.addErrback(lambda f: self.assertRaises(NameError, _raise_it, f))
700 return d
701
702 def testScatterGatherNumpy(self):
703 try:
704 import numpy
705 from numpy.testing.utils import assert_array_equal, assert_array_almost_equal
706 except:
707 return
708 else:
709 self.addEngine(4)
710 a = numpy.arange(16)
711 d = self.multiengine.scatter('a', a)
712 d.addCallback(lambda r: self.multiengine.gather('a'))
713 d.addCallback(lambda r: assert_array_equal(r, a))
714 return d
715
716 def testMap(self):
717 self.addEngine(4)
718 def f(x):
719 return x**2
720 data = range(16)
721 d= self.multiengine.map(f, data)
722 d.addCallback(lambda r: self.assertEquals(r,[f(x) for x in data]))
723 return d
724
725
726 class ISynchronousMultiEngineCoordinatorTestCase(IMultiEngineCoordinatorTestCase):
727
728 def testScatterGatherNonblocking(self):
729 self.addEngine(4)
730 d= self.multiengine.scatter('a', range(16), block=False)
731 d.addCallback(lambda did: self.multiengine.get_pending_deferred(did, True))
732 d.addCallback(lambda r: self.multiengine.gather('a', block=False))
733 d.addCallback(lambda did: self.multiengine.get_pending_deferred(did, True))
734 d.addCallback(lambda r: self.assertEquals(r, range(16)))
735 return d
736
737 def testScatterGatherNumpyNonblocking(self):
738 try:
739 import numpy
740 from numpy.testing.utils import assert_array_equal, assert_array_almost_equal
741 except:
742 return
743 else:
744 self.addEngine(4)
745 a = numpy.arange(16)
746 d = self.multiengine.scatter('a', a, block=False)
747 d.addCallback(lambda did: self.multiengine.get_pending_deferred(did, True))
748 d.addCallback(lambda r: self.multiengine.gather('a', block=False))
749 d.addCallback(lambda did: self.multiengine.get_pending_deferred(did, True))
750 d.addCallback(lambda r: assert_array_equal(r, a))
751 return d
752
753 def testMapNonblocking(self):
754 self.addEngine(4)
755 def f(x):
756 return x**2
757 data = range(16)
758 d= self.multiengine.map(f, data, block=False)
759 d.addCallback(lambda did: self.multiengine.get_pending_deferred(did, True))
760 d.addCallback(lambda r: self.assertEquals(r,[f(x) for x in data]))
761 return d
762
763 def test_clear_pending_deferreds(self):
764 self.addEngine(4)
765 did_list = []
766 d= self.multiengine.scatter('a',range(16),block=False)
767 d.addCallback(lambda did: did_list.append(did))
768 d.addCallback(lambda _: self.multiengine.gather('a',block=False))
769 d.addCallback(lambda did: did_list.append(did))
770 d.addCallback(lambda _: self.multiengine.map(lambda x: x, range(16),block=False))
771 d.addCallback(lambda did: did_list.append(did))
772 d.addCallback(lambda _: self.multiengine.clear_pending_deferreds())
773 d.addCallback(lambda _: self.multiengine.get_pending_deferred(did_list[0],True))
774 d.addErrback(lambda f: self.assertRaises(InvalidDeferredID, f.raiseException))
775 d.addCallback(lambda _: self.multiengine.get_pending_deferred(did_list[1],True))
776 d.addErrback(lambda f: self.assertRaises(InvalidDeferredID, f.raiseException))
777 d.addCallback(lambda _: self.multiengine.get_pending_deferred(did_list[2],True))
778 d.addErrback(lambda f: self.assertRaises(InvalidDeferredID, f.raiseException))
779 return d
780
781 #-------------------------------------------------------------------------------
782 # Extras test cases
783 #-------------------------------------------------------------------------------
784
785 class IMultiEngineExtrasTestCase(object):
786
787 def testZipPull(self):
788 self.addEngine(4)
789 d= self.multiengine.push(dict(a=10,b=20))
790 d.addCallback(lambda r: self.multiengine.zip_pull(('a','b')))
791 d.addCallback(lambda r: self.assert_(r, [4*[10],4*[20]]))
792 return d
793
794 def testRun(self):
795 self.addEngine(4)
796 import tempfile
797 fname = tempfile.mktemp('foo.py')
798 f= open(fname, 'w')
799 f.write('a = 10\nb=30')
800 f.close()
801 d= self.multiengine.run(fname)
802 d.addCallback(lambda r: self.multiengine.pull(('a','b')))
803 d.addCallback(lambda r: self.assertEquals(r, 4*[[10,30]]))
804 return d
805
806
807 class ISynchronousMultiEngineExtrasTestCase(IMultiEngineExtrasTestCase):
808
809 def testZipPullNonblocking(self):
810 self.addEngine(4)
811 d= self.multiengine.push(dict(a=10,b=20))
812 d.addCallback(lambda r: self.multiengine.zip_pull(('a','b'), block=False))
813 d.addCallback(lambda did: self.multiengine.get_pending_deferred(did, True))
814 d.addCallback(lambda r: self.assert_(r, [4*[10],4*[20]]))
815 return d
816
817 def testRunNonblocking(self):
818 self.addEngine(4)
819 import tempfile
820 fname = tempfile.mktemp('foo.py')
821 f= open(fname, 'w')
822 f.write('a = 10\nb=30')
823 f.close()
824 d= self.multiengine.run(fname, block=False)
825 d.addCallback(lambda did: self.multiengine.get_pending_deferred(did, True))
826 d.addCallback(lambda r: self.multiengine.pull(('a','b')))
827 d.addCallback(lambda r: self.assertEquals(r, 4*[[10,30]]))
828 return d
829
830
831 #-------------------------------------------------------------------------------
832 # IFullSynchronousMultiEngineTestCase
833 #-------------------------------------------------------------------------------
834
835 class IFullSynchronousMultiEngineTestCase(ISynchronousMultiEngineTestCase,
836 ISynchronousMultiEngineCoordinatorTestCase,
837 ISynchronousMultiEngineExtrasTestCase):
838 pass
@@ -0,0 +1,158 b''
1 #!/usr/bin/env python
2 # encoding: utf-8
3
4 __docformat__ = "restructuredtext en"
5
6 #-------------------------------------------------------------------------------
7 # Copyright (C) 2008 The IPython Development Team
8 #
9 # Distributed under the terms of the BSD License. The full license is in
10 # the file COPYING, distributed as part of this software.
11 #-------------------------------------------------------------------------------
12
13 #-------------------------------------------------------------------------------
14 # Imports
15 #-------------------------------------------------------------------------------
16
17 import time
18
19 from IPython.kernel import task, engineservice as es
20 from IPython.kernel.util import printer
21 from IPython.kernel import error
22
23 #-------------------------------------------------------------------------------
24 # Tests
25 #-------------------------------------------------------------------------------
26
27 def _raise_it(f):
28 try:
29 f.raiseException()
30 except CompositeError, e:
31 e.raise_exception()
32
33 class TaskTestBase(object):
34
35 def addEngine(self, n=1):
36 for i in range(n):
37 e = es.EngineService()
38 e.startService()
39 regDict = self.controller.register_engine(es.QueuedEngine(e), None)
40 e.id = regDict['id']
41 self.engines.append(e)
42
43
44 class ITaskControllerTestCase(TaskTestBase):
45
46 def testTaskIDs(self):
47 self.addEngine(1)
48 d = self.tc.run(task.Task('a=5'))
49 d.addCallback(lambda r: self.assertEquals(r, 0))
50 d.addCallback(lambda r: self.tc.run(task.Task('a=5')))
51 d.addCallback(lambda r: self.assertEquals(r, 1))
52 d.addCallback(lambda r: self.tc.run(task.Task('a=5')))
53 d.addCallback(lambda r: self.assertEquals(r, 2))
54 d.addCallback(lambda r: self.tc.run(task.Task('a=5')))
55 d.addCallback(lambda r: self.assertEquals(r, 3))
56 return d
57
58 def testAbort(self):
59 """Cannot do a proper abort test, because blocking execution prevents
60 abort from being called before task completes"""
61 self.addEngine(1)
62 t = task.Task('a=5')
63 d = self.tc.abort(0)
64 d.addErrback(lambda f: self.assertRaises(IndexError, f.raiseException))
65 d.addCallback(lambda _:self.tc.run(t))
66 d.addCallback(self.tc.abort)
67 d.addErrback(lambda f: self.assertRaises(IndexError, f.raiseException))
68 return d
69
70 def testAbortType(self):
71 self.addEngine(1)
72 d = self.tc.abort('asdfadsf')
73 d.addErrback(lambda f: self.assertRaises(TypeError, f.raiseException))
74 return d
75
76 def testClears(self):
77 self.addEngine(1)
78 t = task.Task('a=1', clear_before=True, pull='b', clear_after=True)
79 d = self.multiengine.execute('b=1', targets=0)
80 d.addCallback(lambda _: self.tc.run(t))
81 d.addCallback(lambda tid: self.tc.get_task_result(tid,block=True))
82 d.addCallback(lambda tr: tr.failure)
83 d.addErrback(lambda f: self.assertRaises(NameError, f.raiseException))
84 d.addCallback(lambda _:self.multiengine.pull('a', targets=0))
85 d.addErrback(lambda f: self.assertRaises(NameError, _raise_it, f))
86 return d
87
88 def testSimpleRetries(self):
89 self.addEngine(1)
90 t = task.Task("i += 1\nassert i == 16", pull='i',retries=10)
91 t2 = task.Task("i += 1\nassert i == 16", pull='i',retries=10)
92 d = self.multiengine.execute('i=0', targets=0)
93 d.addCallback(lambda r: self.tc.run(t))
94 d.addCallback(self.tc.get_task_result, block=True)
95 d.addCallback(lambda tr: tr.ns.i)
96 d.addErrback(lambda f: self.assertRaises(AssertionError, f.raiseException))
97
98 d.addCallback(lambda r: self.tc.run(t2))
99 d.addCallback(self.tc.get_task_result, block=True)
100 d.addCallback(lambda tr: tr.ns.i)
101 d.addCallback(lambda r: self.assertEquals(r, 16))
102 return d
103
104 def testRecoveryTasks(self):
105 self.addEngine(1)
106 t = task.Task("i=16", pull='i')
107 t2 = task.Task("raise Exception", recovery_task=t, retries = 2)
108
109 d = self.tc.run(t2)
110 d.addCallback(self.tc.get_task_result, block=True)
111 d.addCallback(lambda tr: tr.ns.i)
112 d.addCallback(lambda r: self.assertEquals(r, 16))
113 return d
114
115 # def testInfiniteRecoveryLoop(self):
116 # self.addEngine(1)
117 # t = task.Task("raise Exception", retries = 5)
118 # t2 = task.Task("assert True", retries = 2, recovery_task = t)
119 # t.recovery_task = t2
120 #
121 # d = self.tc.run(t)
122 # d.addCallback(self.tc.get_task_result, block=True)
123 # d.addCallback(lambda tr: tr.ns.i)
124 # d.addBoth(printer)
125 # d.addErrback(lambda f: self.assertRaises(AssertionError, f.raiseException))
126 # return d
127 #
128 def testSetupNS(self):
129 self.addEngine(1)
130 d = self.multiengine.execute('a=0', targets=0)
131 ns = dict(a=1, b=0)
132 t = task.Task("", push=ns, pull=['a','b'])
133 d.addCallback(lambda r: self.tc.run(t))
134 d.addCallback(self.tc.get_task_result, block=True)
135 d.addCallback(lambda tr: {'a':tr.ns.a, 'b':tr['b']})
136 d.addCallback(lambda r: self.assertEquals(r, ns))
137 return d
138
139 def testTaskResults(self):
140 self.addEngine(1)
141 t1 = task.Task('a=5', pull='a')
142 d = self.tc.run(t1)
143 d.addCallback(self.tc.get_task_result, block=True)
144 d.addCallback(lambda tr: (tr.ns.a,tr['a'],tr.failure, tr.raiseException()))
145 d.addCallback(lambda r: self.assertEquals(r, (5,5,None,None)))
146
147 t2 = task.Task('7=5')
148 d.addCallback(lambda r: self.tc.run(t2))
149 d.addCallback(self.tc.get_task_result, block=True)
150 d.addCallback(lambda tr: tr.ns)
151 d.addErrback(lambda f: self.assertRaises(SyntaxError, f.raiseException))
152
153 t3 = task.Task('', pull='b')
154 d.addCallback(lambda r: self.tc.run(t3))
155 d.addCallback(self.tc.get_task_result, block=True)
156 d.addCallback(lambda tr: tr.ns)
157 d.addErrback(lambda f: self.assertRaises(NameError, f.raiseException))
158 return d
@@ -0,0 +1,40 b''
1 # encoding: utf-8
2
3 """This file contains unittests for the kernel.engineservice.py module.
4
5 Things that should be tested:
6
7 - Should the EngineService return Deferred objects?
8 - Run the same tests that are run in shell.py.
9 - Make sure that the Interface is really implemented.
10 - The startService and stopService methods.
11 """
12
13 __docformat__ = "restructuredtext en"
14
15 #-------------------------------------------------------------------------------
16 # Copyright (C) 2008 The IPython Development Team
17 #
18 # Distributed under the terms of the BSD License. The full license is in
19 # the file COPYING, distributed as part of this software.
20 #-------------------------------------------------------------------------------
21
22 #-------------------------------------------------------------------------------
23 # Imports
24 #-------------------------------------------------------------------------------
25
26 from twisted.application.service import IService
27 from IPython.kernel.controllerservice import ControllerService
28 from IPython.kernel.tests import multienginetest as met
29 from controllertest import IControllerCoreTestCase
30 from IPython.testutils.util import DeferredTestCase
31
32 class BasicControllerServiceTest(DeferredTestCase,
33 IControllerCoreTestCase):
34
35 def setUp(self):
36 self.controller = ControllerService()
37 self.controller.startService()
38
39 def tearDown(self):
40 self.controller.stopService()
@@ -0,0 +1,89 b''
1 # encoding: utf-8
2
3 """This file contains unittests for the enginepb.py module."""
4
5 __docformat__ = "restructuredtext en"
6
7 #-------------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
13
14 #-------------------------------------------------------------------------------
15 # Imports
16 #-------------------------------------------------------------------------------
17
18
19 from twisted.python import components
20 from twisted.internet import reactor, defer
21 from twisted.spread import pb
22 from twisted.internet.base import DelayedCall
23 DelayedCall.debug = True
24
25 import zope.interface as zi
26
27 from IPython.kernel.fcutil import Tub, UnauthenticatedTub
28 from IPython.kernel import engineservice as es
29 from IPython.testutils.util import DeferredTestCase
30 from IPython.kernel.controllerservice import IControllerBase
31 from IPython.kernel.enginefc import FCRemoteEngineRefFromService, IEngineBase
32 from IPython.kernel.engineservice import IEngineQueued
33 from IPython.kernel.engineconnector import EngineConnector
34
35 from IPython.kernel.tests.engineservicetest import \
36 IEngineCoreTestCase, \
37 IEngineSerializedTestCase, \
38 IEngineQueuedTestCase
39
40 class EngineFCTest(DeferredTestCase,
41 IEngineCoreTestCase,
42 IEngineSerializedTestCase,
43 IEngineQueuedTestCase
44 ):
45
46 zi.implements(IControllerBase)
47
48 def setUp(self):
49
50 # Start a server and append to self.servers
51 self.controller_reference = FCRemoteEngineRefFromService(self)
52 self.controller_tub = Tub()
53 self.controller_tub.listenOn('tcp:10105:interface=127.0.0.1')
54 self.controller_tub.setLocation('127.0.0.1:10105')
55
56 furl = self.controller_tub.registerReference(self.controller_reference)
57 self.controller_tub.startService()
58
59 # Start an EngineService and append to services/client
60 self.engine_service = es.EngineService()
61 self.engine_service.startService()
62 self.engine_tub = Tub()
63 self.engine_tub.startService()
64 engine_connector = EngineConnector(self.engine_tub)
65 d = engine_connector.connect_to_controller(self.engine_service, furl)
66 # This deferred doesn't fire until after register_engine has returned and
67 # thus, self.engine has been defined and the tets can proceed.
68 return d
69
70 def tearDown(self):
71 dlist = []
72 # Shut down the engine
73 d = self.engine_tub.stopService()
74 dlist.append(d)
75 # Shut down the controller
76 d = self.controller_tub.stopService()
77 dlist.append(d)
78 return defer.DeferredList(dlist)
79
80 #---------------------------------------------------------------------------
81 # Make me look like a basic controller
82 #---------------------------------------------------------------------------
83
84 def register_engine(self, engine_ref, id=None, ip=None, port=None, pid=None):
85 self.engine = IEngineQueued(IEngineBase(engine_ref))
86 return {'id':id}
87
88 def unregister_engine(self, id):
89 pass No newline at end of file
@@ -0,0 +1,64 b''
1 # encoding: utf-8
2
3 """This file contains unittests for the kernel.engineservice.py module.
4
5 Things that should be tested:
6
7 - Should the EngineService return Deferred objects?
8 - Run the same tests that are run in shell.py.
9 - Make sure that the Interface is really implemented.
10 - The startService and stopService methods.
11 """
12
13 __docformat__ = "restructuredtext en"
14
15 #-------------------------------------------------------------------------------
16 # Copyright (C) 2008 The IPython Development Team
17 #
18 # Distributed under the terms of the BSD License. The full license is in
19 # the file COPYING, distributed as part of this software.
20 #-------------------------------------------------------------------------------
21
22 #-------------------------------------------------------------------------------
23 # Imports
24 #-------------------------------------------------------------------------------
25
26 from twisted.internet import defer
27 from twisted.application.service import IService
28
29 from IPython.kernel import engineservice as es
30 from IPython.testutils.util import DeferredTestCase
31 from IPython.kernel.tests.engineservicetest import \
32 IEngineCoreTestCase, \
33 IEngineSerializedTestCase, \
34 IEngineQueuedTestCase, \
35 IEnginePropertiesTestCase
36
37
38 class BasicEngineServiceTest(DeferredTestCase,
39 IEngineCoreTestCase,
40 IEngineSerializedTestCase,
41 IEnginePropertiesTestCase):
42
43 def setUp(self):
44 self.engine = es.EngineService()
45 self.engine.startService()
46
47 def tearDown(self):
48 return self.engine.stopService()
49
50 class QueuedEngineServiceTest(DeferredTestCase,
51 IEngineCoreTestCase,
52 IEngineSerializedTestCase,
53 IEnginePropertiesTestCase,
54 IEngineQueuedTestCase):
55
56 def setUp(self):
57 self.rawEngine = es.EngineService()
58 self.rawEngine.startService()
59 self.engine = es.IEngineQueued(self.rawEngine)
60
61 def tearDown(self):
62 return self.rawEngine.stopService()
63
64
@@ -0,0 +1,52 b''
1 # encoding: utf-8
2
3 """"""
4
5 __docformat__ = "restructuredtext en"
6
7 #-------------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
13
14 #-------------------------------------------------------------------------------
15 # Imports
16 #-------------------------------------------------------------------------------
17
18 from twisted.internet import defer
19 from IPython.testutils.util import DeferredTestCase
20 from IPython.kernel.controllerservice import ControllerService
21 from IPython.kernel import multiengine as me
22 from IPython.kernel.tests.multienginetest import (IMultiEngineTestCase,
23 ISynchronousMultiEngineTestCase)
24
25
26 class BasicMultiEngineTestCase(DeferredTestCase, IMultiEngineTestCase):
27
28 def setUp(self):
29 self.controller = ControllerService()
30 self.controller.startService()
31 self.multiengine = me.IMultiEngine(self.controller)
32 self.engines = []
33
34 def tearDown(self):
35 self.controller.stopService()
36 for e in self.engines:
37 e.stopService()
38
39
40 class SynchronousMultiEngineTestCase(DeferredTestCase, ISynchronousMultiEngineTestCase):
41
42 def setUp(self):
43 self.controller = ControllerService()
44 self.controller.startService()
45 self.multiengine = me.ISynchronousMultiEngine(me.IMultiEngine(self.controller))
46 self.engines = []
47
48 def tearDown(self):
49 self.controller.stopService()
50 for e in self.engines:
51 e.stopService()
52
@@ -0,0 +1,68 b''
1 #!/usr/bin/env python
2 # encoding: utf-8
3
4 __docformat__ = "restructuredtext en"
5
6 #-------------------------------------------------------------------------------
7 # Copyright (C) 2008 The IPython Development Team
8 #
9 # Distributed under the terms of the BSD License. The full license is in
10 # the file COPYING, distributed as part of this software.
11 #-------------------------------------------------------------------------------
12
13 #-------------------------------------------------------------------------------
14 # Imports
15 #-------------------------------------------------------------------------------
16
17 from twisted.internet import defer, reactor
18
19 from IPython.kernel.fcutil import Tub, UnauthenticatedTub
20
21 from IPython.testutils.util import DeferredTestCase
22 from IPython.kernel.controllerservice import ControllerService
23 from IPython.kernel.multiengine import IMultiEngine
24 from IPython.kernel.tests.multienginetest import IFullSynchronousMultiEngineTestCase
25 from IPython.kernel.multienginefc import IFCSynchronousMultiEngine
26 from IPython.kernel import multiengine as me
27 from IPython.kernel.clientconnector import ClientConnector
28
29
30 class FullSynchronousMultiEngineTestCase(DeferredTestCase, IFullSynchronousMultiEngineTestCase):
31
32 def setUp(self):
33
34 self.engines = []
35
36 self.controller = ControllerService()
37 self.controller.startService()
38 self.imultiengine = IMultiEngine(self.controller)
39 self.mec_referenceable = IFCSynchronousMultiEngine(self.imultiengine)
40
41 self.controller_tub = Tub()
42 self.controller_tub.listenOn('tcp:10105:interface=127.0.0.1')
43 self.controller_tub.setLocation('127.0.0.1:10105')
44
45 furl = self.controller_tub.registerReference(self.mec_referenceable)
46 self.controller_tub.startService()
47
48 self.client_tub = ClientConnector()
49 d = self.client_tub.get_multiengine_client(furl)
50 d.addCallback(self.handle_got_client)
51 return d
52
53 def handle_got_client(self, client):
54 self.multiengine = client
55
56 def tearDown(self):
57 dlist = []
58 # Shut down the multiengine client
59 d = self.client_tub.tub.stopService()
60 dlist.append(d)
61 # Shut down the engines
62 for e in self.engines:
63 e.stopService()
64 # Shut down the controller
65 d = self.controller_tub.stopService()
66 d.addBoth(lambda _: self.controller.stopService())
67 dlist.append(d)
68 return defer.DeferredList(dlist)
@@ -0,0 +1,99 b''
1 # encoding: utf-8
2
3 """This file contains unittests for the shell.py module."""
4
5 __docformat__ = "restructuredtext en"
6
7 #-------------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
13
14 #-------------------------------------------------------------------------------
15 # Imports
16 #-------------------------------------------------------------------------------
17
18 import zope.interface as zi
19 from twisted.trial import unittest
20 from IPython.testutils.util import DeferredTestCase
21
22 from IPython.kernel.newserialized import \
23 ISerialized, \
24 IUnSerialized, \
25 Serialized, \
26 UnSerialized, \
27 SerializeIt, \
28 UnSerializeIt
29
30 #-------------------------------------------------------------------------------
31 # Tests
32 #-------------------------------------------------------------------------------
33
34 class SerializedTestCase(unittest.TestCase):
35
36 def setUp(self):
37 pass
38
39 def tearDown(self):
40 pass
41
42 def testSerializedInterfaces(self):
43
44 us = UnSerialized({'a':10, 'b':range(10)})
45 s = ISerialized(us)
46 uss = IUnSerialized(s)
47 self.assert_(ISerialized.providedBy(s))
48 self.assert_(IUnSerialized.providedBy(us))
49 self.assert_(IUnSerialized.providedBy(uss))
50 for m in list(ISerialized):
51 self.assert_(hasattr(s, m))
52 for m in list(IUnSerialized):
53 self.assert_(hasattr(us, m))
54 for m in list(IUnSerialized):
55 self.assert_(hasattr(uss, m))
56
57 def testPickleSerialized(self):
58 obj = {'a':1.45345, 'b':'asdfsdf', 'c':10000L}
59 original = UnSerialized(obj)
60 originalSer = ISerialized(original)
61 firstData = originalSer.getData()
62 firstTD = originalSer.getTypeDescriptor()
63 firstMD = originalSer.getMetadata()
64 self.assert_(firstTD == 'pickle')
65 self.assert_(firstMD == {})
66 unSerialized = IUnSerialized(originalSer)
67 secondObj = unSerialized.getObject()
68 for k, v in secondObj.iteritems():
69 self.assert_(obj[k] == v)
70 secondSer = ISerialized(UnSerialized(secondObj))
71 self.assert_(firstData == secondSer.getData())
72 self.assert_(firstTD == secondSer.getTypeDescriptor() )
73 self.assert_(firstMD == secondSer.getMetadata())
74
75 def testNDArraySerialized(self):
76 try:
77 import numpy
78 except ImportError:
79 pass
80 else:
81 a = numpy.linspace(0.0, 1.0, 1000)
82 unSer1 = UnSerialized(a)
83 ser1 = ISerialized(unSer1)
84 td = ser1.getTypeDescriptor()
85 self.assert_(td == 'ndarray')
86 md = ser1.getMetadata()
87 self.assert_(md['shape'] == a.shape)
88 self.assert_(md['dtype'] == a.dtype.str)
89 buff = ser1.getData()
90 self.assert_(buff == numpy.getbuffer(a))
91 s = Serialized(buff, td, md)
92 us = IUnSerialized(s)
93 final = us.getObject()
94 self.assert_(numpy.getbuffer(a) == numpy.getbuffer(final))
95 self.assert_(a.dtype.str == final.dtype.str)
96 self.assert_(a.shape == final.shape)
97
98
99 No newline at end of file
@@ -0,0 +1,215 b''
1 #!/usr/bin/env python
2 # encoding: utf-8
3
4 """Tests for pendingdeferred.py"""
5
6 __docformat__ = "restructuredtext en"
7
8 #-------------------------------------------------------------------------------
9 # Copyright (C) 2008 The IPython Development Team
10 #
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
13 #-------------------------------------------------------------------------------
14
15 #-------------------------------------------------------------------------------
16 # Imports
17 #-------------------------------------------------------------------------------
18
19 from twisted.internet import defer
20 from twisted.python import failure
21
22 from IPython.testutils import tcommon
23 from IPython.testutils.tcommon import *
24 from IPython.testutils.util import DeferredTestCase
25 import IPython.kernel.pendingdeferred as pd
26 from IPython.kernel import error
27 from IPython.kernel.util import printer
28
29
30 #-------------------------------------------------------------------------------
31 # Setup for inline and standalone doctests
32 #-------------------------------------------------------------------------------
33
34
35 # If you have standalone doctests in a separate file, set their names in the
36 # dt_files variable (as a single string or a list thereof):
37 dt_files = []
38
39 # If you have any modules whose docstrings should be scanned for embedded tests
40 # as examples accorging to standard doctest practice, set them here (as a
41 # single string or a list thereof):
42 dt_modules = []
43
44 #-------------------------------------------------------------------------------
45 # Regular Unittests
46 #-------------------------------------------------------------------------------
47
48
49 class Foo(object):
50
51 def bar(self, bahz):
52 return defer.succeed('blahblah: %s' % bahz)
53
54 class TwoPhaseFoo(pd.PendingDeferredManager):
55
56 def __init__(self, foo):
57 self.foo = foo
58 pd.PendingDeferredManager.__init__(self)
59
60 @pd.two_phase
61 def bar(self, bahz):
62 return self.foo.bar(bahz)
63
64 class PendingDeferredManagerTest(DeferredTestCase):
65
66 def setUp(self):
67 self.pdm = pd.PendingDeferredManager()
68
69 def tearDown(self):
70 pass
71
72 def testBasic(self):
73 dDict = {}
74 # Create 10 deferreds and save them
75 for i in range(10):
76 d = defer.Deferred()
77 did = self.pdm.save_pending_deferred(d)
78 dDict[did] = d
79 # Make sure they are begin saved
80 for k in dDict.keys():
81 self.assert_(self.pdm.quick_has_id(k))
82 # Get the pending deferred (block=True), then callback with 'foo' and compare
83 for did in dDict.keys()[0:5]:
84 d = self.pdm.get_pending_deferred(did,block=True)
85 dDict[did].callback('foo')
86 d.addCallback(lambda r: self.assert_(r=='foo'))
87 # Get the pending deferreds with (block=False) and make sure ResultNotCompleted is raised
88 for did in dDict.keys()[5:10]:
89 d = self.pdm.get_pending_deferred(did,block=False)
90 d.addErrback(lambda f: self.assertRaises(error.ResultNotCompleted, f.raiseException))
91 # Now callback the last 5, get them and compare.
92 for did in dDict.keys()[5:10]:
93 dDict[did].callback('foo')
94 d = self.pdm.get_pending_deferred(did,block=False)
95 d.addCallback(lambda r: self.assert_(r=='foo'))
96
97 def test_save_then_delete(self):
98 d = defer.Deferred()
99 did = self.pdm.save_pending_deferred(d)
100 self.assert_(self.pdm.quick_has_id(did))
101 self.pdm.delete_pending_deferred(did)
102 self.assert_(not self.pdm.quick_has_id(did))
103
104 def test_save_get_delete(self):
105 d = defer.Deferred()
106 did = self.pdm.save_pending_deferred(d)
107 d2 = self.pdm.get_pending_deferred(did,True)
108 d2.addErrback(lambda f: self.assertRaises(error.AbortedPendingDeferredError, f.raiseException))
109 self.pdm.delete_pending_deferred(did)
110 return d2
111
112 def test_double_get(self):
113 d = defer.Deferred()
114 did = self.pdm.save_pending_deferred(d)
115 d2 = self.pdm.get_pending_deferred(did,True)
116 d3 = self.pdm.get_pending_deferred(did,True)
117 d3.addErrback(lambda f: self.assertRaises(error.InvalidDeferredID, f.raiseException))
118
119 def test_get_after_callback(self):
120 d = defer.Deferred()
121 did = self.pdm.save_pending_deferred(d)
122 d.callback('foo')
123 d2 = self.pdm.get_pending_deferred(did,True)
124 d2.addCallback(lambda r: self.assertEquals(r,'foo'))
125 self.assert_(not self.pdm.quick_has_id(did))
126
127 def test_get_before_callback(self):
128 d = defer.Deferred()
129 did = self.pdm.save_pending_deferred(d)
130 d2 = self.pdm.get_pending_deferred(did,True)
131 d.callback('foo')
132 d2.addCallback(lambda r: self.assertEquals(r,'foo'))
133 self.assert_(not self.pdm.quick_has_id(did))
134 d = defer.Deferred()
135 did = self.pdm.save_pending_deferred(d)
136 d2 = self.pdm.get_pending_deferred(did,True)
137 d2.addCallback(lambda r: self.assertEquals(r,'foo'))
138 d.callback('foo')
139 self.assert_(not self.pdm.quick_has_id(did))
140
141 def test_get_after_errback(self):
142 class MyError(Exception):
143 pass
144 d = defer.Deferred()
145 did = self.pdm.save_pending_deferred(d)
146 d.errback(failure.Failure(MyError('foo')))
147 d2 = self.pdm.get_pending_deferred(did,True)
148 d2.addErrback(lambda f: self.assertRaises(MyError, f.raiseException))
149 self.assert_(not self.pdm.quick_has_id(did))
150
151 def test_get_before_errback(self):
152 class MyError(Exception):
153 pass
154 d = defer.Deferred()
155 did = self.pdm.save_pending_deferred(d)
156 d2 = self.pdm.get_pending_deferred(did,True)
157 d.errback(failure.Failure(MyError('foo')))
158 d2.addErrback(lambda f: self.assertRaises(MyError, f.raiseException))
159 self.assert_(not self.pdm.quick_has_id(did))
160 d = defer.Deferred()
161 did = self.pdm.save_pending_deferred(d)
162 d2 = self.pdm.get_pending_deferred(did,True)
163 d2.addErrback(lambda f: self.assertRaises(MyError, f.raiseException))
164 d.errback(failure.Failure(MyError('foo')))
165 self.assert_(not self.pdm.quick_has_id(did))
166
167 def test_noresult_noblock(self):
168 d = defer.Deferred()
169 did = self.pdm.save_pending_deferred(d)
170 d2 = self.pdm.get_pending_deferred(did,False)
171 d2.addErrback(lambda f: self.assertRaises(error.ResultNotCompleted, f.raiseException))
172
173 def test_with_callbacks(self):
174 d = defer.Deferred()
175 d.addCallback(lambda r: r+' foo')
176 d.addCallback(lambda r: r+' bar')
177 did = self.pdm.save_pending_deferred(d)
178 d2 = self.pdm.get_pending_deferred(did,True)
179 d.callback('bam')
180 d2.addCallback(lambda r: self.assertEquals(r,'bam foo bar'))
181
182 def test_with_errbacks(self):
183 class MyError(Exception):
184 pass
185 d = defer.Deferred()
186 d.addCallback(lambda r: 'foo')
187 d.addErrback(lambda f: 'caught error')
188 did = self.pdm.save_pending_deferred(d)
189 d2 = self.pdm.get_pending_deferred(did,True)
190 d.errback(failure.Failure(MyError('bam')))
191 d2.addErrback(lambda f: self.assertRaises(MyError, f.raiseException))
192
193 def test_nested_deferreds(self):
194 d = defer.Deferred()
195 d2 = defer.Deferred()
196 d.addCallback(lambda r: d2)
197 did = self.pdm.save_pending_deferred(d)
198 d.callback('foo')
199 d3 = self.pdm.get_pending_deferred(did,False)
200 d3.addErrback(lambda f: self.assertRaises(error.ResultNotCompleted, f.raiseException))
201 d2.callback('bar')
202 d3 = self.pdm.get_pending_deferred(did,False)
203 d3.addCallback(lambda r: self.assertEquals(r,'bar'))
204
205 #-------------------------------------------------------------------------------
206 # Regular Unittests
207 #-------------------------------------------------------------------------------
208
209 # This ensures that the code will run either standalone as a script, or that it
210 # can be picked up by Twisted's `trial` test wrapper to run all the tests.
211 if tcommon.pexpect is not None:
212 if __name__ == '__main__':
213 unittest.main(testLoader=IPDocTestLoader(dt_files,dt_modules))
214 else:
215 testSuite = lambda : makeTestSuite(__name__,dt_files,dt_modules)
@@ -0,0 +1,47 b''
1 # encoding: utf-8
2
3 """This file contains unittests for the kernel.task.py module."""
4
5 __docformat__ = "restructuredtext en"
6
7 #-------------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
13
14 #-------------------------------------------------------------------------------
15 # Imports
16 #-------------------------------------------------------------------------------
17
18 import time
19
20 from twisted.internet import defer
21 from twisted.trial import unittest
22
23 from IPython.kernel import task, controllerservice as cs, engineservice as es
24 from IPython.kernel.multiengine import IMultiEngine
25 from IPython.testutils.util import DeferredTestCase
26 from IPython.kernel.tests.tasktest import ITaskControllerTestCase
27
28 #-------------------------------------------------------------------------------
29 # Tests
30 #-------------------------------------------------------------------------------
31
32 class BasicTaskControllerTestCase(DeferredTestCase, ITaskControllerTestCase):
33
34 def setUp(self):
35 self.controller = cs.ControllerService()
36 self.controller.startService()
37 self.multiengine = IMultiEngine(self.controller)
38 self.tc = task.ITaskController(self.controller)
39 self.tc.failurePenalty = 0
40 self.engines=[]
41
42 def tearDown(self):
43 self.controller.stopService()
44 for e in self.engines:
45 e.stopService()
46
47
@@ -0,0 +1,86 b''
1 #!/usr/bin/env python
2 # encoding: utf-8
3
4 __docformat__ = "restructuredtext en"
5
6 #-------------------------------------------------------------------------------
7 # Copyright (C) 2008 The IPython Development Team
8 #
9 # Distributed under the terms of the BSD License. The full license is in
10 # the file COPYING, distributed as part of this software.
11 #-------------------------------------------------------------------------------
12
13 #-------------------------------------------------------------------------------
14 # Imports
15 #-------------------------------------------------------------------------------
16
17 import time
18
19 from twisted.internet import defer, reactor
20
21 from IPython.kernel.fcutil import Tub, UnauthenticatedTub
22
23 from IPython.kernel import task as taskmodule
24 from IPython.kernel import controllerservice as cs
25 import IPython.kernel.multiengine as me
26 from IPython.testutils.util import DeferredTestCase
27 from IPython.kernel.multienginefc import IFCSynchronousMultiEngine
28 from IPython.kernel.taskfc import IFCTaskController
29 from IPython.kernel.util import printer
30 from IPython.kernel.tests.tasktest import ITaskControllerTestCase
31 from IPython.kernel.clientconnector import ClientConnector
32
33 #-------------------------------------------------------------------------------
34 # Tests
35 #-------------------------------------------------------------------------------
36
37 class TaskTest(DeferredTestCase, ITaskControllerTestCase):
38
39 def setUp(self):
40
41 self.engines = []
42
43 self.controller = cs.ControllerService()
44 self.controller.startService()
45 self.imultiengine = me.IMultiEngine(self.controller)
46 self.itc = taskmodule.ITaskController(self.controller)
47 self.itc.failurePenalty = 0
48
49 self.mec_referenceable = IFCSynchronousMultiEngine(self.imultiengine)
50 self.tc_referenceable = IFCTaskController(self.itc)
51
52 self.controller_tub = Tub()
53 self.controller_tub.listenOn('tcp:10105:interface=127.0.0.1')
54 self.controller_tub.setLocation('127.0.0.1:10105')
55
56 mec_furl = self.controller_tub.registerReference(self.mec_referenceable)
57 tc_furl = self.controller_tub.registerReference(self.tc_referenceable)
58 self.controller_tub.startService()
59
60 self.client_tub = ClientConnector()
61 d = self.client_tub.get_multiengine_client(mec_furl)
62 d.addCallback(self.handle_mec_client)
63 d.addCallback(lambda _: self.client_tub.get_task_client(tc_furl))
64 d.addCallback(self.handle_tc_client)
65 return d
66
67 def handle_mec_client(self, client):
68 self.multiengine = client
69
70 def handle_tc_client(self, client):
71 self.tc = client
72
73 def tearDown(self):
74 dlist = []
75 # Shut down the multiengine client
76 d = self.client_tub.tub.stopService()
77 dlist.append(d)
78 # Shut down the engines
79 for e in self.engines:
80 e.stopService()
81 # Shut down the controller
82 d = self.controller_tub.stopService()
83 d.addBoth(lambda _: self.controller.stopService())
84 dlist.append(d)
85 return defer.DeferredList(dlist)
86
@@ -0,0 +1,206 b''
1 #!/usr/bin/env python
2 # encoding: utf-8
3
4 """Things directly related to all of twisted."""
5
6 __docformat__ = "restructuredtext en"
7
8 #-------------------------------------------------------------------------------
9 # Copyright (C) 2008 The IPython Development Team
10 #
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
13 #-------------------------------------------------------------------------------
14
15 #-------------------------------------------------------------------------------
16 # Imports
17 #-------------------------------------------------------------------------------
18
19 import threading, Queue, atexit
20 import twisted
21
22 from twisted.internet import defer, reactor
23 from twisted.python import log, failure
24
25 #-------------------------------------------------------------------------------
26 # Classes related to twisted and threads
27 #-------------------------------------------------------------------------------
28
29
30 class ReactorInThread(threading.Thread):
31 """Run the twisted reactor in a different thread.
32
33 For the process to be able to exit cleanly, do the following:
34
35 rit = ReactorInThread()
36 rit.setDaemon(True)
37 rit.start()
38
39 """
40
41 def run(self):
42 reactor.run(installSignalHandlers=0)
43 # self.join()
44
45 def stop(self):
46 # I don't think this does anything useful.
47 blockingCallFromThread(reactor.stop)
48 self.join()
49
50 if(twisted.version.major >= 8):
51 import twisted.internet.threads
52 def blockingCallFromThread(f, *a, **kw):
53 """
54 Run a function in the reactor from a thread, and wait for the result
55 synchronously, i.e. until the callback chain returned by the function get a
56 result.
57
58 Delegates to twisted.internet.threads.blockingCallFromThread(reactor, f, *a, **kw),
59 passing twisted.internet.reactor for the first argument.
60
61 @param f: the callable to run in the reactor thread
62 @type f: any callable.
63 @param a: the arguments to pass to C{f}.
64 @param kw: the keyword arguments to pass to C{f}.
65
66 @return: the result of the callback chain.
67 @raise: any error raised during the callback chain.
68 """
69 return twisted.internet.threads.blockingCallFromThread(reactor, f, *a, **kw)
70
71 else:
72 def blockingCallFromThread(f, *a, **kw):
73 """
74 Run a function in the reactor from a thread, and wait for the result
75 synchronously, i.e. until the callback chain returned by the function get a
76 result.
77
78 @param f: the callable to run in the reactor thread
79 @type f: any callable.
80 @param a: the arguments to pass to C{f}.
81 @param kw: the keyword arguments to pass to C{f}.
82
83 @return: the result of the callback chain.
84 @raise: any error raised during the callback chain.
85 """
86 from twisted.internet import reactor
87 queue = Queue.Queue()
88 def _callFromThread():
89 result = defer.maybeDeferred(f, *a, **kw)
90 result.addBoth(queue.put)
91
92 reactor.callFromThread(_callFromThread)
93 result = queue.get()
94 if isinstance(result, failure.Failure):
95 # This makes it easier for the debugger to get access to the instance
96 try:
97 result.raiseException()
98 except Exception, e:
99 raise e
100 return result
101
102
103
104 #-------------------------------------------------------------------------------
105 # Things for managing deferreds
106 #-------------------------------------------------------------------------------
107
108
109 def parseResults(results):
110 """Pull out results/Failures from a DeferredList."""
111 return [x[1] for x in results]
112
113 def gatherBoth(dlist, fireOnOneCallback=0,
114 fireOnOneErrback=0,
115 consumeErrors=0,
116 logErrors=0):
117 """This is like gatherBoth, but sets consumeErrors=1."""
118 d = DeferredList(dlist, fireOnOneCallback, fireOnOneErrback,
119 consumeErrors, logErrors)
120 if not fireOnOneCallback:
121 d.addCallback(parseResults)
122 return d
123
124 SUCCESS = True
125 FAILURE = False
126
127 class DeferredList(defer.Deferred):
128 """I combine a group of deferreds into one callback.
129
130 I track a list of L{Deferred}s for their callbacks, and make a single
131 callback when they have all completed, a list of (success, result)
132 tuples, 'success' being a boolean.
133
134 Note that you can still use a L{Deferred} after putting it in a
135 DeferredList. For example, you can suppress 'Unhandled error in Deferred'
136 messages by adding errbacks to the Deferreds *after* putting them in the
137 DeferredList, as a DeferredList won't swallow the errors. (Although a more
138 convenient way to do this is simply to set the consumeErrors flag)
139
140 Note: This is a modified version of the twisted.internet.defer.DeferredList
141 """
142
143 fireOnOneCallback = 0
144 fireOnOneErrback = 0
145
146 def __init__(self, deferredList, fireOnOneCallback=0, fireOnOneErrback=0,
147 consumeErrors=0, logErrors=0):
148 """Initialize a DeferredList.
149
150 @type deferredList: C{list} of L{Deferred}s
151 @param deferredList: The list of deferreds to track.
152 @param fireOnOneCallback: (keyword param) a flag indicating that
153 only one callback needs to be fired for me to call
154 my callback
155 @param fireOnOneErrback: (keyword param) a flag indicating that
156 only one errback needs to be fired for me to call
157 my errback
158 @param consumeErrors: (keyword param) a flag indicating that any errors
159 raised in the original deferreds should be
160 consumed by this DeferredList. This is useful to
161 prevent spurious warnings being logged.
162 """
163 self.resultList = [None] * len(deferredList)
164 defer.Deferred.__init__(self)
165 if len(deferredList) == 0 and not fireOnOneCallback:
166 self.callback(self.resultList)
167
168 # These flags need to be set *before* attaching callbacks to the
169 # deferreds, because the callbacks use these flags, and will run
170 # synchronously if any of the deferreds are already fired.
171 self.fireOnOneCallback = fireOnOneCallback
172 self.fireOnOneErrback = fireOnOneErrback
173 self.consumeErrors = consumeErrors
174 self.logErrors = logErrors
175 self.finishedCount = 0
176
177 index = 0
178 for deferred in deferredList:
179 deferred.addCallbacks(self._cbDeferred, self._cbDeferred,
180 callbackArgs=(index,SUCCESS),
181 errbackArgs=(index,FAILURE))
182 index = index + 1
183
184 def _cbDeferred(self, result, index, succeeded):
185 """(internal) Callback for when one of my deferreds fires.
186 """
187 self.resultList[index] = (succeeded, result)
188
189 self.finishedCount += 1
190 if not self.called:
191 if succeeded == SUCCESS and self.fireOnOneCallback:
192 self.callback((result, index))
193 elif succeeded == FAILURE and self.fireOnOneErrback:
194 # We have modified this to fire the errback chain with the actual
195 # Failure instance the originally occured rather than twisted's
196 # FirstError which wraps the failure
197 self.errback(result)
198 elif self.finishedCount == len(self.resultList):
199 self.callback(self.resultList)
200
201 if succeeded == FAILURE and self.logErrors:
202 log.err(result)
203 if succeeded == FAILURE and self.consumeErrors:
204 result = None
205
206 return result
@@ -0,0 +1,102 b''
1 # encoding: utf-8
2
3 """General utilities for kernel related things."""
4
5 __docformat__ = "restructuredtext en"
6
7 #-------------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
13
14 #-------------------------------------------------------------------------------
15 # Imports
16 #-------------------------------------------------------------------------------
17
18 import os, types
19
20
21 #-------------------------------------------------------------------------------
22 # Code
23 #-------------------------------------------------------------------------------
24
25 def tarModule(mod):
26 """Makes a tarball (as a string) of a locally imported module.
27
28 This method looks at the __file__ attribute of an imported module
29 and makes a tarball of the top level of the module. It then
30 reads the tarball into a binary string.
31
32 The method returns the tarball's name and the binary string
33 representing the tarball.
34
35 Notes:
36
37 - It will handle both single module files, as well as packages.
38 - The byte code files (\*.pyc) are not deleted.
39 - It has not been tested with modules containing extension code, but
40 it should work in most cases.
41 - There are cross platform issues.
42
43 """
44
45 if not isinstance(mod, types.ModuleType):
46 raise TypeError, "Pass an imported module to push_module"
47 module_dir, module_file = os.path.split(mod.__file__)
48
49 # Figure out what the module is called and where it is
50 print "Locating the module..."
51 if "__init__.py" in module_file: # package
52 module_name = module_dir.split("/")[-1]
53 module_dir = "/".join(module_dir.split("/")[:-1])
54 module_file = module_name
55 else: # Simple module
56 module_name = module_file.split(".")[0]
57 module_dir = module_dir
58 print "Module (%s) found in:\n%s" % (module_name, module_dir)
59
60 # Make a tarball of the module in the cwd
61 if module_dir:
62 os.system('tar -cf %s.tar -C %s %s' % \
63 (module_name, module_dir, module_file))
64 else: # must be the cwd
65 os.system('tar -cf %s.tar %s' % \
66 (module_name, module_file))
67
68 # Read the tarball into a binary string
69 tarball_name = module_name + ".tar"
70 tar_file = open(tarball_name,'rb')
71 fileString = tar_file.read()
72 tar_file.close()
73
74 # Remove the local copy of the tarball
75 #os.system("rm %s" % tarball_name)
76
77 return tarball_name, fileString
78
79 #from the Python Cookbook:
80
81 def curry(f, *curryArgs, **curryKWargs):
82 """Curry the function f with curryArgs and curryKWargs."""
83
84 def curried(*args, **kwargs):
85 dikt = dict(kwargs)
86 dikt.update(curryKWargs)
87 return f(*(curryArgs+args), **dikt)
88
89 return curried
90
91 #useful callbacks
92
93 def catcher(r):
94 pass
95
96 def printer(r, msg=''):
97 print "%s\n%r" % (msg, r)
98 return r
99
100
101
102
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
This diff has been collapsed as it changes many lines, (800 lines changed) Show them Hide them
@@ -0,0 +1,800 b''
1 #!/usr/bin/env python
2 """IPython-enhanced doctest module with unittest integration.
3
4 This module is heavily based on the standard library's doctest module, but
5 enhances it with IPython support. This enables docstrings to contain
6 unmodified IPython input and output pasted from real IPython sessions.
7
8 It should be possible to use this module as a drop-in replacement for doctest
9 whenever you wish to use IPython input.
10
11 Since the module absorbs all normal doctest functionality, you can use a mix of
12 both plain Python and IPython examples in any given module, though not in the
13 same docstring.
14
15 See a simple example at the bottom of this code which serves as self-test and
16 demonstration code. Simply run this file (use -v for details) to run the
17 tests.
18
19 This module also contains routines to ease the integration of doctests with
20 regular unittest-based testing. In particular, see the DocTestLoader class and
21 the makeTestSuite utility function.
22
23
24 Limitations:
25
26 - When generating examples for use as doctests, make sure that you have
27 pretty-printing OFF. This can be done either by starting ipython with the
28 flag '--nopprint', by setting pprint to 0 in your ipythonrc file, or by
29 interactively disabling it with %Pprint. This is required so that IPython
30 output matches that of normal Python, which is used by doctest for internal
31 execution.
32
33 - Do not rely on specific prompt numbers for results (such as using
34 '_34==True', for example). For IPython tests run via an external process
35 the prompt numbers may be different, and IPython tests run as normal python
36 code won't even have these special _NN variables set at all.
37
38 - IPython functions that produce output as a side-effect of calling a system
39 process (e.g. 'ls') can be doc-tested, but they must be handled in an
40 external IPython process. Such doctests must be tagged with:
41
42 # ipdoctest: EXTERNAL
43
44 so that the testing machinery handles them differently. Since these are run
45 via pexpect in an external process, they can't deal with exceptions or other
46 fancy featurs of regular doctests. You must limit such tests to simple
47 matching of the output. For this reason, I recommend you limit these kinds
48 of doctests to features that truly require a separate process, and use the
49 normal IPython ones (which have all the features of normal doctests) for
50 everything else. See the examples at the bottom of this file for a
51 comparison of what can be done with both types.
52 """
53
54 # Standard library imports
55 import __builtin__
56 import doctest
57 import inspect
58 import os
59 import re
60 import sys
61 import unittest
62
63 from doctest import *
64
65 # Our own imports
66 from IPython.tools import utils
67
68 ###########################################################################
69 #
70 # We must start our own ipython object and heavily muck with it so that all the
71 # modifications IPython makes to system behavior don't send the doctest
72 # machinery into a fit. This code should be considered a gross hack, but it
73 # gets the job done.
74
75 import IPython
76
77 # Hack to restore __main__, which ipython modifies upon startup
78 _main = sys.modules.get('__main__')
79 ipython = IPython.Shell.IPShell(['--classic','--noterm_title']).IP
80 sys.modules['__main__'] = _main
81
82 # Deactivate the various python system hooks added by ipython for
83 # interactive convenience so we don't confuse the doctest system
84 sys.displayhook = sys.__displayhook__
85 sys.excepthook = sys.__excepthook__
86
87 # So that ipython magics and aliases can be doctested
88 __builtin__._ip = IPython.ipapi.get()
89
90 # for debugging only!!!
91 #from IPython.Shell import IPShellEmbed;ipshell=IPShellEmbed(['--noterm_title']) # dbg
92
93
94 # runner
95 from IPython.irunner import IPythonRunner
96 iprunner = IPythonRunner(echo=False)
97
98 ###########################################################################
99
100 # A simple subclassing of the original with a different class name, so we can
101 # distinguish and treat differently IPython examples from pure python ones.
102 class IPExample(doctest.Example): pass
103
104 class IPExternalExample(doctest.Example):
105 """Doctest examples to be run in an external process."""
106
107 def __init__(self, source, want, exc_msg=None, lineno=0, indent=0,
108 options=None):
109 # Parent constructor
110 doctest.Example.__init__(self,source,want,exc_msg,lineno,indent,options)
111
112 # An EXTRA newline is needed to prevent pexpect hangs
113 self.source += '\n'
114
115 class IPDocTestParser(doctest.DocTestParser):
116 """
117 A class used to parse strings containing doctest examples.
118
119 Note: This is a version modified to properly recognize IPython input and
120 convert any IPython examples into valid Python ones.
121 """
122 # This regular expression is used to find doctest examples in a
123 # string. It defines three groups: `source` is the source code
124 # (including leading indentation and prompts); `indent` is the
125 # indentation of the first (PS1) line of the source code; and
126 # `want` is the expected output (including leading indentation).
127
128 # Classic Python prompts or default IPython ones
129 _PS1_PY = r'>>>'
130 _PS2_PY = r'\.\.\.'
131
132 _PS1_IP = r'In\ \[\d+\]:'
133 _PS2_IP = r'\ \ \ \.\.\.+:'
134
135 _RE_TPL = r'''
136 # Source consists of a PS1 line followed by zero or more PS2 lines.
137 (?P<source>
138 (?:^(?P<indent> [ ]*) (?P<ps1> %s) .*) # PS1 line
139 (?:\n [ ]* (?P<ps2> %s) .*)*) # PS2 lines
140 \n? # a newline
141 # Want consists of any non-blank lines that do not start with PS1.
142 (?P<want> (?:(?![ ]*$) # Not a blank line
143 (?![ ]*%s) # Not a line starting with PS1
144 (?![ ]*%s) # Not a line starting with PS2
145 .*$\n? # But any other line
146 )*)
147 '''
148
149 _EXAMPLE_RE_PY = re.compile( _RE_TPL % (_PS1_PY,_PS2_PY,_PS1_PY,_PS2_PY),
150 re.MULTILINE | re.VERBOSE)
151
152 _EXAMPLE_RE_IP = re.compile( _RE_TPL % (_PS1_IP,_PS2_IP,_PS1_IP,_PS2_IP),
153 re.MULTILINE | re.VERBOSE)
154
155 def ip2py(self,source):
156 """Convert input IPython source into valid Python."""
157 out = []
158 newline = out.append
159 for line in source.splitlines():
160 newline(ipython.prefilter(line,True))
161 newline('') # ensure a closing newline, needed by doctest
162 return '\n'.join(out)
163
164 def parse(self, string, name='<string>'):
165 """
166 Divide the given string into examples and intervening text,
167 and return them as a list of alternating Examples and strings.
168 Line numbers for the Examples are 0-based. The optional
169 argument `name` is a name identifying this string, and is only
170 used for error messages.
171 """
172 string = string.expandtabs()
173 # If all lines begin with the same indentation, then strip it.
174 min_indent = self._min_indent(string)
175 if min_indent > 0:
176 string = '\n'.join([l[min_indent:] for l in string.split('\n')])
177
178 output = []
179 charno, lineno = 0, 0
180
181 # Whether to convert the input from ipython to python syntax
182 ip2py = False
183 # Find all doctest examples in the string. First, try them as Python
184 # examples, then as IPython ones
185 terms = list(self._EXAMPLE_RE_PY.finditer(string))
186 if terms:
187 # Normal Python example
188 Example = doctest.Example
189 else:
190 # It's an ipython example. Note that IPExamples are run
191 # in-process, so their syntax must be turned into valid python.
192 # IPExternalExamples are run out-of-process (via pexpect) so they
193 # don't need any filtering (a real ipython will be executing them).
194 terms = list(self._EXAMPLE_RE_IP.finditer(string))
195 if re.search(r'#\s*ipdoctest:\s*EXTERNAL',string):
196 #print '-'*70 # dbg
197 #print 'IPExternalExample, Source:\n',string # dbg
198 #print '-'*70 # dbg
199 Example = IPExternalExample
200 else:
201 #print '-'*70 # dbg
202 #print 'IPExample, Source:\n',string # dbg
203 #print '-'*70 # dbg
204 Example = IPExample
205 ip2py = True
206
207 for m in terms:
208 # Add the pre-example text to `output`.
209 output.append(string[charno:m.start()])
210 # Update lineno (lines before this example)
211 lineno += string.count('\n', charno, m.start())
212 # Extract info from the regexp match.
213 (source, options, want, exc_msg) = \
214 self._parse_example(m, name, lineno,ip2py)
215 if Example is IPExternalExample:
216 options[doctest.NORMALIZE_WHITESPACE] = True
217 # Create an Example, and add it to the list.
218 if not self._IS_BLANK_OR_COMMENT(source):
219 output.append(Example(source, want, exc_msg,
220 lineno=lineno,
221 indent=min_indent+len(m.group('indent')),
222 options=options))
223 # Update lineno (lines inside this example)
224 lineno += string.count('\n', m.start(), m.end())
225 # Update charno.
226 charno = m.end()
227 # Add any remaining post-example text to `output`.
228 output.append(string[charno:])
229
230 return output
231
232 def _parse_example(self, m, name, lineno,ip2py=False):
233 """
234 Given a regular expression match from `_EXAMPLE_RE` (`m`),
235 return a pair `(source, want)`, where `source` is the matched
236 example's source code (with prompts and indentation stripped);
237 and `want` is the example's expected output (with indentation
238 stripped).
239
240 `name` is the string's name, and `lineno` is the line number
241 where the example starts; both are used for error messages.
242
243 Optional:
244 `ip2py`: if true, filter the input via IPython to convert the syntax
245 into valid python.
246 """
247
248 # Get the example's indentation level.
249 indent = len(m.group('indent'))
250
251 # Divide source into lines; check that they're properly
252 # indented; and then strip their indentation & prompts.
253 source_lines = m.group('source').split('\n')
254
255 # We're using variable-length input prompts
256 ps1 = m.group('ps1')
257 ps2 = m.group('ps2')
258 ps1_len = len(ps1)
259
260 self._check_prompt_blank(source_lines, indent, name, lineno,ps1_len)
261 if ps2:
262 self._check_prefix(source_lines[1:], ' '*indent + ps2, name, lineno)
263
264 source = '\n'.join([sl[indent+ps1_len+1:] for sl in source_lines])
265
266 if ip2py:
267 # Convert source input from IPython into valid Python syntax
268 source = self.ip2py(source)
269
270 # Divide want into lines; check that it's properly indented; and
271 # then strip the indentation. Spaces before the last newline should
272 # be preserved, so plain rstrip() isn't good enough.
273 want = m.group('want')
274 want_lines = want.split('\n')
275 if len(want_lines) > 1 and re.match(r' *$', want_lines[-1]):
276 del want_lines[-1] # forget final newline & spaces after it
277 self._check_prefix(want_lines, ' '*indent, name,
278 lineno + len(source_lines))
279
280 # Remove ipython output prompt that might be present in the first line
281 want_lines[0] = re.sub(r'Out\[\d+\]: \s*?\n?','',want_lines[0])
282
283 want = '\n'.join([wl[indent:] for wl in want_lines])
284
285 # If `want` contains a traceback message, then extract it.
286 m = self._EXCEPTION_RE.match(want)
287 if m:
288 exc_msg = m.group('msg')
289 else:
290 exc_msg = None
291
292 # Extract options from the source.
293 options = self._find_options(source, name, lineno)
294
295 return source, options, want, exc_msg
296
297 def _check_prompt_blank(self, lines, indent, name, lineno, ps1_len):
298 """
299 Given the lines of a source string (including prompts and
300 leading indentation), check to make sure that every prompt is
301 followed by a space character. If any line is not followed by
302 a space character, then raise ValueError.
303
304 Note: IPython-modified version which takes the input prompt length as a
305 parameter, so that prompts of variable length can be dealt with.
306 """
307 space_idx = indent+ps1_len
308 min_len = space_idx+1
309 for i, line in enumerate(lines):
310 if len(line) >= min_len and line[space_idx] != ' ':
311 raise ValueError('line %r of the docstring for %s '
312 'lacks blank after %s: %r' %
313 (lineno+i+1, name,
314 line[indent:space_idx], line))
315
316
317 SKIP = register_optionflag('SKIP')
318
319 class IPDocTestRunner(doctest.DocTestRunner):
320 """Modified DocTestRunner which can also run IPython tests.
321
322 This runner is capable of handling IPython doctests that require
323 out-of-process output capture (such as system calls via !cmd or aliases).
324 Note however that because these tests are run in a separate process, many
325 of doctest's fancier capabilities (such as detailed exception analysis) are
326 not available. So try to limit such tests to simple cases of matching
327 actual output.
328 """
329
330 #/////////////////////////////////////////////////////////////////
331 # DocTest Running
332 #/////////////////////////////////////////////////////////////////
333
334 def _run_iptest(self, test, out):
335 """
336 Run the examples in `test`. Write the outcome of each example with one
337 of the `DocTestRunner.report_*` methods, using the writer function
338 `out`. Return a tuple `(f, t)`, where `t` is the number of examples
339 tried, and `f` is the number of examples that failed. The examples are
340 run in the namespace `test.globs`.
341
342 IPython note: this is a modified version of the original __run()
343 private method to handle out-of-process examples.
344 """
345
346 if out is None:
347 out = sys.stdout.write
348
349 # Keep track of the number of failures and tries.
350 failures = tries = 0
351
352 # Save the option flags (since option directives can be used
353 # to modify them).
354 original_optionflags = self.optionflags
355
356 SUCCESS, FAILURE, BOOM = range(3) # `outcome` state
357
358 check = self._checker.check_output
359
360 # Process each example.
361 for examplenum, example in enumerate(test.examples):
362
363 # If REPORT_ONLY_FIRST_FAILURE is set, then supress
364 # reporting after the first failure.
365 quiet = (self.optionflags & REPORT_ONLY_FIRST_FAILURE and
366 failures > 0)
367
368 # Merge in the example's options.
369 self.optionflags = original_optionflags
370 if example.options:
371 for (optionflag, val) in example.options.items():
372 if val:
373 self.optionflags |= optionflag
374 else:
375 self.optionflags &= ~optionflag
376
377 # If 'SKIP' is set, then skip this example.
378 if self.optionflags & SKIP:
379 continue
380
381 # Record that we started this example.
382 tries += 1
383 if not quiet:
384 self.report_start(out, test, example)
385
386 # Run the example in the given context (globs), and record
387 # any exception that gets raised. (But don't intercept
388 # keyboard interrupts.)
389 try:
390 # Don't blink! This is where the user's code gets run.
391 got = ''
392 # The code is run in an external process
393 got = iprunner.run_source(example.source,get_output=True)
394 except KeyboardInterrupt:
395 raise
396 except:
397 self.debugger.set_continue() # ==== Example Finished ====
398
399 outcome = FAILURE # guilty until proved innocent or insane
400
401 if check(example.want, got, self.optionflags):
402 outcome = SUCCESS
403
404 # Report the outcome.
405 if outcome is SUCCESS:
406 if not quiet:
407 self.report_success(out, test, example, got)
408 elif outcome is FAILURE:
409 if not quiet:
410 self.report_failure(out, test, example, got)
411 failures += 1
412 elif outcome is BOOM:
413 if not quiet:
414 self.report_unexpected_exception(out, test, example,
415 exc_info)
416 failures += 1
417 else:
418 assert False, ("unknown outcome", outcome)
419
420 # Restore the option flags (in case they were modified)
421 self.optionflags = original_optionflags
422
423 # Record and return the number of failures and tries.
424
425 # Hack to access a parent private method by working around Python's
426 # name mangling (which is fortunately simple).
427 doctest.DocTestRunner._DocTestRunner__record_outcome(self,test,
428 failures, tries)
429 return failures, tries
430
431 def run(self, test, compileflags=None, out=None, clear_globs=True):
432 """Run examples in `test`.
433
434 This method will defer to the parent for normal Python examples, but it
435 will run IPython ones via pexpect.
436 """
437 if not test.examples:
438 return
439
440 if isinstance(test.examples[0],IPExternalExample):
441 self._run_iptest(test,out)
442 else:
443 DocTestRunner.run(self,test,compileflags,out,clear_globs)
444
445
446 class IPDebugRunner(IPDocTestRunner,doctest.DebugRunner):
447 """IPython-modified DebugRunner, see the original class for details."""
448
449 def run(self, test, compileflags=None, out=None, clear_globs=True):
450 r = IPDocTestRunner.run(self, test, compileflags, out, False)
451 if clear_globs:
452 test.globs.clear()
453 return r
454
455
456 class IPDocTestLoader(unittest.TestLoader):
457 """A test loader with IPython-enhanced doctest support.
458
459 Instances of this loader will automatically add doctests found in a module
460 to the test suite returned by the loadTestsFromModule method. In
461 addition, at initialization time a string of doctests can be given to the
462 loader, enabling it to add doctests to a module which didn't have them in
463 its docstring, coming from an external source."""
464
465
466 def __init__(self,dt_files=None,dt_modules=None,test_finder=None):
467 """Initialize the test loader.
468
469 :Keywords:
470
471 dt_files : list (None)
472 List of names of files to be executed as doctests.
473
474 dt_modules : list (None)
475 List of module names to be scanned for doctests in their
476 docstrings.
477
478 test_finder : instance (None)
479 Instance of a testfinder (see doctest for details).
480 """
481
482 if dt_files is None: dt_files = []
483 if dt_modules is None: dt_modules = []
484 self.dt_files = utils.list_strings(dt_files)
485 self.dt_modules = utils.list_strings(dt_modules)
486 if test_finder is None:
487 test_finder = doctest.DocTestFinder(parser=IPDocTestParser())
488 self.test_finder = test_finder
489
490 def loadTestsFromModule(self, module):
491 """Return a suite of all tests cases contained in the given module.
492
493 If the loader was initialized with a doctests argument, then this
494 string is assigned as the module's docstring."""
495
496 # Start by loading any tests in the called module itself
497 suite = super(self.__class__,self).loadTestsFromModule(module)
498
499 # Now, load also tests referenced at construction time as companion
500 # doctests that reside in standalone files
501 for fname in self.dt_files:
502 #print 'mod:',module # dbg
503 #print 'fname:',fname # dbg
504 #suite.addTest(doctest.DocFileSuite(fname))
505 suite.addTest(doctest.DocFileSuite(fname,module_relative=False))
506 # Add docstring tests from module, if given at construction time
507 for mod in self.dt_modules:
508 suite.addTest(doctest.DocTestSuite(mod,
509 test_finder=self.test_finder))
510
511 #ipshell() # dbg
512 return suite
513
514 def my_import(name):
515 """Module importer - taken from the python documentation.
516
517 This function allows importing names with dots in them."""
518
519 mod = __import__(name)
520 components = name.split('.')
521 for comp in components[1:]:
522 mod = getattr(mod, comp)
523 return mod
524
525 def makeTestSuite(module_name,dt_files=None,dt_modules=None,idt=True):
526 """Make a TestSuite object for a given module, specified by name.
527
528 This extracts all the doctests associated with a module using a
529 DocTestLoader object.
530
531 :Parameters:
532
533 - module_name: a string containing the name of a module with unittests.
534
535 :Keywords:
536
537 dt_files : list of strings
538 List of names of plain text files to be treated as doctests.
539
540 dt_modules : list of strings
541 List of names of modules to be scanned for doctests in docstrings.
542
543 idt : bool (True)
544 If True, return integrated doctests. This means that each filename
545 listed in dt_files is turned into a *single* unittest, suitable for
546 running via unittest's runner or Twisted's Trial runner. If false, the
547 dt_files parameter is returned unmodified, so that other test runners
548 (such as oilrun) can run the doctests with finer granularity.
549 """
550
551 mod = my_import(module_name)
552 if idt:
553 suite = IPDocTestLoader(dt_files,dt_modules).loadTestsFromModule(mod)
554 else:
555 suite = IPDocTestLoader(None,dt_modules).loadTestsFromModule(mod)
556
557 if idt:
558 return suite
559 else:
560 return suite,dt_files
561
562 # Copied from doctest in py2.5 and modified for our purposes (since they don't
563 # parametrize what we need)
564
565 # For backward compatibility, a global instance of a DocTestRunner
566 # class, updated by testmod.
567 master = None
568
569 def testmod(m=None, name=None, globs=None, verbose=None,
570 report=True, optionflags=0, extraglobs=None,
571 raise_on_error=False, exclude_empty=False):
572 """m=None, name=None, globs=None, verbose=None, report=True,
573 optionflags=0, extraglobs=None, raise_on_error=False,
574 exclude_empty=False
575
576 Note: IPython-modified version which loads test finder and runners that
577 recognize IPython syntax in doctests.
578
579 Test examples in docstrings in functions and classes reachable
580 from module m (or the current module if m is not supplied), starting
581 with m.__doc__.
582
583 Also test examples reachable from dict m.__test__ if it exists and is
584 not None. m.__test__ maps names to functions, classes and strings;
585 function and class docstrings are tested even if the name is private;
586 strings are tested directly, as if they were docstrings.
587
588 Return (#failures, #tests).
589
590 See doctest.__doc__ for an overview.
591
592 Optional keyword arg "name" gives the name of the module; by default
593 use m.__name__.
594
595 Optional keyword arg "globs" gives a dict to be used as the globals
596 when executing examples; by default, use m.__dict__. A copy of this
597 dict is actually used for each docstring, so that each docstring's
598 examples start with a clean slate.
599
600 Optional keyword arg "extraglobs" gives a dictionary that should be
601 merged into the globals that are used to execute examples. By
602 default, no extra globals are used. This is new in 2.4.
603
604 Optional keyword arg "verbose" prints lots of stuff if true, prints
605 only failures if false; by default, it's true iff "-v" is in sys.argv.
606
607 Optional keyword arg "report" prints a summary at the end when true,
608 else prints nothing at the end. In verbose mode, the summary is
609 detailed, else very brief (in fact, empty if all tests passed).
610
611 Optional keyword arg "optionflags" or's together module constants,
612 and defaults to 0. This is new in 2.3. Possible values (see the
613 docs for details):
614
615 DONT_ACCEPT_TRUE_FOR_1
616 DONT_ACCEPT_BLANKLINE
617 NORMALIZE_WHITESPACE
618 ELLIPSIS
619 SKIP
620 IGNORE_EXCEPTION_DETAIL
621 REPORT_UDIFF
622 REPORT_CDIFF
623 REPORT_NDIFF
624 REPORT_ONLY_FIRST_FAILURE
625
626 Optional keyword arg "raise_on_error" raises an exception on the
627 first unexpected exception or failure. This allows failures to be
628 post-mortem debugged.
629
630 Advanced tomfoolery: testmod runs methods of a local instance of
631 class doctest.Tester, then merges the results into (or creates)
632 global Tester instance doctest.master. Methods of doctest.master
633 can be called directly too, if you want to do something unusual.
634 Passing report=0 to testmod is especially useful then, to delay
635 displaying a summary. Invoke doctest.master.summarize(verbose)
636 when you're done fiddling.
637 """
638 global master
639
640 # If no module was given, then use __main__.
641 if m is None:
642 # DWA - m will still be None if this wasn't invoked from the command
643 # line, in which case the following TypeError is about as good an error
644 # as we should expect
645 m = sys.modules.get('__main__')
646
647 # Check that we were actually given a module.
648 if not inspect.ismodule(m):
649 raise TypeError("testmod: module required; %r" % (m,))
650
651 # If no name was given, then use the module's name.
652 if name is None:
653 name = m.__name__
654
655 #----------------------------------------------------------------------
656 # fperez - make IPython finder and runner:
657 # Find, parse, and run all tests in the given module.
658 finder = DocTestFinder(exclude_empty=exclude_empty,
659 parser=IPDocTestParser())
660
661 if raise_on_error:
662 runner = IPDebugRunner(verbose=verbose, optionflags=optionflags)
663 else:
664 runner = IPDocTestRunner(verbose=verbose, optionflags=optionflags,
665 #checker=IPOutputChecker() # dbg
666 )
667
668 # /fperez - end of ipython changes
669 #----------------------------------------------------------------------
670
671 for test in finder.find(m, name, globs=globs, extraglobs=extraglobs):
672 runner.run(test)
673
674 if report:
675 runner.summarize()
676
677 if master is None:
678 master = runner
679 else:
680 master.merge(runner)
681
682 return runner.failures, runner.tries
683
684
685 # Simple testing and example code
686 if __name__ == "__main__":
687
688 def ipfunc():
689 """
690 Some ipython tests...
691
692 In [1]: import os
693
694 In [2]: cd /
695 /
696
697 In [3]: 2+3
698 Out[3]: 5
699
700 In [26]: for i in range(3):
701 ....: print i,
702 ....: print i+1,
703 ....:
704 0 1 1 2 2 3
705
706
707 Examples that access the operating system work:
708
709 In [19]: cd /tmp
710 /tmp
711
712 In [20]: mkdir foo_ipython
713
714 In [21]: cd foo_ipython
715 /tmp/foo_ipython
716
717 In [23]: !touch bar baz
718
719 # We unfortunately can't just call 'ls' because its output is not
720 # seen by doctest, since it happens in a separate process
721
722 In [24]: os.listdir('.')
723 Out[24]: ['bar', 'baz']
724
725 In [25]: cd /tmp
726 /tmp
727
728 In [26]: rm -rf foo_ipython
729
730
731 It's OK to use '_' for the last result, but do NOT try to use IPython's
732 numbered history of _NN outputs, since those won't exist under the
733 doctest environment:
734
735 In [7]: 3+4
736 Out[7]: 7
737
738 In [8]: _+3
739 Out[8]: 10
740 """
741
742 def ipfunc_external():
743 """
744 Tests that must be run in an external process
745
746
747 # ipdoctest: EXTERNAL
748
749 In [11]: for i in range(10):
750 ....: print i,
751 ....: print i+1,
752 ....:
753 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 10
754
755
756 In [1]: import os
757
758 In [1]: print "hello"
759 hello
760
761 In [19]: cd /tmp
762 /tmp
763
764 In [20]: mkdir foo_ipython2
765
766 In [21]: cd foo_ipython2
767 /tmp/foo_ipython2
768
769 In [23]: !touch bar baz
770
771 In [24]: ls
772 bar baz
773
774 In [24]: !ls
775 bar baz
776
777 In [25]: cd /tmp
778 /tmp
779
780 In [26]: rm -rf foo_ipython2
781 """
782
783 def pyfunc():
784 """
785 Some pure python tests...
786
787 >>> import os
788
789 >>> 2+3
790 5
791
792 >>> for i in range(3):
793 ... print i,
794 ... print i+1,
795 ...
796 0 1 1 2 2 3
797 """
798
799 # Call the global testmod() just like you would with normal doctest
800 testmod()
@@ -0,0 +1,244 b''
1 #!/usr/bin/env python
2 """Utility for making a doctest file out of Python or IPython input.
3
4 %prog [options] input_file [output_file]
5
6 This script is a convenient generator of doctest files that uses IPython's
7 irunner script to execute valid Python or IPython input in a separate process,
8 capture all of the output, and write it to an output file.
9
10 It can be used in one of two ways:
11
12 1. With a plain Python or IPython input file (denoted by extensions '.py' or
13 '.ipy'. In this case, the output is an auto-generated reST file with a
14 basic header, and the captured Python input and output contained in an
15 indented code block.
16
17 If no output filename is given, the input name is used, with the extension
18 replaced by '.txt'.
19
20 2. With an input template file. Template files are simply plain text files
21 with special directives of the form
22
23 %run filename
24
25 to include the named file at that point.
26
27 If no output filename is given and the input filename is of the form
28 'base.tpl.txt', the output will be automatically named 'base.txt'.
29 """
30
31 # Standard library imports
32
33 import optparse
34 import os
35 import re
36 import sys
37 import tempfile
38
39 # IPython-specific libraries
40 from IPython import irunner
41 from IPython.genutils import fatal
42
43 class IndentOut(object):
44 """A simple output stream that indents all output by a fixed amount.
45
46 Instances of this class trap output to a given stream and first reformat it
47 to indent every input line."""
48
49 def __init__(self,out=sys.stdout,indent=4):
50 """Create an indented writer.
51
52 :Keywords:
53
54 - `out` : stream (sys.stdout)
55 Output stream to actually write to after indenting.
56
57 - `indent` : int
58 Number of spaces to indent every input line by.
59 """
60
61 self.indent_text = ' '*indent
62 self.indent = re.compile('^',re.MULTILINE).sub
63 self.out = out
64 self._write = out.write
65 self.buffer = []
66 self._closed = False
67
68 def write(self,data):
69 """Write a string to the output stream."""
70
71 if self._closed:
72 raise ValueError('I/O operation on closed file')
73 self.buffer.append(data)
74
75 def flush(self):
76 if self.buffer:
77 data = ''.join(self.buffer)
78 self.buffer[:] = []
79 self._write(self.indent(self.indent_text,data))
80
81 def close(self):
82 self.flush()
83 self._closed = True
84
85 class RunnerFactory(object):
86 """Code runner factory.
87
88 This class provides an IPython code runner, but enforces that only one
89 runner is every instantiated. The runner is created based on the extension
90 of the first file to run, and it raises an exception if a runner is later
91 requested for a different extension type.
92
93 This ensures that we don't generate example files for doctest with a mix of
94 python and ipython syntax.
95 """
96
97 def __init__(self,out=sys.stdout):
98 """Instantiate a code runner."""
99
100 self.out = out
101 self.runner = None
102 self.runnerClass = None
103
104 def _makeRunner(self,runnerClass):
105 self.runnerClass = runnerClass
106 self.runner = runnerClass(out=self.out)
107 return self.runner
108
109 def __call__(self,fname):
110 """Return a runner for the given filename."""
111
112 if fname.endswith('.py'):
113 runnerClass = irunner.PythonRunner
114 elif fname.endswith('.ipy'):
115 runnerClass = irunner.IPythonRunner
116 else:
117 raise ValueError('Unknown file type for Runner: %r' % fname)
118
119 if self.runner is None:
120 return self._makeRunner(runnerClass)
121 else:
122 if runnerClass==self.runnerClass:
123 return self.runner
124 else:
125 e='A runner of type %r can not run file %r' % \
126 (self.runnerClass,fname)
127 raise ValueError(e)
128
129 TPL = """
130 =========================
131 Auto-generated doctests
132 =========================
133
134 This file was auto-generated by IPython in its entirety. If you need finer
135 control over the contents, simply make a manual template. See the
136 mkdoctests.py script for details.
137
138 %%run %s
139 """
140
141 def main():
142 """Run as a script."""
143
144 # Parse options and arguments.
145 parser = optparse.OptionParser(usage=__doc__)
146 newopt = parser.add_option
147 newopt('-f','--force',action='store_true',dest='force',default=False,
148 help='Force overwriting of the output file.')
149 newopt('-s','--stdout',action='store_true',dest='stdout',default=False,
150 help='Use stdout instead of a file for output.')
151
152 opts,args = parser.parse_args()
153 if len(args) < 1:
154 parser.error("incorrect number of arguments")
155
156 # Input filename
157 fname = args[0]
158
159 # We auto-generate the output file based on a trivial template to make it
160 # really easy to create simple doctests.
161
162 auto_gen_output = False
163 try:
164 outfname = args[1]
165 except IndexError:
166 outfname = None
167
168 if fname.endswith('.tpl.txt') and outfname is None:
169 outfname = fname.replace('.tpl.txt','.txt')
170 else:
171 bname, ext = os.path.splitext(fname)
172 if ext in ['.py','.ipy']:
173 auto_gen_output = True
174 if outfname is None:
175 outfname = bname+'.txt'
176
177 # Open input file
178
179 # In auto-gen mode, we actually change the name of the input file to be our
180 # auto-generated template
181 if auto_gen_output:
182 infile = tempfile.TemporaryFile()
183 infile.write(TPL % fname)
184 infile.flush()
185 infile.seek(0)
186 else:
187 infile = open(fname)
188
189 # Now open the output file. If opts.stdout was given, this overrides any
190 # explicit choice of output filename and just directs all output to
191 # stdout.
192 if opts.stdout:
193 outfile = sys.stdout
194 else:
195 # Argument processing finished, start main code
196 if os.path.isfile(outfname) and not opts.force:
197 fatal("Output file %r exists, use --force (-f) to overwrite."
198 % outfname)
199 outfile = open(outfname,'w')
200
201
202 # all output from included files will be indented
203 indentOut = IndentOut(outfile,4)
204 getRunner = RunnerFactory(indentOut)
205
206 # Marker in reST for transition lines
207 rst_transition = '\n'+'-'*76+'\n\n'
208
209 # local shorthand for loop
210 write = outfile.write
211
212 # Process input, simply writing back out all normal lines and executing the
213 # files in lines marked as '%run filename'.
214 for line in infile:
215 if line.startswith('%run '):
216 # We don't support files with spaces in their names.
217 incfname = line.split()[1]
218
219 # We make the output of the included file appear bracketed between
220 # clear reST transition marks, and indent it so that if anyone
221 # makes an HTML or PDF out of the file, all doctest input and
222 # output appears in proper literal blocks.
223 write(rst_transition)
224 write('Begin included file %s::\n\n' % incfname)
225
226 # I deliberately do NOT trap any exceptions here, so that if
227 # there's any problem, the user running this at the command line
228 # finds out immediately by the code blowing up, rather than ending
229 # up silently with an incomplete or incorrect file.
230 getRunner(incfname).run_file(incfname)
231
232 write('\nEnd included file %s\n' % incfname)
233 write(rst_transition)
234 else:
235 # The rest of the input file is just written out
236 write(line)
237 infile.close()
238
239 # Don't close sys.stdout!!!
240 if outfile is not sys.stdout:
241 outfile.close()
242
243 if __name__ == '__main__':
244 main()
@@ -0,0 +1,55 b''
1 """Parametric testing on top of twisted.trial.unittest.
2
3 """
4
5 __all__ = ['parametric','Parametric']
6
7 from twisted.trial.unittest import TestCase
8
9 def partial(f, *partial_args, **partial_kwargs):
10 """Generate a partial class method.
11
12 """
13 def partial_func(self, *args, **kwargs):
14 dikt = dict(kwargs)
15 dikt.update(partial_kwargs)
16 return f(self, *(partial_args+args), **dikt)
17
18 return partial_func
19
20 def parametric(f):
21 """Mark f as a parametric test.
22
23 """
24 f._parametric = True
25 return classmethod(f)
26
27 def Parametric(cls):
28 """Register parametric tests with a class.
29
30 """
31 # Walk over all tests marked with @parametric
32 test_generators = [getattr(cls,f) for f in dir(cls)
33 if f.startswith('test')]
34 test_generators = [m for m in test_generators if hasattr(m,'_parametric')]
35 for test_gen in test_generators:
36 test_name = test_gen.func_name
37
38 # Insert a new test for each parameter
39 for n,test_and_params in enumerate(test_gen()):
40 test_method = test_and_params[0]
41 test_params = test_and_params[1:]
42
43 # Here we use partial (defined above), which returns a
44 # class method of type ``types.FunctionType``, unlike
45 # functools.partial which returns a function of type
46 # ``functools.partial``.
47 partial_func = partial(test_method,*test_params)
48 # rename the test to look like a testcase
49 partial_func.__name__ = 'test_' + partial_func.__name__
50
51 # insert the new function into the class as a test
52 setattr(cls, test_name + '_%s' % n, partial_func)
53
54 # rename test generator so it isn't called again by nose
55 test_gen.im_func.func_name = '__done_' + test_name
@@ -0,0 +1,36 b''
1 """Common utilities for testing IPython.
2
3 This file is meant to be used as
4
5 from IPython.testutils.tcommon import *
6
7 by any test code.
8
9 While a bit ugly, this helps us keep all testing facilities in one place, and
10 start coding standalone test scripts easily, which can then be pasted into the
11 larger test suites without any modifications required.
12 """
13
14 # Required modules and packages
15
16 # Standard Python lib
17 import cPickle as pickle
18 import doctest
19 import math
20 import os
21 import sys
22 import unittest
23
24 from pprint import pformat, pprint
25
26 # From the IPython test lib
27 import tutils
28 from tutils import fullPath
29
30 try:
31 import pexpect
32 except ImportError:
33 pexpect = None
34 else:
35 from IPython.testutils.ipdoctest import IPDocTestLoader,makeTestSuite
36
@@ -0,0 +1,66 b''
1 #!/usr/bin/env python
2 # encoding: utf-8
3 """Simple template for unit tests.
4
5 This file should be renamed to
6
7 test_FEATURE.py
8
9 so that it is recognized by the overall test driver (Twisted's 'trial'), which
10 looks for all test_*.py files in the current directory to extract tests from
11 them.
12 """
13 __docformat__ = "restructuredtext en"
14
15 #-------------------------------------------------------------------------------
16 # Copyright (C) 2005 Fernando Perez <fperez@colorado.edu>
17 # Brian E Granger <ellisonbg@gmail.com>
18 # Benjamin Ragan-Kelley <benjaminrk@gmail.com>
19 #
20 # Distributed under the terms of the BSD License. The full license is in
21 # the file COPYING, distributed as part of this software.
22 #-------------------------------------------------------------------------------
23
24 #-------------------------------------------------------------------------------
25 # Imports
26 #-------------------------------------------------------------------------------
27
28 from IPython.testutils import tcommon
29 from IPython.testutils.tcommon import *
30
31 #-------------------------------------------------------------------------------
32 # Setup for inline and standalone doctests
33 #-------------------------------------------------------------------------------
34
35
36 # If you have standalone doctests in a separate file, set their names in the
37 # dt_files variable (as a single string or a list thereof). The mkPath call
38 # forms an absolute path based on the current file, it is not needed if you
39 # provide the full pahts.
40 dt_files = fullPath(__file__,[])
41
42
43 # If you have any modules whose docstrings should be scanned for embedded tests
44 # as examples accorging to standard doctest practice, set them here (as a
45 # single string or a list thereof):
46 dt_modules = []
47
48 #-------------------------------------------------------------------------------
49 # Regular Unittests
50 #-------------------------------------------------------------------------------
51
52 class FooTestCase(unittest.TestCase):
53 def test_foo(self):
54 pass
55
56 #-------------------------------------------------------------------------------
57 # Regular Unittests
58 #-------------------------------------------------------------------------------
59
60 # This ensures that the code will run either standalone as a script, or that it
61 # can be picked up by Twisted's `trial` test wrapper to run all the tests.
62 if tcommon.pexpect is not None:
63 if __name__ == '__main__':
64 unittest.main(testLoader=IPDocTestLoader(dt_files,dt_modules))
65 else:
66 testSuite = lambda : makeTestSuite(__name__,dt_files,dt_modules)
@@ -0,0 +1,10 b''
1 # encoding: utf-8
2 __docformat__ = "restructuredtext en"
3 #-------------------------------------------------------------------------------
4 # Copyright (C) 2005 Fernando Perez <fperez@colorado.edu>
5 # Brian E Granger <ellisonbg@gmail.com>
6 # Benjamin Ragan-Kelley <benjaminrk@gmail.com>
7 #
8 # Distributed under the terms of the BSD License. The full license is in
9 # the file COPYING, distributed as part of this software.
10 #-------------------------------------------------------------------------------
@@ -0,0 +1,66 b''
1 #!/usr/bin/env python
2 # encoding: utf-8
3 """Simple template for unit tests.
4
5 This file should be renamed to
6
7 test_FEATURE.py
8
9 so that it is recognized by the overall test driver (Twisted's 'trial'), which
10 looks for all test_*.py files in the current directory to extract tests from
11 them.
12 """
13 __docformat__ = "restructuredtext en"
14
15 #-------------------------------------------------------------------------------
16 # Copyright (C) 2005 Fernando Perez <fperez@colorado.edu>
17 # Brian E Granger <ellisonbg@gmail.com>
18 # Benjamin Ragan-Kelley <benjaminrk@gmail.com>
19 #
20 # Distributed under the terms of the BSD License. The full license is in
21 # the file COPYING, distributed as part of this software.
22 #-------------------------------------------------------------------------------
23
24 #-------------------------------------------------------------------------------
25 # Imports
26 #-------------------------------------------------------------------------------
27
28 from IPython.testutils import tcommon
29 from IPython.testutils.tcommon import *
30
31 #-------------------------------------------------------------------------------
32 # Setup for inline and standalone doctests
33 #-------------------------------------------------------------------------------
34
35
36 # If you have standalone doctests in a separate file, set their names in the
37 # dt_files variable (as a single string or a list thereof). The mkPath call
38 # forms an absolute path based on the current file, it is not needed if you
39 # provide the full pahts.
40 dt_files = fullPath(__file__,[])
41
42
43 # If you have any modules whose docstrings should be scanned for embedded tests
44 # as examples accorging to standard doctest practice, set them here (as a
45 # single string or a list thereof):
46 dt_modules = ['IPython.testutils.tutils']
47
48 #-------------------------------------------------------------------------------
49 # Regular Unittests
50 #-------------------------------------------------------------------------------
51
52 ## class FooTestCase(unittest.TestCase):
53 ## def test_foo(self):
54 ## pass
55
56 #-------------------------------------------------------------------------------
57 # Regular Unittests
58 #-------------------------------------------------------------------------------
59
60 # This ensures that the code will run either standalone as a script, or that it
61 # can be picked up by Twisted's `trial` test wrapper to run all the tests.
62 if tcommon.pexpect is not None:
63 if __name__ == '__main__':
64 unittest.main(testLoader=IPDocTestLoader(dt_files,dt_modules))
65 else:
66 testSuite = lambda : makeTestSuite(__name__,dt_files,dt_modules)
@@ -0,0 +1,16 b''
1 """Run this file with
2
3 irunner --python filename
4
5 to generate valid doctest input.
6
7 NOTE: make sure to ALWAYS have a blank line before comments, otherwise doctest
8 gets confused."""
9
10 #---------------------------------------------------------------------------
11
12 # Setup - all imports are done in tcommon
13 import tcommon; reload(tcommon) # for interactive use
14 from IPython.testutils.tcommon import *
15
16 # Doctest code begins here
@@ -0,0 +1,24 b''
1 Doctests for the ``XXX`` module
2 =====================================
3
4 The way doctest loads these, the entire document is applied as a single test
5 rather than multiple individual ones, unfortunately.
6
7
8 Auto-generated tests
9 --------------------
10
11 The tests below are generated from the companion file
12 test_toeplitz_doctest.py, which is run via IPython's irunner script to create
13 valid doctest input automatically.
14
15 # Setup - all imports are done in tcommon
16 >>> from IPython.testutils.tcommon import *
17
18 # Rest of doctest goes here...
19
20
21 Manually generated tests
22 ------------------------
23
24 These are one-off tests written by hand, copied from an interactive prompt.
@@ -0,0 +1,72 b''
1 """Utilities for testing code.
2 """
3
4 # Required modules and packages
5
6 # Standard Python lib
7 import os
8 import sys
9
10 # From this project
11 from IPython.tools import utils
12
13 # path to our own installation, so we can find source files under this.
14 TEST_PATH = os.path.dirname(os.path.abspath(__file__))
15
16 # Global flag, used by vprint
17 VERBOSE = '-v' in sys.argv or '--verbose' in sys.argv
18
19 ##########################################################################
20 # Code begins
21
22 # Some utility functions
23 def vprint(*args):
24 """Print-like function which relies on a global VERBOSE flag."""
25 if not VERBOSE:
26 return
27
28 write = sys.stdout.write
29 for item in args:
30 write(str(item))
31 write('\n')
32 sys.stdout.flush()
33
34 def test_path(path):
35 """Return a path as a subdir of the test package.
36
37 This finds the correct path of the test package on disk, and prepends it
38 to the input path."""
39
40 return os.path.join(TEST_PATH,path)
41
42 def fullPath(startPath,files):
43 """Make full paths for all the listed files, based on startPath.
44
45 Only the base part of startPath is kept, since this routine is typically
46 used with a script's __file__ variable as startPath. The base of startPath
47 is then prepended to all the listed files, forming the output list.
48
49 :Parameters:
50 startPath : string
51 Initial path to use as the base for the results. This path is split
52 using os.path.split() and only its first component is kept.
53
54 files : string or list
55 One or more files.
56
57 :Examples:
58
59 >>> fullPath('/foo/bar.py',['a.txt','b.txt'])
60 ['/foo/a.txt', '/foo/b.txt']
61
62 >>> fullPath('/foo',['a.txt','b.txt'])
63 ['/a.txt', '/b.txt']
64
65 If a single file is given, the output is still a list:
66 >>> fullPath('/foo','a.txt')
67 ['/a.txt']
68 """
69
70 files = utils.list_strings(files)
71 base = os.path.split(startPath)[0]
72 return [ os.path.join(base,f) for f in files ]
@@ -0,0 +1,64 b''
1 # encoding: utf-8
2 """This file contains utility classes for performing tests with Deferreds.
3 """
4 __docformat__ = "restructuredtext en"
5 #-------------------------------------------------------------------------------
6 # Copyright (C) 2005 Fernando Perez <fperez@colorado.edu>
7 # Brian E Granger <ellisonbg@gmail.com>
8 # Benjamin Ragan-Kelley <benjaminrk@gmail.com>
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
13
14 #-------------------------------------------------------------------------------
15 # Imports
16 #-------------------------------------------------------------------------------
17
18 from twisted.trial import unittest
19 from twisted.internet import defer
20
21 class DeferredTestCase(unittest.TestCase):
22
23 def assertDeferredEquals(self, deferred, expectedResult,
24 chainDeferred=None):
25 """Calls assertEquals on the result of the deferred and expectedResult.
26
27 chainDeferred can be used to pass in previous Deferred objects that
28 have tests being run on them. This chaining of Deferred's in tests
29 is needed to insure that all Deferred's are cleaned up at the end of
30 a test.
31 """
32
33 if chainDeferred is None:
34 chainDeferred = defer.succeed(None)
35
36 def gotResult(actualResult):
37 self.assertEquals(actualResult, expectedResult)
38
39 deferred.addCallback(gotResult)
40
41 return chainDeferred.addCallback(lambda _: deferred)
42
43 def assertDeferredRaises(self, deferred, expectedException,
44 chainDeferred=None):
45 """Calls assertRaises on the Failure of the deferred and expectedException.
46
47 chainDeferred can be used to pass in previous Deferred objects that
48 have tests being run on them. This chaining of Deferred's in tests
49 is needed to insure that all Deferred's are cleaned up at the end of
50 a test.
51 """
52
53 if chainDeferred is None:
54 chainDeferred = defer.succeed(None)
55
56 def gotFailure(f):
57 #f.printTraceback()
58 self.assertRaises(expectedException, f.raiseException)
59 #return f
60
61 deferred.addBoth(gotFailure)
62
63 return chainDeferred.addCallback(lambda _: deferred)
64
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
@@ -0,0 +1,49 b''
1 #!/usr/bin/env python
2 # encoding: utf-8
3
4
5 class IPythonGrowlError(Exception):
6 pass
7
8 class Notifier(object):
9
10 def __init__(self, app_name):
11 try:
12 import Growl
13 except ImportError:
14 self.g_notifier = None
15 else:
16 self.g_notifier = Growl.GrowlNotifier(app_name, ['kernel', 'core'])
17 self.g_notifier.register()
18
19 def _notify(self, title, msg):
20 if self.g_notifier is not None:
21 self.g_notifier.notify('kernel', title, msg)
22
23 def notify(self, title, msg):
24 self._notify(title, msg)
25
26 def notify_deferred(self, r, msg):
27 title = "Deferred Result"
28 msg = msg + '\n' + repr(r)
29 self._notify(title, msg)
30 return r
31
32 _notifier = None
33
34 def notify(title, msg):
35 pass
36
37 def notify_deferred(r, msg):
38 return r
39
40 def start(app_name):
41 global _notifier, notify, notify_deferred
42 if _notifier is not None:
43 raise IPythonGrowlError("this process is already registered with Growl")
44 else:
45 _notifier = Notifier(app_name)
46 notify = _notifier.notify
47 notify_deferred = _notifier.notify_deferred
48
49
@@ -0,0 +1,10 b''
1 # encoding: utf-8
2 __docformat__ = "restructuredtext en"
3 #-------------------------------------------------------------------------------
4 # Copyright (C) 2005 Fernando Perez <fperez@colorado.edu>
5 # Brian E Granger <ellisonbg@gmail.com>
6 # Benjamin Ragan-Kelley <benjaminrk@gmail.com>
7 #
8 # Distributed under the terms of the BSD License. The full license is in
9 # the file COPYING, distributed as part of this software.
10 #-------------------------------------------------------------------------------
@@ -0,0 +1,36 b''
1 #!/usr/bin/env python
2 """Testing script for the tools.utils module.
3 """
4
5 # Module imports
6 from IPython.testutils import tcommon
7 from IPython.testutils.tcommon import *
8
9 # If you have standalone doctests in a separate file, set their names in the
10 # dt_files variable (as a single string or a list thereof). The mkPath call
11 # forms an absolute path based on the current file, it is not needed if you
12 # provide the full pahts.
13 dt_files = fullPath(__file__,['tst_tools_utils_doctest.txt',
14 'tst_tools_utils_doctest2.txt'])
15
16 # If you have any modules whose docstrings should be scanned for embedded tests
17 # as examples accorging to standard doctest practice, set them here (as a
18 # single string or a list thereof):
19 dt_modules = 'IPython.tools.utils'
20
21 ##########################################################################
22 ### Regular unittest test classes go here
23
24 ## class utilsTestCase(unittest.TestCase):
25 ## def test_foo(self):
26 ## pass
27
28 ##########################################################################
29 ### Main
30 # This ensures that the code will run either standalone as a script, or that it
31 # can be picked up by Twisted's `trial` test wrapper to run all the tests.
32 if tcommon.pexpect is not None:
33 if __name__ == '__main__':
34 unittest.main(testLoader=IPDocTestLoader(dt_files,dt_modules))
35 else:
36 testSuite = lambda : makeTestSuite(__name__,dt_files,dt_modules)
@@ -0,0 +1,12 b''
1 # Setup - all imports are done in tcommon
2 from IPython.testutils import tcommon
3 from IPython.testutils.tcommon import *
4
5 # Doctest code begins here
6 from IPython.tools import utils
7
8 for i in range(10):
9 print i,
10 print i+1
11
12 print 'simple loop is over'
@@ -0,0 +1,18 b''
1 =========================================
2 Doctests for the ``tools.utils`` module
3 =========================================
4
5 The way doctest loads these, the entire document is applied as a single test
6 rather than multiple individual ones, unfortunately.
7
8
9 Auto-generated tests
10 ====================
11
12 %run tst_tools_utils_doctest.py
13
14
15 Manually generated tests
16 ========================
17
18 These are one-off tests written by hand, copied from an interactive prompt.
@@ -0,0 +1,42 b''
1
2 =========================
3 Auto-generated doctests
4 =========================
5
6 This file was auto-generated by IPython in its entirety. If you need finer
7 control over the contents, simply make a manual template. See the
8 mkdoctests.py script for details.
9
10
11 ----------------------------------------------------------------------------
12
13 Begin included file tst_tools_utils_doctest.py::
14
15 # Setup - all imports are done in tcommon
16 >>> from IPython.testutils import tcommon
17 >>> from IPython.testutils.tcommon import *
18
19 # Doctest code begins here
20 >>> from IPython.tools import utils
21
22 >>> for i in range(10):
23 ... print i,
24 ... print i+1
25 ...
26 0 1
27 1 2
28 2 3
29 3 4
30 4 5
31 5 6
32 6 7
33 7 8
34 8 9
35 9 10
36 >>> print 'simple loop is over'
37 simple loop is over
38
39 End included file tst_tools_utils_doctest.py
40
41 ----------------------------------------------------------------------------
42
@@ -0,0 +1,13 b''
1 # Setup - all imports are done in tcommon
2 from IPython.testutils import tcommon
3 from IPython.testutils.tcommon import *
4
5 # Doctest code begins here
6 from IPython.tools import utils
7
8 # Some other tests for utils
9
10 utils.marquee('Testing marquee')
11
12 utils.marquee('Another test',30,'.')
13
@@ -0,0 +1,18 b''
1 =========================================
2 Doctests for the ``tools.utils`` module
3 =========================================
4
5 The way doctest loads these, the entire document is applied as a single test
6 rather than multiple individual ones, unfortunately.
7
8
9 Auto-generated tests
10 ====================
11
12 %run tst_tools_utils_doctest2.py
13
14
15 Manually generated tests
16 ========================
17
18 These are one-off tests written by hand, copied from an interactive prompt.
@@ -0,0 +1,42 b''
1 =========================================
2 Doctests for the ``tools.utils`` module
3 =========================================
4
5 The way doctest loads these, the entire document is applied as a single test
6 rather than multiple individual ones, unfortunately.
7
8
9 Auto-generated tests
10 ====================
11
12
13 ----------------------------------------------------------------------------
14
15 Begin included file tst_tools_utils_doctest2.py::
16
17 # Setup - all imports are done in tcommon
18 >>> from IPython.testutils import tcommon
19 >>> from IPython.testutils.tcommon import *
20
21 # Doctest code begins here
22 >>> from IPython.tools import utils
23
24 # Some other tests for utils
25
26 >>> utils.marquee('Testing marquee')
27 '****************************** Testing marquee ******************************'
28
29 >>> utils.marquee('Another test',30,'.')
30 '........ Another test ........'
31
32
33 End included file tst_tools_utils_doctest2.py
34
35 ----------------------------------------------------------------------------
36
37
38
39 Manually generated tests
40 ========================
41
42 These are one-off tests written by hand, copied from an interactive prompt.
@@ -0,0 +1,128 b''
1 # encoding: utf-8
2 """Generic utilities for use by IPython's various subsystems.
3 """
4
5 __docformat__ = "restructuredtext en"
6
7 #-------------------------------------------------------------------------------
8 # Copyright (C) 2006 Fernando Perez <fperez@colorado.edu>
9 # Brian E Granger <ellisonbg@gmail.com>
10 #
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
13 #-------------------------------------------------------------------------------
14
15 #---------------------------------------------------------------------------
16 # Stdlib imports
17 #---------------------------------------------------------------------------
18
19 import os
20 import sys
21
22 #---------------------------------------------------------------------------
23 # Other IPython utilities
24 #---------------------------------------------------------------------------
25
26
27 #---------------------------------------------------------------------------
28 # Normal code begins
29 #---------------------------------------------------------------------------
30
31 def extractVars(*names,**kw):
32 """Extract a set of variables by name from another frame.
33
34 :Parameters:
35 - `*names`: strings
36 One or more variable names which will be extracted from the caller's
37 frame.
38
39 :Keywords:
40 - `depth`: integer (0)
41 How many frames in the stack to walk when looking for your variables.
42
43
44 Examples:
45
46 In [2]: def func(x):
47 ...: y = 1
48 ...: print extractVars('x','y')
49 ...:
50
51 In [3]: func('hello')
52 {'y': 1, 'x': 'hello'}
53 """
54
55 depth = kw.get('depth',0)
56
57 callerNS = sys._getframe(depth+1).f_locals
58 return dict((k,callerNS[k]) for k in names)
59
60
61 def extractVarsAbove(*names):
62 """Extract a set of variables by name from another frame.
63
64 Similar to extractVars(), but with a specified depth of 1, so that names
65 are exctracted exactly from above the caller.
66
67 This is simply a convenience function so that the very common case (for us)
68 of skipping exactly 1 frame doesn't have to construct a special dict for
69 keyword passing."""
70
71 callerNS = sys._getframe(2).f_locals
72 return dict((k,callerNS[k]) for k in names)
73
74 def shexp(s):
75 """Expand $VARS and ~names in a string, like a shell
76
77 :Examples:
78
79 In [2]: os.environ['FOO']='test'
80
81 In [3]: shexp('variable FOO is $FOO')
82 Out[3]: 'variable FOO is test'
83 """
84 return os.path.expandvars(os.path.expanduser(s))
85
86
87 def list_strings(arg):
88 """Always return a list of strings, given a string or list of strings
89 as input.
90
91 :Examples:
92
93 In [7]: list_strings('A single string')
94 Out[7]: ['A single string']
95
96 In [8]: list_strings(['A single string in a list'])
97 Out[8]: ['A single string in a list']
98
99 In [9]: list_strings(['A','list','of','strings'])
100 Out[9]: ['A', 'list', 'of', 'strings']
101 """
102
103 if isinstance(arg,basestring): return [arg]
104 else: return arg
105
106 def marquee(txt='',width=78,mark='*'):
107 """Return the input string centered in a 'marquee'.
108
109 :Examples:
110
111 In [16]: marquee('A test',40)
112 Out[16]: '**************** A test ****************'
113
114 In [17]: marquee('A test',40,'-')
115 Out[17]: '---------------- A test ----------------'
116
117 In [18]: marquee('A test',40,' ')
118 Out[18]: ' A test '
119
120 """
121 if not txt:
122 return (mark*width)[:width]
123 nmark = (width-len(txt)-2)/len(mark)/2
124 if nmark < 0: nmark =0
125 marks = mark*nmark
126 return '%s %s %s' % (marks,txt,marks)
127
128
General Comments 0
You need to be logged in to leave comments. Login now