summaryrefslogtreecommitdiff
path: root/indra/lib/python
diff options
context:
space:
mode:
Diffstat (limited to 'indra/lib/python')
-rw-r--r--indra/lib/python/indra/__init__.py12
-rw-r--r--indra/lib/python/indra/base/__init__.py7
-rw-r--r--indra/lib/python/indra/base/config.py46
-rw-r--r--indra/lib/python/indra/base/llsd.py808
-rw-r--r--indra/lib/python/indra/base/lluuid.py265
-rw-r--r--indra/lib/python/indra/ipc/__init__.py7
-rw-r--r--indra/lib/python/indra/ipc/compatibility.py103
-rw-r--r--indra/lib/python/indra/ipc/httputil.py94
-rw-r--r--indra/lib/python/indra/ipc/llmessage.py353
-rw-r--r--indra/lib/python/indra/ipc/llsdhttp.py257
-rw-r--r--indra/lib/python/indra/ipc/russ.py123
-rw-r--r--indra/lib/python/indra/ipc/servicebuilder.py52
-rw-r--r--indra/lib/python/indra/ipc/tokenstream.py134
-rw-r--r--indra/lib/python/indra/ipc/xml_rpc.py253
-rw-r--r--indra/lib/python/indra/util/__init__.py7
-rw-r--r--indra/lib/python/indra/util/helpformatter.py32
-rw-r--r--indra/lib/python/indra/util/llmanifest.py562
17 files changed, 3110 insertions, 5 deletions
diff --git a/indra/lib/python/indra/__init__.py b/indra/lib/python/indra/__init__.py
index 7db53666e8..bbfdbaf98d 100644
--- a/indra/lib/python/indra/__init__.py
+++ b/indra/lib/python/indra/__init__.py
@@ -1,5 +1,7 @@
-# @file __init__.py
-# @brief Initialization file for the indra module.
-#
-# Copyright (c) 2006-$CurrentYear$, Linden Research, Inc.
-# $License$
+"""\
+@file __init__.py
+@brief Initialization file for the indra module.
+
+Copyright (c) 2006-2007, Linden Research, Inc.
+$License$
+"""
diff --git a/indra/lib/python/indra/base/__init__.py b/indra/lib/python/indra/base/__init__.py
new file mode 100644
index 0000000000..e615ab6870
--- /dev/null
+++ b/indra/lib/python/indra/base/__init__.py
@@ -0,0 +1,7 @@
+"""\
+@file __init__.py
+@brief Initialization file for the indra.base module.
+
+Copyright (c) 2007, Linden Research, Inc.
+$License$
+"""
diff --git a/indra/lib/python/indra/base/config.py b/indra/lib/python/indra/base/config.py
new file mode 100644
index 0000000000..d76bd7edd3
--- /dev/null
+++ b/indra/lib/python/indra/base/config.py
@@ -0,0 +1,46 @@
+"""\
+@file config.py
+@brief Utility module for parsing and accessing the indra.xml config file.
+
+Copyright (c) 2006-2007, Linden Research, Inc.
+$License$
+"""
+
+from os.path import dirname, join, realpath
+import types
+from indra.base import llsd
+
+_g_config_dict = None
+
+def load(indra_xml_file=None):
+ global _g_config_dict
+ if _g_config_dict == None:
+ if indra_xml_file is None:
+ ## going from:
+ ## "/opt/linden/indra/lib/python/indra/base/config.py"
+ ## to:
+ ## "/opt/linden/etc/indra.xml"
+ indra_xml_file = realpath(
+ dirname(realpath(__file__)) + "../../../../../../etc/indra.xml")
+ config_file = file(indra_xml_file)
+ _g_config_dict = llsd.LLSD().parse(config_file.read())
+ config_file.close()
+ #print "loaded config from",indra_xml_file,"into",_g_config_dict
+
+def update(xml_file):
+ """Load an XML file and apply its map as overrides or additions
+ to the existing config. The dataserver does this with indra.xml
+ and dataserver.xml."""
+ global _g_config_dict
+ if _g_config_dict == None:
+ raise Exception("no config loaded before config.update()")
+ config_file = file(xml_file)
+ overrides = llsd.LLSD().parse(config_file.read())
+ config_file.close()
+ _g_config_dict.update(overrides)
+
+def get(key):
+ global _g_config_dict
+ if _g_config_dict == None:
+ load()
+ return _g_config_dict.get(key)
diff --git a/indra/lib/python/indra/base/llsd.py b/indra/lib/python/indra/base/llsd.py
new file mode 100644
index 0000000000..1af07e5885
--- /dev/null
+++ b/indra/lib/python/indra/base/llsd.py
@@ -0,0 +1,808 @@
+"""\
+@file llsd.py
+@brief Types as well as parsing and formatting functions for handling LLSD.
+
+Copyright (c) 2006-2007, Linden Research, Inc.
+$License$
+"""
+
+import datetime
+import base64
+import struct
+import time
+import types
+import re
+
+#from cElementTree import fromstring ## This does not work under Windows
+try:
+ ## This is the old name of elementtree, for use with 2.3
+ from elementtree.ElementTree import fromstring
+except ImportError:
+ ## This is the name of elementtree under python 2.5
+ from xml.etree.ElementTree import fromstring
+
+from indra.base import lluuid
+
+int_regex = re.compile("[-+]?\d+")
+real_regex = re.compile("[-+]?(\d+(\.\d*)?|\d*\.\d+)([eE][-+]?\d+)?")
+alpha_regex = re.compile("[a-zA-Z]+")
+date_regex = re.compile("(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})T(?P<hour>\d{2}):(?P<minute>\d{2}):(?P<second>\d{2})(?P<second_float>\.\d{2})?Z")
+#date: d"YYYY-MM-DDTHH:MM:SS.FFZ"
+
+class LLSDParseError(Exception):
+ pass
+
+class LLSDSerializationError(Exception):
+ pass
+
+
+class binary(str):
+ pass
+
+class uri(str):
+ pass
+
+
+BOOL_TRUE = ('1', '1.0', 'true')
+BOOL_FALSE = ('0', '0.0', 'false', '')
+
+
+def bool_to_python(node):
+ val = node.text or ''
+ if val in BOOL_TRUE:
+ return True
+ else:
+ return False
+
+def int_to_python(node):
+ val = node.text or ''
+ if not val.strip():
+ return 0
+ return int(val)
+
+def real_to_python(node):
+ val = node.text or ''
+ if not val.strip():
+ return 0.0
+ return float(val)
+
+def uuid_to_python(node):
+ return lluuid.UUID(node.text)
+
+def str_to_python(node):
+ return unicode(node.text or '').encode('utf8', 'replace')
+
+def bin_to_python(node):
+ return binary(base64.decodestring(node.text or ''))
+
+def date_to_python(node):
+ val = node.text or ''
+ if not val:
+ val = "1970-01-01T00:00:00Z"
+ return datetime.datetime(
+ *time.strptime(val, '%Y-%m-%dT%H:%M:%SZ')[:6])
+
+def uri_to_python(node):
+ val = node.text or ''
+ if not val:
+ return None
+ return uri(val)
+
+def map_to_python(node):
+ result = {}
+ for index in range(len(node))[::2]:
+ result[node[index].text] = to_python(node[index+1])
+ return result
+
+def array_to_python(node):
+ return [to_python(child) for child in node]
+
+
+NODE_HANDLERS = dict(
+ undef=lambda x: None,
+ boolean=bool_to_python,
+ integer=int_to_python,
+ real=real_to_python,
+ uuid=uuid_to_python,
+ string=str_to_python,
+ binary=bin_to_python,
+ date=date_to_python,
+ uri=uri_to_python,
+ map=map_to_python,
+ array=array_to_python,
+ )
+
+def to_python(node):
+ return NODE_HANDLERS[node.tag](node)
+
+class Nothing(object):
+ pass
+
+
+class LLSDXMLFormatter(object):
+ def __init__(self):
+ self.type_map = {
+ type(None) : self.UNDEF,
+ bool : self.BOOLEAN,
+ int : self.INTEGER,
+ long : self.INTEGER,
+ float : self.REAL,
+ lluuid.UUID : self.UUID,
+ binary : self.BINARY,
+ str : self.STRING,
+ unicode : self.STRING,
+ uri : self.URI,
+ datetime.datetime : self.DATE,
+ list : self.ARRAY,
+ tuple : self.ARRAY,
+ types.GeneratorType : self.ARRAY,
+ dict : self.MAP
+ }
+
+ def elt(self, name, contents=None):
+ if(contents is None or contents is ''):
+ return "<%s />" % (name,)
+ else:
+ return "<%s>%s</%s>" % (name, contents, name)
+
+ def xml_esc(self, v):
+ return v.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')
+
+ def UNDEF(self, v):
+ return self.elt('undef')
+ def BOOLEAN(self, v):
+ if v:
+ return self.elt('boolean', 'true')
+ else:
+ return self.elt('boolean', 'false')
+ def INTEGER(self, v):
+ return self.elt('integer', v)
+ def REAL(self, v):
+ return self.elt('real', v)
+ def UUID(self, v):
+ if(v.isNull()):
+ return self.elt('uuid')
+ else:
+ return self.elt('uuid', v)
+ def BINARY(self, v):
+ return self.elt('binary', base64.encodestring(v))
+ def STRING(self, v):
+ return self.elt('string', self.xml_esc(v))
+ def URI(self, v):
+ return self.elt('uri', self.xml_esc(str(v)))
+ def DATE(self, v):
+ return self.elt('date', v.strftime('%Y-%m-%dT%H:%M:%SZ'))
+ def ARRAY(self, v):
+ return self.elt('array', ''.join([self.generate(item) for item in v]))
+ def MAP(self, v):
+ return self.elt(
+ 'map',
+ ''.join(["%s%s" % (self.elt('key', key), self.generate(value))
+ for key, value in v.items()]))
+
+ def generate(self, something):
+ t = type(something)
+ if self.type_map.has_key(t):
+ return self.type_map[t](something)
+ else:
+ raise LLSDSerializationError("Cannot serialize unknown type: %s (%s)" % (
+ t, something))
+
+ def format(self, something):
+ return '<?xml version="1.0" ?>' + self.elt("llsd", self.generate(something))
+
+def format_xml(something):
+ return LLSDXMLFormatter().format(something)
+
+class LLSDNotationFormatter(object):
+ def __init__(self):
+ self.type_map = {
+ type(None) : self.UNDEF,
+ bool : self.BOOLEAN,
+ int : self.INTEGER,
+ long : self.INTEGER,
+ float : self.REAL,
+ lluuid.UUID : self.UUID,
+ binary : self.BINARY,
+ str : self.STRING,
+ unicode : self.STRING,
+ uri : self.URI,
+ datetime.datetime : self.DATE,
+ list : self.ARRAY,
+ tuple : self.ARRAY,
+ types.GeneratorType : self.ARRAY,
+ dict : self.MAP
+ }
+
+ def UNDEF(self, v):
+ return '!'
+ def BOOLEAN(self, v):
+ if v:
+ return 'true'
+ else:
+ return 'false'
+ def INTEGER(self, v):
+ return "i%s" % v
+ def REAL(self, v):
+ return "r%s" % v
+ def UUID(self, v):
+ return "u%s" % v
+ def BINARY(self, v):
+ raise LLSDSerializationError("binary notation not yet supported")
+ def STRING(self, v):
+ return "'%s'" % v.replace("\\", "\\\\").replace("'", "\\'")
+ def URI(self, v):
+ return 'l"%s"' % str(v).replace("\\", "\\\\").replace('"', '\\"')
+ def DATE(self, v):
+ second_str = ""
+ if v.microsecond > 0:
+ seconds = v.second + float(v.microsecond) / 1000000
+ second_str = "%05.2f" % seconds
+ else:
+ second_str = "%d" % v.second
+ return 'd"%s%sZ"' % (v.strftime('%Y-%m-%dT%H:%M:'), second_str)
+ def ARRAY(self, v):
+ return "[%s]" % ','.join([self.generate(item) for item in v])
+ def MAP(self, v):
+ return "{%s}" % ','.join(["'%s':%s" % (key.replace("\\", "\\\\").replace("'", "\\'"), self.generate(value))
+ for key, value in v.items()])
+
+ def generate(self, something):
+ t = type(something)
+ if self.type_map.has_key(t):
+ return self.type_map[t](something)
+ else:
+ raise LLSDSerializationError("Cannot serialize unknown type: %s (%s)" % (
+ t, something))
+
+ def format(self, something):
+ return self.generate(something)
+
+def format_notation(something):
+ return LLSDNotationFormatter().format(something)
+
+def _hex_as_nybble(hex):
+ if (hex >= '0') and (hex <= '9'):
+ return ord(hex) - ord('0')
+ elif (hex >= 'a') and (hex <='f'):
+ return 10 + ord(hex) - ord('a')
+ elif (hex >= 'A') and (hex <='F'):
+ return 10 + ord(hex) - ord('A');
+
+class LLSDBinaryParser(object):
+ def __init__(self):
+ pass
+
+ def parse(self, buffer, ignore_binary = False):
+ """
+ This is the basic public interface for parsing.
+
+ @param buffer the binary data to parse in an indexable sequence.
+ @param ignore_binary parser throws away data in llsd binary nodes.
+ @return returns a python object.
+ """
+ self._buffer = buffer
+ self._index = 0
+ self._keep_binary = not ignore_binary
+ return self._parse()
+
+ def _parse(self):
+ cc = self._buffer[self._index]
+ self._index += 1
+ if cc == '{':
+ return self._parse_map()
+ elif cc == '[':
+ return self._parse_array()
+ elif cc == '!':
+ return None
+ elif cc == '0':
+ return False
+ elif cc == '1':
+ return True
+ elif cc == 'i':
+ # 'i' = integer
+ idx = self._index
+ self._index += 4
+ return struct.unpack("!i", self._buffer[idx:idx+4])[0]
+ elif cc == ('r'):
+ # 'r' = real number
+ idx = self._index
+ self._index += 8
+ return struct.unpack("!d", self._buffer[idx:idx+8])[0]
+ elif cc == 'u':
+ # 'u' = uuid
+ idx = self._index
+ self._index += 16
+ return lluuid.uuid_bits_to_uuid(self._buffer[idx:idx+16])
+ elif cc == 's':
+ # 's' = string
+ return self._parse_string()
+ elif cc in ("'", '"'):
+ # delimited/escaped string
+ return self._parse_string_delim(cc)
+ elif cc == 'l':
+ # 'l' = uri
+ return uri(self._parse_string())
+ elif cc == ('d'):
+ # 'd' = date in seconds since epoch
+ idx = self._index
+ self._index += 8
+ seconds = struct.unpack("!d", self._buffer[idx:idx+8])[0]
+ return datetime.datetime.fromtimestamp(seconds)
+ elif cc == 'b':
+ binary = self._parse_string()
+ if self._keep_binary:
+ return binary
+ # *NOTE: maybe have a binary placeholder which has the
+ # length.
+ return None
+ else:
+ raise LLSDParseError("invalid binary token at byte %d: %d" % (
+ self._index - 1, ord(cc)))
+
+ def _parse_map(self):
+ rv = {}
+ size = struct.unpack("!i", self._buffer[self._index:self._index+4])[0]
+ self._index += 4
+ count = 0
+ cc = self._buffer[self._index]
+ self._index += 1
+ key = ''
+ while (cc != '}') and (count < size):
+ if cc == 'k':
+ key = self._parse_string()
+ elif cc in ("'", '"'):
+ key = self._parse_string_delim(cc)
+ else:
+ raise LLSDParseError("invalid map key at byte %d." % (
+ self._index - 1,))
+ value = self._parse()
+ #print "kv:",key,value
+ rv[key] = value
+ count += 1
+ cc = self._buffer[self._index]
+ self._index += 1
+ if cc != '}':
+ raise LLSDParseError("invalid map close token at byte %d." % (
+ self._index,))
+ return rv
+
+ def _parse_array(self):
+ rv = []
+ size = struct.unpack("!i", self._buffer[self._index:self._index+4])[0]
+ self._index += 4
+ count = 0
+ cc = self._buffer[self._index]
+ while (cc != ']') and (count < size):
+ rv.append(self._parse())
+ count += 1
+ cc = self._buffer[self._index]
+ if cc != ']':
+ raise LLSDParseError("invalid array close token at byte %d." % (
+ self._index,))
+ self._index += 1
+ return rv
+
+ def _parse_string(self):
+ size = struct.unpack("!i", self._buffer[self._index:self._index+4])[0]
+ self._index += 4
+ rv = self._buffer[self._index:self._index+size]
+ self._index += size
+ return rv
+
+ def _parse_string_delim(self, delim):
+ list = []
+ found_escape = False
+ found_hex = False
+ found_digit = False
+ byte = 0
+ while True:
+ cc = self._buffer[self._index]
+ self._index += 1
+ if found_escape:
+ if found_hex:
+ if found_digit:
+ found_escape = False
+ found_hex = False
+ found_digit = False
+ byte <<= 4
+ byte |= _hex_as_nybble(cc)
+ list.append(chr(byte))
+ byte = 0
+ else:
+ found_digit = True
+ byte = _hex_as_nybble(cc)
+ elif cc == 'x':
+ found_hex = True
+ else:
+ if cc == 'a':
+ list.append('\a')
+ elif cc == 'b':
+ list.append('\b')
+ elif cc == 'f':
+ list.append('\f')
+ elif cc == 'n':
+ list.append('\n')
+ elif cc == 'r':
+ list.append('\r')
+ elif cc == 't':
+ list.append('\t')
+ elif cc == 'v':
+ list.append('\v')
+ else:
+ list.append(cc)
+ found_escape = False
+ elif cc == '\\':
+ found_escape = True
+ elif cc == delim:
+ break
+ else:
+ list.append(cc)
+ return ''.join(list)
+
+class LLSDNotationParser(object):
+ """ Parse LLSD notation:
+ map: { string:object, string:object }
+ array: [ object, object, object ]
+ undef: !
+ boolean: true | false | 1 | 0 | T | F | t | f | TRUE | FALSE
+ integer: i####
+ real: r####
+ uuid: u####
+ string: "g'day" | 'have a "nice" day' | s(size)"raw data"
+ uri: l"escaped"
+ date: d"YYYY-MM-DDTHH:MM:SS.FFZ"
+ binary: b##"ff3120ab1" | b(size)"raw data" """
+ def __init__(self):
+ pass
+
+ def parse(self, buffer, ignore_binary = False):
+ """
+ This is the basic public interface for parsing.
+
+ @param buffer the notation string to parse.
+ @param ignore_binary parser throws away data in llsd binary nodes.
+ @return returns a python object.
+ """
+ if buffer == "":
+ return False
+
+ self._buffer = buffer
+ self._index = 0
+ return self._parse()
+
+ def _parse(self):
+ cc = self._buffer[self._index]
+ self._index += 1
+ if cc == '{':
+ return self._parse_map()
+ elif cc == '[':
+ return self._parse_array()
+ elif cc == '!':
+ return None
+ elif cc == '0':
+ return False
+ elif cc == '1':
+ return True
+ elif cc in ('F', 'f'):
+ self._skip_alpha()
+ return False
+ elif cc in ('T', 't'):
+ self._skip_alpha()
+ return True
+ elif cc == 'i':
+ # 'i' = integer
+ return self._parse_integer()
+ elif cc == ('r'):
+ # 'r' = real number
+ return self._parse_real()
+ elif cc == 'u':
+ # 'u' = uuid
+ return self._parse_uuid()
+ elif cc in ("'", '"', 's'):
+ return self._parse_string(cc)
+ elif cc == 'l':
+ # 'l' = uri
+ delim = self._buffer[self._index]
+ self._index += 1
+ val = uri(self._parse_string(delim))
+ if len(val) == 0:
+ return None
+ return val
+ elif cc == ('d'):
+ # 'd' = date in seconds since epoch
+ return self._parse_date()
+ elif cc == 'b':
+ raise LLSDParseError("binary notation not yet supported")
+ else:
+ print cc
+ raise LLSDParseError("invalid token at index %d: %d" % (
+ self._index - 1, ord(cc)))
+
+ def _parse_map(self):
+ """ map: { string:object, string:object } """
+ rv = {}
+ cc = self._buffer[self._index]
+ self._index += 1
+ key = ''
+ found_key = False
+ while (cc != '}'):
+ if not found_key:
+ if cc in ("'", '"', 's'):
+ key = self._parse_string(cc)
+ found_key = True
+ #print "key:",key
+ elif cc.isspace() or cc == ',':
+ cc = self._buffer[self._index]
+ self._index += 1
+ else:
+ raise LLSDParseError("invalid map key at byte %d." % (
+ self._index - 1,))
+ else:
+ if cc.isspace() or cc == ':':
+ #print "skipping whitespace '%s'" % cc
+ cc = self._buffer[self._index]
+ self._index += 1
+ continue
+ self._index += 1
+ value = self._parse()
+ #print "kv:",key,value
+ rv[key] = value
+ found_key = False
+ cc = self._buffer[self._index]
+ self._index += 1
+ #if cc == '}':
+ # break
+ #cc = self._buffer[self._index]
+ #self._index += 1
+
+ return rv
+
+ def _parse_array(self):
+ """ array: [ object, object, object ] """
+ rv = []
+ cc = self._buffer[self._index]
+ while (cc != ']'):
+ if cc.isspace() or cc == ',':
+ self._index += 1
+ cc = self._buffer[self._index]
+ continue
+ rv.append(self._parse())
+ cc = self._buffer[self._index]
+
+ if cc != ']':
+ raise LLSDParseError("invalid array close token at index %d." % (
+ self._index,))
+ self._index += 1
+ return rv
+
+ def _parse_uuid(self):
+ match = re.match(lluuid.UUID.uuid_regex, self._buffer[self._index:])
+ if not match:
+ raise LLSDParseError("invalid uuid token at index %d." % self._index)
+
+ (start, end) = match.span()
+ start += self._index
+ end += self._index
+ self._index = end
+ return lluuid.UUID(self._buffer[start:end])
+
+ def _skip_alpha(self):
+ match = re.match(alpha_regex, self._buffer[self._index:])
+ if match:
+ self._index += match.end()
+
+ def _parse_date(self):
+ delim = self._buffer[self._index]
+ self._index += 1
+ datestr = self._parse_string(delim)
+
+ if datestr == "":
+ return datetime.datetime(1970, 1, 1)
+
+ match = re.match(date_regex, datestr)
+ if not match:
+ raise LLSDParseError("invalid date string '%s'." % datestr)
+
+ year = int(match.group('year'))
+ month = int(match.group('month'))
+ day = int(match.group('day'))
+ hour = int(match.group('hour'))
+ minute = int(match.group('minute'))
+ second = int(match.group('second'))
+ seconds_float = match.group('second_float')
+ microsecond = 0
+ if seconds_float:
+ microsecond = int(seconds_float[1:]) * 10000
+ return datetime.datetime(year, month, day, hour, minute, second, microsecond)
+
+ def _parse_real(self):
+ match = re.match(real_regex, self._buffer[self._index:])
+ if not match:
+ raise LLSDParseError("invalid real token at index %d." % self._index)
+
+ (start, end) = match.span()
+ start += self._index
+ end += self._index
+ self._index = end
+ return float( self._buffer[start:end] )
+
+ def _parse_integer(self):
+ match = re.match(int_regex, self._buffer[self._index:])
+ if not match:
+ raise LLSDParseError("invalid integer token at index %d." % self._index)
+
+ (start, end) = match.span()
+ start += self._index
+ end += self._index
+ self._index = end
+ return int( self._buffer[start:end] )
+
+ def _parse_string(self, delim):
+ """ string: "g'day" | 'have a "nice" day' | s(size)"raw data" """
+ rv = ""
+
+ if delim in ("'", '"'):
+ rv = self._parse_string_delim(delim)
+ elif delim == 's':
+ rv = self._parse_string_raw()
+ else:
+ raise LLSDParseError("invalid string token at index %d." % self._index)
+
+ return rv
+
+
+ def _parse_string_delim(self, delim):
+ """ string: "g'day" | 'have a "nice" day' """
+ list = []
+ found_escape = False
+ found_hex = False
+ found_digit = False
+ byte = 0
+ while True:
+ cc = self._buffer[self._index]
+ self._index += 1
+ if found_escape:
+ if found_hex:
+ if found_digit:
+ found_escape = False
+ found_hex = False
+ found_digit = False
+ byte <<= 4
+ byte |= _hex_as_nybble(cc)
+ list.append(chr(byte))
+ byte = 0
+ else:
+ found_digit = True
+ byte = _hex_as_nybble(cc)
+ elif cc == 'x':
+ found_hex = True
+ else:
+ if cc == 'a':
+ list.append('\a')
+ elif cc == 'b':
+ list.append('\b')
+ elif cc == 'f':
+ list.append('\f')
+ elif cc == 'n':
+ list.append('\n')
+ elif cc == 'r':
+ list.append('\r')
+ elif cc == 't':
+ list.append('\t')
+ elif cc == 'v':
+ list.append('\v')
+ else:
+ list.append(cc)
+ found_escape = False
+ elif cc == '\\':
+ found_escape = True
+ elif cc == delim:
+ break
+ else:
+ list.append(cc)
+ return ''.join(list)
+
+ def _parse_string_raw(self):
+ """ string: s(size)"raw data" """
+ # Read the (size) portion.
+ cc = self._buffer[self._index]
+ self._index += 1
+ if cc != '(':
+ raise LLSDParseError("invalid string token at index %d." % self._index)
+
+ rparen = self._buffer.find(')', self._index)
+ if rparen == -1:
+ raise LLSDParseError("invalid string token at index %d." % self._index)
+
+ size = int(self._buffer[self._index:rparen])
+
+ self._index = rparen + 1
+ delim = self._buffer[self._index]
+ self._index += 1
+ if delim not in ("'", '"'):
+ raise LLSDParseError("invalid string token at index %d." % self._index)
+
+ rv = self._buffer[self._index:(self._index + size)]
+ self._index += size
+ cc = self._buffer[self._index]
+ self._index += 1
+ if cc != delim:
+ raise LLSDParseError("invalid string token at index %d." % self._index)
+
+ return rv
+
+def format_binary(something):
+ return '<?llsd/binary?>\n' + _format_binary_recurse(something)
+
+def _format_binary_recurse(something):
+ if something is None:
+ return '!'
+ elif isinstance(something, bool):
+ if something:
+ return '1'
+ else:
+ return '0'
+ elif isinstance(something, (int, long)):
+ return 'i' + struct.pack('!i', something)
+ elif isinstance(something, float):
+ return 'r' + struct.pack('!d', something)
+ elif isinstance(something, lluuid.UUID):
+ return 'u' + something._bits
+ elif isinstance(something, binary):
+ return 'b' + struct.pack('!i', len(something)) + something
+ elif isinstance(something, (str, unicode)):
+ return 's' + struct.pack('!i', len(something)) + something
+ elif isinstance(something, uri):
+ return 'l' + struct.pack('!i', len(something)) + something
+ elif isinstance(something, datetime.datetime):
+ seconds_since_epoch = time.mktime(something.timetuple())
+ return 'd' + struct.pack('!d', seconds_since_epoch)
+ elif isinstance(something, (list, tuple)):
+ array_builder = []
+ array_builder.append('[' + struct.pack('!i', len(something)))
+ for item in something:
+ array_builder.append(_format_binary_recurse(item))
+ array_builder.append(']')
+ return ''.join(array_builder)
+ elif isinstance(something, dict):
+ map_builder = []
+ map_builder.append('{' + struct.pack('!i', len(something)))
+ for key, value in something.items():
+ map_builder.append('k' + struct.pack('!i', len(key)) + key)
+ map_builder.append(_format_binary_recurse(value))
+ map_builder.append('}')
+ return ''.join(map_builder)
+ else:
+ raise LLSDSerializationError("Cannot serialize unknown type: %s (%s)" % (
+ type(something), something))
+
+
+def parse(something):
+ try:
+ if something.startswith('<?llsd/binary?>'):
+ just_binary = something.split('\n', 1)[1]
+ return LLSDBinaryParser().parse(just_binary)
+ # This should be better.
+ elif something.startswith('<'):
+ return to_python(fromstring(something)[0])
+ else:
+ return LLSDNotationParser().parse(something)
+ except KeyError:
+ raise Exception('LLSD could not be parsed: %s' % (something,))
+
+class LLSD(object):
+ def __init__(self, thing=None):
+ self.thing = thing
+
+ def __str__(self):
+ return self.toXML(self.thing)
+
+ parse = staticmethod(parse)
+ toXML = staticmethod(format_xml)
+ toBinary = staticmethod(format_binary)
+ toNotation = staticmethod(format_notation)
+
+
+undef = LLSD(None)
+
diff --git a/indra/lib/python/indra/base/lluuid.py b/indra/lib/python/indra/base/lluuid.py
new file mode 100644
index 0000000000..a86ee03e72
--- /dev/null
+++ b/indra/lib/python/indra/base/lluuid.py
@@ -0,0 +1,265 @@
+"""\
+@file lluuid.py
+@brief UUID parser/generator.
+
+Copyright (c) 2004-2007, Linden Research, Inc.
+$License$
+"""
+
+import md5, random, socket, string, time, re
+
+def _int2binstr(i,l):
+ s=''
+ for a in range(l):
+ s=chr(i&0xFF)+s
+ i>>=8
+ return s
+
+def _binstr2int(s):
+ i = long(0)
+ for c in s:
+ i = (i<<8) + ord(c)
+ return i
+
+class UUID(object):
+ """
+ A class which represents a 16 byte integer. Stored as a 16 byte 8
+ bit character string.
+
+ The string version is to be of the form:
+ AAAAAAAA-AAAA-BBBB-BBBB-BBBBBBCCCCCC (a 128-bit number in hex)
+ where A=network address, B=timestamp, C=random.
+ """
+
+ NULL_STR = "00000000-0000-0000-0000-000000000000"
+
+ # the UUIDREGEX_STRING is helpful for parsing UUID's in text
+ hex_wildcard = r"[0-9a-fA-F]"
+ word = hex_wildcard + r"{4,4}-"
+ long_word = hex_wildcard + r"{8,8}-"
+ very_long_word = hex_wildcard + r"{12,12}"
+ UUID_REGEX_STRING = long_word + word + word + word + very_long_word
+ uuid_regex = re.compile(UUID_REGEX_STRING)
+
+ rand = random.Random()
+ ip = ''
+ try:
+ ip = socket.gethostbyname(socket.gethostname())
+ except(socket.gaierror):
+ # no ip address, so just default to somewhere in 10.x.x.x
+ ip = '10'
+ for i in range(3):
+ ip += '.' + str(rand.randrange(1,254))
+ hexip = ''.join(["%04x" % long(i) for i in ip.split('.')])
+ lastid = ''
+
+ def __init__(self, string_with_uuid=None):
+ """
+ Initialize to first valid UUID in string argument,
+ or to null UUID if none found or string is not supplied.
+ """
+ self._bits = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
+ if string_with_uuid:
+ uuid_match = UUID.uuid_regex.search(string_with_uuid)
+ if uuid_match:
+ uuid_string = uuid_match.group()
+ s = string.replace(uuid_string, '-', '')
+ self._bits = _int2binstr(string.atol(s[:8],16),4) + \
+ _int2binstr(string.atol(s[8:16],16),4) + \
+ _int2binstr(string.atol(s[16:24],16),4) + \
+ _int2binstr(string.atol(s[24:],16),4)
+
+ def __len__(self):
+ """
+ Used by the len() builtin.
+ """
+ return 36
+
+ def __nonzero__(self):
+ return self._bits != "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
+
+ def __str__(self):
+ uuid_string = self.toString()
+ return uuid_string
+
+ __repr__ = __str__
+
+ def __getitem__(self, index):
+ return str(self)[index]
+
+ def __eq__(self, other):
+ if isinstance(other, (str, unicode)):
+ other = UUID(other)
+ return self._bits == getattr(other, '_bits', '')
+
+ def __le__(self, other):
+ return self._bits <= other._bits
+
+ def __ge__(self, other):
+ return self._bits >= other._bits
+
+ def __lt__(self, other):
+ return self._bits < other._bits
+
+ def __gt__(self, other):
+ return self._bits > other._bits
+
+ def __hash__(self):
+ return hash(self._bits)
+
+ def set(self, uuid):
+ self._bits = uuid._bits
+
+ def setFromString(self, uuid_string):
+ """
+ Given a string version of a uuid, set self bits
+ appropriately. Returns self.
+ """
+ s = string.replace(uuid_string, '-', '')
+ self._bits = _int2binstr(string.atol(s[:8],16),4) + \
+ _int2binstr(string.atol(s[8:16],16),4) + \
+ _int2binstr(string.atol(s[16:24],16),4) + \
+ _int2binstr(string.atol(s[24:],16),4)
+ return self
+
+ def setFromMemoryDump(self, gdb_string):
+ """
+ We expect to get gdb_string as four hex units. eg:
+ 0x147d54db 0xc34b3f1b 0x714f989b 0x0a892fd2
+ Which will be translated to:
+ db547d14-1b3f4bc3-9b984f71-d22f890a
+ Returns self.
+ """
+ s = string.replace(gdb_string, '0x', '')
+ s = string.replace(s, ' ', '')
+ t = ''
+ for i in range(8,40,8):
+ for j in range(0,8,2):
+ t = t + s[i-j-2:i-j]
+ self.setFromString(t)
+
+ def toString(self):
+ """
+ Return as a string matching the LL standard
+ AAAAAAAA-AAAA-BBBB-BBBB-BBBBBBCCCCCC (a 128-bit number in hex)
+ where A=network address, B=timestamp, C=random.
+ """
+ return uuid_bits_to_string(self._bits)
+
+ def getAsString(self):
+ """
+ Return a different string representation of the form
+ AAAAAAAA-AAAABBBB-BBBBBBBB-BBCCCCCC (a 128-bit number in hex)
+ where A=network address, B=timestamp, C=random.
+ """
+ i1 = _binstr2int(self._bits[0:4])
+ i2 = _binstr2int(self._bits[4:8])
+ i3 = _binstr2int(self._bits[8:12])
+ i4 = _binstr2int(self._bits[12:16])
+ return '%08lx-%08lx-%08lx-%08lx' % (i1,i2,i3,i4)
+
+ def generate(self):
+ """
+ Generate a new uuid. This algorithm is slightly different
+ from c++ implementation for portability reasons.
+ Returns self.
+ """
+ newid = self.__class__.lastid
+ while newid == self.__class__.lastid:
+ now = long(time.time() * 1000)
+ newid = ("%016x" % now) + self.__class__.hexip
+ newid += ("%03x" % (self.__class__.rand.randrange(0,4095)))
+ self.__class__.lastid = newid
+ m = md5.new()
+ m.update(newid)
+ self._bits = m.digest()
+ return self
+
+ def isNull(self):
+ """
+ Returns 1 if the uuid is null - ie, equal to default uuid.
+ """
+ return (self._bits == "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0")
+
+ def xor(self, rhs):
+ """
+ xors self with rhs.
+ """
+ v1 = _binstr2int(self._bits[0:4]) ^ _binstr2int(rhs._bits[0:4])
+ v2 = _binstr2int(self._bits[4:8]) ^ _binstr2int(rhs._bits[4:8])
+ v3 = _binstr2int(self._bits[8:12]) ^ _binstr2int(rhs._bits[8:12])
+ v4 = _binstr2int(self._bits[12:16]) ^ _binstr2int(rhs._bits[12:16])
+ self._bits = _int2binstr(v1,4) + \
+ _int2binstr(v2,4) + \
+ _int2binstr(v3,4) + \
+ _int2binstr(v4,4)
+
+def printTranslatedMemory(four_hex_uints):
+ """
+ We expect to get the string as four hex units. eg:
+ 0x147d54db 0xc34b3f1b 0x714f989b 0x0a892fd2
+ Which will be translated to:
+ db547d14-1b3f4bc3-9b984f71-d22f890a
+ """
+ uuid = UUID()
+ uuid.setFromMemoryDump(four_hex_uints)
+ print uuid.toString()
+
+def isPossiblyID(id_str):
+ """
+ This function returns 1 if the string passed has some uuid-like
+ characteristics. Otherwise returns 0.
+ """
+ if not id_str or len(id_str) < 5 or len(id_str) > 36:
+ return 0
+
+ if isinstance(id_str, UUID) or UUID.uuid_regex.match(id_str):
+ return 1
+ # build a string which matches every character.
+ hex_wildcard = r"[0-9a-fA-F]"
+ chars = len(id_str)
+ next = min(chars, 8)
+ matcher = hex_wildcard+"{"+str(next)+","+str(next)+"}"
+ chars = chars - next
+ if chars > 0:
+ matcher = matcher + "-"
+ chars = chars - 1
+ for block in range(3):
+ next = max(min(chars, 4), 0)
+ if next:
+ matcher = matcher + hex_wildcard+"{"+str(next)+","+str(next)+"}"
+ chars = chars - next
+ if chars > 0:
+ matcher = matcher + "-"
+ chars = chars - 1
+ if chars > 0:
+ next = min(chars, 12)
+ matcher = matcher + hex_wildcard+"{"+str(next)+","+str(next)+"}"
+ #print matcher
+ uuid_matcher = re.compile(matcher)
+ if uuid_matcher.match(id_str):
+ return 1
+ return 0
+
+def uuid_bits_to_string(bits):
+ i1 = _binstr2int(bits[0:4])
+ i2 = _binstr2int(bits[4:6])
+ i3 = _binstr2int(bits[6:8])
+ i4 = _binstr2int(bits[8:10])
+ i5 = _binstr2int(bits[10:12])
+ i6 = _binstr2int(bits[12:16])
+ return '%08lx-%04lx-%04lx-%04lx-%04lx%08lx' % (i1,i2,i3,i4,i5,i6)
+
+def uuid_bits_to_uuid(bits):
+ return UUID(uuid_bits_to_string(bits))
+
+
+try:
+ from mulib import mu
+except ImportError:
+ print "Couldn't import mulib, not registering UUID converter"
+else:
+ def convertUUID(req, uuid):
+ return str(uuid)
+
+ mu.registerConverter(UUID, convertUUID)
diff --git a/indra/lib/python/indra/ipc/__init__.py b/indra/lib/python/indra/ipc/__init__.py
new file mode 100644
index 0000000000..6576513f9d
--- /dev/null
+++ b/indra/lib/python/indra/ipc/__init__.py
@@ -0,0 +1,7 @@
+"""\
+@file __init__.py
+@brief Initialization file for the indra ipc module.
+
+Copyright (c) 2006-2007, Linden Research, Inc.
+$License$
+"""
diff --git a/indra/lib/python/indra/ipc/compatibility.py b/indra/lib/python/indra/ipc/compatibility.py
new file mode 100644
index 0000000000..a430e46ab4
--- /dev/null
+++ b/indra/lib/python/indra/ipc/compatibility.py
@@ -0,0 +1,103 @@
+"""\
+@file compatibility.py
+@brief Classes that manage compatibility states.
+
+Copyright (c) 2007, Linden Research, Inc.
+$License$
+"""
+
+
+"""Compatibility combination table:
+
+ I M O N S
+ -- -- -- -- --
+I: I I I I I
+M: I M M M M
+O: I M O M O
+N: I M M N N
+S: I M O N S
+
+"""
+
+class _Compatibility(object):
+ def __init__(self, reason):
+ self.reasons = [ ]
+ if reason:
+ self.reasons.append(reason)
+
+ def combine(self, other):
+ if self._level() <= other._level():
+ return self._buildclone(other)
+ else:
+ return other._buildclone(self)
+
+ def prefix(self, leadin):
+ self.reasons = [ leadin + r for r in self.reasons ]
+
+ def same(self): return self._level() >= 1
+ def deployable(self): return self._level() > 0
+ def resolved(self): return self._level() > -1
+ def compatible(self): return self._level() > -2
+
+ def explain(self):
+ return self.__class__.__name__ + "\n" + "\n".join(self.reasons) + "\n"
+
+ def _buildclone(self, other=None):
+ c = self._buildinstance()
+ c.reasons = self.reasons
+ if other:
+ c.reasons = c.reasons + other.reasons
+ return c
+
+ def _buildinstance(self):
+ return self.__class__(None)
+
+# def _level(self):
+# raise RuntimeError('implement in subclass')
+
+
+class Incompatible(_Compatibility):
+ def _level(self):
+ return -2
+
+class Mixed(_Compatibility):
+ def __init__(self, *inputs):
+ _Compatibility.__init__(self, None)
+ for i in inputs:
+ self.reasons += i.reasons
+
+ def _buildinstance(self):
+ return self.__class__()
+
+ def _level(self):
+ return -1
+
+class _Aged(_Compatibility):
+ def combine(self, other):
+ if self._level() == other._level():
+ return self._buildclone(other)
+ if int(self._level()) == int(other._level()):
+ return Mixed(self, other)
+ return _Compatibility.combine(self, other)
+
+class Older(_Aged):
+ def _level(self):
+ return -0.25
+
+class Newer(_Aged):
+ def _level(self):
+ return 0.25
+
+class Same(_Compatibility):
+ def __init__(self):
+ _Compatibility.__init__(self, None)
+
+ def _buildinstance(self):
+ return self.__class__()
+
+ def _level(self):
+ return 1
+
+
+
+
diff --git a/indra/lib/python/indra/ipc/httputil.py b/indra/lib/python/indra/ipc/httputil.py
new file mode 100644
index 0000000000..85e4a645bc
--- /dev/null
+++ b/indra/lib/python/indra/ipc/httputil.py
@@ -0,0 +1,94 @@
+"""\
+@file httputil.py
+@brief HTTP utilities. HTTP date conversion and non-blocking HTTP
+client support.
+
+Copyright (c) 2006-2007, Linden Research, Inc.
+$License$
+"""
+
+
+import os
+import time
+import urlparse
+
+import httplib
+try:
+ from mx.DateTime import Parser
+
+ parse_date = Parser.DateTimeFromString
+except ImportError:
+ from dateutil import parser
+
+ parse_date = parser.parse
+
+
+HTTP_TIME_FORMAT = '%a, %d %b %Y %H:%M:%S GMT'
+
+
+to_http_time = lambda t: time.strftime(HTTP_TIME_FORMAT, time.gmtime(t))
+from_http_time = lambda t: int(parse_date(t).gmticks())
+
+def host_and_port_from_url(url):
+ """@breif Simple function to get host and port from an http url.
+ @return Returns host, port and port may be None.
+ """
+ host = None
+ port = None
+ parsed_url = urlparse.urlparse(url)
+ try:
+ host, port = parsed_url[1].split(':')
+ except ValueError:
+ host = parsed_url[1].split(':')
+ return host, port
+
+
+def better_putrequest(self, method, url, skip_host=0):
+ self.method = method
+ self.path = url
+ self.old_putrequest(method, url, skip_host)
+
+
+class HttpClient(httplib.HTTPConnection):
+ """A subclass of httplib.HTTPConnection which works around a bug
+ in the interaction between eventlet sockets and httplib. httplib relies
+ on gc to close the socket, causing the socket to be closed too early.
+
+ This is an awful hack and the bug should be fixed properly ASAP.
+ """
+ def __init__(self, host, port=None, strict=None):
+ httplib.HTTPConnection.__init__(self, host, port, strict)
+
+ def close(self):
+ pass
+
+ old_putrequest = httplib.HTTPConnection.putrequest
+ putrequest = better_putrequest
+
+
+class HttpsClient(httplib.HTTPSConnection):
+ def close(self):
+ pass
+ old_putrequest = httplib.HTTPSConnection.putrequest
+ putrequest = better_putrequest
+
+
+
+scheme_to_factory_map = {
+ 'http': HttpClient,
+ 'https': HttpsClient,
+}
+
+
+def makeConnection(scheme, location, use_proxy):
+ if use_proxy:
+ if "http_proxy" in os.environ:
+ location = os.environ["http_proxy"]
+ elif "ALL_PROXY" in os.environ:
+ location = os.environ["ALL_PROXY"]
+ else:
+ location = "localhost:3128" #default to local squid
+ if location.startswith("http://"):
+ location = location[len("http://"):]
+ return scheme_to_factory_map[scheme](location)
+
diff --git a/indra/lib/python/indra/ipc/llmessage.py b/indra/lib/python/indra/ipc/llmessage.py
new file mode 100644
index 0000000000..5a9e867ecd
--- /dev/null
+++ b/indra/lib/python/indra/ipc/llmessage.py
@@ -0,0 +1,353 @@
+"""\
+@file llmessage.py
+@brief Message template parsing and compatiblity
+
+Copyright (c) 2007, Linden Research, Inc.
+$License$
+"""
+
+from sets import Set, ImmutableSet
+
+from compatibility import Incompatible, Older, Newer, Same
+from tokenstream import TokenStream
+
+###
+### Message Template
+###
+
+class Template:
+ def __init__(self):
+ self.messages = { }
+
+ def addMessage(self, m):
+ self.messages[m.name] = m
+
+ def compatibleWithBase(self, base):
+ messagenames = (
+ ImmutableSet(self.messages.keys())
+ | ImmutableSet(base.messages.keys())
+ )
+
+ compatibility = Same()
+ for name in messagenames:
+ selfmessage = self.messages.get(name, None)
+ basemessage = base.messages.get(name, None)
+
+ if not selfmessage:
+ c = Older("missing message %s, did you mean to deprecate?" % name)
+ elif not basemessage:
+ c = Newer("added message %s" % name)
+ else:
+ c = selfmessage.compatibleWithBase(basemessage)
+ c.prefix("in message %s: " % name)
+
+ compatibility = compatibility.combine(c)
+
+ return compatibility
+
+
+
+class Message:
+ HIGH = "High"
+ MEDIUM = "Medium"
+ LOW = "Low"
+ FIXED = "Fixed"
+ priorities = [ HIGH, MEDIUM, LOW, FIXED ]
+ prioritieswithnumber = [ FIXED ]
+
+ TRUSTED = "Trusted"
+ NOTTRUSTED = "NotTrusted"
+ trusts = [ TRUSTED, NOTTRUSTED ]
+
+ UNENCODED = "Unencoded"
+ ZEROCODED = "Zerocoded"
+ encodings = [ UNENCODED, ZEROCODED ]
+
+ NOTDEPRECATED = "NotDeprecated"
+ DEPRECATED = "Deprecated"
+ UDPDEPRECATED = "UDPDeprecated"
+ deprecations = [ NOTDEPRECATED, UDPDEPRECATED, DEPRECATED ]
+ # in order of increasing deprecation
+
+ def __init__(self, name, number, priority, trust, coding):
+ self.name = name
+ self.number = number
+ self.priority = priority
+ self.trust = trust
+ self.coding = coding
+ self.deprecateLevel = 0
+ self.blocks = [ ]
+
+ def deprecated(self):
+ return self.deprecateLevel != 0
+
+ def deprecate(self, deprecation):
+ self.deprecateLevel = self.deprecations.index(deprecation)
+
+ def addBlock(self, block):
+ self.blocks.append(block)
+
+ def compatibleWithBase(self, base):
+ if self.name != base.name:
+ # this should never happen in real life because of the
+ # way Template matches up messages by name
+ return Incompatible("has different name: %s vs. %s in base"
+ % (self.name, base.name))
+ if self.priority != base.priority:
+ return Incompatible("has different priority: %s vs. %s in base"
+ % (self.priority, base.priority))
+ if self.trust != base.trust:
+ return Incompatible("has different trust: %s vs. %s in base"
+ % (self.trust, base.trust))
+ if self.coding != base.coding:
+ return Incompatible("has different coding: %s vs. %s in base"
+ % (self.coding, base.coding))
+ if self.number != base.number:
+ return Incompatible("has different number: %s vs. %s in base"
+ % (self.number, base.number))
+
+ compatibility = Same()
+
+ if self.deprecateLevel != base.deprecateLevel:
+ if self.deprecateLevel < base.deprecateLevel:
+ c = Older("is less deprecated: %s vs. %s in base" % (
+ self.deprecations[self.deprecateLevel],
+ self.deprecations[base.deprecateLevel]))
+ else:
+ c = Newer("is more deprecated: %s vs. %s in base" % (
+ self.deprecations[self.deprecateLevel],
+ self.deprecations[base.deprecateLevel]))
+ compatibility = compatibility.combine(c)
+
+ selflen = len(self.blocks)
+ baselen = len(base.blocks)
+ samelen = min(selflen, baselen)
+
+ for i in xrange(0, samelen):
+ selfblock = self.blocks[i]
+ baseblock = base.blocks[i]
+
+ c = selfblock.compatibleWithBase(baseblock)
+ if not c.same():
+ c = Incompatible("block %d isn't identical" % i)
+ compatibility = compatibility.combine(c)
+
+ if selflen > baselen:
+ c = Newer("has %d extra blocks" % (selflen - baselen))
+ elif selflen < baselen:
+ c = Older("missing %d extra blocks" % (baselen - selflen))
+ else:
+ c = Same()
+
+ compatibility = compatibility.combine(c)
+ return compatibility
+
+
+
+class Block(object):
+ SINGLE = "Single"
+ MULTIPLE = "Multiple"
+ VARIABLE = "Variable"
+ repeats = [ SINGLE, MULTIPLE, VARIABLE ]
+ repeatswithcount = [ MULTIPLE ]
+
+ def __init__(self, name, repeat, count=None):
+ self.name = name
+ self.repeat = repeat
+ self.count = count
+ self.variables = [ ]
+
+ def addVariable(self, variable):
+ self.variables.append(variable)
+
+ def compatibleWithBase(self, base):
+ if self.name != base.name:
+ return Incompatible("has different name: %s vs. %s in base"
+ % (self.name, base.name))
+ if self.repeat != base.repeat:
+ return Incompatible("has different repeat: %s vs. %s in base"
+ % (self.repeat, base.repeat))
+ if self.repeat in Block.repeatswithcount:
+ if self.count != base.count:
+ return Incompatible("has different count: %s vs. %s in base"
+ % (self.count, base.count))
+
+ compatibility = Same()
+
+ selflen = len(self.variables)
+ baselen = len(base.variables)
+
+ for i in xrange(0, min(selflen, baselen)):
+ selfvar = self.variables[i]
+ basevar = base.variables[i]
+
+ c = selfvar.compatibleWithBase(basevar)
+ if not c.same():
+ c = Incompatible("variable %d isn't identical" % i)
+ compatibility = compatibility.combine(c)
+
+ if selflen > baselen:
+ c = Newer("has %d extra variables" % (selflen - baselen))
+ elif selflen < baselen:
+ c = Older("missing %d extra variables" % (baselen - selflen))
+ else:
+ c = Same()
+
+ compatibility = compatibility.combine(c)
+ return compatibility
+
+
+
+class Variable:
+ U8 = "U8"; U16 = "U16"; U32 = "U32"; U64 = "U64"
+ S8 = "S8"; S16 = "S16"; S32 = "S32"; S64 = "S64"
+ F32 = "F32"; F64 = "F64"
+ LLVECTOR3 = "LLVector3"; LLVECTOR3D = "LLVector3d"; LLVECTOR4 = "LLVector4"
+ LLQUATERNION = "LLQuaternion"
+ LLUUID = "LLUUID"
+ BOOL = "BOOL"
+ IPADDR = "IPADDR"; IPPORT = "IPPORT"
+ FIXED = "Fixed"
+ VARIABLE = "Variable"
+ types = [ U8, U16, U32, U64, S8, S16, S32, S64, F32, F64,
+ LLVECTOR3, LLVECTOR3D, LLVECTOR4, LLQUATERNION,
+ LLUUID, BOOL, IPADDR, IPPORT, FIXED, VARIABLE ]
+ typeswithsize = [ FIXED, VARIABLE ]
+
+ def __init__(self, name, type, size):
+ self.name = name
+ self.type = type
+ self.size = size
+
+ def compatibleWithBase(self, base):
+ if self.name != base.name:
+ return Incompatible("has different name: %s vs. %s in base"
+ % (self.name, base.name))
+ if self.type != base.type:
+ return Incompatible("has different type: %s vs. %s in base"
+ % (self.type, base.type))
+ if self.type in Variable.typeswithsize:
+ if self.size != base.size:
+ return Incompatible("has different size: %s vs. %s in base"
+ % (self.size, base.size))
+ return Same()
+
+
+
+###
+### Parsing Message Templates
+###
+
+class TemplateParser:
+ def __init__(self, tokens):
+ self._tokens = tokens
+ self._version = 0
+ self._numbers = { }
+ for p in Message.priorities:
+ self._numbers[p] = 0
+
+ def parseTemplate(self):
+ tokens = self._tokens
+ t = Template()
+ while True:
+ if tokens.want("version"):
+ v = float(tokens.require(tokens.wantFloat()))
+ self._version = v
+ t.version = v
+ continue
+
+ m = self.parseMessage()
+ if m:
+ t.addMessage(m)
+ continue
+
+ if self._version >= 2.0:
+ tokens.require(tokens.wantEOF())
+ break
+ else:
+ if tokens.wantEOF():
+ break
+
+ tokens.consume()
+ # just assume (gulp) that this is a comment
+ # line 468: "sim -> dataserver"
+ return t
+
+
+ def parseMessage(self):
+ tokens = self._tokens
+ if not tokens.want("{"):
+ return None
+
+ name = tokens.require(tokens.wantSymbol())
+ priority = tokens.require(tokens.wantOneOf(Message.priorities))
+
+ if self._version >= 2.0 or priority in Message.prioritieswithnumber:
+ number = int("+" + tokens.require(tokens.wantInteger()), 0)
+ else:
+ self._numbers[priority] += 1
+ number = self._numbers[priority]
+
+ trust = tokens.require(tokens.wantOneOf(Message.trusts))
+ coding = tokens.require(tokens.wantOneOf(Message.encodings))
+
+ m = Message(name, number, priority, trust, coding)
+
+ if self._version >= 2.0:
+ d = tokens.wantOneOf(Message.deprecations)
+ if d:
+ m.deprecate(d)
+
+ while True:
+ b = self.parseBlock()
+ if not b:
+ break
+ m.addBlock(b)
+
+ tokens.require(tokens.want("}"))
+
+ return m
+
+
+ def parseBlock(self):
+ tokens = self._tokens
+ if not tokens.want("{"):
+ return None
+ name = tokens.require(tokens.wantSymbol())
+ repeat = tokens.require(tokens.wantOneOf(Block.repeats))
+ if repeat in Block.repeatswithcount:
+ count = int(tokens.require(tokens.wantInteger()))
+ else:
+ count = None
+
+ b = Block(name, repeat, count)
+
+ while True:
+ v = self.parseVariable()
+ if not v:
+ break
+ b.addVariable(v)
+
+ tokens.require(tokens.want("}"))
+ return b
+
+
+ def parseVariable(self):
+ tokens = self._tokens
+ if not tokens.want("{"):
+ return None
+ name = tokens.require(tokens.wantSymbol())
+ type = tokens.require(tokens.wantOneOf(Variable.types))
+ if type in Variable.typeswithsize:
+ size = tokens.require(tokens.wantInteger())
+ else:
+ tokens.wantInteger() # in LandStatRequest: "{ ParcelLocalID S32 1 }"
+ size = None
+ tokens.require(tokens.want("}"))
+ return Variable(name, type, size)
+
+def parseTemplateString(s):
+ return TemplateParser(TokenStream().fromString(s)).parseTemplate()
+
+def parseTemplateFile(f):
+ return TemplateParser(TokenStream().fromFile(f)).parseTemplate()
diff --git a/indra/lib/python/indra/ipc/llsdhttp.py b/indra/lib/python/indra/ipc/llsdhttp.py
new file mode 100644
index 0000000000..a2a889742a
--- /dev/null
+++ b/indra/lib/python/indra/ipc/llsdhttp.py
@@ -0,0 +1,257 @@
+"""\
+@file llsdhttp.py
+@brief Functions to ease moving llsd over http
+
+Copyright (c) 2006-2007, Linden Research, Inc.
+$License$
+"""
+
+import os.path
+import os
+import os
+import urlparse
+
+from indra.base import llsd
+from indra.ipc import httputil
+LLSD = llsd.LLSD()
+
+
+class ConnectionError(Exception):
+ def __init__(self, method, host, port, path, status, reason, body):
+ self.method = method
+ self.host = host
+ self.port = port
+ self.path = path
+ self.status = status
+ self.reason = reason
+ self.body = body
+ Exception.__init__(self)
+
+ def __str__(self):
+ return "%s(%r, %r, %r, %r, %r, %r, %r)" % (
+ type(self).__name__,
+ self.method, self.host, self.port,
+ self.path, self.status, self.reason, self.body)
+
+
+class NotFound(ConnectionError):
+ pass
+
+class Forbidden(ConnectionError):
+ pass
+
+class MalformedResponse(ConnectionError):
+ pass
+
+class NotImplemented(ConnectionError):
+ pass
+
+def runConnection(connection, raise_parse_errors=True):
+ response = connection.getresponse()
+ if (response.status not in [200, 201, 204]):
+ klass = {404:NotFound,
+ 403:Forbidden,
+ 501:NotImplemented}.get(response.status, ConnectionError)
+ raise klass(
+ connection.method, connection.host, connection.port,
+ connection.path, response.status, response.reason, response.read())
+ content_length = response.getheader('content-length')
+
+ if content_length: # Check to see whether it is not None
+ content_length = int(content_length)
+
+ if content_length: # Check to see whether the length is not 0
+ body = response.read(content_length)
+ else:
+ body = ''
+
+ if not body.strip():
+ #print "No body:", `body`
+ return None
+ try:
+ return LLSD.parse(body)
+ except Exception, e:
+ if raise_parse_errors:
+ print "Exception: %s, Could not parse LLSD: " % (e), body
+ raise MalformedResponse(
+ connection.method, connection.host, connection.port,
+ connection.path, response.status, response.reason, body)
+ else:
+ return None
+
+class FileScheme(object):
+ """Retarded scheme to local file wrapper."""
+ host = '<file>'
+ port = '<file>'
+ reason = '<none>'
+
+ def __init__(self, location):
+ pass
+
+ def request(self, method, fullpath, body='', headers=None):
+ self.status = 200
+ self.path = fullpath.split('?')[0]
+ self.method = method = method.lower()
+ assert method in ('get', 'put', 'delete')
+ if method == 'delete':
+ try:
+ os.remove(self.path)
+ except OSError:
+ pass # don't complain if already deleted
+ elif method == 'put':
+ try:
+ f = file(self.path, 'w')
+ f.write(body)
+ f.close()
+ except IOError, e:
+ self.status = 500
+ self.raise_connection_error()
+ elif method == 'get':
+ if not os.path.exists(self.path):
+ self.status = 404
+ self.raise_connection_error(NotFound)
+
+ def connect(self):
+ pass
+
+ def getresponse(self):
+ return self
+
+ def getheader(self, header):
+ if header == 'content-length':
+ try:
+ return os.path.getsize(self.path)
+ except OSError:
+ return 0
+
+ def read(self, howmuch=None):
+ if self.method == 'get':
+ try:
+ return file(self.path, 'r').read(howmuch)
+ except IOError:
+ self.status = 500
+ self.raise_connection_error()
+ return ''
+
+ def raise_connection_error(self, klass=ConnectionError):
+ raise klass(
+ self.method, self.host, self.port,
+ self.path, self.status, self.reason, '')
+
+scheme_to_factory_map = {
+ 'http': httputil.HttpClient,
+ 'https': httputil.HttpsClient,
+ 'file': FileScheme,
+}
+
+def makeConnection(scheme, location, use_proxy):
+ if use_proxy:
+ if "http_proxy" in os.environ:
+ location = os.environ["http_proxy"]
+ elif "ALL_PROXY" in os.environ:
+ location = os.environ["ALL_PROXY"]
+ else:
+ location = "localhost:3128" #default to local squid
+ if location.startswith("http://"):
+ location = location[len("http://"):]
+ return scheme_to_factory_map[scheme](location)
+
+
+def get(url, headers=None, use_proxy=False):
+ if headers is None:
+ headers = {}
+ scheme, location, path, params, query, id = urlparse.urlparse(url)
+ connection = makeConnection(scheme, location, use_proxy=use_proxy)
+ fullpath = path
+ if query:
+ fullpath += "?"+query
+ connection.connect()
+ if headers is None:
+ headers=dict()
+ if use_proxy:
+ headers.update({ "Host" : location })
+ connection.request("GET", url, headers=headers)
+ else:
+ connection.request("GET", fullpath, headers=headers)
+
+ return runConnection(connection)
+
+def put(url, data, headers=None):
+ body = LLSD.toXML(data)
+ scheme, location, path, params, query, id = urlparse.urlparse(url)
+ connection = makeConnection(scheme, location, use_proxy=False)
+ if headers is None:
+ headers = {}
+ headers.update({'Content-Type': 'application/xml'})
+ fullpath = path
+ if query:
+ fullpath += "?"+query
+ connection.request("PUT", fullpath, body, headers)
+ return runConnection(connection, raise_parse_errors=False)
+
+def delete(url):
+ scheme, location, path, params, query, id = urlparse.urlparse(url)
+ connection = makeConnection(scheme, location, use_proxy=False)
+ connection.request("DELETE", path+"?"+query)
+ return runConnection(connection, raise_parse_errors=False)
+
+def post(url, data=None):
+ body = LLSD.toXML(data)
+ scheme, location, path, params, query, id = urlparse.urlparse(url)
+ connection = makeConnection(scheme, location, use_proxy=False)
+ connection.request("POST", path+"?"+query, body, {'Content-Type': 'application/xml'})
+ return runConnection(connection)
+
+def postFile(url, filename):
+ f = open(filename)
+ body = f.read()
+ f.close()
+ scheme, location, path, params, query, id = urlparse.urlparse(url)
+ connection = makeConnection(scheme, location, use_proxy=False)
+ connection.request("POST", path+"?"+query, body, {'Content-Type': 'application/octet-stream'})
+ return runConnection(connection)
+
+def getStatus(url, use_proxy=False):
+ scheme, location, path, params, query, id = urlparse.urlparse(url)
+ connection = makeConnection(scheme, location, use_proxy=use_proxy)
+ if use_proxy:
+ connection.request("GET", url, headers={ "Host" : location })
+ else:
+ connection.request("GET", path+"?"+query)
+ return connection.getresponse().status
+
+def putStatus(url, data):
+ body = LLSD.toXML(data)
+ scheme, location, path, params, query, id = urlparse.urlparse(url)
+ connection = makeConnection(scheme, location, use_proxy=False)
+ connection.request("PUT", path+"?"+query, body, {'Content-Type': 'application/xml'})
+ return connection.getresponse().status
+
+def deleteStatus(url):
+ scheme, location, path, params, query, id = urlparse.urlparse(url)
+ connection = makeConnection(scheme, location, use_proxy=False)
+ connection.request("DELETE", path+"?"+query)
+ return connection.getresponse().status
+
+def postStatus(url, data):
+ body = LLSD.toXML(data)
+ scheme, location, path, params, query, id = urlparse.urlparse(url)
+ connection = makeConnection(scheme, location, use_proxy=False)
+ connection.request("POST", path+"?"+query, body)
+ return connection.getresponse().status
+
+def postFileStatus(url, filename):
+ f = open(filename)
+ body = f.read()
+ f.close()
+ scheme, location, path, params, query, id = urlparse.urlparse(url)
+ connection = makeConnection(scheme, location, use_proxy=False)
+ connection.request("POST", path+"?"+query, body, {'Content-Type': 'application/octet-stream'})
+ response = connection.getresponse()
+ return response.status, response.read()
+
+def getFromSimulator(path, use_proxy=False):
+ return get('http://' + simulatorHostAndPort + path, use_proxy=use_proxy)
+
+def postToSimulator(path, data=None):
+ return post('http://' + simulatorHostAndPort + path, data)
diff --git a/indra/lib/python/indra/ipc/russ.py b/indra/lib/python/indra/ipc/russ.py
new file mode 100644
index 0000000000..f6f67d4314
--- /dev/null
+++ b/indra/lib/python/indra/ipc/russ.py
@@ -0,0 +1,123 @@
+"""\
+@file russ.py
+@brief Recursive URL Substitution Syntax helpers
+@author Phoenix
+
+Many details on how this should work is available on the wiki:
+https://wiki.secondlife.com/wiki/Recursive_URL_Substitution_Syntax
+
+Adding features to this should be reflected in that page in the
+implementations section.
+
+Copyright (c) 2007, Linden Research, Inc.
+$License$
+"""
+
+import urllib
+from indra.ipc import llsdhttp
+
+class UnbalancedBraces(Exception):
+ pass
+
+class UnknownDirective(Exception):
+ pass
+
+class BadDirective(Exception):
+ pass
+
+def format(format_str, context):
+ """@brief Format format string according to rules for RUSS.
+@see https://osiris.lindenlab.com/mediawiki/index.php/Recursive_URL_Substitution_Syntax
+@param format_str The input string to format.
+@param context A map used for string substitutions.
+@return Returns the formatted string. If no match, the braces remain intact.
+"""
+ while True:
+ #print "format_str:", format_str
+ all_matches = _find_sub_matches(format_str)
+ if not all_matches:
+ break
+ substitutions = 0
+ while True:
+ matches = all_matches.pop()
+ # we work from right to left to make sure we do not
+ # invalidate positions earlier in format_str
+ matches.reverse()
+ for pos in matches:
+ # Use index since _find_sub_matches should have raised
+ # an exception, and failure to find now is an exception.
+ end = format_str.index('}', pos)
+ #print "directive:", format_str[pos+1:pos+5]
+ if format_str[pos + 1] == '$':
+ value = context.get(format_str[pos + 2:end])
+ elif format_str[pos + 1] == '%':
+ value = _build_query_string(
+ context.get(format_str[pos + 2:end]))
+ elif format_str[pos+1:pos+5] == 'http' or format_str[pos+1:pos+5] == 'file':
+ value = _fetch_url_directive(format_str[pos + 1:end])
+ else:
+ raise UnknownDirective, format_str[pos:end + 1]
+ if not value == None:
+ format_str = format_str[:pos]+str(value)+format_str[end+1:]
+ substitutions += 1
+
+ # If there were any substitutions at this depth, re-parse
+ # since this may have revealed new things to substitute
+ if substitutions:
+ break
+ if not all_matches:
+ break
+
+ # If there were no substitutions at all, and we have exhausted
+ # the possible matches, bail.
+ if not substitutions:
+ break
+ return format_str
+
+def _find_sub_matches(format_str):
+ """@brief Find all of the substitution matches.
+@param format_str the RUSS conformant format string.
+@return Returns an array of depths of arrays of positional matches in input.
+"""
+ depth = 0
+ matches = []
+ for pos in range(len(format_str)):
+ if format_str[pos] == '{':
+ depth += 1
+ if not len(matches) == depth:
+ matches.append([])
+ matches[depth - 1].append(pos)
+ continue
+ if format_str[pos] == '}':
+ depth -= 1
+ continue
+ if not depth == 0:
+ raise UnbalancedBraces, format_str
+ return matches
+
+def _build_query_string(query_dict):
+ """\
+ @breif given a dict, return a query string. utility wrapper for urllib.
+ @param query_dict input query dict
+ @returns Returns an urlencoded query string including leading '?'.
+ """
+ if query_dict:
+ return '?' + urllib.urlencode(query_dict)
+ else:
+ return ''
+
+def _fetch_url_directive(directive):
+ "*FIX: This only supports GET"
+ commands = directive.split('|')
+ resource = llsdhttp.get(commands[0])
+ if len(commands) == 3:
+ resource = _walk_resource(resource, commands[2])
+ return resource
+
+def _walk_resource(resource, path):
+ path = path.split('/')
+ for child in path:
+ if not child:
+ continue
+ resource = resource[child]
+ return resource
diff --git a/indra/lib/python/indra/ipc/servicebuilder.py b/indra/lib/python/indra/ipc/servicebuilder.py
new file mode 100644
index 0000000000..d422b2ed42
--- /dev/null
+++ b/indra/lib/python/indra/ipc/servicebuilder.py
@@ -0,0 +1,52 @@
+"""\
+@file servicebuilder.py
+@author Phoenix
+@brief Class which will generate service urls.
+
+Copyright (c) 2007, Linden Research, Inc.
+$License$
+"""
+
+from indra.base import config
+from indra.ipc import llsdhttp
+from indra.ipc import russ
+
+services_config = {}
+try:
+ services_config = llsdhttp.get(config.get('services-config'))
+except:
+ pass
+
+class ServiceBuilder(object):
+ def __init__(self, services_definition = services_config):
+ """\
+ @brief
+ @brief Create a ServiceBuilder.
+ @param services_definition Complete services definition, services.xml.
+ """
+ # no need to keep a copy of the services section of the
+ # complete services definition, but it doesn't hurt much.
+ self.services = services_definition['services']
+ self.builders = {}
+ for service in self.services:
+ service_builder = service.get('service-builder')
+ if not service_builder:
+ continue
+ if isinstance(service_builder, dict):
+ # We will be constructing several builders
+ for name, builder in service_builder.items():
+ full_builder_name = service['name'] + '-' + name
+ self.builders[full_builder_name] = builder
+ else:
+ self.builders[service['name']] = service_builder
+
+ def buildServiceURL(self, name, context):
+ """\
+ @brief given the environment on construction, return a service URL.
+ @param name The name of the service.
+ @param context A dict of name value lookups for the service.
+ @returns Returns the
+ """
+ base_url = config.get('services-base-url')
+ svc_path = russ.format(self.builders[name], context)
+ return base_url + svc_path
diff --git a/indra/lib/python/indra/ipc/tokenstream.py b/indra/lib/python/indra/ipc/tokenstream.py
new file mode 100644
index 0000000000..f757f2f8af
--- /dev/null
+++ b/indra/lib/python/indra/ipc/tokenstream.py
@@ -0,0 +1,134 @@
+"""\
+@file tokenstream.py
+@brief Message template parsing utility class
+
+Copyright (c) 2007, Linden Research, Inc.
+$License$
+"""
+
+import re
+
+class _EOF(object):
+ pass
+
+EOF = _EOF()
+
+class _LineMarker(int):
+ pass
+
+_commentRE = re.compile(r'//.*')
+_symbolRE = re.compile(r'[a-zA-Z_][a-zA-Z_0-9]*')
+_integerRE = re.compile(r'(0x[0-9A-Fa-f]+|0\d*|[1-9]\d*)')
+_floatRE = re.compile(r'\d+(\.\d*)?')
+
+
+class ParseError(Exception):
+ def __init__(self, stream, reason):
+ self.line = stream.line
+ self.context = stream._context()
+ self.reason = reason
+
+ def _contextString(self):
+ c = [ ]
+ for t in self.context:
+ if isinstance(t, _LineMarker):
+ break
+ c.append(t)
+ return " ".join(c)
+
+ def __str__(self):
+ return "line %d: %s @ ... %s" % (
+ self.line, self.reason, self._contextString())
+
+ def __nonzero__(self):
+ return False
+
+
+def _optionText(options):
+ n = len(options)
+ if n == 1:
+ return '"%s"' % options[0]
+ return '"' + '", "'.join(options[0:(n-1)]) + '" or "' + options[-1] + '"'
+
+
+class TokenStream(object):
+ def __init__(self):
+ self.line = 0
+ self.tokens = [ ]
+
+ def fromString(self, string):
+ return self.fromLines(string.split('\n'))
+
+ def fromFile(self, file):
+ return self.fromLines(file)
+
+ def fromLines(self, lines):
+ i = 0
+ for line in lines:
+ i += 1
+ self.tokens.append(_LineMarker(i))
+ self.tokens.extend(_commentRE.sub(" ", line).split())
+ self._consumeLines()
+ return self
+
+ def consume(self):
+ if not self.tokens:
+ return EOF
+ t = self.tokens.pop(0)
+ self._consumeLines()
+ return t
+
+ def _consumeLines(self):
+ while self.tokens and isinstance(self.tokens[0], _LineMarker):
+ self.line = self.tokens.pop(0)
+
+ def peek(self):
+ if not self.tokens:
+ return EOF
+ return self.tokens[0]
+
+ def want(self, t):
+ if t == self.peek():
+ return self.consume()
+ return ParseError(self, 'expected "%s"' % t)
+
+ def wantOneOf(self, options):
+ assert len(options)
+ if self.peek() in options:
+ return self.consume()
+ return ParseError(self, 'expected one of %s' % _optionText(options))
+
+ def wantEOF(self):
+ return self.want(EOF)
+
+ def wantRE(self, re, message=None):
+ t = self.peek()
+ if t != EOF:
+ m = re.match(t)
+ if m and m.end() == len(t):
+ return self.consume()
+ if not message:
+ message = "expected match for r'%s'" % re.pattern
+ return ParseError(self, message)
+
+ def wantSymbol(self):
+ return self.wantRE(_symbolRE, "expected symbol")
+
+ def wantInteger(self):
+ return self.wantRE(_integerRE, "expected integer")
+
+ def wantFloat(self):
+ return self.wantRE(_floatRE, "expected float")
+
+ def _context(self):
+ n = min(5, len(self.tokens))
+ return self.tokens[0:n]
+
+ def require(self, t):
+ if t:
+ return t
+ if isinstance(t, ParseError):
+ raise t
+ else:
+ raise ParseError(self, "unmet requirement")
+
diff --git a/indra/lib/python/indra/ipc/xml_rpc.py b/indra/lib/python/indra/ipc/xml_rpc.py
new file mode 100644
index 0000000000..66375b4ac3
--- /dev/null
+++ b/indra/lib/python/indra/ipc/xml_rpc.py
@@ -0,0 +1,253 @@
+"""\
+@file xml_rpc.py
+@brief An implementation of a parser/generator for the XML-RPC xml format.
+
+Copyright (c) 2006-2007, Linden Research, Inc.
+$License$
+"""
+
+
+from greenlet import greenlet
+
+from mulib import mu
+
+from xml.sax import handler
+from xml.sax import parseString
+
+
+# States
+class Expected(object):
+ def __init__(self, tag):
+ self.tag = tag
+
+ def __getattr__(self, name):
+ return type(self)(name)
+
+ def __repr__(self):
+ return '%s(%r)' % (
+ type(self).__name__, self.tag)
+
+
+class START(Expected):
+ pass
+
+
+class END(Expected):
+ pass
+
+
+class STR(object):
+ tag = ''
+
+
+START = START('')
+END = END('')
+
+
+class Malformed(Exception):
+ pass
+
+
+class XMLParser(handler.ContentHandler):
+ def __init__(self, state_machine, next_states):
+ handler.ContentHandler.__init__(self)
+ self.state_machine = state_machine
+ if not isinstance(next_states, tuple):
+ next_states = (next_states, )
+ self.next_states = next_states
+ self._character_buffer = ''
+
+ def assertState(self, state, name, *rest):
+ if not isinstance(self.next_states, tuple):
+ self.next_states = (self.next_states, )
+ for next in self.next_states:
+ if type(state) == type(next):
+ if next.tag and next.tag != name:
+ raise Malformed(
+ "Expected %s, got %s %s %s" % (
+ next, state, name, rest))
+ break
+ else:
+ raise Malformed(
+ "Expected %s, got %s %s %s" % (
+ self.next_states, state, name, rest))
+
+ def startElement(self, name, attrs):
+ self.assertState(START, name.lower(), attrs)
+ self.next_states = self.state_machine.switch(START, (name.lower(), dict(attrs)))
+
+ def endElement(self, name):
+ if self._character_buffer.strip():
+ characters = self._character_buffer.strip()
+ self._character_buffer = ''
+ self.assertState(STR, characters)
+ self.next_states = self.state_machine.switch(characters)
+ self.assertState(END, name.lower())
+ self.next_states = self.state_machine.switch(END, name.lower())
+
+ def error(self, exc):
+ self.bozo = 1
+ self.exc = exc
+
+ def fatalError(self, exc):
+ self.error(exc)
+ raise exc
+
+ def characters(self, characters):
+ self._character_buffer += characters
+
+
+def parse(what):
+ child = greenlet(xml_rpc)
+ me = greenlet.getcurrent()
+ startup_states = child.switch(me)
+ parser = XMLParser(child, startup_states)
+ try:
+ parseString(what, parser)
+ except Malformed:
+ print what
+ raise
+ return child.switch()
+
+
+def xml_rpc(yielder):
+ yielder.switch(START.methodcall)
+ yielder.switch(START.methodname)
+ methodName = yielder.switch(STR)
+ yielder.switch(END.methodname)
+
+ yielder.switch(START.params)
+
+ root = None
+ params = []
+ while True:
+ state, _ = yielder.switch(START.param, END.params)
+ if state == END:
+ break
+
+ yielder.switch(START.value)
+
+ params.append(
+ handle(yielder))
+
+ yielder.switch(END.value)
+ yielder.switch(END.param)
+
+ yielder.switch(END.methodcall)
+ ## Resume parse
+ yielder.switch()
+ ## Return result to parse
+ return methodName.strip(), params
+
+
+def handle(yielder):
+ _, (tag, attrs) = yielder.switch(START)
+ if tag in ['int', 'i4']:
+ result = int(yielder.switch(STR))
+ elif tag == 'boolean':
+ result = bool(int(yielder.switch(STR)))
+ elif tag == 'string':
+ result = yielder.switch(STR)
+ elif tag == 'double':
+ result = float(yielder.switch(STR))
+ elif tag == 'datetime.iso8601':
+ result = yielder.switch(STR)
+ elif tag == 'base64':
+ result = base64.b64decode(yielder.switch(STR))
+ elif tag == 'struct':
+ result = {}
+ while True:
+ state, _ = yielder.switch(START.member, END.struct)
+ if state == END:
+ break
+
+ yielder.switch(START.name)
+ key = yielder.switch(STR)
+ yielder.switch(END.name)
+
+ yielder.switch(START.value)
+ result[key] = handle(yielder)
+ yielder.switch(END.value)
+
+ yielder.switch(END.member)
+ ## We already handled </struct> above, don't want to handle it below
+ return result
+ elif tag == 'array':
+ result = []
+ yielder.switch(START.data)
+ while True:
+ state, _ = yielder.switch(START.value, END.data)
+ if state == END:
+ break
+
+ result.append(handle(yielder))
+
+ yielder.switch(END.value)
+
+ yielder.switch(getattr(END, tag))
+
+ return result
+
+
+VALUE = mu.tagFactory('value')
+BOOLEAN = mu.tagFactory('boolean')
+INT = mu.tagFactory('int')
+STRUCT = mu.tagFactory('struct')
+MEMBER = mu.tagFactory('member')
+NAME = mu.tagFactory('name')
+ARRAY = mu.tagFactory('array')
+DATA = mu.tagFactory('data')
+STRING = mu.tagFactory('string')
+DOUBLE = mu.tagFactory('double')
+METHODRESPONSE = mu.tagFactory('methodResponse')
+PARAMS = mu.tagFactory('params')
+PARAM = mu.tagFactory('param')
+
+mu.inline_elements['string'] = True
+mu.inline_elements['boolean'] = True
+mu.inline_elements['name'] = True
+
+
+def _generate(something):
+ if isinstance(something, dict):
+ result = STRUCT()
+ for key, value in something.items():
+ result[
+ MEMBER[
+ NAME[key], _generate(value)]]
+ return VALUE[result]
+ elif isinstance(something, list):
+ result = DATA()
+ for item in something:
+ result[_generate(item)]
+ return VALUE[ARRAY[[result]]]
+ elif isinstance(something, basestring):
+ return VALUE[STRING[something]]
+ elif isinstance(something, bool):
+ if something:
+ return VALUE[BOOLEAN['1']]
+ return VALUE[BOOLEAN['0']]
+ elif isinstance(something, int):
+ return VALUE[INT[something]]
+ elif isinstance(something, float):
+ return VALUE[DOUBLE[something]]
+
+def generate(*args):
+ params = PARAMS()
+ for arg in args:
+ params[PARAM[_generate(arg)]]
+ return METHODRESPONSE[params]
+
+
+if __name__ == '__main__':
+ print parse("""<?xml version="1.0"?> <methodCall> <methodName>examples.getStateName</methodName> <params> <param> <value><i4>41</i4></value> </param> </params> </methodCall>
+""")
+
+
+
+
+
+
+
+
+
diff --git a/indra/lib/python/indra/util/__init__.py b/indra/lib/python/indra/util/__init__.py
new file mode 100644
index 0000000000..c8b1848dfb
--- /dev/null
+++ b/indra/lib/python/indra/util/__init__.py
@@ -0,0 +1,7 @@
+"""\
+@file __init__.py
+@brief Initialization file for the indra util module.
+
+Copyright (c) 2006-2007, Linden Research, Inc.
+$License$
+"""
diff --git a/indra/lib/python/indra/util/helpformatter.py b/indra/lib/python/indra/util/helpformatter.py
new file mode 100644
index 0000000000..539669f665
--- /dev/null
+++ b/indra/lib/python/indra/util/helpformatter.py
@@ -0,0 +1,32 @@
+"""\
+@file helpformatter.py
+@author Phoenix
+@brief Class for formatting optparse descriptions.
+
+Copyright (c) 2007, Linden Research, Inc.
+$License$
+"""
+
+import optparse
+import textwrap
+
+class Formatter(optparse.IndentedHelpFormatter):
+ def __init__(
+ self,
+ p_indentIncrement = 2,
+ p_maxHelpPosition = 24,
+ p_width = 79,
+ p_shortFirst = 1) :
+ optparse.HelpFormatter.__init__(
+ self,
+ p_indentIncrement,
+ p_maxHelpPosition,
+ p_width,
+ p_shortFirst)
+ def format_description(self, p_description):
+ t_descWidth = self.width - self.current_indent
+ t_indent = " " * (self.current_indent + 2)
+ return "\n".join(
+ [textwrap.fill(descr, t_descWidth, initial_indent = t_indent,
+ subsequent_indent = t_indent)
+ for descr in p_description.split("\n")] )
diff --git a/indra/lib/python/indra/util/llmanifest.py b/indra/lib/python/indra/util/llmanifest.py
new file mode 100644
index 0000000000..5ab4a892a7
--- /dev/null
+++ b/indra/lib/python/indra/util/llmanifest.py
@@ -0,0 +1,562 @@
+#!/usr/bin/python
+"""\
+@file llmanifest.py
+@author Ryan Williams
+@brief Library for specifying operations on a set of files.
+
+Copyright (c) 2007, Linden Research, Inc.
+$License$
+"""
+
+import commands
+import filecmp
+import fnmatch
+import getopt
+import glob
+import os
+import os.path
+import re
+import shutil
+import sys
+import tarfile
+
+def path_ancestors(path):
+ path = os.path.normpath(path)
+ result = []
+ while len(path) > 0:
+ result.append(path)
+ path, sub = os.path.split(path)
+ return result
+
+def proper_windows_path(path, current_platform = sys.platform):
+ """ This function takes an absolute Windows or Cygwin path and
+ returns a path appropriately formatted for the platform it's
+ running on (as determined by sys.platform)"""
+ path = path.strip()
+ drive_letter = None
+ rel = None
+ match = re.match("/cygdrive/([a-z])/(.*)", path)
+ if(not match):
+ match = re.match('([a-zA-Z]):\\\(.*)', path)
+ if(not match):
+ return None # not an absolute path
+ drive_letter = match.group(1)
+ rel = match.group(2)
+ if(current_platform == "cygwin"):
+ return "/cygdrive/" + drive_letter.lower() + '/' + rel.replace('\\', '/')
+ else:
+ return drive_letter.upper() + ':\\' + rel.replace('/', '\\')
+
+def get_default_platform(dummy):
+ return {'linux2':'linux',
+ 'linux1':'linux',
+ 'cygwin':'windows',
+ 'win32':'windows',
+ 'darwin':'darwin'
+ }[sys.platform]
+
+def get_default_version(srctree):
+ # look up llversion.h and parse out the version info
+ paths = [os.path.join(srctree, x, 'llversion.h') for x in ['llcommon', '../llcommon', '../../indra/llcommon.h']]
+ for p in paths:
+ if os.path.exists(p):
+ contents = open(p, 'r').read()
+ major = re.search("LL_VERSION_MAJOR\s=\s([0-9]+)", contents).group(1)
+ minor = re.search("LL_VERSION_MINOR\s=\s([0-9]+)", contents).group(1)
+ patch = re.search("LL_VERSION_PATCH\s=\s([0-9]+)", contents).group(1)
+ build = re.search("LL_VERSION_BUILD\s=\s([0-9]+)", contents).group(1)
+ return major, minor, patch, build
+
+DEFAULT_CHANNEL = 'Second Life Release'
+
+ARGUMENTS=[
+ dict(name='actions',
+ description="""This argument specifies the actions that are to be taken when the
+ script is run. The meaningful actions are currently:
+ copy - copies the files specified by the manifest into the
+ destination directory.
+ package - bundles up the files in the destination directory into
+ an installer for the current platform
+ unpacked - bundles up the files in the destination directory into
+ a simple tarball
+ Example use: %(name)s --actions="copy unpacked" """,
+ default="copy package"),
+ dict(name='arch',
+ description="""This argument is appended to the platform string for
+ determining which manifest class to run.
+ Example use: %(name)s --arch=i686
+ On Linux this would try to use Linux_i686Manifest.""",
+ default=""),
+ dict(name='configuration',
+ description="""The build configuration used. Only used on OS X for
+ now, but it could be used for other platforms as well.""",
+ default="Universal"),
+ dict(name='grid',
+ description="""Which grid the client will try to connect to. Even
+ though it's not strictly a grid, 'firstlook' is also an acceptable
+ value for this parameter.""",
+ default=""),
+ dict(name='channel',
+ description="""The channel to use for updates.""",
+ default=DEFAULT_CHANNEL),
+ dict(name='installer_name',
+ description=""" The name of the file that the installer should be
+ packaged up into. Only used on Linux at the moment.""",
+ default=None),
+ dict(name='login_url',
+ description="""The url that the login screen displays in the client.""",
+ default=None),
+ dict(name='platform',
+ description="""The current platform, to be used for looking up which
+ manifest class to run.""",
+ default=get_default_platform),
+ dict(name='version',
+ description="""This specifies the version of Second Life that is
+ being packaged up.""",
+ default=get_default_version)
+ ]
+
+def usage(srctree=""):
+ nd = {'name':sys.argv[0]}
+ print """Usage:
+ %(name)s [options] [destdir]
+ Options:
+ """ % nd
+ for arg in ARGUMENTS:
+ default = arg['default']
+ if hasattr(default, '__call__'):
+ default = "(computed value) \"" + str(default(srctree)) + '"'
+ elif default is not None:
+ default = '"' + default + '"'
+ print "\t--%s Default: %s\n\t%s\n" % (
+ arg['name'],
+ default,
+ arg['description'] % nd)
+
+def main(argv=None, srctree='.', dsttree='./dst'):
+ if(argv == None):
+ argv = sys.argv
+
+ option_names = [arg['name'] + '=' for arg in ARGUMENTS]
+ option_names.append('help')
+ options, remainder = getopt.getopt(argv[1:], "", option_names)
+ if len(remainder) >= 1:
+ dsttree = remainder[0]
+
+ print "Source tree:", srctree
+ print "Destination tree:", dsttree
+
+ # convert options to a hash
+ args = {}
+ for opt in options:
+ args[opt[0].replace("--", "")] = opt[1]
+
+ # early out for help
+ if args.has_key('help'):
+ # *TODO: it is a huge hack to pass around the srctree like this
+ usage(srctree)
+ return
+
+ # defaults
+ for arg in ARGUMENTS:
+ if not args.has_key(arg['name']):
+ default = arg['default']
+ if hasattr(default, '__call__'):
+ default = default(srctree)
+ if default is not None:
+ args[arg['name']] = default
+
+ # fix up version
+ if args.has_key('version') and type(args['version']) == str:
+ args['version'] = args['version'].split('.')
+
+ # default and agni are default
+ if args['grid'] in ['default', 'agni']:
+ args['grid'] = ''
+
+ if args.has_key('actions'):
+ args['actions'] = args['actions'].split()
+
+ # debugging
+ for opt in args:
+ print "Option:", opt, "=", args[opt]
+
+ wm = LLManifest.for_platform(args['platform'], args.get('arch'))(srctree, dsttree, args)
+ wm.do(*args['actions'])
+ return 0
+
+class LLManifestRegistry(type):
+ def __init__(cls, name, bases, dct):
+ super(LLManifestRegistry, cls).__init__(name, bases, dct)
+ match = re.match("(\w+)Manifest", name)
+ if(match):
+ cls.manifests[match.group(1).lower()] = cls
+
+class LLManifest(object):
+ __metaclass__ = LLManifestRegistry
+ manifests = {}
+ def for_platform(self, platform, arch = None):
+ if arch:
+ platform = platform + '_' + arch
+ return self.manifests[platform.lower()]
+ for_platform = classmethod(for_platform)
+
+ def __init__(self, srctree, dsttree, args):
+ super(LLManifest, self).__init__()
+ self.args = args
+ self.file_list = []
+ self.excludes = []
+ self.actions = []
+ self.src_prefix = [srctree]
+ self.dst_prefix = [dsttree]
+ self.created_paths = []
+
+ def default_grid(self):
+ return self.args.get('grid', None) == ''
+ def default_channel(self):
+ return self.args.get('channel', None) == DEFAULT_CHANNEL
+
+ def construct(self):
+ """ Meant to be overriden by LLManifest implementors with code that
+ constructs the complete destination hierarchy."""
+ pass # override this method
+
+ def exclude(self, glob):
+ """ Excludes all files that match the glob from being included
+ in the file list by path()."""
+ self.excludes.append(glob)
+
+ def prefix(self, src='', dst=None):
+ """ Pushes a prefix onto the stack. Until end_prefix is
+ called, all relevant method calls (esp. to path()) will prefix
+ paths with the entire prefix stack. Source and destination
+ prefixes can be different, though if only one is provided they
+ are both equal. To specify a no-op, use an empty string, not
+ None."""
+ if(dst == None):
+ dst = src
+ self.src_prefix.append(src)
+ self.dst_prefix.append(dst)
+ return True # so that you can wrap it in an if to get indentation
+
+ def end_prefix(self, descr=None):
+ """Pops a prefix off the stack. If given an argument, checks
+ the argument against the top of the stack. If the argument
+ matches neither the source or destination prefixes at the top
+ of the stack, then misnesting must have occurred and an
+ exception is raised."""
+ # as an error-prevention mechanism, check the prefix and see if it matches the source or destination prefix. If not, improper nesting may have occurred.
+ src = self.src_prefix.pop()
+ dst = self.dst_prefix.pop()
+ if descr and not(src == descr or dst == descr):
+ raise ValueError, "End prefix '" + descr + "' didn't match '" +src+ "' or '" +dst + "'"
+
+ def get_src_prefix(self):
+ """ Returns the current source prefix."""
+ return os.path.join(*self.src_prefix)
+
+ def get_dst_prefix(self):
+ """ Returns the current destination prefix."""
+ return os.path.join(*self.dst_prefix)
+
+ def src_path_of(self, relpath):
+ """Returns the full path to a file or directory specified
+ relative to the source directory."""
+ return os.path.join(self.get_src_prefix(), relpath)
+
+ def dst_path_of(self, relpath):
+ """Returns the full path to a file or directory specified
+ relative to the destination directory."""
+ return os.path.join(self.get_dst_prefix(), relpath)
+
+ def ensure_src_dir(self, reldir):
+ """Construct the path for a directory relative to the
+ source path, and ensures that it exists. Returns the
+ full path."""
+ path = os.path.join(self.get_src_prefix(), reldir)
+ self.cmakedirs(path)
+ return path
+
+ def ensure_dst_dir(self, reldir):
+ """Construct the path for a directory relative to the
+ destination path, and ensures that it exists. Returns the
+ full path."""
+ path = os.path.join(self.get_dst_prefix(), reldir)
+ self.cmakedirs(path)
+ return path
+
+ def run_command(self, command):
+ """ Runs an external command, and returns the output. Raises
+ an exception if the command reurns a nonzero status code. For
+ debugging/informational purpoases, prints out the command's
+ output as it is received."""
+ print "Running command:", command
+ fd = os.popen(command, 'r')
+ lines = []
+ while True:
+ lines.append(fd.readline())
+ if(lines[-1] == ''):
+ break
+ else:
+ print lines[-1],
+ output = ''.join(lines)
+ status = fd.close()
+ if(status):
+ raise RuntimeError, "Command " + command + " returned non-zero status (" + str(status) + ")"
+ return output
+
+ def created_path(self, path):
+ """ Declare that you've created a path in order to
+ a) verify that you really have created it
+ b) schedule it for cleanup"""
+ if not os.path.exists(path):
+ raise RuntimeError, "Should be something at path " + path
+ self.created_paths.append(path)
+
+ def put_in_file(self, contents, dst):
+ # write contents as dst
+ f = open(self.dst_path_of(dst), "wb")
+ f.write(contents)
+ f.close()
+
+ def replace_in(self, src, dst=None, searchdict={}):
+ if(dst == None):
+ dst = src
+ # read src
+ f = open(self.src_path_of(src), "rbU")
+ contents = f.read()
+ f.close()
+ # apply dict replacements
+ for old, new in searchdict.iteritems():
+ contents = contents.replace(old, new)
+ self.put_in_file(contents, dst)
+ self.created_paths.append(dst)
+
+ def copy_action(self, src, dst):
+ if(src and (os.path.exists(src) or os.path.islink(src))):
+ # ensure that destination path exists
+ self.cmakedirs(os.path.dirname(dst))
+ self.created_paths.append(dst)
+ if(not os.path.isdir(src)):
+ self.ccopy(src,dst)
+ else:
+ # src is a dir
+ self.ccopytree(src,dst)
+ else:
+ print "Doesn't exist:", src
+
+ def package_action(self, src, dst):
+ pass
+
+ def copy_finish(self):
+ pass
+
+ def package_finish(self):
+ pass
+
+ def unpacked_finish(self):
+ unpacked_file_name = "unpacked_%(plat)s_%(vers)s.tar" % {
+ 'plat':self.args['platform'],
+ 'vers':'_'.join(self.args['version'])}
+ print "Creating unpacked file:", unpacked_file_name
+ # could add a gz here but that doubles the time it takes to do this step
+ tf = tarfile.open(self.src_path_of(unpacked_file_name), 'w:')
+ # add the entire installation package, at the very top level
+ tf.add(self.get_dst_prefix(), "")
+ tf.close()
+
+ def cleanup_finish(self):
+ """ Delete paths that were specified to have been created by this script"""
+ for c in self.created_paths:
+ # *TODO is this gonna be useful?
+ print "Cleaning up " + c
+
+ def process_file(self, src, dst):
+ if(self.includes(src, dst)):
+# print src, "=>", dst
+ for action in self.actions:
+ methodname = action + "_action"
+ method = getattr(self, methodname, None)
+ if method is not None:
+ method(src, dst)
+ self.file_list.append([src, dst])
+ else:
+ print "Excluding: ", src, dst
+
+
+ def process_directory(self, src, dst):
+ if(not self.includes(src, dst)):
+ print "Excluding: ", src, dst
+ return
+ names = os.listdir(src)
+ self.cmakedirs(dst)
+ errors = []
+ for name in names:
+ srcname = os.path.join(src, name)
+ dstname = os.path.join(dst, name)
+ if os.path.isdir(srcname):
+ self.process_directory(srcname, dstname)
+ else:
+ self.process_file(srcname, dstname)
+
+
+
+ def includes(self, src, dst):
+ if src:
+ for excl in self.excludes:
+ if fnmatch.fnmatch(src, excl):
+ return False
+ return True
+
+ def remove(self, *paths):
+ for path in paths:
+ if(os.path.exists(path)):
+ print "Removing path", path
+ if(os.path.isdir(path)):
+ shutil.rmtree(path)
+ else:
+ os.remove(path)
+
+ def ccopy(self, src, dst):
+ """ Copy a single file or symlink. Uses filecmp to skip copying for existing files."""
+ if os.path.islink(src):
+ linkto = os.readlink(src)
+ if(os.path.islink(dst) or os.path.exists(dst)):
+ os.remove(dst) # because symlinking over an existing link fails
+ os.symlink(linkto, dst)
+ else:
+ # Don't recopy file if it's up-to-date.
+ # If we seem to be not not overwriting files that have been
+ # updated, set the last arg to False, but it will take longer.
+ if(os.path.exists(dst) and filecmp.cmp(src, dst, True)):
+ return
+ # only copy if it's not excluded
+ if(self.includes(src, dst)):
+ shutil.copy2(src, dst)
+
+ def ccopytree(self, src, dst):
+ """Direct copy of shutil.copytree with the additional
+ feature that the destination directory can exist. It
+ is so dumb that Python doesn't come with this. Also it
+ implements the excludes functionality."""
+ if(not self.includes(src, dst)):
+ return
+ names = os.listdir(src)
+ self.cmakedirs(dst)
+ errors = []
+ for name in names:
+ srcname = os.path.join(src, name)
+ dstname = os.path.join(dst, name)
+ try:
+ if os.path.isdir(srcname):
+ self.ccopytree(srcname, dstname)
+ else:
+ self.ccopy(srcname, dstname)
+ # XXX What about devices, sockets etc.?
+ except (IOError, os.error), why:
+ errors.append((srcname, dstname, why))
+ if errors:
+ raise RuntimeError, errors
+
+
+ def cmakedirs(self, path):
+ """Ensures that a directory exists, and doesn't throw an exception
+ if you call it on an existing directory."""
+# print "making path: ", path
+ path = os.path.normpath(path)
+ self.created_paths.append(path)
+ if not os.path.exists(path):
+ os.makedirs(path)
+
+ def find_existing_file(self, *list):
+ for f in list:
+ if(os.path.exists(f)):
+ return f
+ # didn't find it, return last item in list
+ if len(list) > 0:
+ return list[-1]
+ else:
+ return None
+
+ def contents_of_tar(self, src_tar, dst_dir):
+ """ Extracts the contents of the tarfile (specified
+ relative to the source prefix) into the directory
+ specified relative to the destination directory."""
+ self.check_file_exists(src_tar)
+ tf = tarfile.open(self.src_path_of(src_tar), 'r')
+ for member in tf.getmembers():
+ tf.extract(member, self.ensure_dst_dir(dst_dir))
+ # TODO get actions working on these dudes, perhaps we should extract to a temporary directory and then process_directory on it?
+ self.file_list.append([src_tar,
+ self.dst_path_of(os.path.join(dst_dir,member.name))])
+ tf.close()
+
+
+ def wildcard_regex(self, src_glob, dst_glob):
+ # print "regex_pair:", src_glob, dst_glob
+ src_re = re.escape(src_glob)
+ src_re = src_re.replace('\*', '([-a-zA-Z0-9._ ]+)')
+ dst_temp = dst_glob
+ i = 1
+ while(dst_temp.count("*") > 0):
+ dst_temp = dst_temp.replace('*', '\g<' + str(i) + '>', 1)
+ i = i+1
+ # print "regex_result:", src_re, dst_temp
+ return re.compile(src_re), dst_temp
+
+ def check_file_exists(self, path):
+ if(not os.path.exists(path) and not os.path.islink(path)):
+ raise RuntimeError("Path %s doesn't exist" % (
+ os.path.normpath(os.path.join(os.getcwd(), path)),))
+
+
+ wildcard_pattern = re.compile('\*')
+ def expand_globs(self, src, dst):
+ def fw_slash(str):
+ return str.replace('\\', '/')
+ def os_slash(str):
+ return str.replace('/', os.path.sep)
+ dst = fw_slash(dst)
+ src = fw_slash(src)
+ src_list = glob.glob(src)
+ src_re, d_template = self.wildcard_regex(src, dst)
+ for s in src_list:
+ s = fw_slash(s)
+ d = src_re.sub(d_template, s)
+ #print "s:",s, "d_t", d_template, "dst", dst, "d", d
+ yield os_slash(s), os_slash(d)
+
+ def path(self, src, dst=None):
+ print "Processing", src, "=>", dst
+ if src == None:
+ raise RuntimeError("No source file, dst is " + dst)
+ if dst == None:
+ dst = src
+ dst = os.path.join(self.get_dst_prefix(), dst)
+ src = os.path.join(self.get_src_prefix(), src)
+
+ # expand globs
+ if(self.wildcard_pattern.search(src)):
+ for s,d in self.expand_globs(src, dst):
+ self.process_file(s, d)
+ else:
+ # if we're specifying a single path (not a glob),
+ # we should error out if it doesn't exist
+ self.check_file_exists(src)
+ # if it's a directory, recurse through it
+ if(os.path.isdir(src)):
+ self.process_directory(src, dst)
+ else:
+ self.process_file(src, dst)
+
+
+ def do(self, *actions):
+ self.actions = actions
+ self.construct()
+ # perform finish actions
+ for action in self.actions:
+ methodname = action + "_finish"
+ method = getattr(self, methodname, None)
+ if method is not None:
+ method()
+ return self.file_list