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
00051
00052
00053
00054
00055
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
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
00076
00077 matches.reverse()
00078 for pos in matches:
00079
00080
00081 end = format_str.index('}', pos)
00082
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
00099
00100 if substitutions:
00101 break
00102 if not all_matches:
00103 break
00104
00105
00106
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