示例#1
0
class Response(object):
    """Response class to handle all output related tasks in one place.

    This class is basically a wrapper arround a ``StringIO`` instance.
    It also provides methods for managing http headers.
    """
    def __init__(self, request):
        """Sets the ``Request`` object that leaded to this response.
        Creates a ``StringIO`` that is used as a output buffer.
        """
        self._request = request
        self._out = StringIO()
        self._headers_sent = False
        self.headers = {}
        self.status = "200 OK"

        self.close = self._out.close
        self.flush = self._out.flush
        self.read = self._out.read
        self.readline = self._out.readline
        self.readlines = self._out.readlines
        self.seek = self._out.seek
        self.tell = self._out.tell
        self.write = self._out.write
        self.writelines = self._out.writelines

    def __iter__(self):
        """Can't copy the ``__iter__`` method over from the
        ``StringIO`` instance because iter looks for the method in the
        class instead of the instance.

        See
        http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/252151
        """
        return self._out

    def set_status(self, status):
        """Sets the status code for this response.  The status should
        be a valid HTTP response status.

        Examples:

        >>> resp = Response('some fake request')
        >>> resp.set_status("200 OK")
        >>> resp.set_status("404 Not Found")

        :param status: the status string.
        """
        self.status = status

    setStatus = tools.deprecated_function(set_status)

    def get_status(self):
        """Returns the status code and message of this response.
        """
        return self.status

    def add_header(self, key, value):
        """Populates the HTTP header with lines of text.  Sets the
        status code on this response object if the given argument list
        contains a 'Status' header.

        Example:

        >>> resp = Response('some fake request')
        >>> resp.add_header("Content-type", "text/plain")
        >>> resp.add_header("Content-Length", "10500")

        :raises ValueError: This happens when the parameters are
                            not correct.
        """
        key = key.strip()
        if key.find(' ') != -1 or key.find(':') != -1:
            raise ValueError, 'There should be no spaces in header keys'
        value = value.strip()
        if key.lower() == "status":
            self.setStatus(str(value))
        else:
            self.headers.update({key: str(value)})

    addHeader = tools.deprecated_function(add_header)

    def get_headers(self):
        """Returns the headers.
        """
        return self.headers

    getHeaders = tools.deprecated_function(get_headers)

    def send_headers(self, out):
        """Send HTTP Headers to the given output stream.

        .. Note::

            This prints the headers and then the ``\\n\\n`` that
            separates headers from the body.

        :param out: The file-like object to print headers to.
        """
        out.write("Status: %s\n" % self.status)
        out.write('\n'.join([
            '%s: %s' % (hkey, self.headers[hkey])
            for hkey in self.headers.keys()
        ]))
        out.write('\n\n')
        self._headers_sent = True

    sendHeaders = tools.deprecated_function(send_headers)

    def send_body(self, out):
        """Send the response body to the given output stream.

        :param out: the file-like object to print the body to.
        """
        self.seek(0)
        try:
            out.write(self.read())
        except IOError:
            # this is usually a Broken Pipe because the client dropped the
            # connection.  so we skip it.
            pass

    sendBody = tools.deprecated_function(send_body)
示例#2
0
class RendererBase:
    """
    Pyblosxom core handles the Input and Process of the system and
    passes the result of the process to the Renderers for output. All
    renderers are child classes of RendererBase. RenderBase will
    contain the public interfaces for all Renderer object.
    """
    def __init__(self, request, stdoutput=sys.stdout):
        """
        Constructor: Initializes the Renderer

        :param request: The ``Pyblosxom.pyblosxom.Request`` object
        :param stdoutput: File like object to print to.
        """
        self._request = request

        # this is a list of tuples of the form (key, value)
        self._header = []

        self._out = stdoutput
        self._content = None
        self._content_mtime = None
        self._needs_content_type = 1
        self.rendered = None

    def write(self, data):
        """
        Convenience method for programs to use instead of accessing
        self._out.write()

        Other classes can override this if there is a unique way to
        write out data, for example, a two stream output, e.g. one
        output stream and one output log stream.

        Another use for this could be a plugin that writes out binary
        files, but because renderers and other frameworks may probably
        not want you to write to ``stdout`` directly, this method
        assists you nicely. For example::

            def cb_start(args):
                req = args['request']
                renderer = req['renderer']

                if reqIsGif and gifFileExists(theGifFile):
                    # Read the file
                    data = open(theGifFile).read()

                    # Modify header
                    renderer.addHeader('Content-type', 'image/gif')
                    renderer.addHeader('Content-Length', len(data))
                    renderer.showHeaders()

                    # Write to output
                    renderer.write(data)

                    # Tell pyblosxom not to render anymore as data is
                    # processed already
                    renderer.rendered = 1

        This simple piece of pseudo-code explains what you could do
        with this method, though I highly don't recommend this, unless
        pyblosxom is running continuously.

        :param data: Piece of string you want printed
        """
        self._out.write(data)

    def add_header(self, *args):
        """
        Populates the HTTP header with lines of text

        :param args: Paired list of headers

        :raises ValueError: This happens when the parameters are not
                            correct
        """
        args = list(args)
        if len(args) % 2 != 0:
            raise ValueError('Headers recieved are not in the correct form')

        while args:
            key = args.pop(0).strip()
            if key.find(' ') != -1 or key.find(':') != -1:
                raise ValueError('There should be no spaces in header keys')
            value = args.pop(0).strip()
            self._header.append( (key, value) )

    addHeader = tools.deprecated_function(add_header)

    def set_content(self, content):
        """
        Sets the content.  The content can be any of the following:

        * dict
        * list of entries

        :param content: the content to be displayed
        """
        self._content = content
        if isinstance(self._content, dict):
            mtime = self._content.get("mtime", time.time())
        elif isinstance(self._content, list):
            mtime = self._content[0].get("mtime", time.time())
        else:
            mtime = time.time()
        self._content_mtime = mtime

    setContent = tools.deprecated_function(set_content)

    def get_content(self):
        """
        Return the content field

        This is exposed for blosxom callbacks.

        :returns: content
        """
        return self._content

    getContent = tools.deprecated_function(get_content)

    def needs_content_type(self, flag):
        """
        Use the renderer to determine 'Content-Type: x/x' default is
        to use the renderer for Content-Type, set flag to None to
        indicate no Content-Type generation.

        :param flag: True of false value
        """
        self._needs_content_type = flag

    needsContentType = tools.deprecated_function(needs_content_type)

    def show_headers(self):
        """
        Updated the headers of the
        ``Response<Pyblosxom.pyblosxom.Response>`` instance.

        This is here for backwards compatibility.
        """
        response = self._request.getResponse()
        for k, v in self._header:
            response.addHeader(k, v)

    showHeaders = tools.deprecated_function(show_headers)

    def render(self, header=True):
        """
        Do final rendering.

        :param header: whether (True) or not (False) to show the
                       headers
        """
        if header:
            if self._header:
                self.show_headers()
            else:
                self.add_header('Content-Type', 'text/plain')
                self.show_headers()

        if self._content:
            self.write(self._content)
        self.rendered = 1
