Annotation of /linden_release/linden/indra/lib/python/indra/ipc/russ.py
Parent Directory
|
Revision Log
Revision 58 - (view) (download) (as text)
| 1 : | mjm | 57 | """\ |
| 2 : | @file russ.py | ||
| 3 : | @brief Recursive URL Substitution Syntax helpers | ||
| 4 : | @author Phoenix | ||
| 5 : | |||
| 6 : | Many details on how this should work is available on the wiki: | ||
| 7 : | https://wiki.secondlife.com/wiki/Recursive_URL_Substitution_Syntax | ||
| 8 : | |||
| 9 : | Adding features to this should be reflected in that page in the | ||
| 10 : | implementations section. | ||
| 11 : | |||
| 12 : | $LicenseInfo:firstyear=2007&license=mit$ | ||
| 13 : | |||
| 14 : | Copyright (c) 2007-2008, Linden Research, Inc. | ||
| 15 : | |||
| 16 : | Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| 17 : | of this software and associated documentation files (the "Software"), to deal | ||
| 18 : | in the Software without restriction, including without limitation the rights | ||
| 19 : | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
| 20 : | copies of the Software, and to permit persons to whom the Software is | ||
| 21 : | furnished to do so, subject to the following conditions: | ||
| 22 : | |||
| 23 : | The above copyright notice and this permission notice shall be included in | ||
| 24 : | all copies or substantial portions of the Software. | ||
| 25 : | |||
| 26 : | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| 27 : | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| 28 : | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| 29 : | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| 30 : | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| 31 : | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
| 32 : | THE SOFTWARE. | ||
| 33 : | $/LicenseInfo$ | ||
| 34 : | """ | ||
| 35 : | |||
| 36 : | import urllib | ||
| 37 : | from indra.ipc import llsdhttp | ||
| 38 : | |||
| 39 : | class UnbalancedBraces(Exception): | ||
| 40 : | pass | ||
| 41 : | |||
| 42 : | class UnknownDirective(Exception): | ||
| 43 : | pass | ||
| 44 : | |||
| 45 : | class BadDirective(Exception): | ||
| 46 : | pass | ||
| 47 : | |||
| 48 : | def format_value_for_path(value): | ||
| 49 : | if type(value) in [list, tuple]: | ||
| 50 : | # *NOTE: treat lists as unquoted path components so that the quoting | ||
| 51 : | # doesn't get out-of-hand. This is a workaround for the fact that | ||
| 52 : | # russ always quotes, even if the data it's given is already quoted, | ||
| 53 : | # and it's not safe to simply unquote a path directly, so if we want | ||
| 54 : | # russ to substitute urls parts inside other url parts we always | ||
| 55 : | # have to do so via lists of unquoted path components. | ||
| 56 : | return '/'.join([urllib.quote(str(item)) for item in value]) | ||
| 57 : | else: | ||
| 58 : | return urllib.quote(str(value)) | ||
| 59 : | |||
| 60 : | def format(format_str, context): | ||
| 61 : | """@brief Format format string according to rules for RUSS. | ||
| 62 : | @see https://osiris.lindenlab.com/mediawiki/index.php/Recursive_URL_Substitution_Syntax | ||
| 63 : | @param format_str The input string to format. | ||
| 64 : | @param context A map used for string substitutions. | ||
| 65 : | @return Returns the formatted string. If no match, the braces remain intact. | ||
| 66 : | """ | ||
| 67 : | while True: | ||
| 68 : | #print "format_str:", format_str | ||
| 69 : | all_matches = _find_sub_matches(format_str) | ||
| 70 : | if not all_matches: | ||
| 71 : | break | ||
| 72 : | substitutions = 0 | ||
| 73 : | while True: | ||
| 74 : | matches = all_matches.pop() | ||
| 75 : | # we work from right to left to make sure we do not | ||
| 76 : | # invalidate positions earlier in format_str | ||
| 77 : | matches.reverse() | ||
| 78 : | for pos in matches: | ||
| 79 : | # Use index since _find_sub_matches should have raised | ||
| 80 : | # an exception, and failure to find now is an exception. | ||
| 81 : | end = format_str.index('}', pos) | ||
| 82 : | #print "directive:", format_str[pos+1:pos+5] | ||
| 83 : | if format_str[pos + 1] == '$': | ||
| 84 : | value = context[format_str[pos + 2:end]] | ||
| 85 : | if value is not None: | ||
| 86 : | value = format_value_for_path(value) | ||
| 87 : | elif format_str[pos + 1] == '%': | ||
| 88 : | value = _build_query_string( | ||
| 89 : | context.get(format_str[pos + 2:end])) | ||
| 90 : | elif format_str[pos+1:pos+5] == 'http' or format_str[pos+1:pos+5] == 'file': | ||
| 91 : | value = _fetch_url_directive(format_str[pos + 1:end]) | ||
| 92 : | else: | ||
| 93 : | raise UnknownDirective, format_str[pos:end + 1] | ||
| 94 : | if value is not None: | ||
| 95 : | format_str = format_str[:pos]+str(value)+format_str[end+1:] | ||
| 96 : | substitutions += 1 | ||
| 97 : | |||
| 98 : | # If there were any substitutions at this depth, re-parse | ||
| 99 : | # since this may have revealed new things to substitute | ||
| 100 : | if substitutions: | ||
| 101 : | break | ||
| 102 : | if not all_matches: | ||
| 103 : | break | ||
| 104 : | |||
| 105 : | # If there were no substitutions at all, and we have exhausted | ||
| 106 : | # the possible matches, bail. | ||
| 107 : | if not substitutions: | ||
| 108 : | break | ||
| 109 : | return format_str | ||
| 110 : | |||
| 111 : | def _find_sub_matches(format_str): | ||
| 112 : | """@brief Find all of the substitution matches. | ||
| 113 : | @param format_str the RUSS conformant format string. | ||
| 114 : | @return Returns an array of depths of arrays of positional matches in input. | ||
| 115 : | """ | ||
| 116 : | depth = 0 | ||
| 117 : | matches = [] | ||
| 118 : | for pos in range(len(format_str)): | ||
| 119 : | if format_str[pos] == '{': | ||
| 120 : | depth += 1 | ||
| 121 : | if not len(matches) == depth: | ||
| 122 : | matches.append([]) | ||
| 123 : | matches[depth - 1].append(pos) | ||
| 124 : | continue | ||
| 125 : | if format_str[pos] == '}': | ||
| 126 : | depth -= 1 | ||
| 127 : | continue | ||
| 128 : | if not depth == 0: | ||
| 129 : | raise UnbalancedBraces, format_str | ||
| 130 : | return matches | ||
| 131 : | |||
| 132 : | def _build_query_string(query_dict): | ||
| 133 : | """\ | ||
| 134 : | @breif given a dict, return a query string. utility wrapper for urllib. | ||
| 135 : | @param query_dict input query dict | ||
| 136 : | @returns Returns an urlencoded query string including leading '?'. | ||
| 137 : | """ | ||
| 138 : | if query_dict: | ||
| 139 : | keys = query_dict.keys() | ||
| 140 : | keys.sort() | ||
| 141 : | def stringize(value): | ||
| 142 : | if type(value) in (str,unicode): | ||
| 143 : | return value | ||
| 144 : | else: | ||
| 145 : | return str(value) | ||
| 146 : | query_list = [urllib.quote(str(key)) + '=' + urllib.quote(stringize(query_dict[key])) for key in keys] | ||
| 147 : | return '?' + '&'.join(query_list) | ||
| 148 : | else: | ||
| 149 : | return '' | ||
| 150 : | |||
| 151 : | def _fetch_url_directive(directive): | ||
| 152 : | "*FIX: This only supports GET" | ||
| 153 : | commands = directive.split('|') | ||
| 154 : | resource = llsdhttp.get(commands[0]) | ||
| 155 : | if len(commands) == 3: | ||
| 156 : | resource = _walk_resource(resource, commands[2]) | ||
| 157 : | return resource | ||
| 158 : | |||
| 159 : | def _walk_resource(resource, path): | ||
| 160 : | path = path.split('/') | ||
| 161 : | for child in path: | ||
| 162 : | if not child: | ||
| 163 : | continue | ||
| 164 : | resource = resource[child] | ||
| 165 : | return resource |
| ViewVC Help | |
| Powered by ViewVC 1.0.0 |

