From: Loic Baron Date: Thu, 26 Sep 2013 10:12:27 +0000 (+0200) Subject: Autocomplete working in query_editor plugin X-Git-Tag: myslice-0.2-5~85^2~7^2~1 X-Git-Url: http://git.onelab.eu/?p=myslice.git;a=commitdiff_plain;h=2f4f816b579ec2fd97dff5a318fe638ed26ebf3c Autocomplete working in query_editor plugin --- diff --git a/manifold/util/autolog.py b/manifold/util/autolog.py new file mode 100644 index 00000000..70c5c056 --- /dev/null +++ b/manifold/util/autolog.py @@ -0,0 +1,422 @@ +# Written by Brendan O'Connor, brenocon@gmail.com, www.anyall.org +# * Originally written Aug. 2005 +# * Posted to gist.github.com/16173 on Oct. 2008 + +# Copyright (c) 2003-2006 Open Source Applications Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import re, sys, types + +""" +Have all your function & method calls automatically logged, in indented outline +form - unlike the stack snapshots in an interactive debugger, it tracks call +structure & stack depths across time! + +It hooks into all function calls that you specify, and logs each time they're +called. I find it especially useful when I don't know what's getting called +when, or need to continuously test for state changes. (by hacking this file) + +Originally inspired from the python cookbook: +http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/198078 + +Currently you can + - tag functions or individual methods to be autologged + - tag an entire class's methods to be autologged + - tag an entire module's classes and functions to be autologged + +TODO: + - allow tagging of ALL modules in the program on startup? + +CAVEATS: + - certain classes barf when you logclass() them -- most notably, + SWIG-generated wrappers, and perhaps others. + +USAGE: see examples on the bottom of this file. + + +Viewing tips +============ + +If your terminal can't keep up, try xterm or putty, they seem to be highest +performance. xterm is available for all platforms through X11... + +Also try: (RunChandler > log &); tail -f log + +Also, you can "less -R log" afterward and get the colors correct. + +If you have long lines, less -RS kills wrapping, enhancing readability. Also +can chop at formatAllArgs(). + +If you want long lines to be chopped realtime, try piping through less:: + + RunChandler | less -RS + +but then you have to hit 'space' lots to prevent chandler from freezing. +less's 'F' command is supposed to do this correctly but doesn't work for me. +""" + + +#@@@ should use the standard python logging system? +log = sys.stdout + +# Globally incremented across function calls, so tracks stack depth +indent = 0 +indStr = ' ' + + +# ANSI escape codes for terminals. +# X11 xterm: always works, all platforms +# cygwin dosbox: run through |cat and then colors work +# linux: works on console & gnome-terminal +# mac: untested + + +BLACK = "\033[0;30m" +BLUE = "\033[0;34m" +GREEN = "\033[0;32m" +CYAN = "\033[0;36m" +RED = "\033[0;31m" +PURPLE = "\033[0;35m" +BROWN = "\033[0;33m" +GRAY = "\033[0;37m" +BOLDGRAY = "\033[1;30m" +BOLDBLUE = "\033[1;34m" +BOLDGREEN = "\033[1;32m" +BOLDCYAN = "\033[1;36m" +BOLDRED = "\033[1;31m" +BOLDPURPLE = "\033[1;35m" +BOLDYELLOW = "\033[1;33m" +WHITE = "\033[1;37m" + +NORMAL = "\033[0m" + + +def indentlog(message): + global log, indStr, indent + print >>log, "%s%s" %(indStr*indent, message) + log.flush() + +def shortstr(obj): + """ + Where to put gritty heuristics to make an object appear in most useful + form. defaults to __str__. + """ + if "wx." in str(obj.__class__) or obj.__class__.__name__.startswith("wx"): + shortclassname = obj.__class__.__name__ + ##shortclassname = str(obj.__class__).split('.')[-1] + if hasattr(obj, "blockItem") and hasattr(obj.blockItem, "blockName"): + moreInfo = "block:'%s'" %obj.blockItem.blockName + else: + moreInfo = "at %d" %id(obj) + return "<%s %s>" % (shortclassname, moreInfo) + else: + return str(obj) + +def formatAllArgs(args, kwds): + """ + makes a nice string representation of all the arguments + """ + allargs = [] + for item in args: + allargs.append('%s' % shortstr(item)) + for key,item in kwds.items(): + allargs.append('%s=%s' % (key,shortstr(item))) + formattedArgs = ', '.join(allargs) + if len(formattedArgs) > 150: + return formattedArgs[:146] + " ..." + return formattedArgs + + +def logmodules(listOfModules): + for m in listOfModules: + bindmodule(m) + +def logmodule(module, logMatch=".*", logNotMatch="nomatchasfdasdf"): + """ + WARNING: this seems to break if you import SWIG wrapper classes + directly into the module namespace ... logclass() creates weirdness when + used on them, for some reason. + + @param module: could be either an actual module object, or the string + you can import (which seems to be the same thing as its + __name__). So you can say logmodule(__name__) at the end + of a module definition, to log all of it. + """ + + allow = lambda s: re.match(logMatch, s) and not re.match(logNotMatch, s) + + if isinstance(module, str): + d = {} + exec "import %s" % module in d + import sys + module = sys.modules[module] + + names = module.__dict__.keys() + for name in names: + if not allow(name): continue + + value = getattr(module, name) + if isinstance(value, type): + setattr(module, name, logclass(value)) + print>>log,"autolog.logmodule(): bound %s" %name + elif isinstance(value, types.FunctionType): + setattr(module, name, logfunction(value)) + print>>log,"autolog.logmodule(): bound %s" %name + +def logfunction(theFunction, displayName=None): + """a decorator.""" + if not displayName: displayName = theFunction.__name__ + + def _wrapper(*args, **kwds): + global indent + argstr = formatAllArgs(args, kwds) + + # Log the entry into the function + indentlog("%s%s%s (%s) " % (BOLDRED,displayName,NORMAL, argstr)) + log.flush() + + indent += 1 + returnval = theFunction(*args,**kwds) + indent -= 1 + + # Log return + ##indentlog("return: %s"% shortstr(returnval) + return returnval + return _wrapper + +def logmethod(theMethod, displayName=None): + """use this for class or instance methods, it formats with the object out front.""" + if not displayName: displayName = theMethod.__name__ + def _methodWrapper(self, *args, **kwds): + "Use this one for instance or class methods" + global indent + + argstr = formatAllArgs(args, kwds) + selfstr = shortstr(self) + + #print >> log,"%s%s. %s (%s) " % (indStr*indent,selfstr,methodname,argstr) + indentlog("%s.%s%s%s (%s) " % (selfstr, BOLDRED,theMethod.__name__,NORMAL, argstr)) + log.flush() + + indent += 1 + + if theMethod.__name__ == 'OnSize': + indentlog("position, size = %s%s %s%s" %(BOLDBLUE, self.GetPosition(), self.GetSize(), NORMAL)) + + returnval = theMethod(self, *args,**kwds) + + indent -= 1 + + return returnval + return _methodWrapper + + +def logclass(cls, methodsAsFunctions=False, + logMatch=".*", logNotMatch="asdfnomatch"): + """ + A class "decorator". But python doesn't support decorator syntax for + classes, so do it manually:: + + class C(object): + ... + C = logclass(C) + + @param methodsAsFunctions: set to True if you always want methodname first + in the display. Probably breaks if you're using class/staticmethods? + """ + + allow = lambda s: re.match(logMatch, s) and not re.match(logNotMatch, s) and \ + s not in ('__str__','__repr__') + + namesToCheck = cls.__dict__.keys() + + for name in namesToCheck: + if not allow(name): continue + # unbound methods show up as mere functions in the values of + # cls.__dict__,so we have to go through getattr + value = getattr(cls, name) + + if methodsAsFunctions and callable(value): + setattr(cls, name, logfunction(value)) + elif isinstance(value, types.MethodType): + #a normal instance method + if value.im_self == None: + setattr(cls, name, logmethod(value)) + + #class & static method are more complex. + #a class method + elif value.im_self == cls: + w = logmethod(value.im_func, + displayName="%s.%s" %(cls.__name__, value.__name__)) + setattr(cls, name, classmethod(w)) + else: assert False + + #a static method + elif isinstance(value, types.FunctionType): + w = logfunction(value, + displayName="%s.%s" %(cls.__name__, value.__name__)) + setattr(cls, name, staticmethod(w)) + return cls + +class LogMetaClass(type): + """ + Alternative to logclass(), you set this as a class's __metaclass__. + + It will not work if the metaclass has already been overridden (e.g. + schema.Item or zope.interface (used in Twisted) + + Also, it should fail for class/staticmethods, that hasnt been added here + yet. + """ + + def __new__(cls,classname,bases,classdict): + logmatch = re.compile(classdict.get('logMatch','.*')) + lognotmatch = re.compile(classdict.get('logNotMatch', 'nevermatchthisstringasdfasdf')) + + for attr,item in classdict.items(): + if callable(item) and logmatch.match(attr) and not lognotmatch.match(attr): + classdict['_H_%s'%attr] = item # rebind the method + classdict[attr] = logmethod(item) # replace method by wrapper + + return type.__new__(cls,classname,bases,classdict) + + + +# ---------------------------- Tests and examples ---------------------------- + +if __name__=='__main__': + print; print "------------------- single function logging ---------------" + @logfunction + def test(): + return 42 + + test() + + print; print "------------------- single method logging -----------------" + class Test1(object): + def __init__(self): + self.a = 10 + + @logmethod + def add(self,a,b): return a+b + + @logmethod + def fac(self,val): + if val == 1: + return 1 + else: + return val * self.fac(val-1) + + @logfunction + def fac2(self, val): + if val == 1: + return 1 + else: + return val * self.fac2(val-1) + + t = Test1() + t.add(5,6) + t.fac(4) + print "--- tagged as @logfunction, doesn't understand 'self' is special:" + t.fac2(4) + + + print; print """-------------------- class "decorator" usage ------------------""" + class Test2(object): + #will be ignored + def __init__(self): + self.a = 10 + def ignoreThis(self): pass + + + def add(self,a,b):return a+b + def fac(self,val): + if val == 1: + return 1 + else: + return val * self.fac(val-1) + + Test2 = logclass(Test2, logMatch='fac|add') + + t2 = Test2() + t2.add(5,6) + t2.fac(4) + t2.ignoreThis() + + + print; print "-------------------- metaclass usage ------------------" + class Test3(object): + __metaclass__ = LogMetaClass + logNotMatch = 'ignoreThis' + + def __init__(self): pass + + def fac(self,val): + if val == 1: + return 1 + else: + return val * self.fac(val-1) + def ignoreThis(self): pass + t3 = Test3() + t3.fac(4) + t3.ignoreThis() + + print; print "-------------- testing static & classmethods --------------" + class Test4(object): + @classmethod + def cm(cls, a, b): + print cls + return a+b + + def im(self, a, b): + print self + return a+b + + @staticmethod + def sm(a,b): return a+b + + Test4 = logclass(Test4) + + Test4.cm(4,3) + Test4.sm(4,3) + + t4 = Test4() + t4.im(4,3) + t4.sm(4,3) + t4.cm(4,3) + + #print; print "-------------- static & classmethods: where to put decorators? --------------" + #class Test5(object): + #@classmethod + #@logmethod + #def cm(cls, a, b): + #print cls + #return a+b + #@logmethod + #def im(self, a, b): + #print self + #return a+b + + #@staticmethod + #@logfunction + #def sm(a,b): return a+b + + + #Test5.cm(4,3) + #Test5.sm(4,3) + + #t5 = Test5() + #t5.im(4,3) + #t5.sm(4,3) + #t5.cm(4,3) diff --git a/manifold/util/callback.py b/manifold/util/callback.py new file mode 100644 index 00000000..03e82806 --- /dev/null +++ b/manifold/util/callback.py @@ -0,0 +1,49 @@ +from manifold.operators import LAST_RECORD +import threading + +#------------------------------------------------------------------ +# Class callback +#------------------------------------------------------------------ + +class Callback: + def __init__(self, deferred=None, router=None, cache_id=None): + #def __init__(self, deferred=None, event=None, router=None, cache_id=None): + self.results = [] + self._deferred = deferred + + #if not self.event: + self.event = threading.Event() + #else: + # self.event = event + + # Used for caching... + self.router = router + self.cache_id = cache_id + + def __call__(self, value): + # End of the list of records sent by Gateway + if value == LAST_RECORD: + if self.cache_id: + # Add query results to cache (expires in 30min) + #print "Result added to cached under id", self.cache_id + self.router.cache[self.cache_id] = (self.results, time.time() + CACHE_LIFETIME) + + if self._deferred: + # Send results back using deferred object + self._deferred.callback(self.results) + else: + # Not using deferred, trigger the event to return results + self.event.set() + return self.event + + # Not LAST_RECORD add the value to the results + self.results.append(value) + + def wait(self): + self.event.wait() + self.event.clear() + + def get_results(self): + self.wait() + return self.results + diff --git a/manifold/util/colors.py b/manifold/util/colors.py new file mode 100644 index 00000000..82639bbf --- /dev/null +++ b/manifold/util/colors.py @@ -0,0 +1,38 @@ +# ANSI escape codes for terminals. +# X11 xterm: always works, all platforms +# cygwin dosbox: run through |cat and then colors work +# linux: works on console & gnome-terminal +# mac: untested + +BLACK = "\033[0;30m" +BLUE = "\033[0;34m" +GREEN = "\033[0;32m" +CYAN = "\033[0;36m" +RED = "\033[0;31m" +PURPLE = "\033[0;35m" +BROWN = "\033[0;33m" +GRAY = "\033[0;37m" +BOLDGRAY = "\033[1;30m" +BOLDBLUE = "\033[1;34m" +BOLDGREEN = "\033[1;32m" +BOLDCYAN = "\033[1;36m" +BOLDRED = "\033[1;31m" +BOLDPURPLE = "\033[1;35m" +BOLDYELLOW = "\033[1;33m" +WHITE = "\033[1;37m" + +MYGREEN = '\033[92m' +MYBLUE = '\033[94m' +MYWARNING = '\033[93m' +MYRED = '\033[91m' +MYHEADER = '\033[95m' +MYEND = '\033[0m' + +NORMAL = "\033[0m" + +if __name__ == '__main__': + # Display color names in their color + for name, color in locals().items(): + if name.startswith('__'): continue + print color, name, MYEND + diff --git a/manifold/util/daemon.py b/manifold/util/daemon.py new file mode 100644 index 00000000..2e5d760e --- /dev/null +++ b/manifold/util/daemon.py @@ -0,0 +1,343 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Daemon: superclass used to implement a daemon easily +# +# Copyright (C)2009-2012, UPMC Paris Universitas +# Authors: +# Marc-Olivier Buob + +# see also: http://www.jejik.com/files/examples/daemon3x.py + +# This is used to import the daemon package instead of the local module which is +# named identically... +from __future__ import absolute_import + +from manifold.util.singleton import Singleton +from manifold.util.log import Log +from manifold.util.options import Options + +import atexit, os, signal, lockfile, logging, sys + +class Daemon(object): + __metaclass__ = Singleton + + DEFAULTS = { + # Running + "uid" : os.getuid(), + "gid" : os.getgid(), + "working_directory" : "/", + "debugmode" : False, + "no_daemon" : False, + "pid_filename" : "/var/run/%s.pid" % Options().get_name() + } + + #------------------------------------------------------------------------- + # Checks + #------------------------------------------------------------------------- + + def check_python_daemon(self): + """ + \brief Check whether python-daemon is properly installed + \return True if everything is file, False otherwise + """ + # http://www.python.org/dev/peps/pep-3143/ + ret = False + try: + import daemon + getattr(daemon, "DaemonContext") + ret = True + except AttributeError, e: + print e + # daemon and python-daemon conflict with each other + Log.critical("Please install python-daemon instead of daemon. Remove daemon first.") + except ImportError: + Log.critical("Please install python-daemon - easy_install python-daemon.") + return ret + + #------------------------------------------------------------------------ + # Initialization + #------------------------------------------------------------------------ + + def make_handler_rsyslog(self, rsyslog_host, rsyslog_port, log_level): + """ + \brief (Internal usage) Prepare logging via rsyslog + \param rsyslog_host The hostname of the rsyslog server + \param rsyslog_port The port of the rsyslog server + \param log_level Log level + """ + # Prepare the handler + shandler = handlers.SysLogHandler( + (rsyslog_host, rsyslog_port), + facility = handlers.SysLogHandler.LOG_DAEMON + ) + + # The log file must remain open while daemonizing + self.files_to_keep.append(shandler.socket) + self.prepare_handler(shandler, log_level) + return shandler + + def make_handler_locallog(self, log_filename, log_level): + """ + \brief (Internal usage) Prepare local logging + \param log_filename The file in which we write the logs + \param log_level Log level + """ + # Create directory in which we store the log file + log_dir = os.path.dirname(log_filename) + if not os.path.exists(log_dir): + try: + os.makedirs(log_dir) + except OSError, why: + log_error("OS error: %s" % why) + + # Prepare the handler + shandler = logging.handlers.RotatingFileHandler( + log_filename, + backupCount = 0 + ) + + # The log file must remain open while daemonizing + self.files_to_keep.append(shandler.stream) + self.prepare_handler(shandler, log_level) + return shandler + + def prepare_handler(self, shandler, log_level): + """ + \brief (Internal usage) + \param shandler Handler used to log information + \param log_level Log level + """ + shandler.setLevel(log_level) + formatter = logging.Formatter("%(asctime)s: %(name)s: %(levelname)s %(message)s") + shandler.setFormatter(formatter) + self.log.addHandler(shandler) + self.log.setLevel(getattr(logging, log_level, logging.INFO)) + + def __init__( + self, + #daemon_name, + terminate_callback = None + #uid = os.getuid(), + #gid = os.getgid(), + #working_directory = "/", + #pid_filename = None, + #no_daemon = False, + #debug = False, + #log = None, # logging.getLogger("plop") + #rsyslog_host = "localhost", # Pass None if no rsyslog server + #rsyslog_port = 514, + #log_file = None, + #log_level = logging.INFO + ): + """ + \brief Constructor + \param daemon_name The name of the daemon + \param uid UID used to run the daemon + \param gid GID used to run the daemon + \param working_directory Working directory used to run the daemon. + Example: /var/lib/foo/ + \param pid_filename Absolute path of the PID file + Example: /var/run/foo.pid + (ignored if no_daemon == True) + \param no_daemon Do not detach the daemon from the terminal + \param debug Run daemon in debug mode + \param log The logger, pass None if unused + Example: logging.getLogger('foo')) + \param rsyslog_host Rsyslog hostname, pass None if unused. + If rsyslog_host is set to None, log are stored locally + \param rsyslog_port Rsyslog port + \param log_file Absolute path of the local log file. + Example: /var/log/foo.log) + \param log_level Log level + Example: logging.INFO + """ + + # Daemon parameters + #self.daemon_name = daemon_name + self.terminate_callback = terminate_callback + #Options().uid = uid + #Options().gid = gid + #Options().working_directory = working_directory + #self.pid_filename = None if no_daemon else pid_filename + #Options().no_daemon = no_daemon + #Options().lock_file = None + #Options().debug = debug + #self.log = log + #self.rsyslog_host = rsyslog_host + #self.rsyslog_port = rsyslog_port + #self.log_file = log_file + #self.log_level = log_level + + # Reference which file descriptors must remain opened while + # daemonizing (for instance the file descriptor related to + # the logger) + self.files_to_keep = [] + + # Initialize self.log (require self.files_to_keep) + #if self.log: # for debugging by using stdout, log may be equal to None + # if rsyslog_host: + # shandler = self.make_handler_rsyslog( + # rsyslog_host, + # rsyslog_port, + # log_level + # ) + # elif log_file: + # shandler = self.make_handler_locallog( + # log_file, + # log_level + # ) + + @classmethod + def init_options(self): + opt = Options() + + opt.add_option( + "--uid", dest = "uid", + help = "UID used to run the dispatcher.", + default = self.DEFAULTS['uid'] + ) + opt.add_option( + "--gid", dest = "gid", + help = "GID used to run the dispatcher.", + default = self.DEFAULTS['gid'] + ) + opt.add_option( + "-w", "--working-directory", dest = "working_directory", + help = "Working directory.", + default = self.DEFAULTS['working_directory'] + ) + opt.add_option( + "-D", "--debugmode", action = "store_false", dest = "debugmode", + help = "Daemon debug mode (useful for developers).", + default = self.DEFAULTS['debugmode'] + ) + opt.add_option( + "-n", "--no-daemon", action = "store_true", dest = "no_daemon", + help = "Run as daemon (detach from terminal).", + default = self.DEFAULTS["no_daemon"] + ) + opt.add_option( + "-i", "--pid-file", dest = "pid_filename", + help = "Absolute path to the pid-file to use when running as daemon.", + default = self.DEFAULTS['pid_filename'] + ) + + + + #------------------------------------------------------------------------ + # Daemon stuff + #------------------------------------------------------------------------ + + def remove_pid_file(self): + """ + \brief Remove the pid file (internal usage) + """ + # The lock file is implicitely released while removing the pid file + Log.debug("Removing %s" % Options().pid_filename) + if os.path.exists(Options().pid_filename) == True: + os.remove(Options().pid_filename) + + def make_pid_file(self): + """ + \brief Create a pid file in which we store the PID of the daemon if needed + """ + if Options().pid_filename and Options().no_daemon == False: + atexit.register(self.remove_pid_file) + file(Options().pid_filename, "w+").write("%s\n" % str(os.getpid())) + + def get_pid_from_pid_file(self): + """ + \brief Retrieve the PID of the daemon thanks to the pid file. + \return None if the pid file is not readable or does not exists + """ + pid = None + if Options().pid_filename: + try: + f_pid = file(Options().pid_filename, "r") + pid = int(f_pid.read().strip()) + f_pid.close() + except IOError: + pid = None + return pid + + def make_lock_file(self): + """ + \brief Prepare the lock file required to manage the pid file + Initialize Options().lock_file + """ + if Options().pid_filename and Options().no_daemon == False: + Log.debug("Daemonizing using pid file '%s'" % Options().pid_filename) + Options().lock_file = lockfile.FileLock(Options().pid_filename) + if Options().lock_file.is_locked() == True: + log_error("'%s' is already running ('%s' is locked)." % (Options().get_name(), Options().pid_filename)) + self.terminate() + Options().lock_file.acquire() + else: + Options().lock_file = None + + def start(self): + """ + \brief Start the daemon + """ + # Check whether daemon module is properly installed + if self.check_python_daemon() == False: + self.terminate() + import daemon + + # Prepare Options().lock_file + self.make_lock_file() + + # Prepare the daemon context + dcontext = daemon.DaemonContext( + detach_process = (not Options().no_daemon), + working_directory = Options().working_directory, + pidfile = Options().lock_file if not Options().no_daemon else None, + stdin = sys.stdin, + stdout = sys.stdout, + stderr = sys.stderr, + uid = Options().uid, + gid = Options().gid, + files_preserve = Log().files_to_keep + ) + + # Prepare signal handling to stop properly if the daemon is killed + # Note that signal.SIGKILL can't be handled: + # http://crunchtools.com/unixlinux-signals-101/ + dcontext.signal_map = { + signal.SIGTERM : self.signal_handler, + signal.SIGQUIT : self.signal_handler, + signal.SIGINT : self.signal_handler + } + + if Options().debugmode == True: + self.main() + else: + with dcontext: + self.make_pid_file() + try: + self.main() + except Exception, why: + Log.error("Unhandled exception in start: %s" % why) + + def signal_handler(self, signal_id, frame): + """ + \brief Stop the daemon (signal handler) + The lockfile is implicitly released by the daemon package + \param signal_id The integer identifying the signal + (see also "man 7 signal") + Example: 15 if the received signal is signal.SIGTERM + \param frame + """ + self.terminate() + + def stop(self): + Log.debug("Stopping '%s'" % self.daemon_name) + + def terminate(self): + if self.terminate_callback: + self.terminate_callback() + else: + sys.exit(0) + +Daemon.init_options() diff --git a/manifold/util/dfs.py b/manifold/util/dfs.py new file mode 100644 index 00000000..019645d6 --- /dev/null +++ b/manifold/util/dfs.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Depth first search algorithm +# Based on http://www.boost.org/doc/libs/1_52_0/libs/graph/doc/depth_first_search.html +# +# Copyright (C) UPMC Paris Universitas +# Authors: +# Marc-Olivier Buob +# Jordan Augé + +class dfs_color: + WHITE = 1 # not yet visited + GRAY = 2 # currently visited + BLACK = 3 # visited + +#DFS(G) +# for each vertex u in V +# color[u] := WHITE +# p[u] = u +# end for +# time := 0 +# if there is a starting vertex s +# call DFS-VISIT(G, s) +# for each vertex u in V +# if color[u] = WHITE +# call DFS-VISIT(G, u) +# end for +# return (p,d_time,f_time) + +def dfs(graph, root, exclude_uv=None): + """ + \brief Run the DFS algorithm + \param graph The graph we explore + \param root The starting vertex + \return A dictionnary which maps each vertex of the tree + to its predecessor, None otherwise. + Only the root node as a predecessor equal to None. + Nodes not referenced in this dictionnary do not + belong to the tree. + """ + # Initialization + map_vertex_color = {} + map_vertex_pred = {} + for u in graph.nodes(): + map_vertex_color[u] = dfs_color.WHITE + map_vertex_pred[u] = None + + # Recursive calls + if not exclude_uv: + exclude_uv = lambda u,v: False + dfs_visit(graph, root, map_vertex_color, map_vertex_pred, exclude_uv) + + # Remove from map_vertex_pred the vertices having no + # predecessor but the root node. + for v, u in map_vertex_pred.items(): + if u == None and v != root: + del map_vertex_pred[v] + + return map_vertex_pred + +#DFS-VISIT(G, u) +# color[u] := GRAY +# d_time[u] := time := time + 1 +# for each v in Adj[u] +# if (color[v] = WHITE) +# p[v] = u +# call DFS-VISIT(G, v) +# else if (color[v] = GRAY) +# ... +# else if (color[v] = BLACK) +# ... +# end for +# color[u] := BLACK +# f_time[u] := time := time + 1 + +def dfs_visit(graph, u, map_vertex_color, map_vertex_pred, exclude_uv): + """ + \brief Internal usage (DFS implementation) + \param graph The graph we explore + \param u The current node + \param map_vertex_color: maps each vertex to a color + - dfs_color.WHITE: iif the vertex is not reachable from the root node + - dfs_color.BLACK: otherwise + \param map_vertex_pred: maps each vertex to its predecessor (if any) visited + during the DFS exploration, None otherwise + """ + map_vertex_color[u] = dfs_color.GRAY + for v in graph.successors(u): + color_v = map_vertex_color[v] + if color_v == dfs_color.WHITE and not exclude_uv(u, v): + map_vertex_pred[v] = u + dfs_visit(graph, v, map_vertex_color, map_vertex_pred, exclude_uv) + map_vertex_color[u] = dfs_color.BLACK + diff --git a/manifold/util/enum.py b/manifold/util/enum.py new file mode 100644 index 00000000..4f3c577b --- /dev/null +++ b/manifold/util/enum.py @@ -0,0 +1,7 @@ +class Enum(object): + def __init__(self, *keys): + self.__dict__.update(zip(keys, range(len(keys)))) + self.invmap = {v:k for k, v in self.__dict__.items()} + + def get_str(self, value): + return self.invmap[value] diff --git a/manifold/util/functional.py b/manifold/util/functional.py new file mode 100644 index 00000000..a4119398 --- /dev/null +++ b/manifold/util/functional.py @@ -0,0 +1,53 @@ +"""Borrowed from Django.""" + +from threading import Lock + +class LazyObject(object): + """ + A wrapper for another class that can be used to delay instantiation of the + wrapped class. + + By subclassing, you have the opportunity to intercept and alter the + instantiation. If you don't need to do that, use SimpleLazyObject. + """ + def __init__(self): + self._wrapped = None + self._lock = Lock() + + def __getattr__(self, name): + self._lock.acquire() + if self._wrapped is None: + self._setup() + self._lock.release() + return getattr(self._wrapped, name) + + def __setattr__(self, name, value): + if name in ["_wrapped", "_lock"]: + # Assign to __dict__ to avoid infinite __setattr__ loops. + self.__dict__[name] = value + else: + if self._wrapped is None: + self._setup() + setattr(self._wrapped, name, value) + + def __delattr__(self, name): + if name == "_wrapped": + raise TypeError("can't delete _wrapped.") + if self._wrapped is None: + self._setup() + delattr(self._wrapped, name) + + def _setup(self): + """ + Must be implemented by subclasses to initialise the wrapped object. + """ + raise NotImplementedError + + # introspection support: + __members__ = property(lambda self: self.__dir__()) + + def __dir__(self): + if self._wrapped is None: + self._setup() + return dir(self._wrapped) + diff --git a/manifold/util/ipaddr.py b/manifold/util/ipaddr.py new file mode 100644 index 00000000..ad27ae9d --- /dev/null +++ b/manifold/util/ipaddr.py @@ -0,0 +1,1897 @@ +#!/usr/bin/python +# +# Copyright 2007 Google Inc. +# Licensed to PSF under a Contributor Agreement. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. See the License for the specific language governing +# permissions and limitations under the License. + +"""A fast, lightweight IPv4/IPv6 manipulation library in Python. + +This library is used to create/poke/manipulate IPv4 and IPv6 addresses +and networks. + +""" + +__version__ = '2.1.10' + +import struct + +IPV4LENGTH = 32 +IPV6LENGTH = 128 + + +class AddressValueError(ValueError): + """A Value Error related to the address.""" + + +class NetmaskValueError(ValueError): + """A Value Error related to the netmask.""" + + +def IPAddress(address, version=None): + """Take an IP string/int and return an object of the correct type. + + Args: + address: A string or integer, the IP address. Either IPv4 or + IPv6 addresses may be supplied; integers less than 2**32 will + be considered to be IPv4 by default. + version: An Integer, 4 or 6. If set, don't try to automatically + determine what the IP address type is. important for things + like IPAddress(1), which could be IPv4, '0.0.0.1', or IPv6, + '::1'. + + Returns: + An IPv4Address or IPv6Address object. + + Raises: + ValueError: if the string passed isn't either a v4 or a v6 + address. + + """ + if version: + if version == 4: + return IPv4Address(address) + elif version == 6: + return IPv6Address(address) + + try: + return IPv4Address(address) + except (AddressValueError, NetmaskValueError): + pass + + try: + return IPv6Address(address) + except (AddressValueError, NetmaskValueError): + pass + + raise ValueError('%r does not appear to be an IPv4 or IPv6 address' % + address) + + +def IPNetwork(address, version=None, strict=False): + """Take an IP string/int and return an object of the correct type. + + Args: + address: A string or integer, the IP address. Either IPv4 or + IPv6 addresses may be supplied; integers less than 2**32 will + be considered to be IPv4 by default. + version: An Integer, if set, don't try to automatically + determine what the IP address type is. important for things + like IPNetwork(1), which could be IPv4, '0.0.0.1/32', or IPv6, + '::1/128'. + + Returns: + An IPv4Network or IPv6Network object. + + Raises: + ValueError: if the string passed isn't either a v4 or a v6 + address. Or if a strict network was requested and a strict + network wasn't given. + + """ + if version: + if version == 4: + return IPv4Network(address, strict) + elif version == 6: + return IPv6Network(address, strict) + + try: + return IPv4Network(address, strict) + except (AddressValueError, NetmaskValueError): + pass + + try: + return IPv6Network(address, strict) + except (AddressValueError, NetmaskValueError): + pass + + raise ValueError('%r does not appear to be an IPv4 or IPv6 network' % + address) + + +def v4_int_to_packed(address): + """The binary representation of this address. + + Args: + address: An integer representation of an IPv4 IP address. + + Returns: + The binary representation of this address. + + Raises: + ValueError: If the integer is too large to be an IPv4 IP + address. + """ + if address > _BaseV4._ALL_ONES: + raise ValueError('Address too large for IPv4') + return Bytes(struct.pack('!I', address)) + + +def v6_int_to_packed(address): + """The binary representation of this address. + + Args: + address: An integer representation of an IPv4 IP address. + + Returns: + The binary representation of this address. + """ + return Bytes(struct.pack('!QQ', address >> 64, address & (2**64 - 1))) + + +def _find_address_range(addresses): + """Find a sequence of addresses. + + Args: + addresses: a list of IPv4 or IPv6 addresses. + + Returns: + A tuple containing the first and last IP addresses in the sequence. + + """ + first = last = addresses[0] + for ip in addresses[1:]: + if ip._ip == last._ip + 1: + last = ip + else: + break + return (first, last) + +def _get_prefix_length(number1, number2, bits): + """Get the number of leading bits that are same for two numbers. + + Args: + number1: an integer. + number2: another integer. + bits: the maximum number of bits to compare. + + Returns: + The number of leading bits that are the same for two numbers. + + """ + for i in range(bits): + if number1 >> i == number2 >> i: + return bits - i + return 0 + +def _count_righthand_zero_bits(number, bits): + """Count the number of zero bits on the right hand side. + + Args: + number: an integer. + bits: maximum number of bits to count. + + Returns: + The number of zero bits on the right hand side of the number. + + """ + if number == 0: + return bits + for i in range(bits): + if (number >> i) % 2: + return i + +def summarize_address_range(first, last): + """Summarize a network range given the first and last IP addresses. + + Example: + >>> summarize_address_range(IPv4Address('1.1.1.0'), + IPv4Address('1.1.1.130')) + [IPv4Network('1.1.1.0/25'), IPv4Network('1.1.1.128/31'), + IPv4Network('1.1.1.130/32')] + + Args: + first: the first IPv4Address or IPv6Address in the range. + last: the last IPv4Address or IPv6Address in the range. + + Returns: + The address range collapsed to a list of IPv4Network's or + IPv6Network's. + + Raise: + TypeError: + If the first and last objects are not IP addresses. + If the first and last objects are not the same version. + ValueError: + If the last object is not greater than the first. + If the version is not 4 or 6. + + """ + if not (isinstance(first, _BaseIP) and isinstance(last, _BaseIP)): + raise TypeError('first and last must be IP addresses, not networks') + if first.version != last.version: + raise TypeError("%s and %s are not of the same version" % ( + str(first), str(last))) + if first > last: + raise ValueError('last IP address must be greater than first') + + networks = [] + + if first.version == 4: + ip = IPv4Network + elif first.version == 6: + ip = IPv6Network + else: + raise ValueError('unknown IP version') + + ip_bits = first._max_prefixlen + first_int = first._ip + last_int = last._ip + while first_int <= last_int: + nbits = _count_righthand_zero_bits(first_int, ip_bits) + current = None + while nbits >= 0: + addend = 2**nbits - 1 + current = first_int + addend + nbits -= 1 + if current <= last_int: + break + prefix = _get_prefix_length(first_int, current, ip_bits) + net = ip('%s/%d' % (str(first), prefix)) + networks.append(net) + if current == ip._ALL_ONES: + break + first_int = current + 1 + first = IPAddress(first_int, version=first._version) + return networks + +def _collapse_address_list_recursive(addresses): + """Loops through the addresses, collapsing concurrent netblocks. + + Example: + + ip1 = IPv4Network('1.1.0.0/24') + ip2 = IPv4Network('1.1.1.0/24') + ip3 = IPv4Network('1.1.2.0/24') + ip4 = IPv4Network('1.1.3.0/24') + ip5 = IPv4Network('1.1.4.0/24') + ip6 = IPv4Network('1.1.0.1/22') + + _collapse_address_list_recursive([ip1, ip2, ip3, ip4, ip5, ip6]) -> + [IPv4Network('1.1.0.0/22'), IPv4Network('1.1.4.0/24')] + + This shouldn't be called directly; it is called via + collapse_address_list([]). + + Args: + addresses: A list of IPv4Network's or IPv6Network's + + Returns: + A list of IPv4Network's or IPv6Network's depending on what we were + passed. + + """ + ret_array = [] + optimized = False + + for cur_addr in addresses: + if not ret_array: + ret_array.append(cur_addr) + continue + if cur_addr in ret_array[-1]: + optimized = True + elif cur_addr == ret_array[-1].supernet().subnet()[1]: + ret_array.append(ret_array.pop().supernet()) + optimized = True + else: + ret_array.append(cur_addr) + + if optimized: + return _collapse_address_list_recursive(ret_array) + + return ret_array + + +def collapse_address_list(addresses): + """Collapse a list of IP objects. + + Example: + collapse_address_list([IPv4('1.1.0.0/24'), IPv4('1.1.1.0/24')]) -> + [IPv4('1.1.0.0/23')] + + Args: + addresses: A list of IPv4Network or IPv6Network objects. + + Returns: + A list of IPv4Network or IPv6Network objects depending on what we + were passed. + + Raises: + TypeError: If passed a list of mixed version objects. + + """ + i = 0 + addrs = [] + ips = [] + nets = [] + + # split IP addresses and networks + for ip in addresses: + if isinstance(ip, _BaseIP): + if ips and ips[-1]._version != ip._version: + raise TypeError("%s and %s are not of the same version" % ( + str(ip), str(ips[-1]))) + ips.append(ip) + elif ip._prefixlen == ip._max_prefixlen: + if ips and ips[-1]._version != ip._version: + raise TypeError("%s and %s are not of the same version" % ( + str(ip), str(ips[-1]))) + ips.append(ip.ip) + else: + if nets and nets[-1]._version != ip._version: + raise TypeError("%s and %s are not of the same version" % ( + str(ip), str(ips[-1]))) + nets.append(ip) + + # sort and dedup + ips = sorted(set(ips)) + nets = sorted(set(nets)) + + while i < len(ips): + (first, last) = _find_address_range(ips[i:]) + i = ips.index(last) + 1 + addrs.extend(summarize_address_range(first, last)) + + return _collapse_address_list_recursive(sorted( + addrs + nets, key=_BaseNet._get_networks_key)) + +# backwards compatibility +CollapseAddrList = collapse_address_list + +# We need to distinguish between the string and packed-bytes representations +# of an IP address. For example, b'0::1' is the IPv4 address 48.58.58.49, +# while '0::1' is an IPv6 address. +# +# In Python 3, the native 'bytes' type already provides this functionality, +# so we use it directly. For earlier implementations where bytes is not a +# distinct type, we create a subclass of str to serve as a tag. +# +# Usage example (Python 2): +# ip = ipaddr.IPAddress(ipaddr.Bytes('xxxx')) +# +# Usage example (Python 3): +# ip = ipaddr.IPAddress(b'xxxx') +try: + if bytes is str: + raise TypeError("bytes is not a distinct type") + Bytes = bytes +except (NameError, TypeError): + class Bytes(str): + def __repr__(self): + return 'Bytes(%s)' % str.__repr__(self) + +def get_mixed_type_key(obj): + """Return a key suitable for sorting between networks and addresses. + + Address and Network objects are not sortable by default; they're + fundamentally different so the expression + + IPv4Address('1.1.1.1') <= IPv4Network('1.1.1.1/24') + + doesn't make any sense. There are some times however, where you may wish + to have ipaddr sort these for you anyway. If you need to do this, you + can use this function as the key= argument to sorted(). + + Args: + obj: either a Network or Address object. + Returns: + appropriate key. + + """ + if isinstance(obj, _BaseNet): + return obj._get_networks_key() + elif isinstance(obj, _BaseIP): + return obj._get_address_key() + return NotImplemented + +class _IPAddrBase(object): + + """The mother class.""" + + def __index__(self): + return self._ip + + def __int__(self): + return self._ip + + def __hex__(self): + return hex(self._ip) + + @property + def exploded(self): + """Return the longhand version of the IP address as a string.""" + return self._explode_shorthand_ip_string() + + @property + def compressed(self): + """Return the shorthand version of the IP address as a string.""" + return str(self) + + +class _BaseIP(_IPAddrBase): + + """A generic IP object. + + This IP class contains the version independent methods which are + used by single IP addresses. + + """ + + def __eq__(self, other): + try: + return (self._ip == other._ip + and self._version == other._version) + except AttributeError: + return NotImplemented + + def __ne__(self, other): + eq = self.__eq__(other) + if eq is NotImplemented: + return NotImplemented + return not eq + + def __le__(self, other): + gt = self.__gt__(other) + if gt is NotImplemented: + return NotImplemented + return not gt + + def __ge__(self, other): + lt = self.__lt__(other) + if lt is NotImplemented: + return NotImplemented + return not lt + + def __lt__(self, other): + if self._version != other._version: + raise TypeError('%s and %s are not of the same version' % ( + str(self), str(other))) + if not isinstance(other, _BaseIP): + raise TypeError('%s and %s are not of the same type' % ( + str(self), str(other))) + if self._ip != other._ip: + return self._ip < other._ip + return False + + def __gt__(self, other): + if self._version != other._version: + raise TypeError('%s and %s are not of the same version' % ( + str(self), str(other))) + if not isinstance(other, _BaseIP): + raise TypeError('%s and %s are not of the same type' % ( + str(self), str(other))) + if self._ip != other._ip: + return self._ip > other._ip + return False + + # Shorthand for Integer addition and subtraction. This is not + # meant to ever support addition/subtraction of addresses. + def __add__(self, other): + if not isinstance(other, int): + return NotImplemented + return IPAddress(int(self) + other, version=self._version) + + def __sub__(self, other): + if not isinstance(other, int): + return NotImplemented + return IPAddress(int(self) - other, version=self._version) + + def __repr__(self): + return '%s(%r)' % (self.__class__.__name__, str(self)) + + def __str__(self): + return '%s' % self._string_from_ip_int(self._ip) + + def __hash__(self): + return hash(hex(long(self._ip))) + + def _get_address_key(self): + return (self._version, self) + + @property + def version(self): + raise NotImplementedError('BaseIP has no version') + + +class _BaseNet(_IPAddrBase): + + """A generic IP object. + + This IP class contains the version independent methods which are + used by networks. + + """ + + def __init__(self, address): + self._cache = {} + + def __repr__(self): + return '%s(%r)' % (self.__class__.__name__, str(self)) + + def iterhosts(self): + """Generate Iterator over usable hosts in a network. + + This is like __iter__ except it doesn't return the network + or broadcast addresses. + + """ + cur = int(self.network) + 1 + bcast = int(self.broadcast) - 1 + while cur <= bcast: + cur += 1 + yield IPAddress(cur - 1, version=self._version) + + def __iter__(self): + cur = int(self.network) + bcast = int(self.broadcast) + while cur <= bcast: + cur += 1 + yield IPAddress(cur - 1, version=self._version) + + def __getitem__(self, n): + network = int(self.network) + broadcast = int(self.broadcast) + if n >= 0: + if network + n > broadcast: + raise IndexError + return IPAddress(network + n, version=self._version) + else: + n += 1 + if broadcast + n < network: + raise IndexError + return IPAddress(broadcast + n, version=self._version) + + def __lt__(self, other): + if self._version != other._version: + raise TypeError('%s and %s are not of the same version' % ( + str(self), str(other))) + if not isinstance(other, _BaseNet): + raise TypeError('%s and %s are not of the same type' % ( + str(self), str(other))) + if self.network != other.network: + return self.network < other.network + if self.netmask != other.netmask: + return self.netmask < other.netmask + return False + + def __gt__(self, other): + if self._version != other._version: + raise TypeError('%s and %s are not of the same version' % ( + str(self), str(other))) + if not isinstance(other, _BaseNet): + raise TypeError('%s and %s are not of the same type' % ( + str(self), str(other))) + if self.network != other.network: + return self.network > other.network + if self.netmask != other.netmask: + return self.netmask > other.netmask + return False + + def __le__(self, other): + gt = self.__gt__(other) + if gt is NotImplemented: + return NotImplemented + return not gt + + def __ge__(self, other): + lt = self.__lt__(other) + if lt is NotImplemented: + return NotImplemented + return not lt + + def __eq__(self, other): + try: + return (self._version == other._version + and self.network == other.network + and int(self.netmask) == int(other.netmask)) + except AttributeError: + if isinstance(other, _BaseIP): + return (self._version == other._version + and self._ip == other._ip) + + def __ne__(self, other): + eq = self.__eq__(other) + if eq is NotImplemented: + return NotImplemented + return not eq + + def __str__(self): + return '%s/%s' % (str(self.ip), + str(self._prefixlen)) + + def __hash__(self): + return hash(int(self.network) ^ int(self.netmask)) + + def __contains__(self, other): + # always false if one is v4 and the other is v6. + if self._version != other._version: + return False + # dealing with another network. + if isinstance(other, _BaseNet): + return (self.network <= other.network and + self.broadcast >= other.broadcast) + # dealing with another address + else: + return (int(self.network) <= int(other._ip) <= + int(self.broadcast)) + + def overlaps(self, other): + """Tell if self is partly contained in other.""" + return self.network in other or self.broadcast in other or ( + other.network in self or other.broadcast in self) + + @property + def network(self): + x = self._cache.get('network') + if x is None: + x = IPAddress(self._ip & int(self.netmask), version=self._version) + self._cache['network'] = x + return x + + @property + def broadcast(self): + x = self._cache.get('broadcast') + if x is None: + x = IPAddress(self._ip | int(self.hostmask), version=self._version) + self._cache['broadcast'] = x + return x + + @property + def hostmask(self): + x = self._cache.get('hostmask') + if x is None: + x = IPAddress(int(self.netmask) ^ self._ALL_ONES, + version=self._version) + self._cache['hostmask'] = x + return x + + @property + def with_prefixlen(self): + return '%s/%d' % (str(self.ip), self._prefixlen) + + @property + def with_netmask(self): + return '%s/%s' % (str(self.ip), str(self.netmask)) + + @property + def with_hostmask(self): + return '%s/%s' % (str(self.ip), str(self.hostmask)) + + @property + def numhosts(self): + """Number of hosts in the current subnet.""" + return int(self.broadcast) - int(self.network) + 1 + + @property + def version(self): + raise NotImplementedError('BaseNet has no version') + + @property + def prefixlen(self): + return self._prefixlen + + def address_exclude(self, other): + """Remove an address from a larger block. + + For example: + + addr1 = IPNetwork('10.1.1.0/24') + addr2 = IPNetwork('10.1.1.0/26') + addr1.address_exclude(addr2) = + [IPNetwork('10.1.1.64/26'), IPNetwork('10.1.1.128/25')] + + or IPv6: + + addr1 = IPNetwork('::1/32') + addr2 = IPNetwork('::1/128') + addr1.address_exclude(addr2) = [IPNetwork('::0/128'), + IPNetwork('::2/127'), + IPNetwork('::4/126'), + IPNetwork('::8/125'), + ... + IPNetwork('0:0:8000::/33')] + + Args: + other: An IPvXNetwork object of the same type. + + Returns: + A sorted list of IPvXNetwork objects addresses which is self + minus other. + + Raises: + TypeError: If self and other are of difffering address + versions, or if other is not a network object. + ValueError: If other is not completely contained by self. + + """ + if not self._version == other._version: + raise TypeError("%s and %s are not of the same version" % ( + str(self), str(other))) + + if not isinstance(other, _BaseNet): + raise TypeError("%s is not a network object" % str(other)) + + if other not in self: + raise ValueError('%s not contained in %s' % (str(other), + str(self))) + if other == self: + return [] + + ret_addrs = [] + + # Make sure we're comparing the network of other. + other = IPNetwork('%s/%s' % (str(other.network), str(other.prefixlen)), + version=other._version) + + s1, s2 = self.subnet() + while s1 != other and s2 != other: + if other in s1: + ret_addrs.append(s2) + s1, s2 = s1.subnet() + elif other in s2: + ret_addrs.append(s1) + s1, s2 = s2.subnet() + else: + # If we got here, there's a bug somewhere. + assert True == False, ('Error performing exclusion: ' + 's1: %s s2: %s other: %s' % + (str(s1), str(s2), str(other))) + if s1 == other: + ret_addrs.append(s2) + elif s2 == other: + ret_addrs.append(s1) + else: + # If we got here, there's a bug somewhere. + assert True == False, ('Error performing exclusion: ' + 's1: %s s2: %s other: %s' % + (str(s1), str(s2), str(other))) + + return sorted(ret_addrs, key=_BaseNet._get_networks_key) + + def compare_networks(self, other): + """Compare two IP objects. + + This is only concerned about the comparison of the integer + representation of the network addresses. This means that the + host bits aren't considered at all in this method. If you want + to compare host bits, you can easily enough do a + 'HostA._ip < HostB._ip' + + Args: + other: An IP object. + + Returns: + If the IP versions of self and other are the same, returns: + + -1 if self < other: + eg: IPv4('1.1.1.0/24') < IPv4('1.1.2.0/24') + IPv6('1080::200C:417A') < IPv6('1080::200B:417B') + 0 if self == other + eg: IPv4('1.1.1.1/24') == IPv4('1.1.1.2/24') + IPv6('1080::200C:417A/96') == IPv6('1080::200C:417B/96') + 1 if self > other + eg: IPv4('1.1.1.0/24') > IPv4('1.1.0.0/24') + IPv6('1080::1:200C:417A/112') > + IPv6('1080::0:200C:417A/112') + + If the IP versions of self and other are different, returns: + + -1 if self._version < other._version + eg: IPv4('10.0.0.1/24') < IPv6('::1/128') + 1 if self._version > other._version + eg: IPv6('::1/128') > IPv4('255.255.255.0/24') + + """ + if self._version < other._version: + return -1 + if self._version > other._version: + return 1 + # self._version == other._version below here: + if self.network < other.network: + return -1 + if self.network > other.network: + return 1 + # self.network == other.network below here: + if self.netmask < other.netmask: + return -1 + if self.netmask > other.netmask: + return 1 + # self.network == other.network and self.netmask == other.netmask + return 0 + + def _get_networks_key(self): + """Network-only key function. + + Returns an object that identifies this address' network and + netmask. This function is a suitable "key" argument for sorted() + and list.sort(). + + """ + return (self._version, self.network, self.netmask) + + def _ip_int_from_prefix(self, prefixlen=None): + """Turn the prefix length netmask into a int for comparison. + + Args: + prefixlen: An integer, the prefix length. + + Returns: + An integer. + + """ + if not prefixlen and prefixlen != 0: + prefixlen = self._prefixlen + return self._ALL_ONES ^ (self._ALL_ONES >> prefixlen) + + def _prefix_from_ip_int(self, ip_int, mask=32): + """Return prefix length from the decimal netmask. + + Args: + ip_int: An integer, the IP address. + mask: The netmask. Defaults to 32. + + Returns: + An integer, the prefix length. + + """ + while mask: + if ip_int & 1 == 1: + break + ip_int >>= 1 + mask -= 1 + + return mask + + def _ip_string_from_prefix(self, prefixlen=None): + """Turn a prefix length into a dotted decimal string. + + Args: + prefixlen: An integer, the netmask prefix length. + + Returns: + A string, the dotted decimal netmask string. + + """ + if not prefixlen: + prefixlen = self._prefixlen + return self._string_from_ip_int(self._ip_int_from_prefix(prefixlen)) + + def iter_subnets(self, prefixlen_diff=1, new_prefix=None): + """The subnets which join to make the current subnet. + + In the case that self contains only one IP + (self._prefixlen == 32 for IPv4 or self._prefixlen == 128 + for IPv6), return a list with just ourself. + + Args: + prefixlen_diff: An integer, the amount the prefix length + should be increased by. This should not be set if + new_prefix is also set. + new_prefix: The desired new prefix length. This must be a + larger number (smaller prefix) than the existing prefix. + This should not be set if prefixlen_diff is also set. + + Returns: + An iterator of IPv(4|6) objects. + + Raises: + ValueError: The prefixlen_diff is too small or too large. + OR + prefixlen_diff and new_prefix are both set or new_prefix + is a smaller number than the current prefix (smaller + number means a larger network) + + """ + if self._prefixlen == self._max_prefixlen: + yield self + return + + if new_prefix is not None: + if new_prefix < self._prefixlen: + raise ValueError('new prefix must be longer') + if prefixlen_diff != 1: + raise ValueError('cannot set prefixlen_diff and new_prefix') + prefixlen_diff = new_prefix - self._prefixlen + + if prefixlen_diff < 0: + raise ValueError('prefix length diff must be > 0') + new_prefixlen = self._prefixlen + prefixlen_diff + + if not self._is_valid_netmask(str(new_prefixlen)): + raise ValueError( + 'prefix length diff %d is invalid for netblock %s' % ( + new_prefixlen, str(self))) + + first = IPNetwork('%s/%s' % (str(self.network), + str(self._prefixlen + prefixlen_diff)), + version=self._version) + + yield first + current = first + while True: + broadcast = current.broadcast + if broadcast == self.broadcast: + return + new_addr = IPAddress(int(broadcast) + 1, version=self._version) + current = IPNetwork('%s/%s' % (str(new_addr), str(new_prefixlen)), + version=self._version) + + yield current + + def masked(self): + """Return the network object with the host bits masked out.""" + return IPNetwork('%s/%d' % (self.network, self._prefixlen), + version=self._version) + + def subnet(self, prefixlen_diff=1, new_prefix=None): + """Return a list of subnets, rather than an iterator.""" + return list(self.iter_subnets(prefixlen_diff, new_prefix)) + + def supernet(self, prefixlen_diff=1, new_prefix=None): + """The supernet containing the current network. + + Args: + prefixlen_diff: An integer, the amount the prefix length of + the network should be decreased by. For example, given a + /24 network and a prefixlen_diff of 3, a supernet with a + /21 netmask is returned. + + Returns: + An IPv4 network object. + + Raises: + ValueError: If self.prefixlen - prefixlen_diff < 0. I.e., you have a + negative prefix length. + OR + If prefixlen_diff and new_prefix are both set or new_prefix is a + larger number than the current prefix (larger number means a + smaller network) + + """ + if self._prefixlen == 0: + return self + + if new_prefix is not None: + if new_prefix > self._prefixlen: + raise ValueError('new prefix must be shorter') + if prefixlen_diff != 1: + raise ValueError('cannot set prefixlen_diff and new_prefix') + prefixlen_diff = self._prefixlen - new_prefix + + + if self.prefixlen - prefixlen_diff < 0: + raise ValueError( + 'current prefixlen is %d, cannot have a prefixlen_diff of %d' % + (self.prefixlen, prefixlen_diff)) + return IPNetwork('%s/%s' % (str(self.network), + str(self.prefixlen - prefixlen_diff)), + version=self._version) + + # backwards compatibility + Subnet = subnet + Supernet = supernet + AddressExclude = address_exclude + CompareNetworks = compare_networks + Contains = __contains__ + + +class _BaseV4(object): + + """Base IPv4 object. + + The following methods are used by IPv4 objects in both single IP + addresses and networks. + + """ + + # Equivalent to 255.255.255.255 or 32 bits of 1's. + _ALL_ONES = (2**IPV4LENGTH) - 1 + _DECIMAL_DIGITS = frozenset('0123456789') + + def __init__(self, address): + self._version = 4 + self._max_prefixlen = IPV4LENGTH + + def _explode_shorthand_ip_string(self): + return str(self) + + def _ip_int_from_string(self, ip_str): + """Turn the given IP string into an integer for comparison. + + Args: + ip_str: A string, the IP ip_str. + + Returns: + The IP ip_str as an integer. + + Raises: + AddressValueError: if ip_str isn't a valid IPv4 Address. + + """ + octets = ip_str.split('.') + if len(octets) != 4: + raise AddressValueError(ip_str) + + packed_ip = 0 + for oc in octets: + try: + packed_ip = (packed_ip << 8) | self._parse_octet(oc) + except ValueError: + raise AddressValueError(ip_str) + return packed_ip + + def _parse_octet(self, octet_str): + """Convert a decimal octet into an integer. + + Args: + octet_str: A string, the number to parse. + + Returns: + The octet as an integer. + + Raises: + ValueError: if the octet isn't strictly a decimal from [0..255]. + + """ + # Whitelist the characters, since int() allows a lot of bizarre stuff. + if not self._DECIMAL_DIGITS.issuperset(octet_str): + raise ValueError + octet_int = int(octet_str, 10) + # Disallow leading zeroes, because no clear standard exists on + # whether these should be interpreted as decimal or octal. + if octet_int > 255 or (octet_str[0] == '0' and len(octet_str) > 1): + raise ValueError + return octet_int + + def _string_from_ip_int(self, ip_int): + """Turns a 32-bit integer into dotted decimal notation. + + Args: + ip_int: An integer, the IP address. + + Returns: + The IP address as a string in dotted decimal notation. + + """ + octets = [] + for _ in xrange(4): + octets.insert(0, str(ip_int & 0xFF)) + ip_int >>= 8 + return '.'.join(octets) + + @property + def max_prefixlen(self): + return self._max_prefixlen + + @property + def packed(self): + """The binary representation of this address.""" + return v4_int_to_packed(self._ip) + + @property + def version(self): + return self._version + + @property + def is_reserved(self): + """Test if the address is otherwise IETF reserved. + + Returns: + A boolean, True if the address is within the + reserved IPv4 Network range. + + """ + return self in IPv4Network('240.0.0.0/4') + + @property + def is_private(self): + """Test if this address is allocated for private networks. + + Returns: + A boolean, True if the address is reserved per RFC 1918. + + """ + return (self in IPv4Network('10.0.0.0/8') or + self in IPv4Network('172.16.0.0/12') or + self in IPv4Network('192.168.0.0/16')) + + @property + def is_multicast(self): + """Test if the address is reserved for multicast use. + + Returns: + A boolean, True if the address is multicast. + See RFC 3171 for details. + + """ + return self in IPv4Network('224.0.0.0/4') + + @property + def is_unspecified(self): + """Test if the address is unspecified. + + Returns: + A boolean, True if this is the unspecified address as defined in + RFC 5735 3. + + """ + return self in IPv4Network('0.0.0.0') + + @property + def is_loopback(self): + """Test if the address is a loopback address. + + Returns: + A boolean, True if the address is a loopback per RFC 3330. + + """ + return self in IPv4Network('127.0.0.0/8') + + @property + def is_link_local(self): + """Test if the address is reserved for link-local. + + Returns: + A boolean, True if the address is link-local per RFC 3927. + + """ + return self in IPv4Network('169.254.0.0/16') + + +class IPv4Address(_BaseV4, _BaseIP): + + """Represent and manipulate single IPv4 Addresses.""" + + def __init__(self, address): + + """ + Args: + address: A string or integer representing the IP + '192.168.1.1' + + Additionally, an integer can be passed, so + IPv4Address('192.168.1.1') == IPv4Address(3232235777). + or, more generally + IPv4Address(int(IPv4Address('192.168.1.1'))) == + IPv4Address('192.168.1.1') + + Raises: + AddressValueError: If ipaddr isn't a valid IPv4 address. + + """ + _BaseV4.__init__(self, address) + + # Efficient constructor from integer. + if isinstance(address, (int, long)): + self._ip = address + if address < 0 or address > self._ALL_ONES: + raise AddressValueError(address) + return + + # Constructing from a packed address + if isinstance(address, Bytes): + try: + self._ip, = struct.unpack('!I', address) + except struct.error: + raise AddressValueError(address) # Wrong length. + return + + # Assume input argument to be string or any object representation + # which converts into a formatted IP string. + addr_str = str(address) + self._ip = self._ip_int_from_string(addr_str) + + +class IPv4Network(_BaseV4, _BaseNet): + + """This class represents and manipulates 32-bit IPv4 networks. + + Attributes: [examples for IPv4Network('1.2.3.4/27')] + ._ip: 16909060 + .ip: IPv4Address('1.2.3.4') + .network: IPv4Address('1.2.3.0') + .hostmask: IPv4Address('0.0.0.31') + .broadcast: IPv4Address('1.2.3.31') + .netmask: IPv4Address('255.255.255.224') + .prefixlen: 27 + + """ + + # the valid octets for host and netmasks. only useful for IPv4. + _valid_mask_octets = set((255, 254, 252, 248, 240, 224, 192, 128, 0)) + + def __init__(self, address, strict=False): + """Instantiate a new IPv4 network object. + + Args: + address: A string or integer representing the IP [& network]. + '192.168.1.1/24' + '192.168.1.1/255.255.255.0' + '192.168.1.1/0.0.0.255' + are all functionally the same in IPv4. Similarly, + '192.168.1.1' + '192.168.1.1/255.255.255.255' + '192.168.1.1/32' + are also functionaly equivalent. That is to say, failing to + provide a subnetmask will create an object with a mask of /32. + + If the mask (portion after the / in the argument) is given in + dotted quad form, it is treated as a netmask if it starts with a + non-zero field (e.g. /255.0.0.0 == /8) and as a hostmask if it + starts with a zero field (e.g. 0.255.255.255 == /8), with the + single exception of an all-zero mask which is treated as a + netmask == /0. If no mask is given, a default of /32 is used. + + Additionally, an integer can be passed, so + IPv4Network('192.168.1.1') == IPv4Network(3232235777). + or, more generally + IPv4Network(int(IPv4Network('192.168.1.1'))) == + IPv4Network('192.168.1.1') + + strict: A boolean. If true, ensure that we have been passed + A true network address, eg, 192.168.1.0/24 and not an + IP address on a network, eg, 192.168.1.1/24. + + Raises: + AddressValueError: If ipaddr isn't a valid IPv4 address. + NetmaskValueError: If the netmask isn't valid for + an IPv4 address. + ValueError: If strict was True and a network address was not + supplied. + + """ + _BaseNet.__init__(self, address) + _BaseV4.__init__(self, address) + + # Constructing from an integer or packed bytes. + if isinstance(address, (int, long, Bytes)): + self.ip = IPv4Address(address) + self._ip = self.ip._ip + self._prefixlen = self._max_prefixlen + self.netmask = IPv4Address(self._ALL_ONES) + return + + # Assume input argument to be string or any object representation + # which converts into a formatted IP prefix string. + addr = str(address).split('/') + + if len(addr) > 2: + raise AddressValueError(address) + + self._ip = self._ip_int_from_string(addr[0]) + self.ip = IPv4Address(self._ip) + + if len(addr) == 2: + mask = addr[1].split('.') + if len(mask) == 4: + # We have dotted decimal netmask. + if self._is_valid_netmask(addr[1]): + self.netmask = IPv4Address(self._ip_int_from_string( + addr[1])) + elif self._is_hostmask(addr[1]): + self.netmask = IPv4Address( + self._ip_int_from_string(addr[1]) ^ self._ALL_ONES) + else: + raise NetmaskValueError('%s is not a valid netmask' + % addr[1]) + + self._prefixlen = self._prefix_from_ip_int(int(self.netmask)) + else: + # We have a netmask in prefix length form. + if not self._is_valid_netmask(addr[1]): + raise NetmaskValueError(addr[1]) + self._prefixlen = int(addr[1]) + self.netmask = IPv4Address(self._ip_int_from_prefix( + self._prefixlen)) + else: + self._prefixlen = self._max_prefixlen + self.netmask = IPv4Address(self._ip_int_from_prefix( + self._prefixlen)) + if strict: + if self.ip != self.network: + raise ValueError('%s has host bits set' % + self.ip) + if self._prefixlen == (self._max_prefixlen - 1): + self.iterhosts = self.__iter__ + + def _is_hostmask(self, ip_str): + """Test if the IP string is a hostmask (rather than a netmask). + + Args: + ip_str: A string, the potential hostmask. + + Returns: + A boolean, True if the IP string is a hostmask. + + """ + bits = ip_str.split('.') + try: + parts = [int(x) for x in bits if int(x) in self._valid_mask_octets] + except ValueError: + return False + if len(parts) != len(bits): + return False + if parts[0] < parts[-1]: + return True + return False + + def _is_valid_netmask(self, netmask): + """Verify that the netmask is valid. + + Args: + netmask: A string, either a prefix or dotted decimal + netmask. + + Returns: + A boolean, True if the prefix represents a valid IPv4 + netmask. + + """ + mask = netmask.split('.') + if len(mask) == 4: + if [x for x in mask if int(x) not in self._valid_mask_octets]: + return False + if [y for idx, y in enumerate(mask) if idx > 0 and + y > mask[idx - 1]]: + return False + return True + try: + netmask = int(netmask) + except ValueError: + return False + return 0 <= netmask <= self._max_prefixlen + + # backwards compatibility + IsRFC1918 = lambda self: self.is_private + IsMulticast = lambda self: self.is_multicast + IsLoopback = lambda self: self.is_loopback + IsLinkLocal = lambda self: self.is_link_local + + +class _BaseV6(object): + + """Base IPv6 object. + + The following methods are used by IPv6 objects in both single IP + addresses and networks. + + """ + + _ALL_ONES = (2**IPV6LENGTH) - 1 + _HEXTET_COUNT = 8 + _HEX_DIGITS = frozenset('0123456789ABCDEFabcdef') + + def __init__(self, address): + self._version = 6 + self._max_prefixlen = IPV6LENGTH + + def _ip_int_from_string(self, ip_str): + """Turn an IPv6 ip_str into an integer. + + Args: + ip_str: A string, the IPv6 ip_str. + + Returns: + A long, the IPv6 ip_str. + + Raises: + AddressValueError: if ip_str isn't a valid IPv6 Address. + + """ + parts = ip_str.split(':') + + # An IPv6 address needs at least 2 colons (3 parts). + if len(parts) < 3: + raise AddressValueError(ip_str) + + # If the address has an IPv4-style suffix, convert it to hexadecimal. + if '.' in parts[-1]: + ipv4_int = IPv4Address(parts.pop())._ip + parts.append('%x' % ((ipv4_int >> 16) & 0xFFFF)) + parts.append('%x' % (ipv4_int & 0xFFFF)) + + # An IPv6 address can't have more than 8 colons (9 parts). + if len(parts) > self._HEXTET_COUNT + 1: + raise AddressValueError(ip_str) + + # Disregarding the endpoints, find '::' with nothing in between. + # This indicates that a run of zeroes has been skipped. + try: + skip_index, = ( + [i for i in xrange(1, len(parts) - 1) if not parts[i]] or + [None]) + except ValueError: + # Can't have more than one '::' + raise AddressValueError(ip_str) + + # parts_hi is the number of parts to copy from above/before the '::' + # parts_lo is the number of parts to copy from below/after the '::' + if skip_index is not None: + # If we found a '::', then check if it also covers the endpoints. + parts_hi = skip_index + parts_lo = len(parts) - skip_index - 1 + if not parts[0]: + parts_hi -= 1 + if parts_hi: + raise AddressValueError(ip_str) # ^: requires ^:: + if not parts[-1]: + parts_lo -= 1 + if parts_lo: + raise AddressValueError(ip_str) # :$ requires ::$ + parts_skipped = self._HEXTET_COUNT - (parts_hi + parts_lo) + if parts_skipped < 1: + raise AddressValueError(ip_str) + else: + # Otherwise, allocate the entire address to parts_hi. The endpoints + # could still be empty, but _parse_hextet() will check for that. + if len(parts) != self._HEXTET_COUNT: + raise AddressValueError(ip_str) + parts_hi = len(parts) + parts_lo = 0 + parts_skipped = 0 + + try: + # Now, parse the hextets into a 128-bit integer. + ip_int = 0L + for i in xrange(parts_hi): + ip_int <<= 16 + ip_int |= self._parse_hextet(parts[i]) + ip_int <<= 16 * parts_skipped + for i in xrange(-parts_lo, 0): + ip_int <<= 16 + ip_int |= self._parse_hextet(parts[i]) + return ip_int + except ValueError: + raise AddressValueError(ip_str) + + def _parse_hextet(self, hextet_str): + """Convert an IPv6 hextet string into an integer. + + Args: + hextet_str: A string, the number to parse. + + Returns: + The hextet as an integer. + + Raises: + ValueError: if the input isn't strictly a hex number from [0..FFFF]. + + """ + # Whitelist the characters, since int() allows a lot of bizarre stuff. + if not self._HEX_DIGITS.issuperset(hextet_str): + raise ValueError + hextet_int = int(hextet_str, 16) + if hextet_int > 0xFFFF: + raise ValueError + return hextet_int + + def _compress_hextets(self, hextets): + """Compresses a list of hextets. + + Compresses a list of strings, replacing the longest continuous + sequence of "0" in the list with "" and adding empty strings at + the beginning or at the end of the string such that subsequently + calling ":".join(hextets) will produce the compressed version of + the IPv6 address. + + Args: + hextets: A list of strings, the hextets to compress. + + Returns: + A list of strings. + + """ + best_doublecolon_start = -1 + best_doublecolon_len = 0 + doublecolon_start = -1 + doublecolon_len = 0 + for index in range(len(hextets)): + if hextets[index] == '0': + doublecolon_len += 1 + if doublecolon_start == -1: + # Start of a sequence of zeros. + doublecolon_start = index + if doublecolon_len > best_doublecolon_len: + # This is the longest sequence of zeros so far. + best_doublecolon_len = doublecolon_len + best_doublecolon_start = doublecolon_start + else: + doublecolon_len = 0 + doublecolon_start = -1 + + if best_doublecolon_len > 1: + best_doublecolon_end = (best_doublecolon_start + + best_doublecolon_len) + # For zeros at the end of the address. + if best_doublecolon_end == len(hextets): + hextets += [''] + hextets[best_doublecolon_start:best_doublecolon_end] = [''] + # For zeros at the beginning of the address. + if best_doublecolon_start == 0: + hextets = [''] + hextets + + return hextets + + def _string_from_ip_int(self, ip_int=None): + """Turns a 128-bit integer into hexadecimal notation. + + Args: + ip_int: An integer, the IP address. + + Returns: + A string, the hexadecimal representation of the address. + + Raises: + ValueError: The address is bigger than 128 bits of all ones. + + """ + if not ip_int and ip_int != 0: + ip_int = int(self._ip) + + if ip_int > self._ALL_ONES: + raise ValueError('IPv6 address is too large') + + hex_str = '%032x' % ip_int + hextets = [] + for x in range(0, 32, 4): + hextets.append('%x' % int(hex_str[x:x+4], 16)) + + hextets = self._compress_hextets(hextets) + return ':'.join(hextets) + + def _explode_shorthand_ip_string(self): + """Expand a shortened IPv6 address. + + Args: + ip_str: A string, the IPv6 address. + + Returns: + A string, the expanded IPv6 address. + + """ + if isinstance(self, _BaseNet): + ip_str = str(self.ip) + else: + ip_str = str(self) + + ip_int = self._ip_int_from_string(ip_str) + parts = [] + for i in xrange(self._HEXTET_COUNT): + parts.append('%04x' % (ip_int & 0xFFFF)) + ip_int >>= 16 + parts.reverse() + if isinstance(self, _BaseNet): + return '%s/%d' % (':'.join(parts), self.prefixlen) + return ':'.join(parts) + + @property + def max_prefixlen(self): + return self._max_prefixlen + + @property + def packed(self): + """The binary representation of this address.""" + return v6_int_to_packed(self._ip) + + @property + def version(self): + return self._version + + @property + def is_multicast(self): + """Test if the address is reserved for multicast use. + + Returns: + A boolean, True if the address is a multicast address. + See RFC 2373 2.7 for details. + + """ + return self in IPv6Network('ff00::/8') + + @property + def is_reserved(self): + """Test if the address is otherwise IETF reserved. + + Returns: + A boolean, True if the address is within one of the + reserved IPv6 Network ranges. + + """ + return (self in IPv6Network('::/8') or + self in IPv6Network('100::/8') or + self in IPv6Network('200::/7') or + self in IPv6Network('400::/6') or + self in IPv6Network('800::/5') or + self in IPv6Network('1000::/4') or + self in IPv6Network('4000::/3') or + self in IPv6Network('6000::/3') or + self in IPv6Network('8000::/3') or + self in IPv6Network('A000::/3') or + self in IPv6Network('C000::/3') or + self in IPv6Network('E000::/4') or + self in IPv6Network('F000::/5') or + self in IPv6Network('F800::/6') or + self in IPv6Network('FE00::/9')) + + @property + def is_unspecified(self): + """Test if the address is unspecified. + + Returns: + A boolean, True if this is the unspecified address as defined in + RFC 2373 2.5.2. + + """ + return self._ip == 0 and getattr(self, '_prefixlen', 128) == 128 + + @property + def is_loopback(self): + """Test if the address is a loopback address. + + Returns: + A boolean, True if the address is a loopback address as defined in + RFC 2373 2.5.3. + + """ + return self._ip == 1 and getattr(self, '_prefixlen', 128) == 128 + + @property + def is_link_local(self): + """Test if the address is reserved for link-local. + + Returns: + A boolean, True if the address is reserved per RFC 4291. + + """ + return self in IPv6Network('fe80::/10') + + @property + def is_site_local(self): + """Test if the address is reserved for site-local. + + Note that the site-local address space has been deprecated by RFC 3879. + Use is_private to test if this address is in the space of unique local + addresses as defined by RFC 4193. + + Returns: + A boolean, True if the address is reserved per RFC 3513 2.5.6. + + """ + return self in IPv6Network('fec0::/10') + + @property + def is_private(self): + """Test if this address is allocated for private networks. + + Returns: + A boolean, True if the address is reserved per RFC 4193. + + """ + return self in IPv6Network('fc00::/7') + + @property + def ipv4_mapped(self): + """Return the IPv4 mapped address. + + Returns: + If the IPv6 address is a v4 mapped address, return the + IPv4 mapped address. Return None otherwise. + + """ + if (self._ip >> 32) != 0xFFFF: + return None + return IPv4Address(self._ip & 0xFFFFFFFF) + + @property + def teredo(self): + """Tuple of embedded teredo IPs. + + Returns: + Tuple of the (server, client) IPs or None if the address + doesn't appear to be a teredo address (doesn't start with + 2001::/32) + + """ + if (self._ip >> 96) != 0x20010000: + return None + return (IPv4Address((self._ip >> 64) & 0xFFFFFFFF), + IPv4Address(~self._ip & 0xFFFFFFFF)) + + @property + def sixtofour(self): + """Return the IPv4 6to4 embedded address. + + Returns: + The IPv4 6to4-embedded address if present or None if the + address doesn't appear to contain a 6to4 embedded address. + + """ + if (self._ip >> 112) != 0x2002: + return None + return IPv4Address((self._ip >> 80) & 0xFFFFFFFF) + + +class IPv6Address(_BaseV6, _BaseIP): + + """Represent and manipulate single IPv6 Addresses. + """ + + def __init__(self, address): + """Instantiate a new IPv6 address object. + + Args: + address: A string or integer representing the IP + + Additionally, an integer can be passed, so + IPv6Address('2001:4860::') == + IPv6Address(42541956101370907050197289607612071936L). + or, more generally + IPv6Address(IPv6Address('2001:4860::')._ip) == + IPv6Address('2001:4860::') + + Raises: + AddressValueError: If address isn't a valid IPv6 address. + + """ + _BaseV6.__init__(self, address) + + # Efficient constructor from integer. + if isinstance(address, (int, long)): + self._ip = address + if address < 0 or address > self._ALL_ONES: + raise AddressValueError(address) + return + + # Constructing from a packed address + if isinstance(address, Bytes): + try: + hi, lo = struct.unpack('!QQ', address) + except struct.error: + raise AddressValueError(address) # Wrong length. + self._ip = (hi << 64) | lo + return + + # Assume input argument to be string or any object representation + # which converts into a formatted IP string. + addr_str = str(address) + if not addr_str: + raise AddressValueError('') + + self._ip = self._ip_int_from_string(addr_str) + + +class IPv6Network(_BaseV6, _BaseNet): + + """This class represents and manipulates 128-bit IPv6 networks. + + Attributes: [examples for IPv6('2001:658:22A:CAFE:200::1/64')] + .ip: IPv6Address('2001:658:22a:cafe:200::1') + .network: IPv6Address('2001:658:22a:cafe::') + .hostmask: IPv6Address('::ffff:ffff:ffff:ffff') + .broadcast: IPv6Address('2001:658:22a:cafe:ffff:ffff:ffff:ffff') + .netmask: IPv6Address('ffff:ffff:ffff:ffff::') + .prefixlen: 64 + + """ + + + def __init__(self, address, strict=False): + """Instantiate a new IPv6 Network object. + + Args: + address: A string or integer representing the IPv6 network or the IP + and prefix/netmask. + '2001:4860::/128' + '2001:4860:0000:0000:0000:0000:0000:0000/128' + '2001:4860::' + are all functionally the same in IPv6. That is to say, + failing to provide a subnetmask will create an object with + a mask of /128. + + Additionally, an integer can be passed, so + IPv6Network('2001:4860::') == + IPv6Network(42541956101370907050197289607612071936L). + or, more generally + IPv6Network(IPv6Network('2001:4860::')._ip) == + IPv6Network('2001:4860::') + + strict: A boolean. If true, ensure that we have been passed + A true network address, eg, 192.168.1.0/24 and not an + IP address on a network, eg, 192.168.1.1/24. + + Raises: + AddressValueError: If address isn't a valid IPv6 address. + NetmaskValueError: If the netmask isn't valid for + an IPv6 address. + ValueError: If strict was True and a network address was not + supplied. + + """ + _BaseNet.__init__(self, address) + _BaseV6.__init__(self, address) + + # Constructing from an integer or packed bytes. + if isinstance(address, (int, long, Bytes)): + self.ip = IPv6Address(address) + self._ip = self.ip._ip + self._prefixlen = self._max_prefixlen + self.netmask = IPv6Address(self._ALL_ONES) + return + + # Assume input argument to be string or any object representation + # which converts into a formatted IP prefix string. + addr = str(address).split('/') + + if len(addr) > 2: + raise AddressValueError(address) + + self._ip = self._ip_int_from_string(addr[0]) + self.ip = IPv6Address(self._ip) + + if len(addr) == 2: + if self._is_valid_netmask(addr[1]): + self._prefixlen = int(addr[1]) + else: + raise NetmaskValueError(addr[1]) + else: + self._prefixlen = self._max_prefixlen + + self.netmask = IPv6Address(self._ip_int_from_prefix(self._prefixlen)) + + if strict: + if self.ip != self.network: + raise ValueError('%s has host bits set' % + self.ip) + if self._prefixlen == (self._max_prefixlen - 1): + self.iterhosts = self.__iter__ + + def _is_valid_netmask(self, prefixlen): + """Verify that the netmask/prefixlen is valid. + + Args: + prefixlen: A string, the netmask in prefix length format. + + Returns: + A boolean, True if the prefix represents a valid IPv6 + netmask. + + """ + try: + prefixlen = int(prefixlen) + except ValueError: + return False + return 0 <= prefixlen <= self._max_prefixlen + + @property + def with_netmask(self): + return self.with_prefixlen diff --git a/manifold/util/log.py b/manifold/util/log.py new file mode 100644 index 00000000..cdb187f1 --- /dev/null +++ b/manifold/util/log.py @@ -0,0 +1,288 @@ +import sys, logging, traceback, inspect, os.path +from logging import handlers +from manifold.util.singleton import Singleton +from manifold.util.options import Options +from manifold.util.misc import caller_name, make_list +from manifold.util import colors + +# TODO Log should take separately message strings and arguments to be able to +# remember which messages are seen several times, and also to allow for +# translation +# TODO How to log to stdout without putting None in self.log + +class Log(object): + __metaclass__ = Singleton + + DEFAULTS = { + # Logging + "rsyslog_enable" : False, + "rsyslog_host" : None, #"log.top-hat.info", + "rsyslog_port" : None, #28514, + "log_file" : "/var/log/manifold.log", + "log_level" : "DEBUG", + "debug" : "default", + "log_duplicates" : False + } + + # COLORS + color_ansi = { + 'DEBUG' : colors.MYGREEN, + 'INFO' : colors.MYBLUE, + 'WARNING': colors.MYWARNING, + 'ERROR' : colors.MYRED, + 'HEADER' : colors.MYHEADER, + 'END' : colors.MYEND, + 'RECORD' : colors.MYBLUE, + 'TMP' : colors.MYRED, + } + + @classmethod + def color(cls, color): + return cls.color_ansi[color] if color else '' + + # To remove duplicate messages + seen = {} + + def __init__(self, name='(default)'): + self.log = None # logging.getLogger(name) + self.files_to_keep = [] + self.init_log() + self.color = True + + + @classmethod + def init_options(self): + opt = Options() + + opt.add_option( + "--rsyslog-enable", action = "store_false", dest = "rsyslog_enable", + help = "Specify if log have to be written to a rsyslog server.", + default = self.DEFAULTS["rsyslog_enable"] + ) + opt.add_option( + "--rsyslog-host", dest = "rsyslog_host", + help = "Rsyslog hostname.", + default = self.DEFAULTS["rsyslog_host"] + ) + opt.add_option( + "--rsyslog-port", type = "int", dest = "rsyslog_port", + help = "Rsyslog port.", + default = self.DEFAULTS["rsyslog_port"] + ) + opt.add_option( + "-o", "--log-file", dest = "log_file", + help = "Log filename.", + default = self.DEFAULTS["log_file"] + ) + opt.add_option( + "-L", "--log-level", dest = "log_level", + choices = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"], + help = "Log level", + default = self.DEFAULTS["log_level"] + ) + opt.add_option( + "-d", "--debug", dest = "debug", + help = "Debug paths (a list of coma-separated python path: path.to.module.function).", + default = self.DEFAULTS["debug"] + ) + opt.add_option( + "", "--log_duplicates", action = "store_true", dest = "log_duplicates", + help = "Remove duplicate messages in logs", + default = self.DEFAULTS["log_duplicates"] + ) + + def init_log(self, options=object()): + # Initialize self.log (require self.files_to_keep) + if self.log: # for debugging by using stdout, log may be equal to None + if Options().rsyslog_host: + shandler = self.make_handler_rsyslog( + Options().rsyslog_host, + Options().rsyslog_port, + Options().log_level + ) + elif Options().log_file: + shandler = self.make_handler_locallog( + Options().log_file, + Options().log_level + ) + + #------------------------------------------------------------------------ + # Log + #------------------------------------------------------------------------ + + def make_handler_rsyslog(self, rsyslog_host, rsyslog_port, log_level): + """ + \brief (Internal usage) Prepare logging via rsyslog + \param rsyslog_host The hostname of the rsyslog server + \param rsyslog_port The port of the rsyslog server + \param log_level Log level + """ + # Prepare the handler + shandler = handlers.SysLogHandler( + (rsyslog_host, rsyslog_port), + facility = handlers.SysLogHandler.LOG_DAEMON + ) + + # The log file must remain open while daemonizing + self.prepare_handler(shandler, log_level) + return shandler + + def make_handler_locallog(self, log_filename, log_level): + """ + \brief (Internal usage) Prepare local logging + \param log_filename The file in which we write the logs + \param log_level Log level + """ + # Create directory in which we store the log file + log_dir = os.path.dirname(log_filename) + if log_dir and not os.path.exists(log_dir): + try: + os.makedirs(log_dir) + except OSError, why: + # XXX here we don't log since log is not initialized yet + print "OS error: %s" % why + + # Prepare the handler + shandler = logging.handlers.RotatingFileHandler( + log_filename, + backupCount = 0 + ) + + # The log file must remain open while daemonizing + self.files_to_keep.append(shandler.stream) + self.prepare_handler(shandler, log_level) + return shandler + + def prepare_handler(self, shandler, log_level): + """ + \brief (Internal usage) + \param shandler Handler used to log information + \param log_level Log level + """ + shandler.setLevel(log_level) + formatter = logging.Formatter("%(asctime)s: %(name)s: %(levelname)s %(message)s") + shandler.setFormatter(formatter) + self.log.addHandler(shandler) + self.log.setLevel(getattr(logging, log_level, logging.INFO)) + + def get_logger(self): + return self.log + + @classmethod + def print_msg(cls, msg, level=None, caller=None): + sys.stdout.write(cls.color(level)) + if level: + print "%s" % level, + if caller: + print "[%30s]" % caller, + print msg, + print cls.color('END') + + #--------------------------------------------------------------------- + # Log: logger abstraction + #--------------------------------------------------------------------- + + @classmethod + def build_message_string(cls, msg, ctx): + if ctx: + msg = [m % ctx for m in msg] + if isinstance(msg, (tuple, list)): + msg = map(lambda s : "%s" % s, msg) + msg = " ".join(msg) + else: + msg = "%s" % msg + return msg + + @classmethod + def log_message(cls, level, msg, ctx): + """ + \brief Logs an message + \param level (string) Log level + \param msg (string / list of strings) Message string, or List of message strings + \param ctx (dict) Context for the message strings + """ + caller = None + + if not Options().log_duplicates: + try: + count = cls.seen.get(msg, 0) + cls.seen[msg] = count + 1 + except TypeError, e: + # Unhashable types in msg + count = 0 + + if count == 1: + msg += (" -- REPEATED -- Future similar messages will be silently ignored. Please use the --log_duplicates option to allow for duplicates",) + elif count > 1: + return + + if level == 'DEBUG': + caller = caller_name(skip=3) + # Eventually remove "" added to the configuration file + try: + paths = tuple(s.strip(' \t\n\r') for s in Options().debug.split(',')) + except: + paths = None + if not paths or not caller.startswith(paths): + return + + logger = Log().get_logger() + msg_str = cls.build_message_string(msg, ctx) + + if logger: + logger_fct = getattr(logger, level.lower()) + logger_fct("%s(): %s" % (inspect.stack()[2][3], msg_str)) + else: + cls.print_msg(msg_str, level, caller) + + + @classmethod + def critical(cls, *msg, **ctx): + if not Options().log_level in ['CRITICAL']: + return + cls.log_message('CRITICAL', msg, ctx) + sys.exit(0) + + @classmethod + def error(cls, *msg, **ctx): + if not Options().log_level in ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']: + return + cls.log_message('ERROR', msg, ctx) + logger = Log().get_logger() + if not Log().get_logger(): + traceback.print_exc() + sys.exit(0) + + @classmethod + def warning(cls, *msg, **ctx): + if not Options().log_level in ['DEBUG', 'INFO', 'WARNING']: + return + cls.log_message('WARNING', msg, ctx) + + @classmethod + def info(cls, *msg, **ctx): + if not Options().log_level in ['DEBUG', 'INFO']: + return + cls.log_message('INFO', msg, ctx) + + @classmethod + def debug(cls, *msg, **ctx): + if not Options().log_level in ['DEBUG']: + return + cls.log_message('DEBUG', msg, ctx) + + @classmethod + def tmp(cls, *msg): + cls.print_msg(' '.join(map(lambda x: "%r"%x, make_list(msg))), 'TMP', caller_name()) + + @classmethod + def record(cls, *msg): + #cls.print_msg(' '.join(map(lambda x: "%r"%x, make_list(msg))), 'RECORD', caller_name()) + pass + + @classmethod + def deprecated(cls, new): + #cls.print_msg("Function %s is deprecated, please use %s" % (caller_name(skip=3), new)) + pass + +Log.init_options() diff --git a/manifold/util/options.py b/manifold/util/options.py new file mode 100644 index 00000000..d6bd9589 --- /dev/null +++ b/manifold/util/options.py @@ -0,0 +1,87 @@ +import sys, optparse, cfgparse +import os.path + +from manifold.util.singleton import Singleton + +# http://docs.python.org/dev/library/argparse.html#upgrading-optparse-code + + +class Options(object): + + __metaclass__ = Singleton + + # We should be able to use another default conf file + CONF_FILE = '/etc/manifold.conf' + + def __init__(self, name = None): + self._opt = optparse.OptionParser() + self._defaults = {} + self._name = name + self.clear() + + def clear(self): + self.options = {} + self.add_option( + "-c", "--config", dest = "cfg_file", + help = "Config file to use.", + default = self.CONF_FILE + ) + self.uptodate = True + + def parse(self): + """ + \brief Parse options passed from command-line + """ + # add options here + + # if we have a logger singleton, add its options here too + # get defaults too + + # Initialize options to default values + cfg = cfgparse.ConfigParser() + cfg.add_optparse_help_option(self._opt) + + # Load configuration file + try: + cfg_filename = sys.argv[sys.argv.index("-c") + 1] + try: + with open(cfg_filename): cfg.add_file(cfg_filename) + except IOError: + raise Exception, "Cannot open specified configuration file: %s" % cfg_filename + except ValueError: + try: + with open(self.CONF_FILE): cfg.add_file(self.CONF_FILE) + except IOError: pass + + for option_name in self._defaults: + cfg.add_option(option_name, default = self._defaults[option_name]) + + # Load/override options from configuration file and command-line + (options, args) = cfg.parse(self._opt) + self.options.update(vars(options)) + self.uptodate = True + + + def add_option(self, *args, **kwargs): + default = kwargs.get('default', None) + self._defaults[kwargs['dest']] = default + if 'default' in kwargs: + # This is very important otherwise file content is not taken into account + del kwargs['default'] + kwargs['help'] += " Defaults to %r." % default + self._opt.add_option(*args, **kwargs) + self.uptodate = False + + def get_name(self): + return self._name if self._name else os.path.basename(sys.argv[0]) + + def __repr__(self): + return "" % self.options + + def __getattr__(self, key): + if not self.uptodate: + self.parse() + return self.options.get(key, None) + + def __setattr(self, key, value): + self.options[key] = value diff --git a/manifold/util/plugin_factory.py b/manifold/util/plugin_factory.py new file mode 100644 index 00000000..1956355a --- /dev/null +++ b/manifold/util/plugin_factory.py @@ -0,0 +1,24 @@ +from manifold.util.log import Log + +class PluginFactory(type): + def __init__(cls, name, bases, dic): + #super(PluginFactory, cls).__init__(name, bases, dic) + type.__init__(cls, name, bases, dic) + + try: + registry = getattr(cls, 'registry') + except AttributeError: + setattr(cls, 'registry', {}) + registry = getattr(cls, 'registry') + # XXX + if name != "Gateway": + if name.endswith('Gateway'): + name = name[:-7] + name = name.lower() + registry[name] = cls + + def get(self, name): + return registry[name.lower()] + + # Adding a class method get to retrieve plugins by name + setattr(cls, 'get', classmethod(get)) diff --git a/manifold/util/reactor_thread.py b/manifold/util/reactor_thread.py new file mode 100644 index 00000000..4eb2c7e2 --- /dev/null +++ b/manifold/util/reactor_thread.py @@ -0,0 +1,103 @@ +# Borrowed from Chandler +# http://chandlerproject.org/Projects/ChandlerTwistedInThreadedEnvironment + +import threading, time +from manifold.util.singleton import Singleton +from manifold.util.log import * +from twisted.internet import defer +from twisted.python import threadable + +__author__ ="Brian Kirsch " + +#required for using threads with the Reactor +threadable.init() + +class ReactorException(Exception): + def __init__(self, *args): + Exception.__init__(self, *args) + + +class ReactorThread(threading.Thread): + """ + Run the Reactor in a Thread to prevent blocking the + Main Thread once reactor.run is called + """ + + __metaclass__ = Singleton + + def __init__(self): + threading.Thread.__init__(self) + self._reactorRunning = False + + # Be sure the import is done only at runtime, we keep a reference in the + # class instance + from twisted.internet import reactor + self.reactor = reactor + + def run(self): + if self._reactorRunning: + raise ReactorException("Reactor Already Running") + + self._reactorRunning = True + + #call run passing a False flag indicating to the + #reactor not to install sig handlers since sig handlers + #only work on the main thread + try: + #signal.signal(signal.SIGINT, signal.default_int_handler) + self.reactor.run(False) + except Exception, e: + print "Reactor exception:", e + + def callInReactor(self, callable, *args, **kw): + if self._reactorRunning: + self.reactor.callFromThread(callable, *args, **kw) + else: + callable(*args, **kw) + + def isReactorRunning(self): + return self._reactorRunning + + def start_reactor(self): + if self._reactorRunning: + log_warning("Reactor already running. This is normal, please remove this debug message") + return + #raise ReactorException("Reactor Already Running") + threading.Thread.start(self) + cpt = 0 + while not self._reactorRunning: + time.sleep(0.1) + cpt +=1 + if cpt > 5: + raise ReactorException, "Reactor thread is too long to start... cancelling" + self.reactor.addSystemEventTrigger('after', 'shutdown', self.__reactorShutDown) + + def stop_reactor(self): + """ + may want a way to force thread to join if reactor does not shutdown + properly. The reactor can get in to a recursive loop condition if reactor.stop + placed in the threads join method. This will require further investigation. + """ + if not self._reactorRunning: + raise ReactorException("Reactor Not Running") + self.reactor.callFromThread(self.reactor.stop) + #self.reactor.join() + + def addReactorEventTrigger(self, phase, eventType, callable): + if self._reactorRunning: + self.reactor.callFromThread(self.reactor.addSystemEventTrigger, phase, eventType, callable) + else: + self.reactor.addSystemEventTrigger(phase, eventType, callable) + + def __reactorShuttingDown(self): + pass + + def __reactorShutDown(self): + """This method called when the reactor is stopped""" + self._reactorRunning = False + + def __getattr__(self, name): + # We transfer missing methods to the reactor + def _missing(*args, **kwargs): + self.reactor.callFromThread(getattr(self.reactor, name), *args, **kwargs) + return _missing diff --git a/manifold/util/reactor_wrapper.py b/manifold/util/reactor_wrapper.py new file mode 100644 index 00000000..eb18874f --- /dev/null +++ b/manifold/util/reactor_wrapper.py @@ -0,0 +1,48 @@ +from manifold.util.singleton import Singleton + +class ReactorWrapper(object): + __metaclass__ = Singleton + + def __init__(self): + # Be sure the import is done only at runtime, we keep a reference in the + # class instance + from twisted.internet import reactor + self.reactor = reactor + + + def callInReactor(self, callable, *args, **kw): + print "ReactorWrapper::callInReactor" + if self._reactorRunning: + self.reactor.callFromThread(callable, *args, **kw) + else: + callable(*args, **kw) + + def isReactorRunning(self): + return self._reactorRunning + + def start_reactor(self): + self.reactor.run() + + def stop_reactor(self): + self.reactor.stop() + + def addReactorEventTrigger(self, phase, eventType, callable): + print "ReactorWrapper::addReactorEventTrigger" + if self._reactorRunning: + self.reactor.callFromThread(self.reactor.addSystemEventTrigger, phase, eventType, callable) + else: + self.reactor.addSystemEventTrigger(phase, eventType, callable) + + def __reactorShuttingDown(self): + pass + + def __reactorShutDown(self): + """This method called when the reactor is stopped""" + print "REACTOR SHUTDOWN" + self._reactorRunning = False + + def __getattr__(self, name): + # We transfer missing methods to the reactor + def _missing(*args, **kwargs): + getattr(self.reactor, name)(*args, **kwargs) + return _missing diff --git a/manifold/util/storage.py b/manifold/util/storage.py new file mode 100644 index 00000000..066993e6 --- /dev/null +++ b/manifold/util/storage.py @@ -0,0 +1,29 @@ +from manifold.gateways import Gateway +from manifold.util.callback import Callback + +#URL='sqlite:///:memory:?check_same_thread=False' +URL='sqlite:////var/myslice/db.sqlite?check_same_thread=False' + +class Storage(object): + pass + # We can read information from files, database, commandline, etc + # Let's focus on the database + + @classmethod + def register(self, object): + """ + Registers a new object that will be stored locally by manifold. + This will live in the + """ + pass + +class DBStorage(Storage): + @classmethod + def execute(self, query, user=None, format='dict'): + # XXX Need to pass local parameters + gw = Gateway.get('sqlalchemy')(config={'url': URL}, user=user, format=format) + gw.set_query(query) + cb = Callback() + gw.set_callback(cb) + gw.start() + return cb.get_results() diff --git a/manifold/util/xmldict.py b/manifold/util/xmldict.py new file mode 100644 index 00000000..e45af734 --- /dev/null +++ b/manifold/util/xmldict.py @@ -0,0 +1,77 @@ +import os +import xml.etree.cElementTree as ElementTree + +class XmlListConfig(list): + def __init__(self, aList): + for element in aList: + if element: + # treat like dict + if len(element) == 1 or element[0].tag != element[1].tag: + self.append(XmlDictConfig(element)) + # treat like list + elif element[0].tag == element[1].tag: + self.append(XmlListConfig(element)) + elif element.text: + text = element.text.strip() + if text: + self.append(text) + + +class XmlDictConfig(dict): + ''' + Example usage: + + >>> tree = ElementTree.parse('your_file.xml') + >>> root = tree.getroot() + >>> xmldict = XmlDictConfig(root) + + Or, if you want to use an XML string: + + >>> root = ElementTree.XML(xml_string) + >>> xmldict = XmlDictConfig(root) + + And then use xmldict for what it is... a dict. + ''' + def __init__(self, parent_element): + childrenNames = [child.tag for child in parent_element.getchildren()] + + if parent_element.items(): #attributes + self.update(dict(parent_element.items())) + for element in parent_element: + if element: + # treat like dict - we assume that if the first two tags + # in a series are different, then they are all different. + if len(element) == 1 or element[0].tag != element[1].tag: + aDict = XmlDictConfig(element) + # treat like list - we assume that if the first two tags + # in a series are the same, then the rest are the same. + else: + # here, we put the list in dictionary; the key is the + # tag name the list elements all share in common, and + # the value is the list itself + aDict = {element[0].tag: XmlListConfig(element)} + # if the tag has attributes, add those to the dict + if element.items(): + aDict.update(dict(element.items())) + + if childrenNames.count(element.tag) > 1: + try: + currentValue = self[element.tag] + currentValue.append(aDict) + self.update({element.tag: currentValue}) + except: #the first of its kind, an empty list must be created + self.update({element.tag: [aDict]}) #aDict is written in [], i.e. it will be a list + + else: + self.update({element.tag: aDict}) + # this assumes that if you've got an attribute in a tag, + # you won't be having any text. This may or may not be a + # good idea -- time will tell. It works for the way we are + # currently doing XML configuration files... + elif element.items(): + self.update({element.tag: dict(element.items())}) + # finally, if there are no child tags and no attributes, extract + # the text + else: + self.update({element.tag: element.text}) + diff --git a/portal/static/js/common.functions.js b/portal/static/js/common.functions.js new file mode 100644 index 00000000..3fe31c44 --- /dev/null +++ b/portal/static/js/common.functions.js @@ -0,0 +1,62 @@ +/* + * This file is included in tophat_render.php + */ + +function getKeySplitId(id,separator){ + // id of elements must respect this rule + // split_id[0] = plugin_uuid + // split_id[1] = element + // split_id[2] = field or key + + var split_id = id.split(separator); + var key = split_id[2]; + + return key; +} + +function errorDisplay(error){ + var out = '
Notice
  • ' + error + '