示例#3
0
class Pyblosxom:
    """Main class for Pyblosxom functionality.  It handles
    initialization, defines default behavior, and also pushes the
    request through all the steps until the output is rendered and
    we're complete.
    """
    def __init__(self, config, environ, data=None):
        """Sets configuration and environment and creates the Request
        object.

        :param config: dict containing the configuration variables.
        :param environ: dict containing the environment variables.
        :param data: dict containing data variables.
        """
        # FIXME: These shouldn't be here.
        config['pyblosxom_name'] = "pyblosxom"
        config['pyblosxom_version'] = __version__

        self._config = config
        self._request = Request(config, environ, data)

    def initialize(self):
        """The initialize step further initializes the Request by
        setting additional information in the ``data`` dict,
        registering plugins, and entryparsers.
        """
        data = self._request.get_data()
        py_http = self._request.get_http()
        config = self._request.get_configuration()

        # initialize the locale, if wanted (will silently fail if locale
        # is not available)
        if config.get('locale', None):
            try:
                locale.setlocale(locale.LC_ALL, config['locale'])
            except locale.Error:
                # invalid locale
                pass

        # initialize the tools module
        tools.initialize(config)

        data["pyblosxom_version"] = __version__
        data['pi_bl'] = ''

        # if the user specifies base_url in config, we use that.
        # otherwise we compose it from SCRIPT_NAME in the environment
        # or we leave it blank.
        if not "base_url" in config:
            if py_http.has_key('SCRIPT_NAME'):
                # allow http and https
                config['base_url'] = '%s://%s%s' % \
                                     (py_http['wsgi.url_scheme'],
                                      py_http['HTTP_HOST'],
                                      py_http['SCRIPT_NAME'])
            else:
                config["base_url"] = ""

        # take off the trailing slash for base_url
        if config['base_url'].endswith("/"):
            config['base_url'] = config['base_url'][:-1]

        data_dir = config["datadir"]
        if data_dir.endswith("/") or data_dir.endswith("\\"):
            data_dir = data_dir[:-1]
            config['datadir'] = data_dir

        # import and initialize plugins
        plugin_utils.initialize_plugins(config.get("plugin_dirs", []),
                                        config.get("load_plugins", None))

        # entryparser callback is run here first to allow other
        # plugins register what file extensions can be used
        data['extensions'] = tools.run_callback("entryparser",
                                                {'txt': blosxom_entry_parser},
                                                mappingfunc=lambda x, y: y,
                                                defaultfunc=lambda x: x)

    def cleanup(self):
        """This cleans up Pyblosxom after a run.

        This should be called when Pyblosxom has done everything it
        needs to do before exiting.
        """
        # log some useful stuff for debugging
        # this will only be logged if the log_level is "debug"
        log = tools.getLogger()
        response = self.get_response()
        log.debug("status = %s" % response.status)
        log.debug("headers = %s" % response.headers)

    def get_request(self):
        """Returns the Request object for this Pyblosxom instance.
        """
        return self._request

    getRequest = tools.deprecated_function(get_request)

    def get_response(self):
        """Returns the Response object associated with this Request.
        """
        return self._request.getResponse()

    getResponse = tools.deprecated_function(get_response)

    def run(self, static=False):
        """This is the main loop for Pyblosxom.  This method will run
        the handle callback to allow registered handlers to handle the
        request.  If nothing handles the request, then we use the
        ``default_blosxom_handler``.

        :param static: True if Pyblosxom should execute in "static rendering
                       mode" and False otherwise.
        """
        self.initialize()

        # buffer the input stream in a StringIO instance if dynamic
        # rendering is used.  This is done to have a known/consistent
        # way of accessing incoming data.
        if not static:
            self.get_request().buffer_input_stream()

        # run the start callback
        tools.run_callback("start", {'request': self._request})

        # allow anyone else to handle the request at this point
        handled = tools.run_callback("handle", {'request': self._request},
                                     mappingfunc=lambda x, y: x,
                                     donefunc=lambda x: x)

        if not handled == 1:
            blosxom_handler(self._request)

        # do end callback
        tools.run_callback("end", {'request': self._request})

        # we're done, clean up.
        # only call this if we're not in static rendering mode.
        if not static:
            self.cleanup()

    def run_callback(self, callback="help"):
        """This method executes the start callback (initializing
        plugins), executes the requested callback, and then executes
        the end callback.

        This is useful for scripts outside of Pyblosxom that need to
        do things inside of the Pyblosxom framework.

        If you want to run a callback from a plugin, use
        ``tools.run_callback`` instead.

        :param callback: the name of the callback to execute.

        :returns: the results of the callback.
        """
        self.initialize()

        # run the start callback
        tools.run_callback("start", {'request': self._request})

        # invoke all callbacks for the 'callback'
        handled = tools.run_callback(callback, {'request': self._request},
                                     mappingfunc=lambda x, y: x,
                                     donefunc=lambda x: x)

        # do end callback
        tools.run_callback("end", {'request': self._request})

        return handled

    runCallback = tools.deprecated_function(run_callback)

    def run_render_one(self, url, headers):
        """Renders a single page from the blog.

        :param url: the url to render--this has to be relative to the
                    base url for this blog.

        :param headers: True if you want headers to be rendered and
                        False if not.
        """
        self.initialize()

        config = self._request.get_configuration()

        if url.find("?") != -1:
            url = url[:url.find("?")]
            query = url[url.find("?") + 1:]
        else:
            query = ""

        url = url.replace(os.sep, "/")
        response = tools.render_url(config, url, query)
        if headers:
            response.send_headers(sys.stdout)
        response.send_body(sys.stdout)

        print response.read()

        # we're done, clean up
        self.cleanup()

    def run_static_renderer(self, incremental=False):
        """This will go through all possible things in the blog and
        statically render everything to the ``static_dir`` specified
        in the config file.

        This figures out all the possible ``path_info`` settings and
        calls ``self.run()`` a bazillion times saving each file.

        :param incremental: Whether (True) or not (False) to
                            incrementally render the pages.  If we're
                            incrementally rendering pages, then we
                            render only the ones that have changed.
        """
        self.initialize()

        config = self._request.get_configuration()
        data = self._request.get_data()
        print "Performing static rendering."
        if incremental:
            print "Incremental is set."

        static_dir = config.get("static_dir", "")
        data_dir = config["datadir"]

        if not static_dir:
            print "Error: You must set static_dir in your config file."
            return 0

        flavours = config.get("static_flavours", ["html"])
        index_flavours = config.get("static_index_flavours", ["html"])

        render_me = []

        month_names = config.get("static_monthnames", True)
        month_numbers = config.get("static_monthnumbers", False)
        year_indexes = config.get("static_yearindexes", True)

        dates = {}
        categories = {}

        # first we handle entries and categories
        listing = tools.walk(self._request, data_dir)

        for mem in listing:
            # skip the ones that have bad extensions
            ext = mem[mem.rfind(".") + 1:]
            if not ext in data["extensions"].keys():
                continue

            # grab the mtime of the entry file
            mtime = time.mktime(tools.filestat(self._request, mem))

            # remove the datadir from the front and the bit at the end
            mem = mem[len(data_dir):mem.rfind(".")]

            # this is the static filename
            fn = os.path.normpath(static_dir + mem)

            # grab the mtime of one of the statically rendered file
            try:
                smtime = os.stat(fn + "." + flavours[0])[8]
            except:
                smtime = 0

            # if the entry is more recent than the static, we want to
            # re-render
            if smtime < mtime or not incremental:

                # grab the categories
                temp = os.path.dirname(mem).split(os.sep)
                for i in range(len(temp) + 1):
                    p = os.sep.join(temp[0:i])
                    categories[p] = 0

                # grab the date
                mtime = time.localtime(mtime)
                year = time.strftime("%Y", mtime)
                month = time.strftime("%m", mtime)
                day = time.strftime("%d", mtime)

                if year_indexes:
                    dates[year] = 1

                if month_numbers:
                    dates[year + "/" + month] = 1
                    dates[year + "/" + month + "/" + day] = 1

                if month_names:
                    monthname = tools.num2month[month]
                    dates[year + "/" + monthname] = 1
                    dates[year + "/" + monthname + "/" + day] = 1

                # toss in the render queue
                for f in flavours:
                    render_me.append((mem + "." + f, ""))

        print "rendering %d entries." % len(render_me)

        # handle categories
        categories = categories.keys()
        categories.sort()

        # if they have stuff in their root category, it'll add a "/"
        # to the category list and we want to remove that because it's
        # a duplicate of "".
        if "/" in categories:
            categories.remove("/")

        print "rendering %d category indexes." % len(categories)

        for mem in categories:
            mem = os.path.normpath(mem + "/index.")
            for f in index_flavours:
                render_me.append((mem + f, ""))

        # now we handle dates
        dates = dates.keys()
        dates.sort()

        dates = ["/" + d for d in dates]

        print "rendering %d date indexes." % len(dates)

        for mem in dates:
            mem = os.path.normpath(mem + "/index.")
            for f in index_flavours:
                render_me.append((mem + f, ""))

        # now we handle arbitrary urls
        additional_stuff = config.get("static_urls", [])
        print "rendering %d arbitrary urls." % len(additional_stuff)

        for mem in additional_stuff:
            if mem.find("?") != -1:
                url = mem[:mem.find("?")]
                query = mem[mem.find("?") + 1:]
            else:
                url = mem
                query = ""

            render_me.append((url, query))

        # now we pass the complete render list to all the plugins via
        # cb_staticrender_filelist and they can add to the filelist
        # any (url, query) tuples they want rendered.
        print "(before) building %s files." % len(render_me)
        tools.run_callback(
            "staticrender_filelist", {
                'request': self._request,
                'filelist': render_me,
                'flavours': flavours,
                'incremental': incremental
            })

        render_me = sorted(set(render_me))

        print "building %s files." % len(render_me)

        for url, q in render_me:
            url = url.replace(os.sep, "/")
            print "rendering '%s' ..." % url

            tools.render_url_statically(dict(config), url, q)

        # we're done, clean up
        self.cleanup()
