Generates a URL based on the inputted path and given query options and
    fragment.  The query should be a dictionary of terms that will be
    generated into the URL, while the fragment is the anchor point within the
    target path that will be navigated to.  If there are any wildcards within
    the path that are found within the query, they will be inserted into the
    path itself and removed from the query string.
    
    :example    |>>> import skyline.gui
                |>>> skyline.gui.build_url('sky://projects/%(project)s',
                |                          {'project': 'Test', 'asset': 'Bob'})
                |'sky://projects/Test/?asset=Bob'
    
    :param      path        | <str>
                query       | <dict> || None
                fragment    | <str> || None
    
    :return     <str> | url

def build(path, query=None, fragment=''):
    """
    Generates a URL based on the inputted path and given query options and
    fragment.  The query should be a dictionary of terms that will be
    generated into the URL, while the fragment is the anchor point within the
    target path that will be navigated to.  If there are any wildcards within
    the path that are found within the query, they will be inserted into the
    path itself and removed from the query string.
    
    :example    |>>> import skyline.gui
                |>>> skyline.gui.build_url('sky://projects/%(project)s',
                |                          {'project': 'Test', 'asset': 'Bob'})
                |'sky://projects/Test/?asset=Bob'
    
    :param      path        | <str>
                query       | <dict> || None
                fragment    | <str> || None
    
    :return     <str> | url
    """
    url = nstr(path)

    # replace the optional arguments in the url
    keys = projex.text.findkeys(path)
    if keys:
        if query is None:
            query = {}

        opts = {}
        for key in keys:
            opts[key] = query.pop(key, '%({})s'.format(key))

        url %= opts

    # add the query
    if query:
        if type(query) is dict:
            mapped_query = {}
            for key, value in query.items():
                mapped_query[nstr(key)] = nstr(value)
            query_str = urllib.urlencode(mapped_query)
        else:
            query_str = nstr(query)

        url += '?' + query_str

    # include the fragment
    if fragment:
        url += '#' + fragment

    return url