'; + return out; +} + +function arrays_equal(a,b) { return !(a B ? 1 : 0)) * [1,-1][+!!reverse]; + //return ((A < B) ? -1 : + // (A > B) ? +1 : 0)) * [-1,1][+!!reverse]; + } +} diff --git a/third-party/jquery-ui/jquery-ui.css b/third-party/jquery-ui/jquery-ui.css new file mode 100644 index 00000000..4cfb50a4 --- /dev/null +++ b/third-party/jquery-ui/jquery-ui.css @@ -0,0 +1,565 @@ +/* + * jQuery UI CSS Framework 1.8.18 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Theming/API + */ + +/* Layout helpers +----------------------------------*/ +.ui-helper-hidden { display: none; } +.ui-helper-hidden-accessible { position: absolute !important; clip: rect(1px 1px 1px 1px); clip: rect(1px,1px,1px,1px); } +.ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; } +.ui-helper-clearfix:before, .ui-helper-clearfix:after { content: ""; display: table; } +.ui-helper-clearfix:after { clear: both; } +.ui-helper-clearfix { zoom: 1; } +.ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); } + + +/* Interaction Cues +----------------------------------*/ +.ui-state-disabled { cursor: default !important; } + + +/* Icons +----------------------------------*/ + +/* states and images */ +.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; } + + +/* Misc visuals +----------------------------------*/ + +/* Overlays */ +.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; } + + +/* + * jQuery UI CSS Framework 1.8.18 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Theming/API + * + * To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Verdana,Arial,sans-serif&fwDefault=normal&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=cccccc&bgTextureHeader=03_highlight_soft.png&bgImgOpacityHeader=75&borderColorHeader=aaaaaa&fcHeader=222222&iconColorHeader=222222&bgColorContent=ffffff&bgTextureContent=01_flat.png&bgImgOpacityContent=75&borderColorContent=aaaaaa&fcContent=222222&iconColorContent=222222&bgColorDefault=e6e6e6&bgTextureDefault=02_glass.png&bgImgOpacityDefault=75&borderColorDefault=d3d3d3&fcDefault=555555&iconColorDefault=888888&bgColorHover=dadada&bgTextureHover=02_glass.png&bgImgOpacityHover=75&borderColorHover=999999&fcHover=212121&iconColorHover=454545&bgColorActive=ffffff&bgTextureActive=02_glass.png&bgImgOpacityActive=65&borderColorActive=aaaaaa&fcActive=212121&iconColorActive=454545&bgColorHighlight=fbf9ee&bgTextureHighlight=02_glass.png&bgImgOpacityHighlight=55&borderColorHighlight=fcefa1&fcHighlight=363636&iconColorHighlight=2e83ff&bgColorError=fef1ec&bgTextureError=02_glass.png&bgImgOpacityError=95&borderColorError=cd0a0a&fcError=cd0a0a&iconColorError=cd0a0a&bgColorOverlay=aaaaaa&bgTextureOverlay=01_flat.png&bgImgOpacityOverlay=0&opacityOverlay=30&bgColorShadow=aaaaaa&bgTextureShadow=01_flat.png&bgImgOpacityShadow=0&opacityShadow=30&thicknessShadow=8px&offsetTopShadow=-8px&offsetLeftShadow=-8px&cornerRadiusShadow=8px + */ + + +/* Component containers +----------------------------------*/ +.ui-widget { font-family: Verdana,Arial,sans-serif; font-size: 1.1em; } +.ui-widget .ui-widget { font-size: 1em; } +.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Verdana,Arial,sans-serif; font-size: 1em; } +.ui-widget-content { border: 1px solid #aaaaaa; background: #ffffff url(images/ui-bg_flat_75_ffffff_40x100.png) 50% 50% repeat-x; color: #222222; } +.ui-widget-content a { color: #222222; } +.ui-widget-header { border: 1px solid #aaaaaa; background: #cccccc url(images/ui-bg_highlight-soft_75_cccccc_1x100.png) 50% 50% repeat-x; color: #222222; font-weight: bold; } +.ui-widget-header a { color: #222222; } + +/* Interaction states +----------------------------------*/ +.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #d3d3d3; background: #e6e6e6 url(images/ui-bg_glass_75_e6e6e6_1x400.png) 50% 50% repeat-x; font-weight: normal; color: #555555; } +.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #555555; text-decoration: none; } +.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { border: 1px solid #999999; background: #dadada url(images/ui-bg_glass_75_dadada_1x400.png) 50% 50% repeat-x; font-weight: normal; color: #212121; } +.ui-state-hover a, .ui-state-hover a:hover { color: #212121; text-decoration: none; } +.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { border: 1px solid #aaaaaa; background: #ffffff url(images/ui-bg_glass_65_ffffff_1x400.png) 50% 50% repeat-x; font-weight: normal; color: #212121; } +.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #212121; text-decoration: none; } +.ui-widget :active { outline: none; } + +/* Interaction Cues +----------------------------------*/ +.ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight {border: 1px solid #fcefa1; background: #fbf9ee url(images/ui-bg_glass_55_fbf9ee_1x400.png) 50% 50% repeat-x; color: #363636; } +.ui-state-highlight a, .ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a { color: #363636; } +.ui-state-error, .ui-widget-content .ui-state-error, .ui-widget-header .ui-state-error {border: 1px solid #cd0a0a; background: #fef1ec url(images/ui-bg_glass_95_fef1ec_1x400.png) 50% 50% repeat-x; color: #cd0a0a; } +.ui-state-error a, .ui-widget-content .ui-state-error a, .ui-widget-header .ui-state-error a { color: #cd0a0a; } +.ui-state-error-text, .ui-widget-content .ui-state-error-text, .ui-widget-header .ui-state-error-text { color: #cd0a0a; } +.ui-priority-primary, .ui-widget-content .ui-priority-primary, .ui-widget-header .ui-priority-primary { font-weight: bold; } +.ui-priority-secondary, .ui-widget-content .ui-priority-secondary, .ui-widget-header .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; } +.ui-state-disabled, .ui-widget-content .ui-state-disabled, .ui-widget-header .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; } + +/* Icons +----------------------------------*/ + +/* states and images */ +.ui-icon { width: 16px; height: 16px; background-image: url(images/ui-icons_222222_256x240.png); } +.ui-widget-content .ui-icon {background-image: url(images/ui-icons_222222_256x240.png); } +.ui-widget-header .ui-icon {background-image: url(images/ui-icons_222222_256x240.png); } +.ui-state-default .ui-icon { background-image: url(images/ui-icons_888888_256x240.png); } +.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(images/ui-icons_454545_256x240.png); } +.ui-state-active .ui-icon {background-image: url(images/ui-icons_454545_256x240.png); } +.ui-state-highlight .ui-icon {background-image: url(images/ui-icons_2e83ff_256x240.png); } +.ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(images/ui-icons_cd0a0a_256x240.png); } + +/* positioning */ +.ui-icon-carat-1-n { background-position: 0 0; } +.ui-icon-carat-1-ne { background-position: -16px 0; } +.ui-icon-carat-1-e { background-position: -32px 0; } +.ui-icon-carat-1-se { background-position: -48px 0; } +.ui-icon-carat-1-s { background-position: -64px 0; } +.ui-icon-carat-1-sw { background-position: -80px 0; } +.ui-icon-carat-1-w { background-position: -96px 0; } +.ui-icon-carat-1-nw { background-position: -112px 0; } +.ui-icon-carat-2-n-s { background-position: -128px 0; } +.ui-icon-carat-2-e-w { background-position: -144px 0; } +.ui-icon-triangle-1-n { background-position: 0 -16px; } +.ui-icon-triangle-1-ne { background-position: -16px -16px; } +.ui-icon-triangle-1-e { background-position: -32px -16px; } +.ui-icon-triangle-1-se { background-position: -48px -16px; } +.ui-icon-triangle-1-s { background-position: -64px -16px; } +.ui-icon-triangle-1-sw { background-position: -80px -16px; } +.ui-icon-triangle-1-w { background-position: -96px -16px; } +.ui-icon-triangle-1-nw { background-position: -112px -16px; } +.ui-icon-triangle-2-n-s { background-position: -128px -16px; } +.ui-icon-triangle-2-e-w { background-position: -144px -16px; } +.ui-icon-arrow-1-n { background-position: 0 -32px; } +.ui-icon-arrow-1-ne { background-position: -16px -32px; } +.ui-icon-arrow-1-e { background-position: -32px -32px; } +.ui-icon-arrow-1-se { background-position: -48px -32px; } +.ui-icon-arrow-1-s { background-position: -64px -32px; } +.ui-icon-arrow-1-sw { background-position: -80px -32px; } +.ui-icon-arrow-1-w { background-position: -96px -32px; } +.ui-icon-arrow-1-nw { background-position: -112px -32px; } +.ui-icon-arrow-2-n-s { background-position: -128px -32px; } +.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; } +.ui-icon-arrow-2-e-w { background-position: -160px -32px; } +.ui-icon-arrow-2-se-nw { background-position: -176px -32px; } +.ui-icon-arrowstop-1-n { background-position: -192px -32px; } +.ui-icon-arrowstop-1-e { background-position: -208px -32px; } +.ui-icon-arrowstop-1-s { background-position: -224px -32px; } +.ui-icon-arrowstop-1-w { background-position: -240px -32px; } +.ui-icon-arrowthick-1-n { background-position: 0 -48px; } +.ui-icon-arrowthick-1-ne { background-position: -16px -48px; } +.ui-icon-arrowthick-1-e { background-position: -32px -48px; } +.ui-icon-arrowthick-1-se { background-position: -48px -48px; } +.ui-icon-arrowthick-1-s { background-position: -64px -48px; } +.ui-icon-arrowthick-1-sw { background-position: -80px -48px; } +.ui-icon-arrowthick-1-w { background-position: -96px -48px; } +.ui-icon-arrowthick-1-nw { background-position: -112px -48px; } +.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; } +.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; } +.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; } +.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; } +.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; } +.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; } +.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; } +.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; } +.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; } +.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; } +.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; } +.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; } +.ui-icon-arrowreturn-1-w { background-position: -64px -64px; } +.ui-icon-arrowreturn-1-n { background-position: -80px -64px; } +.ui-icon-arrowreturn-1-e { background-position: -96px -64px; } +.ui-icon-arrowreturn-1-s { background-position: -112px -64px; } +.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; } +.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; } +.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; } +.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; } +.ui-icon-arrow-4 { background-position: 0 -80px; } +.ui-icon-arrow-4-diag { background-position: -16px -80px; } +.ui-icon-extlink { background-position: -32px -80px; } +.ui-icon-newwin { background-position: -48px -80px; } +.ui-icon-refresh { background-position: -64px -80px; } +.ui-icon-shuffle { background-position: -80px -80px; } +.ui-icon-transfer-e-w { background-position: -96px -80px; } +.ui-icon-transferthick-e-w { background-position: -112px -80px; } +.ui-icon-folder-collapsed { background-position: 0 -96px; } +.ui-icon-folder-open { background-position: -16px -96px; } +.ui-icon-document { background-position: -32px -96px; } +.ui-icon-document-b { background-position: -48px -96px; } +.ui-icon-note { background-position: -64px -96px; } +.ui-icon-mail-closed { background-position: -80px -96px; } +.ui-icon-mail-open { background-position: -96px -96px; } +.ui-icon-suitcase { background-position: -112px -96px; } +.ui-icon-comment { background-position: -128px -96px; } +.ui-icon-person { background-position: -144px -96px; } +.ui-icon-print { background-position: -160px -96px; } +.ui-icon-trash { background-position: -176px -96px; } +.ui-icon-locked { background-position: -192px -96px; } +.ui-icon-unlocked { background-position: -208px -96px; } +.ui-icon-bookmark { background-position: -224px -96px; } +.ui-icon-tag { background-position: -240px -96px; } +.ui-icon-home { background-position: 0 -112px; } +.ui-icon-flag { background-position: -16px -112px; } +.ui-icon-calendar { background-position: -32px -112px; } +.ui-icon-cart { background-position: -48px -112px; } +.ui-icon-pencil { background-position: -64px -112px; } +.ui-icon-clock { background-position: -80px -112px; } +.ui-icon-disk { background-position: -96px -112px; } +.ui-icon-calculator { background-position: -112px -112px; } +.ui-icon-zoomin { background-position: -128px -112px; } +.ui-icon-zoomout { background-position: -144px -112px; } +.ui-icon-search { background-position: -160px -112px; } +.ui-icon-wrench { background-position: -176px -112px; } +.ui-icon-gear { background-position: -192px -112px; } +.ui-icon-heart { background-position: -208px -112px; } +.ui-icon-star { background-position: -224px -112px; } +.ui-icon-link { background-position: -240px -112px; } +.ui-icon-cancel { background-position: 0 -128px; } +.ui-icon-plus { background-position: -16px -128px; } +.ui-icon-plusthick { background-position: -32px -128px; } +.ui-icon-minus { background-position: -48px -128px; } +.ui-icon-minusthick { background-position: -64px -128px; } +.ui-icon-close { background-position: -80px -128px; } +.ui-icon-closethick { background-position: -96px -128px; } +.ui-icon-key { background-position: -112px -128px; } +.ui-icon-lightbulb { background-position: -128px -128px; } +.ui-icon-scissors { background-position: -144px -128px; } +.ui-icon-clipboard { background-position: -160px -128px; } +.ui-icon-copy { background-position: -176px -128px; } +.ui-icon-contact { background-position: -192px -128px; } +.ui-icon-image { background-position: -208px -128px; } +.ui-icon-video { background-position: -224px -128px; } +.ui-icon-script { background-position: -240px -128px; } +.ui-icon-alert { background-position: 0 -144px; } +.ui-icon-info { background-position: -16px -144px; } +.ui-icon-notice { background-position: -32px -144px; } +.ui-icon-help { background-position: -48px -144px; } +.ui-icon-check { background-position: -64px -144px; } +.ui-icon-bullet { background-position: -80px -144px; } +.ui-icon-radio-off { background-position: -96px -144px; } +.ui-icon-radio-on { background-position: -112px -144px; } +.ui-icon-pin-w { background-position: -128px -144px; } +.ui-icon-pin-s { background-position: -144px -144px; } +.ui-icon-play { background-position: 0 -160px; } +.ui-icon-pause { background-position: -16px -160px; } +.ui-icon-seek-next { background-position: -32px -160px; } +.ui-icon-seek-prev { background-position: -48px -160px; } +.ui-icon-seek-end { background-position: -64px -160px; } +.ui-icon-seek-start { background-position: -80px -160px; } +/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */ +.ui-icon-seek-first { background-position: -80px -160px; } +.ui-icon-stop { background-position: -96px -160px; } +.ui-icon-eject { background-position: -112px -160px; } +.ui-icon-volume-off { background-position: -128px -160px; } +.ui-icon-volume-on { background-position: -144px -160px; } +.ui-icon-power { background-position: 0 -176px; } +.ui-icon-signal-diag { background-position: -16px -176px; } +.ui-icon-signal { background-position: -32px -176px; } +.ui-icon-battery-0 { background-position: -48px -176px; } +.ui-icon-battery-1 { background-position: -64px -176px; } +.ui-icon-battery-2 { background-position: -80px -176px; } +.ui-icon-battery-3 { background-position: -96px -176px; } +.ui-icon-circle-plus { background-position: 0 -192px; } +.ui-icon-circle-minus { background-position: -16px -192px; } +.ui-icon-circle-close { background-position: -32px -192px; } +.ui-icon-circle-triangle-e { background-position: -48px -192px; } +.ui-icon-circle-triangle-s { background-position: -64px -192px; } +.ui-icon-circle-triangle-w { background-position: -80px -192px; } +.ui-icon-circle-triangle-n { background-position: -96px -192px; } +.ui-icon-circle-arrow-e { background-position: -112px -192px; } +.ui-icon-circle-arrow-s { background-position: -128px -192px; } +.ui-icon-circle-arrow-w { background-position: -144px -192px; } +.ui-icon-circle-arrow-n { background-position: -160px -192px; } +.ui-icon-circle-zoomin { background-position: -176px -192px; } +.ui-icon-circle-zoomout { background-position: -192px -192px; } +.ui-icon-circle-check { background-position: -208px -192px; } +.ui-icon-circlesmall-plus { background-position: 0 -208px; } +.ui-icon-circlesmall-minus { background-position: -16px -208px; } +.ui-icon-circlesmall-close { background-position: -32px -208px; } +.ui-icon-squaresmall-plus { background-position: -48px -208px; } +.ui-icon-squaresmall-minus { background-position: -64px -208px; } +.ui-icon-squaresmall-close { background-position: -80px -208px; } +.ui-icon-grip-dotted-vertical { background-position: 0 -224px; } +.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; } +.ui-icon-grip-solid-vertical { background-position: -32px -224px; } +.ui-icon-grip-solid-horizontal { background-position: -48px -224px; } +.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; } +.ui-icon-grip-diagonal-se { background-position: -80px -224px; } + + +/* Misc visuals +----------------------------------*/ + +/* Corner radius */ +.ui-corner-all, .ui-corner-top, .ui-corner-left, .ui-corner-tl { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; -khtml-border-top-left-radius: 4px; border-top-left-radius: 4px; } +.ui-corner-all, .ui-corner-top, .ui-corner-right, .ui-corner-tr { -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; -khtml-border-top-right-radius: 4px; border-top-right-radius: 4px; } +.ui-corner-all, .ui-corner-bottom, .ui-corner-left, .ui-corner-bl { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; -khtml-border-bottom-left-radius: 4px; border-bottom-left-radius: 4px; } +.ui-corner-all, .ui-corner-bottom, .ui-corner-right, .ui-corner-br { -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; -khtml-border-bottom-right-radius: 4px; border-bottom-right-radius: 4px; } + +/* Overlays */ +.ui-widget-overlay { background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); } +.ui-widget-shadow { margin: -8px 0 0 -8px; padding: 8px; background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); -moz-border-radius: 8px; -khtml-border-radius: 8px; -webkit-border-radius: 8px; border-radius: 8px; }/* + * jQuery UI Resizable 1.8.18 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Resizable#theming + */ +.ui-resizable { position: relative;} +.ui-resizable-handle { position: absolute;font-size: 0.1px;z-index: 99999; display: block; } +.ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle { display: none; } +.ui-resizable-n { cursor: n-resize; height: 7px; width: 100%; top: -5px; left: 0; } +.ui-resizable-s { cursor: s-resize; height: 7px; width: 100%; bottom: -5px; left: 0; } +.ui-resizable-e { cursor: e-resize; width: 7px; right: -5px; top: 0; height: 100%; } +.ui-resizable-w { cursor: w-resize; width: 7px; left: -5px; top: 0; height: 100%; } +.ui-resizable-se { cursor: se-resize; width: 12px; height: 12px; right: 1px; bottom: 1px; } +.ui-resizable-sw { cursor: sw-resize; width: 9px; height: 9px; left: -5px; bottom: -5px; } +.ui-resizable-nw { cursor: nw-resize; width: 9px; height: 9px; left: -5px; top: -5px; } +.ui-resizable-ne { cursor: ne-resize; width: 9px; height: 9px; right: -5px; top: -5px;}/* + * jQuery UI Selectable 1.8.18 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Selectable#theming + */ +.ui-selectable-helper { position: absolute; z-index: 100; border:1px dotted black; } +/* + * jQuery UI Accordion 1.8.18 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Accordion#theming + */ +/* IE/Win - Fix animation bug - #4615 */ +.ui-accordion { width: 100%; } +.ui-accordion .ui-accordion-header { cursor: pointer; position: relative; margin-top: 1px; zoom: 1; } +.ui-accordion .ui-accordion-li-fix { display: inline; } +.ui-accordion .ui-accordion-header-active { border-bottom: 0 !important; } +.ui-accordion .ui-accordion-header a { display: block; font-size: 1em; padding: .5em .5em .5em .7em; } +.ui-accordion-icons .ui-accordion-header a { padding-left: 2.2em; } +.ui-accordion .ui-accordion-header .ui-icon { position: absolute; left: .5em; top: 50%; margin-top: -8px; } +.ui-accordion .ui-accordion-content { padding: 1em 2.2em; border-top: 0; margin-top: -2px; position: relative; top: 1px; margin-bottom: 2px; overflow: auto; display: none; zoom: 1; } +.ui-accordion .ui-accordion-content-active { display: block; } +/* + * jQuery UI Autocomplete 1.8.18 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Autocomplete#theming + */ +.ui-autocomplete { position: absolute; cursor: default; } + +/* workarounds */ +* html .ui-autocomplete { width:1px; } /* without this, the menu expands to 100% in IE6 */ + +/* + * jQuery UI Menu 1.8.18 + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Menu#theming + */ +.ui-menu { + list-style:none; + padding: 2px; + margin: 0; + display:block; + float: left; +} +.ui-menu .ui-menu { + margin-top: -3px; +} +.ui-menu .ui-menu-item { + margin:0; + padding: 0; + zoom: 1; + float: left; + clear: left; + width: 100%; +} +.ui-menu .ui-menu-item a { + text-decoration:none; + display:block; + padding:.2em .4em; + line-height:1.5; + zoom:1; +} +.ui-menu .ui-menu-item a.ui-state-hover, +.ui-menu .ui-menu-item a.ui-state-active { + font-weight: normal; + margin: -1px; +} +/* + * jQuery UI Button 1.8.18 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Button#theming + */ +.ui-button { display: inline-block; position: relative; padding: 0; margin-right: .1em; text-decoration: none !important; cursor: pointer; text-align: center; zoom: 1; overflow: hidden; *overflow: visible; } /* the overflow property removes extra width in IE */ +.ui-button-icon-only { width: 2.2em; } /* to make room for the icon, a width needs to be set here */ +button.ui-button-icon-only { width: 2.4em; } /* button elements seem to need a little more width */ +.ui-button-icons-only { width: 3.4em; } +button.ui-button-icons-only { width: 3.7em; } + +/*button text element */ +.ui-button .ui-button-text { display: block; line-height: 1.4; } +.ui-button-text-only .ui-button-text { padding: .4em 1em; } +.ui-button-icon-only .ui-button-text, .ui-button-icons-only .ui-button-text { padding: .4em; text-indent: -9999999px; } +.ui-button-text-icon-primary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 1em .4em 2.1em; } +.ui-button-text-icon-secondary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 2.1em .4em 1em; } +.ui-button-text-icons .ui-button-text { padding-left: 2.1em; padding-right: 2.1em; } +/* no icon support for input elements, provide padding by default */ +input.ui-button { padding: .4em 1em; } + +/*button icon element(s) */ +.ui-button-icon-only .ui-icon, .ui-button-text-icon-primary .ui-icon, .ui-button-text-icon-secondary .ui-icon, .ui-button-text-icons .ui-icon, .ui-button-icons-only .ui-icon { position: absolute; top: 50%; margin-top: -8px; } +.ui-button-icon-only .ui-icon { left: 50%; margin-left: -8px; } +.ui-button-text-icon-primary .ui-button-icon-primary, .ui-button-text-icons .ui-button-icon-primary, .ui-button-icons-only .ui-button-icon-primary { left: .5em; } +.ui-button-text-icon-secondary .ui-button-icon-secondary, .ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; } +.ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; } + +/*button sets*/ +.ui-buttonset { margin-right: 7px; } +.ui-buttonset .ui-button { margin-left: 0; margin-right: -.3em; } + +/* workarounds */ +button.ui-button::-moz-focus-inner { border: 0; padding: 0; } /* reset extra padding in Firefox */ +/* + * jQuery UI Dialog 1.8.18 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Dialog#theming + */ +.ui-dialog { position: absolute; padding: .2em; width: 300px; overflow: hidden; } +.ui-dialog .ui-dialog-titlebar { padding: .4em 1em; position: relative; } +.ui-dialog .ui-dialog-title { float: left; margin: .1em 16px .1em 0; } +.ui-dialog .ui-dialog-titlebar-close { position: absolute; right: .3em; top: 50%; width: 19px; margin: -10px 0 0 0; padding: 1px; height: 18px; } +.ui-dialog .ui-dialog-titlebar-close span { display: block; margin: 1px; } +.ui-dialog .ui-dialog-titlebar-close:hover, .ui-dialog .ui-dialog-titlebar-close:focus { padding: 0; } +.ui-dialog .ui-dialog-content { position: relative; border: 0; padding: .5em 1em; background: none; overflow: auto; zoom: 1; } +.ui-dialog .ui-dialog-buttonpane { text-align: left; border-width: 1px 0 0 0; background-image: none; margin: .5em 0 0 0; padding: .3em 1em .5em .4em; } +.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset { float: right; } +.ui-dialog .ui-dialog-buttonpane button { margin: .5em .4em .5em 0; cursor: pointer; } +.ui-dialog .ui-resizable-se { width: 14px; height: 14px; right: 3px; bottom: 3px; } +.ui-draggable .ui-dialog-titlebar { cursor: move; } +/* + * jQuery UI Slider 1.8.18 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Slider#theming + */ +.ui-slider { position: relative; text-align: left; } +.ui-slider .ui-slider-handle { position: absolute; z-index: 2; width: 1.2em; height: 1.2em; cursor: default; } +.ui-slider .ui-slider-range { position: absolute; z-index: 1; font-size: .7em; display: block; border: 0; background-position: 0 0; } + +.ui-slider-horizontal { height: .8em; } +.ui-slider-horizontal .ui-slider-handle { top: -.3em; margin-left: -.6em; } +.ui-slider-horizontal .ui-slider-range { top: 0; height: 100%; } +.ui-slider-horizontal .ui-slider-range-min { left: 0; } +.ui-slider-horizontal .ui-slider-range-max { right: 0; } + +.ui-slider-vertical { width: .8em; height: 100px; } +.ui-slider-vertical .ui-slider-handle { left: -.3em; margin-left: 0; margin-bottom: -.6em; } +.ui-slider-vertical .ui-slider-range { left: 0; width: 100%; } +.ui-slider-vertical .ui-slider-range-min { bottom: 0; } +.ui-slider-vertical .ui-slider-range-max { top: 0; }/* + * jQuery UI Tabs 1.8.18 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Tabs#theming + */ +.ui-tabs { position: relative; padding: .2em; zoom: 1; } /* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */ +.ui-tabs .ui-tabs-nav { margin: 0; padding: .2em .2em 0; } +.ui-tabs .ui-tabs-nav li { list-style: none; float: left; position: relative; top: 1px; margin: 0 .2em 1px 0; border-bottom: 0 !important; padding: 0; white-space: nowrap; } +.ui-tabs .ui-tabs-nav li a { float: left; padding: .5em 1em; text-decoration: none; } +.ui-tabs .ui-tabs-nav li.ui-tabs-selected { margin-bottom: 0; padding-bottom: 1px; } +.ui-tabs .ui-tabs-nav li.ui-tabs-selected a, .ui-tabs .ui-tabs-nav li.ui-state-disabled a, .ui-tabs .ui-tabs-nav li.ui-state-processing a { cursor: text; } +.ui-tabs .ui-tabs-nav li a, .ui-tabs.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-selected a { cursor: pointer; } /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */ +.ui-tabs .ui-tabs-panel { display: block; border-width: 0; padding: 1em 1.4em; background: none; } +.ui-tabs .ui-tabs-hide { display: none !important; } +/* + * jQuery UI Datepicker 1.8.18 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Datepicker#theming + */ +.ui-datepicker { width: 17em; padding: .2em .2em 0; display: none; } +.ui-datepicker .ui-datepicker-header { position:relative; padding:.2em 0; } +.ui-datepicker .ui-datepicker-prev, .ui-datepicker .ui-datepicker-next { position:absolute; top: 2px; width: 1.8em; height: 1.8em; } +.ui-datepicker .ui-datepicker-prev-hover, .ui-datepicker .ui-datepicker-next-hover { top: 1px; } +.ui-datepicker .ui-datepicker-prev { left:2px; } +.ui-datepicker .ui-datepicker-next { right:2px; } +.ui-datepicker .ui-datepicker-prev-hover { left:1px; } +.ui-datepicker .ui-datepicker-next-hover { right:1px; } +.ui-datepicker .ui-datepicker-prev span, .ui-datepicker .ui-datepicker-next span { display: block; position: absolute; left: 50%; margin-left: -8px; top: 50%; margin-top: -8px; } +.ui-datepicker .ui-datepicker-title { margin: 0 2.3em; line-height: 1.8em; text-align: center; } +.ui-datepicker .ui-datepicker-title select { font-size:1em; margin:1px 0; } +.ui-datepicker select.ui-datepicker-month-year {width: 100%;} +.ui-datepicker select.ui-datepicker-month, +.ui-datepicker select.ui-datepicker-year { width: 49%;} +.ui-datepicker table {width: 100%; font-size: .9em; border-collapse: collapse; margin:0 0 .4em; } +.ui-datepicker th { padding: .7em .3em; text-align: center; font-weight: bold; border: 0; } +.ui-datepicker td { border: 0; padding: 1px; } +.ui-datepicker td span, .ui-datepicker td a { display: block; padding: .2em; text-align: right; text-decoration: none; } +.ui-datepicker .ui-datepicker-buttonpane { background-image: none; margin: .7em 0 0 0; padding:0 .2em; border-left: 0; border-right: 0; border-bottom: 0; } +.ui-datepicker .ui-datepicker-buttonpane button { float: right; margin: .5em .2em .4em; cursor: pointer; padding: .2em .6em .3em .6em; width:auto; overflow:visible; } +.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { float:left; } + +/* with multiple calendars */ +.ui-datepicker.ui-datepicker-multi { width:auto; } +.ui-datepicker-multi .ui-datepicker-group { float:left; } +.ui-datepicker-multi .ui-datepicker-group table { width:95%; margin:0 auto .4em; } +.ui-datepicker-multi-2 .ui-datepicker-group { width:50%; } +.ui-datepicker-multi-3 .ui-datepicker-group { width:33.3%; } +.ui-datepicker-multi-4 .ui-datepicker-group { width:25%; } +.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header { border-left-width:0; } +.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { border-left-width:0; } +.ui-datepicker-multi .ui-datepicker-buttonpane { clear:left; } +.ui-datepicker-row-break { clear:both; width:100%; font-size:0em; } + +/* RTL support */ +.ui-datepicker-rtl { direction: rtl; } +.ui-datepicker-rtl .ui-datepicker-prev { right: 2px; left: auto; } +.ui-datepicker-rtl .ui-datepicker-next { left: 2px; right: auto; } +.ui-datepicker-rtl .ui-datepicker-prev:hover { right: 1px; left: auto; } +.ui-datepicker-rtl .ui-datepicker-next:hover { left: 1px; right: auto; } +.ui-datepicker-rtl .ui-datepicker-buttonpane { clear:right; } +.ui-datepicker-rtl .ui-datepicker-buttonpane button { float: left; } +.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current { float:right; } +.ui-datepicker-rtl .ui-datepicker-group { float:right; } +.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header { border-right-width:0; border-left-width:1px; } +.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { border-right-width:0; border-left-width:1px; } + +/* IE6 IFRAME FIX (taken from datepicker 1.5.3 */ +.ui-datepicker-cover { + display: none; /*sorry for IE5*/ + display/**/: block; /*sorry for IE5*/ + position: absolute; /*must have*/ + z-index: -1; /*must have*/ + filter: mask(); /*must have*/ + top: -4px; /*must have*/ + left: -4px; /*must have*/ + width: 200px; /*must have*/ + height: 200px; /*must have*/ +}/* + * jQuery UI Progressbar 1.8.18 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Progressbar#theming + */ +.ui-progressbar { height:2em; text-align: left; overflow: hidden; } +.ui-progressbar .ui-progressbar-value {margin: -1px; height:100%; } \ No newline at end of file