示例#4
0
class Request(object):
    """
    This class holds the Pyblosxom request.  It holds configuration
    information, HTTP/CGI information, and data that we calculate and
    transform over the course of execution.

    There should be only one instance of this class floating around
    and it should get created by ``pyblosxom.cgi`` and passed into the
    Pyblosxom instance which will do further manipulation on the
    Request instance.
    """
    def __init__(self, config, environ, data):
        """Sets configuration and environment.

        Creates the Response object which handles all output related
        functionality.

        :param config: dict containing configuration variables.
        :param environ: dict containing environment variables.
        :param data: dict containing data variables.
        """
        # this holds configuration data that the user changes in
        # config.py
        self._configuration = config

        # this holds HTTP/CGI oriented data specific to the request
        # and the environment in which the request was created
        self._http = EnvDict(self, environ)

        # this holds run-time data which gets created and transformed
        # by pyblosxom during execution
        if data is None:
            self._data = dict()
        else:
            self._data = data

        # this holds the input stream.  initialized for dynamic
        # rendering in Pyblosxom.run.  for static rendering there is
        # no input stream.
        self._in = StringIO()

        # copy methods to the Request object.
        self.read = self._in.read
        self.readline = self._in.readline
        self.readlines = self._in.readlines
        self.seek = self._in.seek
        self.tell = self._in.tell

        # this holds the FieldStorage instance.
        # initialized when request.get_form is called the first time
        self._form = None

        self._response = None

        # create and set the Response
        self.setResponse(Response(self))

    def __iter__(self):
        """
        Can't copy the __iter__ method over from the StringIO instance
        cause iter looks for the method in the class instead of the
        instance.

        See http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/252151
        """
        return self._in

    def buffer_input_stream(self):
        """
        Buffer the input stream in a StringIO instance.  This is done
        to have a known/consistent way of accessing incoming data.
        For example the input stream passed by mod_python does not
        offer the same functionality as ``sys.stdin``.
        """
        # TODO: tests on memory consumption when uploading huge files
        py_http = self.get_http()
        winput = py_http['wsgi.input']
        method = py_http["REQUEST_METHOD"]

        # there's no data on stdin for a GET request.  pyblosxom
        # will block indefinitely on the read for a GET request with
        # thttpd.
        if method != "GET":
            try:
                length = int(py_http.get("CONTENT_LENGTH", 0))
            except ValueError:
                length = 0

            if length > 0:
                self._in.write(winput.read(length))
                # rewind to start
                self._in.seek(0)

    def set_response(self, response):
        """Sets the Response object.
        """
        self._response = response
        # for backwards compatibility
        self.get_configuration()['stdoutput'] = response

    setResponse = tools.deprecated_function(set_response)

    def get_response(self):
        """Returns the Response for this request.
        """
        return self._response

    getResponse = tools.deprecated_function(get_response)

    def _getform(self):
        form = cgi.FieldStorage(fp=self._in,
                                environ=self._http,
                                keep_blank_values=0)
        # rewind the input buffer
        self._in.seek(0)
        return form

    def get_form(self):
        """Returns the form data submitted by the client.  The
        ``form`` instance is created only when requested to prevent
        overhead and unnecessary consumption of the input stream.

        :returns: a ``cgi.FieldStorage`` instance.
        """
        if self._form is None:
            self._form = self._getform()
        return self._form

    getForm = tools.deprecated_function(get_form)

    def get_configuration(self):
        """Returns the *actual* configuration dict.  The configuration
        dict holds values that the user sets in their ``config.py``
        file.

        Modifying the contents of the dict will affect all downstream
        processing.
        """
        return self._configuration

    getConfiguration = tools.deprecated_function(get_configuration)

    def get_http(self):
        """Returns the *actual* http dict.  Holds HTTP/CGI data
        derived from the environment of execution.

        Modifying the contents of the dict will affect all downstream
        processing.
        """
        return self._http

    getHttp = tools.deprecated_function(get_http)

    def get_data(self):
        """Returns the *actual* data dict.  Holds run-time data which
        is created and transformed by pyblosxom during execution.

        Modifying the contents of the dict will affect all downstream
        processing.
        """
        return self._data

    getData = tools.deprecated_function(get_data)

    def add_http(self, d):
        """Takes in a dict and adds/overrides values in the existing
        http dict with the new values.
        """
        self._http.update(d)

    addHttp = tools.deprecated_function(add_http)

    def add_data(self, d):
        """Takes in a dict and adds/overrides values in the existing
        data dict with the new values.
        """
        self._data.update(d)

    addData = tools.deprecated_function(add_data)

    def add_configuration(self, newdict):
        """Takes in a dict and adds/overrides values in the existing
        configuration dict with the new values.
        """
        self._configuration.update(newdict)

    addConfiguration = tools.deprecated_function(add_configuration)

    def __getattr__(self, name):
        if name in ["config", "configuration", "conf"]:
            return self._configuration

        if name == "data":
            return self._data

        if name == "http":
            return self._http

        raise AttributeError, name

    def __repr__(self):
        return "Request"
