# Source code for rdflib.extras.describer

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-

    __doc__ = """
    A Describer is a stateful utility for creating RDF statements in a
    semi-declarative manner. It has methods for creating literal values, rel and
    rev resource relations (somewhat resembling RDFa).

    The `rel` and ``rev`` methods return a context manager which sets the current
    about to the referenced resource for the context scope (for use with the
    ``with`` statement).

    Full example in the ``to_rdf`` method below::

        >>> import datetime
        >>> from rdflib.graph import Graph
        >>> from rdflib.namespace import Namespace, RDFS, FOAF
        >>>
        >>> ORG_URI = "http://example.org/"
        >>>
        >>> CV = Namespace("http://purl.org/captsolo/resume-rdf/0.2/cv#")
        >>>
        >>> class Person(object):
        ...     def __init__(self):
        ...         self.first_name = u"Some"
        ...         self.last_name = u"Body"
        ...         self.username = "some1"
        ...         self.presentation = u"Just a Python & RDF hacker."
        ...         self.image = "/images/persons/" + self.username + ".jpg"
        ...         self.site = "http://example.net/"
        ...         self.start_date = datetime.date(2009, 9, 4)
        ...     def get_full_name(self):
        ...         return u" ".join([self.first_name, self.last_name])
        ...     def get_absolute_url(self):
        ...         return "/persons/" + self.username
        ...     def get_thumbnail_url(self):
        ...         return self.image.replace('.jpg', '-thumb.jpg')
        ...
        ...     def to_rdf(self):
        ...         graph = Graph()
        ...         graph.bind('foaf', FOAF)
        ...         graph.bind('cv', CV)
        ...         lang = 'en'
        ...         d = Describer(graph, base=ORG_URI)
        ...         d.about(self.get_absolute_url()+'#person')
        ...         d.rdftype(FOAF.Person)
        ...         d.value(FOAF.name, self.get_full_name())
        ...         d.value(FOAF.givenName, self.first_name)
        ...         d.value(FOAF.familyName, self.last_name)
        ...         d.rel(FOAF.homepage, self.site)
        ...         d.value(RDFS.comment, self.presentation, lang=lang)
        ...         with d.rel(FOAF.depiction, self.image):
        ...             d.rdftype(FOAF.Image)
        ...             d.rel(FOAF.thumbnail, self.get_thumbnail_url())
        ...         with d.rev(CV.aboutPerson):
        ...             d.rdftype(CV.CV)
        ...             with d.rel(CV.hasWorkHistory):
        ...                 d.value(CV.startDate, self.start_date)
        ...                 d.rel(CV.employedIn, ORG_URI+"#company")
        ...         return graph
        ...
        >>> person_graph = Person().to_rdf()
        >>> expected = Graph().parse(data='''<?xml version="1.0" encoding="utf-8"?>
        ... <rdf:RDF
        ...   xmlns:foaf="http://xmlns.com/foaf/0.1/"
        ...   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
        ...   xmlns:cv="http://purl.org/captsolo/resume-rdf/0.2/cv#"
        ...   xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#">
        ...   <foaf:Person rdf:about="http://example.org/persons/some1#person">
        ...     <foaf:name>Some Body</foaf:name>
        ...     <foaf:givenName>Some</foaf:givenName>
        ...     <foaf:familyName>Body</foaf:familyName>
        ...     <foaf:depiction>
        ...       <foaf:Image
        ...         rdf:about=
        ...             "http://example.org/images/persons/some1.jpg">
        ...         <foaf:thumbnail
        ...         rdf:resource=
        ...             "http://example.org/images/persons/some1-thumb.jpg"/>
        ...       </foaf:Image>
        ...     </foaf:depiction>
        ...     <rdfs:comment xml:lang="en">
        ...             Just a Python &amp; RDF hacker.
        ...     </rdfs:comment>
        ...     <foaf:homepage rdf:resource="http://example.net/"/>
        ...   </foaf:Person>
        ...   <cv:CV>
        ...     <cv:aboutPerson
        ...         rdf:resource="http://example.org/persons/some1#person">
        ...     </cv:aboutPerson>
        ...     <cv:hasWorkHistory>
        ...       <rdf:Description>
        ...         <cv:startDate
        ...             rdf:datatype="http://www.w3.org/2001/XMLSchema#date"
        ...             >2009-09-04</cv:startDate>
        ...         <cv:employedIn rdf:resource="http://example.org/#company"/>
        ...       </rdf:Description>
        ...     </cv:hasWorkHistory>
        ...   </cv:CV>
        ... </rdf:RDF>
        ... ''', format="xml")
        >>>
        >>> from rdflib.compare import isomorphic
        >>> isomorphic(person_graph, expected)  #doctest: +SKIP
        True
    """

    from contextlib import contextmanager
    from rdflib.graph import Graph
    from rdflib.namespace import RDF
    from rdflib.term import BNode
    from rdflib.term import Identifier
    from rdflib.term import Literal
    from rdflib.term import URIRef

    class Describer(object):

        def __init__(self, graph=None, about=None, base=None):
            if graph is None:
                graph = Graph()
            self.graph = graph
            self.base = base
            self._subjects = []
            self.about(about or None)

        def about(self, subject, **kws):
            """
            Sets the current subject. Will convert the given object into an
            ``URIRef`` if it's not an ``Identifier``.

            Usage::

                >>> d = Describer()
                >>> d._current() #doctest: +ELLIPSIS
                rdflib.term.BNode(...)
                >>> d.about("http://example.org/")
                >>> d._current()
                rdflib.term.URIRef(u'http://example.org/')

            """
            kws.setdefault("base", self.base)
            subject = cast_identifier(subject, **kws)
            if self._subjects:
                self._subjects[-1] = subject
            else:
                self._subjects.append(subject)

        def value(self, p, v, **kws):
            """
            Set a literal value for the given property. Will cast the value to an
            ``Literal`` if a plain literal is given.

            Usage::

                >>> from rdflib import URIRef
                >>> from rdflib.namespace import RDF, RDFS
                >>> d = Describer(about="http://example.org/")
                >>> d.value(RDFS.label, "Example")
                >>> d.graph.value(URIRef('http://example.org/'), RDFS.label)
                rdflib.term.Literal(u'Example')

            """
            v = cast_value(v, **kws)
            self.graph.add((self._current(), p, v))

        def rel(self, p, o=None, **kws):
            """Set an object for the given property. Will convert the given object
            into an ``URIRef`` if it's not an ``Identifier``. If none is given, a
            new ``BNode`` is used.

            Returns a context manager for use in a ``with`` block, within which the
            given object is used as current subject.

            Usage::

                >>> from rdflib import URIRef
                >>> from rdflib.namespace import RDF, RDFS
                >>> d = Describer(about="/", base="http://example.org/")
                >>> _ctxt = d.rel(RDFS.seeAlso, "/about")
                >>> d.graph.value(URIRef('http://example.org/'), RDFS.seeAlso)
                rdflib.term.URIRef(u'http://example.org/about')

                >>> with d.rel(RDFS.seeAlso, "/more"):
                ...     d.value(RDFS.label, "More")
                >>> (URIRef('http://example.org/'), RDFS.seeAlso,
                ...         URIRef('http://example.org/more')) in d.graph
                True
                >>> d.graph.value(URIRef('http://example.org/more'), RDFS.label)
                rdflib.term.Literal(u'More')

            """

            kws.setdefault("base", self.base)
            p = cast_identifier(p)
            o = cast_identifier(o, **kws)
            self.graph.add((self._current(), p, o))
            return self._subject_stack(o)

        def rev(self, p, s=None, **kws):
            """
            Same as ``rel``, but uses current subject as *object* of the relation.
            The given resource is still used as subject in the returned context
            manager.

            Usage::

                >>> from rdflib import URIRef
                >>> from rdflib.namespace import RDF, RDFS
                >>> d = Describer(about="http://example.org/")
                >>> with d.rev(RDFS.seeAlso, "http://example.net/"):
                ...     d.value(RDFS.label, "Net")
                >>> (URIRef('http://example.net/'), RDFS.seeAlso,
                ...         URIRef('http://example.org/')) in d.graph
                True
                >>> d.graph.value(URIRef('http://example.net/'), RDFS.label)
                rdflib.term.Literal(u'Net')

            """
            kws.setdefault("base", self.base)
            p = cast_identifier(p)
            s = cast_identifier(s, **kws)
            self.graph.add((s, p, self._current()))
            return self._subject_stack(s)

        def rdftype(self, t):
            """
            Shorthand for setting rdf:type of the current subject.

            Usage::

                >>> from rdflib import URIRef
                >>> from rdflib.namespace import RDF, RDFS
                >>> d = Describer(about="http://example.org/")
                >>> d.rdftype(RDFS.Resource)
                >>> (URIRef('http://example.org/'),
                ...     RDF.type, RDFS.Resource) in d.graph
                True

            """
            self.graph.add((self._current(), RDF.type, t))

        def _current(self):
            return self._subjects[-1]

        @contextmanager
        def _subject_stack(self, subject):
            self._subjects.append(subject)
            yield None
            self._subjects.pop()

    def cast_value(v, **kws):
        if not isinstance(v, Literal):
            v = Literal(v, **kws)
        return v

    def cast_identifier(ref, **kws):
        ref = ref or BNode()
        if not isinstance(ref, Identifier):
            ref = URIRef(ref, **kws)
        return ref