PageRenderTime 17ms CodeModel.GetById 8ms app.highlight 6ms RepoModel.GetById 1ms app.codeStats 0ms

/indra/lib/python/indra/ipc/russ.py

https://bitbucket.org/lindenlab/viewer-beta/
Python | 165 lines | 149 code | 0 blank | 16 comment | 0 complexity | 7cd3276ec6ed30e2af104c524b45b522 MD5 | raw file
  1"""\
  2@file russ.py
  3@brief Recursive URL Substitution Syntax helpers
  4@author Phoenix
  5
  6Many details on how this should work is available on the wiki:
  7https://wiki.secondlife.com/wiki/Recursive_URL_Substitution_Syntax
  8
  9Adding features to this should be reflected in that page in the
 10implementations section.
 11
 12$LicenseInfo:firstyear=2007&license=mit$
 13
 14Copyright (c) 2007-2009, Linden Research, Inc.
 15
 16Permission is hereby granted, free of charge, to any person obtaining a copy
 17of this software and associated documentation files (the "Software"), to deal
 18in the Software without restriction, including without limitation the rights
 19to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 20copies of the Software, and to permit persons to whom the Software is
 21furnished to do so, subject to the following conditions:
 22
 23The above copyright notice and this permission notice shall be included in
 24all copies or substantial portions of the Software.
 25
 26THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 27IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 28FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 29AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 30LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 31OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 32THE SOFTWARE.
 33$/LicenseInfo$
 34"""
 35
 36import urllib
 37from indra.ipc import llsdhttp
 38
 39class UnbalancedBraces(Exception):
 40    pass
 41
 42class UnknownDirective(Exception):
 43    pass
 44
 45class BadDirective(Exception):
 46    pass
 47
 48def 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
 60def 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
111def _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
132def _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
151def _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
159def _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