示例#5
0
        template = args["template"]

        # FIXME - the finaltext.replace(...) below causes \$ to get
        # unescaped in title and body text which is wrong.  this
        # fix alleviates that somewhat, but there are still edge
        # cases regarding function data.  need a real template
        # engine with a real parser here.
        entry = dict(args["entry"])
        for k, v in entry.items():
            if isinstance(v, basestring):
                entry[k] = v.replace(r"\$", r"\\$")

        finaltext = tools.parse(self._request, entry, template)
        return finaltext.replace(r'\$', '$')

    renderTemplate = tools.deprecated_function(render_template)

    def _run_callback(self, chain, input):
        """
        Makes calling blosxom callbacks a bit easier since they all
        have the same mechanics.  This function merely calls
        run_callback with the arguments given and a mappingfunc.

        The mappingfunc copies the ``template`` value from the output to
        the input for the next function.

        Refer to run_callback for more details.
        """
        input.update({"renderer": self})
        input.update({"request": self._request})
示例#6
0
class EntryBase:
    """
    EntryBase is the base class for all the Entry classes.  Each
    instance of an Entry class represents a single entry in the
    weblog, whether it came from a file, or a database, or even
    somewhere off the InterWeeb.

    EntryBase derivatives are dict-like except for one key difference:
    when doing ``__getitem__`` on a nonexistent key, it returns None by
    default.  For example:

    >>> entry = EntryBase('some fake request')
    >>> None == entry["some_nonexistent_key"]
    True
    """
    def __init__(self, request):
        self._data = ""
        self._metadata = dict(tools.STANDARD_FILTERS)
        self._id = ""
        self._mtime = BIGNUM
        self._request = request

    def __repr__(self):
        """
        Returns a friendly debug-able representation of self. Useful
        to know on what entry pyblosxom fails on you (though unlikely)

        :returns: Identifiable representation of object
        """
        return "<Entry instance: %s>\n" % self.getId()

    def get_id(self):
        """
        This should return an id that's unique enough for caching
        purposes.

        Override this.

        :returns: string id
        """
        return self._id

    getId = tools.deprecated_function(get_id)

    def get_data(self):
        """
        Returns the data string.  This method should be overridden to
        provide from pulling the data from other places.

        Override this.

        :returns: the data as a string
        """
        return str(self._data)

    getData = tools.deprecated_function(get_data)

    def set_data(self, data):
        """
        Sets the data content for this entry.  If you are not creating
        the entry, then you have no right to set the data of the
        entry.  Doing so could be hazardous depending on what
        EntryBase subclass you're dealing with.

        Override this.

        :param data: the data
        """
        self._data = data

    setData = tools.deprecated_function(set_data)

    def get_metadata(self, key, default=None):
        """
        Returns a given piece of metadata.

        Override this.

        :param key: the key being sought

        :param default: the default to return if the key does not
                        exist

        :return: either the default (if the key did not exist) or the
                 value of the key in the metadata dict
        """
        return self._metadata.get(key, default)

    getMetadata = tools.deprecated_function(get_metadata)

    def set_metadata(self, key, value):
        """
        Sets a key/value pair in the metadata dict.

        Override this.

        :param key: the key string

        :param value: the value string
        """
        self._metadata[key] = value

    setMetadata = tools.deprecated_function(set_metadata)

    def get_metadata_keys(self):
        """
        Returns the list of keys for which we have values in our
        stored metadata.

        .. Note::

            This list gets modified later downstream.  If you cache
            your list of metadata keys, then this method should return
            a copy of that list and not the list itself lest it get
            adjusted.

        Override this.

        :returns: list of metadata keys
        """
        return self._metadata.keys()

    getMetadataKeys = tools.deprecated_function(get_metadata_keys)

    def get_from_cache(self, entryid):
        """
        Retrieves information from the cache that pertains to this
        specific entryid.

        This is a helper method--call this to get data from the cache.
        Do not override it.

        :param entryid: a unique key for the information you're retrieving

        :returns: dict with the values or None if there's nothing for that
                  entryid
        """
        cache = tools.get_cache(self._request)

        # cache.__getitem__ returns None if the id isn't there
        if cache.has_key(entryid):
            return cache[entryid]

        return None

    getFromCache = tools.deprecated_function(get_from_cache)

    def add_to_cache(self, entryid, data):
        """
        Over-writes the cached dict for key entryid with the data
        dict.

        This is a helper method--call this to add data to the cache.
        Do not override it.

        :param entryid: a unique key for the information you're
                        storing

        :param data: the data to store--this should probably be a dict
        """
        mycache = tools.get_cache(self._request)
        if mycache:
            # This could be extended to cover all keys used by
            # set_time(), but this is the key most likely to turn
            # up in metadata. If #date is not blocked from caching
            # here, the templates will use the raw string value
            # from the user metadata, rather than the value
            # derived from mtime.
            if data.has_key('date'):
                data.pop('date')
            mycache[entryid] = data

    addToCache = tools.deprecated_function(add_to_cache)

    def set_time(self, timetuple):
        """
        This takes in a given time tuple and sets all the magic
        metadata variables we have according to the items in the time
        tuple.

        :param timetuple: the timetuple to use to set the data
                          with--this is the same thing as the
                          mtime/atime portions of an os.stat.  This
                          time is expected to be local time, not UTC.
        """
        self['timetuple'] = timetuple
        self._mtime = time.mktime(timetuple)
        gmtimetuple = time.gmtime(self._mtime)
        self['mtime'] = self._mtime
        self['ti'] = time.strftime('%H:%M', timetuple)
        self['mo'] = time.strftime('%b', timetuple)
        self['mo_num'] = time.strftime('%m', timetuple)
        self['da'] = time.strftime('%d', timetuple)
        self['dw'] = time.strftime('%A', timetuple)
        self['yr'] = time.strftime('%Y', timetuple)
        self['fulltime'] = time.strftime('%Y%m%d%H%M%S', timetuple)
        self['date'] = time.strftime('%a, %d %b %Y', timetuple)

        # YYYY-MM-DDThh:mm:ssZ
        self['w3cdate'] = time.strftime('%Y-%m-%dT%H:%M:%SZ', gmtimetuple)

        # Temporarily disable the set locale, so RFC-compliant date is
        # really RFC-compliant: directives %a and %b are locale
        # dependent.  Technically, we're after english locale, but
        # only 'C' locale is guaranteed to exist.
        loc = locale.getlocale(locale.LC_ALL)
        locale.setlocale(locale.LC_ALL, 'C')

        self['rfc822date'] = time.strftime('%a, %d %b %Y %H:%M GMT', \
                                           gmtimetuple)

        # set the locale back
        locale.setlocale(locale.LC_ALL, loc)

    setTime = tools.deprecated_function(set_time)

    # everything below this point involves convenience functions
    # that work with the above functions.

    def __getitem__(self, key, default=None):
        """
        Retrieves an item from this dict based on the key given.  If
        the item does not exist, then we return the default.

        If the item is ``CONTENT_KEY``, it calls ``get_data``,
        otherwise it calls ``get_metadata``.  Don't override this.

        .. Warning::

            There's no reason to override this--override ``get_data``
            and ``get_metadata`` instead.

        :param key: the key being sought

        :param default: the default to return if the key does not
                        exist

        :returns: the value of ``get_metadata`` or ``get_data``
        """
        if key == CONTENT_KEY:
            return self.get_data()

        return self.get_metadata(key, default)

    def get(self, key, default=None):
        """
        Retrieves an item from the internal dict based on the key
        given.

        All this does is turn aroun and call ``__getitem__``.

        .. Warning::

            There's no reason to override this--override ``get_data``
            and ``get_metadata`` instead.

        :param key: the key being sought

        :param default: the default to return if the key does not
                        exist

        :returns: the value of ``get_metadata`` or ``get_data``
                  (through ``__getitem__``)
        """
        return self.__getitem__(key, default)

    def __setitem__(self, key, value):
        """
        Sets the metadata[key] to the given value.

        This uses ``set_data`` and ``set_metadata``.  Don't override
        this.

        :param key: the given key name

        :param value: the given value
        """
        if key == CONTENT_KEY:
            self.set_data(value)
        else:
            self.set_metadata(key, value)

    def update(self, newdict):
        """
        Updates the contents in this entry with the contents in the
        dict.  It does so by calling ``set_data`` and
        ``set_metadata``.

        .. Warning::

            There's no reason to override this--override ``set_data``
            and ``set_metadata`` instead.

        :param newdict: the dict we're updating this one with
        """
        for mem in newdict.keys():
            if mem == CONTENT_KEY:
                self.set_data(newdict[mem])
            else:
                self.set_metadata(mem, newdict[mem])

    def has_key(self, key):
        """
        Returns whether a given key is in the metadata dict.  If the
        key is the ``CONTENT_KEY``, then we automatically return true.

        .. Warning::

            There's no reason to override this--override
            ``get_metadata`` instead.

        :param key: the key to check in the metadata dict for

        :returns: whether (True) or not (False) the key exists
        """
        if key == CONTENT_KEY or key == CONTENT_KEY + "_escaped":
            return True

        value = self.get_metadata(key, DOESNOTEXIST)
        if value == DOESNOTEXIST:
            value = self.get_metadata(key, DOESNOTEXIST2)
            if value == DOESNOTEXIST2:
                return False

        return True

    def keys(self):
        """
        Returns a list of the keys that can be accessed through
        ``__getitem__``.

        .. Warning::

            There's no reason to override this--override
            ``get_metadata_keys`` instead.

        :returns: list of key names
        """
        keys = self.get_metadata_keys()
        if CONTENT_KEY not in keys:
            keys.append(CONTENT_KEY)
        return keys
