Action function generator to create a link based on the context's current link, or on provided parameters

    :param origin: IRI/string, or list of same; origins for the created relationships.
    If None, the action context provides the parameter.

    :param rel: IRI/string, or list of same; IDs for the created relationships.
    If None, the action context provides the parameter.
    
    :param value: IRI/string, or list of same; values/targets for the created relationships.
    If None, the action context provides the parameter.
    
    :param source: pattern action to be executed, generating contexts to determine the output statements. If given, overrides specific origin, rel or value params

    :return: Versa action function to do the actual work

def link(origin=None, rel=None, value=None, attributes=None, source=None):
    '''
    Action function generator to create a link based on the context's current link, or on provided parameters

    :param origin: IRI/string, or list of same; origins for the created relationships.
    If None, the action context provides the parameter.

    :param rel: IRI/string, or list of same; IDs for the created relationships.
    If None, the action context provides the parameter.
    
    :param value: IRI/string, or list of same; values/targets for the created relationships.
    If None, the action context provides the parameter.
    
    :param source: pattern action to be executed, generating contexts to determine the output statements. If given, overrides specific origin, rel or value params

    :return: Versa action function to do the actual work
    '''
    attributes = attributes or {}
    #rel = I(iri.absolutize(rel, ctx.base))
    def _link(ctx):
        if source:
            if not callable(source):
                raise ValueError('Link source must be a pattern action function')
            contexts = source(ctx)
            for ctx in contexts:
                ctx.output_model.add(ctx.current_link[ORIGIN], ctx.current_link[RELATIONSHIP], ctx.current_link[TARGET], attributes)
            return

        (o, r, v, a) = ctx.current_link
        _origin = origin(ctx) if callable(origin) else origin
        o_list = [o] if _origin is None else (_origin if isinstance(_origin, list) else [_origin])
        #_origin = _origin if isinstance(_origin, set) else set([_origin])
        _rel = rel(ctx) if callable(rel) else rel
        r_list = [r] if _rel is None else (_rel if isinstance(_rel, list) else [_rel])
        #_rel = _rel if isinstance(_rel, set) else set([_rel])
        _value = value(ctx) if callable(value) else value
        v_list = [v] if _value is None else (_value if isinstance(_value, list) else [_value])
        #_target = _target if isinstance(_target, set) else set([_target])
        _attributes = attributes(ctx) if callable(attributes) else attributes

        #(ctx_o, ctx_r, ctx_t, ctx_a) = ctx.current_link

        #FIXME: Add test for IRI output via wrapper action function
        for (o, r, v, a) in [ (o, r, v, a) for o in o_list for r in r_list for v in v_list ]:
            ctx.output_model.add(o, r, v, attributes)

        return
    return _link