russ.py

Go to the documentation of this file.
00001 """\
00002 @file russ.py
00003 @brief Recursive URL Substitution Syntax helpers
00004 @author Phoenix
00005 
00006 Many details on how this should work is available on the wiki:
00007 https://wiki.secondlife.com/wiki/Recursive_URL_Substitution_Syntax
00008 
00009 Adding features to this should be reflected in that page in the
00010 implementations section.
00011 
00012 $LicenseInfo:firstyear=2007&license=mit$
00013 
00014 Copyright (c) 2007-2008, Linden Research, Inc.
00015 
00016 Permission is hereby granted, free of charge, to any person obtaining a copy
00017 of this software and associated documentation files (the "Software"), to deal
00018 in the Software without restriction, including without limitation the rights
00019 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
00020 copies of the Software, and to permit persons to whom the Software is
00021 furnished to do so, subject to the following conditions:
00022 
00023 The above copyright notice and this permission notice shall be included in
00024 all copies or substantial portions of the Software.
00025 
00026 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
00027 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
00028 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
00029 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
00030 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
00031 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
00032 THE SOFTWARE.
00033 $/LicenseInfo$
00034 """
00035 
00036 import urllib
00037 from indra.ipc import llsdhttp
00038 
00039 class UnbalancedBraces(Exception):
00040     pass
00041 
00042 class UnknownDirective(Exception):
00043     pass
00044 
00045 class BadDirective(Exception):
00046     pass
00047 
00048 def format_value_for_path(value):
00049     if type(value) in [list, tuple]:
00050         # *NOTE: treat lists as unquoted path components so that the quoting
00051         # doesn't get out-of-hand.  This is a workaround for the fact that
00052         # russ always quotes, even if the data it's given is already quoted,
00053         # and it's not safe to simply unquote a path directly, so if we want
00054         # russ to substitute urls parts inside other url parts we always
00055         # have to do so via lists of unquoted path components.
00056         return '/'.join([urllib.quote(str(item)) for item in value])
00057     else:
00058         return urllib.quote(str(value))
00059 
00060 def format(format_str, context):
00061     """@brief Format format string according to rules for RUSS.
00062 @see https://osiris.lindenlab.com/mediawiki/index.php/Recursive_URL_Substitution_Syntax
00063 @param format_str The input string to format.
00064 @param context A map used for string substitutions.
00065 @return Returns the formatted string. If no match, the braces remain intact.
00066 """
00067     while True:
00068         #print "format_str:", format_str
00069         all_matches = _find_sub_matches(format_str)
00070         if not all_matches:
00071             break
00072         substitutions = 0
00073         while True:
00074             matches = all_matches.pop()
00075             # we work from right to left to make sure we do not
00076             # invalidate positions earlier in format_str
00077             matches.reverse()
00078             for pos in matches:
00079                 # Use index since _find_sub_matches should have raised
00080                 # an exception, and failure to find now is an exception.
00081                 end = format_str.index('}', pos)
00082                 #print "directive:", format_str[pos+1:pos+5]
00083                 if format_str[pos + 1] == '$':
00084                     value = context[format_str[pos + 2:end]]
00085                     if value is not None:
00086                         value = format_value_for_path(value)
00087                 elif format_str[pos + 1] == '%':
00088                     value = _build_query_string(
00089                         context.get(format_str[pos + 2:end]))
00090                 elif format_str[pos+1:pos+5] == 'http' or format_str[pos+1:pos+5] == 'file':
00091                     value = _fetch_url_directive(format_str[pos + 1:end])
00092                 else:
00093                     raise UnknownDirective, format_str[pos:end + 1]
00094                 if value is not None:
00095                     format_str = format_str[:pos]+str(value)+format_str[end+1:]
00096                     substitutions += 1
00097 
00098             # If there were any substitutions at this depth, re-parse
00099             # since this may have revealed new things to substitute
00100             if substitutions:
00101                 break
00102             if not all_matches:
00103                 break
00104 
00105         # If there were no substitutions at all, and we have exhausted
00106         # the possible matches, bail.
00107         if not substitutions:
00108             break
00109     return format_str
00110 
00111 def _find_sub_matches(format_str):
00112     """@brief Find all of the substitution matches.
00113 @param format_str the RUSS conformant format string.    
00114 @return Returns an array of depths of arrays of positional matches in input.
00115 """
00116     depth = 0
00117     matches = []
00118     for pos in range(len(format_str)):
00119         if format_str[pos] == '{':
00120             depth += 1
00121             if not len(matches) == depth:
00122                 matches.append([])
00123             matches[depth - 1].append(pos)
00124             continue
00125         if format_str[pos] == '}':
00126             depth -= 1
00127             continue
00128     if not depth == 0:
00129         raise UnbalancedBraces, format_str
00130     return matches
00131 
00132 def _build_query_string(query_dict):
00133     """\
00134     @breif given a dict, return a query string. utility wrapper for urllib.
00135     @param query_dict input query dict
00136     @returns Returns an urlencoded query string including leading '?'.
00137     """
00138     if query_dict:
00139         keys = query_dict.keys()
00140         keys.sort()
00141         def stringize(value):
00142             if type(value) in (str,unicode):
00143                 return value
00144             else:
00145                 return str(value)
00146         query_list = [urllib.quote(str(key)) + '=' + urllib.quote(stringize(query_dict[key])) for key in keys]
00147         return '?' + '&'.join(query_list)
00148     else:
00149         return ''
00150 
00151 def _fetch_url_directive(directive):
00152     "*FIX: This only supports GET"
00153     commands = directive.split('|')
00154     resource = llsdhttp.get(commands[0])
00155     if len(commands) == 3:
00156         resource = _walk_resource(resource, commands[2])
00157     return resource
00158 
00159 def _walk_resource(resource, path):
00160     path = path.split('/')
00161     for child in path:
00162         if not child:
00163             continue
00164         resource = resource[child]
00165     return resource

Generated on Fri May 16 08:31:53 2008 for SecondLife by  doxygen 1.5.5