示例#7
0
class FileEntry(base.EntryBase):
    """
    This class gets it's data and metadata from the file specified
    by the filename argument.
    """
    def __init__(self, request, filename, root, datadir=""):
        """
        :param request: the Request object

        :param filename: the complete filename for the file in question
                         including path

        :param root: i have no clue what this is

        :param datadir: the datadir
        """
        base.EntryBase.__init__(self, request)
        self._config = request.get_configuration()
        self._filename = filename.replace(os.sep, '/')
        self._root = root.replace(os.sep, '/')

        self._datadir = datadir or self._config["datadir"]
        if self._datadir.endswith(os.sep):
            self._datadir = self._datadir[:-1]

        self._timetuple = tools.filestat(self._request, self._filename)
        self._mtime = time.mktime(self._timetuple)
        self._fulltime = time.strftime("%Y%m%d%H%M%S", self._timetuple)

        self._populated_data = 0

    def __repr__(self):
        return "<fileentry f'%s' r'%s'>" % (self._filename, self._root)

    def get_id(self):
        """
        Returns the id for this content item--in this case, it's the
        filename.

        :returns: the id of the fileentry (the filename)
        """
        return self._filename

    getId = tools.deprecated_function(get_id)

    def get_data(self):
        """
        Returns the data for this file entry.  The data is the parsed
        (via the entryparser) content of the entry.  We do this on-demand
        by checking to see if we've gotten it and if we haven't then
        we get it at that point.

        :returns: the content for this entry
        """
        if self._populated_data == 0:
            self._populatedata()
        return self._data

    getData = tools.deprecated_function(get_data)

    def get_metadata(self, key, default=None):
        """
        This overrides the ``base.EntryBase`` ``get_metadata`` method.

        .. Note::

            We populate our metadata lazily--only when it's requested.
            This delays parsing of the file as long as we can.
        """
        if self._populated_data == 0:
            self._populatedata()

        return self._metadata.get(key, default)

    getMetadata = tools.deprecated_function(get_metadata)

    def _populatedata(self):
        """
        Fills the metadata dict with metadata about the given file.
        This metadata consists of things we pick up from an os.stat
        call as well as knowledge of the filename and the root
        directory.  We then parse the file and fill in the rest of the
        information that we know.
        """
        file_basename = os.path.basename(self._filename)

        path = self._filename.replace(self._root, '')
        path = path.replace(os.path.basename(self._filename), '')
        path = path[:-1]

        absolute_path = self._filename.replace(self._datadir, '', 1)
        absolute_path = absolute_path.replace(file_basename, '')
        absolute_path = absolute_path[1:][:-1]

        if absolute_path and absolute_path[-1] == "/":
            absolute_path = absolute_path[0:-1]

        filename_no_ext = os.path.splitext(file_basename)[0]
        if absolute_path == '':
            file_path = filename_no_ext
        else:
            file_path = '/'.join((absolute_path, filename_no_ext))

        tb_id = '%s/%s' % (absolute_path, filename_no_ext)
        tb_id = re.sub(r'[^A-Za-z0-9]', '_', tb_id)

        self['path'] = path
        self['tb_id'] = tb_id
        self['absolute_path'] = absolute_path
        self['file_path'] = file_path
        self['fn'] = filename_no_ext
        self['filename'] = self._filename

        self.set_time(self._timetuple)

        data = self._request.get_data()

        entry_dict = self.get_from_cache(self._filename)
        if not entry_dict:
            file_ext = os.path.splitext(self._filename)
            if file_ext:
                file_ext = file_ext[1][1:]

            eparser = data['extensions'][file_ext]
            entry_dict = eparser(self._filename, self._request)
            self.add_to_cache(self._filename, entry_dict)

        self.update(entry_dict)
        self._populated_data = 1
示例#8
0
class BlosxomRenderer(RendererBase):
    """
    This is the default blosxom renderer.  It tries to match the behavior
    of the blosxom renderer.
    """
    def __init__(self, request, stdoutput=sys.stdout):
        RendererBase.__init__(self, request, stdoutput)
        config = request.get_configuration()
        self._request = request
        self.flavour = None

    def get_parse_vars(self):
        """Returns a dict starting with standard filters, config
        information, then data information.  This allows vars
        to override each other correctly.  For example, plugins
        should be adding to the data dict which will override
        stuff in the config dict.
        """
        parsevars = dict(tools.STANDARD_FILTERS)
        parsevars.update(self._request.config)
        parsevars.update(self._request.data)
        return parsevars

    def get_flavour(self, taste='html'):
        """
        This retrieves all the template files for a given flavour
        taste.  This will first pull the templates for the default
        flavour of this taste if there are any.  Then it looks at
        EITHER the configured datadir OR the flavourdir (if
        configured).  It'll go through directories overriding the
        template files it has already picked up descending the
        category path of the Pyblosxom request.

        For example, if the user requested the ``html`` flavour and is
        looking at an entry in the category ``dev/pyblosxom``, then
        ``get_flavour`` will:

        1. pick up the flavour files in the default html flavour
        2. start in EITHER datadir OR flavourdir (if configured)
        3. override the default html flavour files with html flavour
           files in this directory or in ``html.flav/`` subdirectory
        4. override the html flavour files it's picked up so far
           with html files in ``dev/`` or ``dev/html.flav/``
        5. override the html flavour files it's picked up so far
           with html files in ``dev/pyblosxom/`` or
           ``dev/pyblosxom/html.flav/``

        If it doesn't find any flavour files at all, then it returns
        None which indicates the flavour doesn't exist in this blog.

        :param taste: the taste to retrieve flavour files for.

        :returns: mapping of template name to template file data
        """
        data = self._request.get_data()
        config = self._request.get_configuration()
        datadir = config["datadir"]

        # if they have flavourdir set, then we look there.  otherwise
        # we look in the datadir.
        flavourdir = config.get("flavourdir", datadir)

        # first we grab the flavour files for the included flavour (if
        # we have one).
        template_d = get_included_flavour(taste)
        if not template_d:
            template_d = {}

        pathinfo = list(data["path_info"])

        # check the root of flavourdir for templates
        new_files = get_flavour_from_dir(flavourdir, taste)
        if new_files:
            template_d.update(new_files)

        # go through all the directories from the flavourdir all
        # the way up to the root_datadir.  this way template files
        # can override template files in parent directories.
        while len(pathinfo) > 0:
            flavourdir = os.path.join(flavourdir, pathinfo.pop(0))
            if os.path.isfile(flavourdir):
                break

            if not os.path.isdir(flavourdir):
                break

            new_files = get_flavour_from_dir(flavourdir, taste)
            if new_files:
                template_d.update(new_files)

        # if we still haven't found our flavour files, we raise an exception
        if not template_d:
            raise NoSuchFlavourException("Flavour '%s' does not exist." % taste)

        for k in template_d.keys():
            try:
                flav_template = open(template_d[k]).read()
                template_d[k] = flav_template
            except (OSError, IOError):
                pass

        return template_d

    def render_content(self, content):
        """
        Processes the content for the story portion of a page.

        :param content: the content to be rendered

        :returns: the content string
        """
        data = self._request.get_data()

        outputbuffer = []

        if callable(content):
            # if the content is a callable function, then we just spit out
            # whatever it returns as a string
            outputbuffer.append(content())

        elif isinstance(content, dict):
            # if the content is a dict, then we parse it as if it were an
            # entry--except it's distinctly not an EntryBase derivative
            var_dict = self.get_parse_vars()
            var_dict.update(content)

            output = tools.parse(self._request, var_dict, self.flavour['story'])
            outputbuffer.append(output)

        elif isinstance(content, list):
            if len(content) > 0:
                current_date = content[0]["date"]

                if current_date and "date_head" in self.flavour:
                    parse_vars = self.get_parse_vars()
                    parse_vars.update({"date": current_date,
                                       "yr": content[0]["yr"],
                                       "mo": content[0]["mo"],
                                       "da": content[0]["da"]})
                    outputbuffer.append(
                        self.render_template(parse_vars, "date_head"))

                for entry in content:
                    if entry["date"] and entry["date"] != current_date:
                        if "date_foot" in self.flavour:
                            parse_vars = self.get_parse_vars()
                            parse_vars.update({"date": current_date,
                                               "yr": content[0]["yr"],
                                               "mo": content[0]["mo"],
                                               "da": content[0]["da"]})

                            outputbuffer.append(
                                self.render_template(parse_vars, "date_foot"))

                        if "date_head" in self.flavour:
                            current_date = entry["date"]
                            parse_vars = self.get_parse_vars()
                            parse_vars.update({"date": current_date,
                                               "yr": content[0]["yr"],
                                               "mo": content[0]["mo"],
                                               "da": content[0]["da"]})
                            outputbuffer.append(
                                self.render_template(parse_vars, "date_head"))

                    if data['content-type'] == 'text/plain':
                        s = tools.Stripper()
                        s.feed(entry.get_data())
                        s.close()
                        p = ['  ' + line for line in s.gettext().split('\n')]
                        entry.set_data('\n'.join(p))

                    parse_vars = self.get_parse_vars()
                    parse_vars.update(entry)

                    outputbuffer.append(
                        self.render_template(parse_vars, "story", override=1))

                    args = {"entry": parse_vars, "template": ""}
                    args = self._run_callback("story_end", args)
                    outputbuffer.append(args["template"])

                if current_date and "date_foot" in self.flavour:
                    parse_vars = self.get_parse_vars()
                    parse_vars.update({"date": current_date})
                    outputbuffer.append(
                        self.render_template(parse_vars, "date_foot"))

        return outputbuffer

    renderContent = tools.deprecated_function(render_content)

    def render(self, header=True):
        """
        Figures out flavours and such and then renders the content according
        to which flavour we're using.

        :param header: whether (True) or not (False) to render the HTTP headers
        """
        # if we've already rendered, then we don't want to do so again
        if self.rendered:
            return

        data = self._request.get_data()
        config = self._request.get_configuration()

        try:
            self.flavour = self.get_flavour(data.get("flavour", "html"))

        except NoSuchFlavourException, nsfe:
            error_msg = str(nsfe)
            try:
                self.flavour = self.get_flavour("error")
            except NoSuchFlavourException:
                self.flavour = get_included_flavour("error")
                error_msg += "  And your error flavour doesn't exist, either."

            resp = self._request.getResponse()
            resp.set_status("404 Not Found")
            self._content = {"title": "HTTP 404: Flavour error",
                             "body": error_msg}

        data['content-type'] = self.flavour['content_type'].strip()
        if header:
            if self._needs_content_type and data['content-type'] != "":
                self.add_header('Content-type', '%(content-type)s' % data)

            self.show_headers()

        if self._content:
            if "head" in self.flavour:
                self.write(self.render_template(self.get_parse_vars(), "head"))
            if "story" in self.flavour:
                content = self.render_content(self._content)
                for i, mem in enumerate(content):
                    if isinstance(mem, unicode):
                        content[i] = mem.encode("utf-8")
                content = "".join(content)
                self.write(content)
            if "foot" in self.flavour:
                self.write(self.render_template(self.get_parse_vars(), "foot"))

        self.rendered = 1
示例#9
0
        template = args["template"]

        # FIXME - the finaltext.replace(...) below causes \$ to get
        # unescaped in title and body text which is wrong.  this
        # fix alleviates that somewhat, but there are still edge
        # cases regarding function data.  need a real template
        # engine with a real parser here.
        entry = dict(args["entry"])
        for k, v in entry.items():
            if isinstance(v, basestring):
                entry[k] = v.replace(r"\$", r"\\$")

        finaltext = tools.parse(self._request, entry, template)
        return finaltext.replace(r'\$', '$')

    renderTemplate = tools.deprecated_function(render_template)

    def _run_callback(self, chain, input):
        """
        Makes calling blosxom callbacks a bit easier since they all
        have the same mechanics.  This function merely calls
        run_callback with the arguments given and a mappingfunc.

        The mappingfunc copies the ``template`` value from the output to
        the input for the next function.

        Refer to run_callback for more details.
        """
        input.update({"renderer": self})
        input.update({"request": self._request})