#extensions = []
sys.path.append(os.path.abspath('.'))
extensions = ['sphinxext.quote',
+ 'sphinxext.feed',
'sphinx.ext.todo']
# show todo items
# If false, no module index is generated.
#latex_use_modindex = True
+
+# RSS feed
+# --------
+feed_base_url = 'http://neuro.debian.net/blog'
+feed_description = "Debian for neuroscience and neuroscience in Debian"
+feed_filename = 'rss.xml'
--- /dev/null
+from fsdict import FSDict
+import feedgenerator
+from urllib import quote_plus
+import os.path
+
+#global
+feed_entries = None
+
+#constant unlikely to occur in a docname and legal as a filename
+MAGIC_SEPARATOR = '---###---'
+
+def setup(app):
+ """
+ see: http://sphinx.pocoo.org/ext/appapi.html
+ this is the primary extension point for Sphinx
+ """
+ from sphinx.application import Sphinx
+ if not isinstance(app, Sphinx): return
+ app.add_config_value('feed_base_url', '', 'html')
+ app.add_config_value('feed_description', '', 'html')
+ app.add_config_value('feed_filename', 'rss.xml', 'html')
+
+ app.connect('html-page-context', create_feed_item)
+ app.connect('html-page-context', inject_feed_url)
+ app.connect('build-finished', emit_feed)
+ app.connect('builder-inited', create_feed_container)
+ app.connect('env-purge-doc', remove_dead_feed_item)
+
+def create_feed_container(app):
+ """
+ create lazy filesystem stash for keeping RSS entry fragments, since we don't
+ want to store the entire site in the environment (in fact, even if we did,
+ it wasn't persisting for some reason.)
+ """
+ global feed_entries
+ rss_fragment_path = os.path.realpath(os.path.join(app.outdir, '..', 'rss_entry_fragments'))
+ feed_entries = FSDict(work_dir=rss_fragment_path)
+ app.builder.env.feed_url = app.config.feed_base_url + '/' + \
+ app.config.feed_filename
+
+def inject_feed_url(app, pagename, templatename, ctx, doctree):
+ #We like to provide our templates with a way to link to the rss output file
+ ctx['rss_link'] = app.builder.env.feed_url #app.config.feed_base_url + '/' + app.config.feed_filename
+
+def create_feed_item(app, pagename, templatename, ctx, doctree):
+ """
+ Here we have access to nice HTML fragments to use in, say, an RSS feed.
+ We serialize them to disk so that we get them preserved across builds.
+ """
+ global feed_entries
+ import dateutil.parser
+ from absolutify_urls import absolutify
+ date_parser = dateutil.parser.parser()
+ metadata = app.builder.env.metadata.get(pagename, {})
+
+ if 'date' not in metadata:
+ return #don't index dateless articles
+ try:
+ pub_date = date_parser.parse(metadata['date'])
+ except ValueError, exc:
+ #probably a nonsensical date
+ app.builder.warn('date parse error: ' + str(exc) + ' in ' + pagename)
+ return
+
+ # title, link, description, author_email=None,
+ # author_name=None, author_link=None, pubdate=None, comments=None,
+ # unique_id=None, enclosure=None, categories=(), item_copyright=None,
+ # ttl=None,
+ link = app.config.feed_base_url + '/' + ctx['current_page_name'] + ctx['file_suffix']
+ item = {
+ 'title': ctx.get('title'),
+ 'link': link,
+ 'unique_id': link,
+ 'description': absolutify(ctx.get('body'), link),
+ 'pubdate': pub_date
+ }
+ if 'author' in metadata:
+ item['author'] = metadata['author']
+ feed_entries[nice_name(pagename, pub_date)] = item
+
+def remove_dead_feed_item(app, env, docname):
+ """
+ TODO:
+ purge unwanted crap
+ """
+ global feed_entries
+ munged_name = ''.join([MAGIC_SEPARATOR,quote_plus(docname)])
+ for name in feed_entries:
+ if name.endswith(munged_name):
+ del(feed_entries[name])
+
+def emit_feed(app, exc):
+ global feed_entries
+ import os.path
+
+ feed_dict = {
+ 'title': app.config.project,
+ 'link': app.config.feed_base_url,
+ 'feed_url': app.config.feed_base_url,
+ 'description': app.config.feed_description
+ }
+ if app.config.language:
+ feed_dict['language'] = app.config.language
+ if app.config.copyright:
+ feed_dict['feed_copyright'] = app.config.copyright
+ feed = feedgenerator.Rss201rev2Feed(**feed_dict)
+ app.builder.env.feed_feed = feed
+ ordered_keys = feed_entries.keys()
+ ordered_keys.sort(reverse=True)
+ for key in ordered_keys:
+ feed.add_item(**feed_entries[key])
+ outfilename = os.path.join(app.builder.outdir,
+ app.config.feed_filename)
+ fp = open(outfilename, 'w')
+ feed.write(fp, 'utf-8')
+ fp.close()
+
+def nice_name(docname, date):
+ """
+ we need convenient filenames which incorporate dates for ease of sorting and
+ guid for uniqueness, plus will work in the FS without inconvenient
+ characters. NB, at the moment, hour of publication is ignored.
+ """
+ return quote_plus(MAGIC_SEPARATOR.join([date.isoformat(), docname]))
--- /dev/null
+# By Gareth Rees
+# http://gareth-rees.livejournal.com/27148.html
+
+import html5lib
+import html5lib.serializer
+import html5lib.treewalkers
+import urlparse
+
+# List of (ELEMENT, ATTRIBUTE) for HTML5 attributes which contain URLs.
+# Based on the list at http://www.feedparser.org/docs/resolving-relative-links.html
+url_attributes = [
+ ('a', 'href'),
+ ('applet', 'codebase'),
+ ('area', 'href'),
+ ('blockquote', 'cite'),
+ ('body', 'background'),
+ ('del', 'cite'),
+ ('form', 'action'),
+ ('frame', 'longdesc'),
+ ('frame', 'src'),
+ ('iframe', 'longdesc'),
+ ('iframe', 'src'),
+ ('head', 'profile'),
+ ('img', 'longdesc'),
+ ('img', 'src'),
+ ('img', 'usemap'),
+ ('input', 'src'),
+ ('input', 'usemap'),
+ ('ins', 'cite'),
+ ('link', 'href'),
+ ('object', 'classid'),
+ ('object', 'codebase'),
+ ('object', 'data'),
+ ('object', 'usemap'),
+ ('q', 'cite'),
+ ('script', 'src')]
+
+def absolutify(src, base_url):
+ """absolutify(SRC, BASE_URL): Resolve relative URLs in SRC.
+SRC is a string containing HTML. All URLs in SRC are resolved relative
+to BASE_URL. Return the body of the result as HTML."""
+
+ # Parse SRC as HTML.
+ tree_builder = html5lib.treebuilders.getTreeBuilder('dom')
+ parser = html5lib.html5parser.HTMLParser(tree = tree_builder)
+ dom = parser.parse(src)
+
+ # Handle <BASE> if any.
+ head = dom.getElementsByTagName('head')[0]
+ for b in head.getElementsByTagName('base'):
+ u = b.getAttribute('href')
+ if u:
+ base_url = urlparse.urljoin(base_url, u)
+ # HTML5 4.2.3 "if there are multiple base elements with href
+ # attributes, all but the first are ignored."
+ break
+
+ # Change all relative URLs to absolute URLs by resolving them
+ # relative to BASE_URL. Note that we need to do this even for URLs
+ # that consist only of a fragment identifier, because Google Reader
+ # changes href=#foo to href=http://site/#foo
+ for tag, attr in url_attributes:
+ for e in dom.getElementsByTagName(tag):
+ u = e.getAttribute(attr)
+ if u:
+ e.setAttribute(attr, urlparse.urljoin(base_url, u))
+
+ # Return the HTML5 serialization of the <BODY> of the result (we don't
+ # want the <HEAD>: this breaks feed readers).
+ body = dom.getElementsByTagName('body')[0]
+ tree_walker = html5lib.treewalkers.getTreeWalker('dom')
+ html_serializer = html5lib.serializer.htmlserializer.HTMLSerializer()
+ return u''.join(html_serializer.serialize(tree_walker(body)))
+
+
+# Alternative option, from http://stackoverflow.com/questions/589833/how-to-find-a-relative-url-and-translate-it-to-an-absolute-url-in-python/589939#589939
+#
+# import re, urlparse
+#
+# find_re = re.compile(r'\bhref\s*=\s*("[^"]*"|\'[^\']*\'|[^"\'<>=\s]+)')
+#
+# def fix_urls(document, base_url):
+# ret = []
+# last_end = 0
+# for match in find_re.finditer(document):
+# url = match.group(1)
+# if url[0] in "\"'":
+# url = url.strip(url[0])
+# parsed = urlparse.urlparse(url)
+# if parsed.scheme == parsed.netloc == '': #relative to domain
+# url = urlparse.urljoin(base_url, url)
+# ret.append(document[last_end:match.start(1)])
+# ret.append('"%s"' % (url,))
+# last_end = match.end(1)
+# ret.append(document[last_end:])
+# return ''.join(ret)
--- /dev/null
+"""
+utils needed for django's feed generator
+"""
+
+"""
+Utilities for XML generation/parsing.
+from django.utils.xmlutils import SimplerXMLGenerator
+"""
+
+from xml.sax.saxutils import XMLGenerator
+
+class SimplerXMLGenerator(XMLGenerator):
+ def addQuickElement(self, name, contents=None, attrs=None):
+ "Convenience method for adding an element with no children"
+ if attrs is None: attrs = {}
+ self.startElement(name, attrs)
+ if contents is not None:
+ self.characters(contents)
+ self.endElement(name)
+
+"""
+from django.utils.encoding import force_unicode, iri_to_uri
+"""
+import types
+import urllib
+import locale
+import datetime
+import codecs
+from decimal import Decimal
+
+class DjangoUnicodeDecodeError(UnicodeDecodeError):
+ def __init__(self, obj, *args):
+ self.obj = obj
+ UnicodeDecodeError.__init__(self, *args)
+
+ def __str__(self):
+ original = UnicodeDecodeError.__str__(self)
+ return '%s. You passed in %r (%s)' % (original, self.obj,
+ type(self.obj))
+
+class StrAndUnicode(object):
+ """
+ A class whose __str__ returns its __unicode__ as a UTF-8 bytestring.
+
+ Useful as a mix-in.
+ """
+ def __str__(self):
+ return self.__unicode__().encode('utf-8')
+
+def is_protected_type(obj):
+ """Determine if the object instance is of a protected type.
+
+ Objects of protected types are preserved as-is when passed to
+ force_unicode(strings_only=True).
+ """
+ return isinstance(obj, (
+ types.NoneType,
+ int, long,
+ datetime.datetime, datetime.date, datetime.time,
+ float, Decimal)
+ )
+
+def force_unicode(s, encoding='utf-8', strings_only=False, errors='strict'):
+ """
+ Similar to smart_unicode, except that lazy instances are resolved to
+ strings, rather than kept as lazy objects.
+
+ If strings_only is True, don't convert (some) non-string-like objects.
+ """
+ if strings_only and is_protected_type(s):
+ return s
+ try:
+ if not isinstance(s, basestring,):
+ if hasattr(s, '__unicode__'):
+ s = unicode(s)
+ else:
+ try:
+ s = unicode(str(s), encoding, errors)
+ except UnicodeEncodeError:
+ if not isinstance(s, Exception):
+ raise
+ # If we get to here, the caller has passed in an Exception
+ # subclass populated with non-ASCII data without special
+ # handling to display as a string. We need to handle this
+ # without raising a further exception. We do an
+ # approximation to what the Exception's standard str()
+ # output should be.
+ s = ' '.join([force_unicode(arg, encoding, strings_only,
+ errors) for arg in s])
+ elif not isinstance(s, unicode):
+ # Note: We use .decode() here, instead of unicode(s, encoding,
+ # errors), so that if s is a SafeString, it ends up being a
+ # SafeUnicode at the end.
+ s = s.decode(encoding, errors)
+ except UnicodeDecodeError, e:
+ if not isinstance(s, Exception):
+ raise DjangoUnicodeDecodeError(s, *e.args)
+ else:
+ # If we get to here, the caller has passed in an Exception
+ # subclass populated with non-ASCII bytestring data without a
+ # working unicode method. Try to handle this without raising a
+ # further exception by individually forcing the exception args
+ # to unicode.
+ s = ' '.join([force_unicode(arg, encoding, strings_only,
+ errors) for arg in s])
+ return s
+
+def smart_str(s, encoding='utf-8', strings_only=False, errors='strict'):
+ """
+ Returns a bytestring version of 's', encoded as specified in 'encoding'.
+
+ If strings_only is True, don't convert (some) non-string-like objects.
+ """
+ if strings_only and isinstance(s, (types.NoneType, int)):
+ return s
+ elif not isinstance(s, basestring):
+ try:
+ return str(s)
+ except UnicodeEncodeError:
+ if isinstance(s, Exception):
+ # An Exception subclass containing non-ASCII data that doesn't
+ # know how to print itself properly. We shouldn't raise a
+ # further exception.
+ return ' '.join([smart_str(arg, encoding, strings_only,
+ errors) for arg in s])
+ return unicode(s).encode(encoding, errors)
+ elif isinstance(s, unicode):
+ return s.encode(encoding, errors)
+ elif s and encoding != 'utf-8':
+ return s.decode('utf-8', errors).encode(encoding, errors)
+ else:
+ return s
+
+def iri_to_uri(iri):
+ """
+ Convert an Internationalized Resource Identifier (IRI) portion to a URI
+ portion that is suitable for inclusion in a URL.
+
+ This is the algorithm from section 3.1 of RFC 3987. However, since we are
+ assuming input is either UTF-8 or unicode already, we can simplify things a
+ little from the full method.
+
+ Returns an ASCII string containing the encoded result.
+ """
+ # The list of safe characters here is constructed from the "reserved" and
+ # "unreserved" characters specified in sections 2.2 and 2.3 of RFC 3986:
+ # reserved = gen-delims / sub-delims
+ # gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@"
+ # sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
+ # / "*" / "+" / "," / ";" / "="
+ # unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
+ # Of the unreserved characters, urllib.quote already considers all but
+ # the ~ safe.
+ # The % character is also added to the list of safe characters here, as the
+ # end of section 3.1 of RFC 3987 specifically mentions that % must not be
+ # converted.
+ if iri is None:
+ return iri
+ return urllib.quote(smart_str(iri), safe="/#%[]=:;$&()+,!?*@'~")
+
+
+# The encoding of the default system locale but falls back to the
+# given fallback encoding if the encoding is unsupported by python or could
+# not be determined. See tickets #10335 and #5846
+try:
+ DEFAULT_LOCALE_ENCODING = locale.getdefaultlocale()[1] or 'ascii'
+ codecs.lookup(DEFAULT_LOCALE_ENCODING)
+except:
+ DEFAULT_LOCALE_ENCODING = 'ascii'
+
--- /dev/null
+"""
+Syndication feed generation library -- used for generating RSS, etc.
+Included from django http://djangoproject.org/
+
+Sample usage:
+
+>>> from django.utils import feedgenerator
+>>> feed = feedgenerator.Rss201rev2Feed(
+... title=u"Poynter E-Media Tidbits",
+... link=u"http://www.poynter.org/column.asp?id=31",
+... description=u"A group weblog by the sharpest minds in online media/journalism/publishing.",
+... language=u"en",
+... )
+>>> feed.add_item(title="Hello", link=u"http://www.holovaty.com/test/", description="Testing.")
+>>> fp = open('test.rss', 'w')
+>>> feed.write(fp, 'utf-8')
+>>> fp.close()
+
+For definitions of the different versions of RSS, see:
+http://diveintomark.org/archives/2004/02/04/incompatible-rss
+"""
+
+import re
+import datetime
+from django_support import SimplerXMLGenerator, iri_to_uri, force_unicode
+
+def rfc2822_date(date):
+ # We do this ourselves to be timezone aware, email.Utils is not tz aware.
+ if date.tzinfo:
+ time_str = date.strftime('%a, %d %b %Y %H:%M:%S ')
+ offset = date.tzinfo.utcoffset(date)
+ timezone = (offset.days * 24 * 60) + (offset.seconds / 60)
+ hour, minute = divmod(timezone, 60)
+ return time_str + "%+03d%02d" % (hour, minute)
+ else:
+ return date.strftime('%a, %d %b %Y %H:%M:%S -0000')
+
+def rfc3339_date(date):
+ if date.tzinfo:
+ time_str = date.strftime('%Y-%m-%dT%H:%M:%S')
+ offset = date.tzinfo.utcoffset(date)
+ timezone = (offset.days * 24 * 60) + (offset.seconds / 60)
+ hour, minute = divmod(timezone, 60)
+ return time_str + "%+03d:%02d" % (hour, minute)
+ else:
+ return date.strftime('%Y-%m-%dT%H:%M:%SZ')
+
+def get_tag_uri(url, date):
+ "Creates a TagURI. See http://diveintomark.org/archives/2004/05/28/howto-atom-id"
+ tag = re.sub('^http://', '', url)
+ if date is not None:
+ tag = re.sub('/', ',%s:/' % date.strftime('%Y-%m-%d'), tag, 1)
+ tag = re.sub('#', '/', tag)
+ return u'tag:' + tag
+
+class SyndicationFeed(object):
+ "Base class for all syndication feeds. Subclasses should provide write()"
+ def __init__(self, title, link, description, language=None, author_email=None,
+ author_name=None, author_link=None, subtitle=None, categories=None,
+ feed_url=None, feed_copyright=None, feed_guid=None, ttl=None, **kwargs):
+ to_unicode = lambda s: force_unicode(s, strings_only=True)
+ if categories:
+ categories = [force_unicode(c) for c in categories]
+ self.feed = {
+ 'title': to_unicode(title),
+ 'link': iri_to_uri(link),
+ 'description': to_unicode(description),
+ 'language': to_unicode(language),
+ 'author_email': to_unicode(author_email),
+ 'author_name': to_unicode(author_name),
+ 'author_link': iri_to_uri(author_link),
+ 'subtitle': to_unicode(subtitle),
+ 'categories': categories or (),
+ 'feed_url': iri_to_uri(feed_url),
+ 'feed_copyright': to_unicode(feed_copyright),
+ 'id': feed_guid or link,
+ 'ttl': ttl,
+ }
+ self.feed.update(kwargs)
+ self.items = []
+
+ def add_item(self, title, link, description, author_email=None,
+ author_name=None, author_link=None, pubdate=None, comments=None,
+ unique_id=None, enclosure=None, categories=(), item_copyright=None,
+ ttl=None, **kwargs):
+ """
+ Adds an item to the feed. All args are expected to be Python Unicode
+ objects except pubdate, which is a datetime.datetime object, and
+ enclosure, which is an instance of the Enclosure class.
+ """
+ to_unicode = lambda s: force_unicode(s, strings_only=True)
+ if categories:
+ categories = [to_unicode(c) for c in categories]
+ item = {
+ 'title': to_unicode(title),
+ 'link': iri_to_uri(link),
+ 'description': to_unicode(description),
+ 'author_email': to_unicode(author_email),
+ 'author_name': to_unicode(author_name),
+ 'author_link': iri_to_uri(author_link),
+ 'pubdate': pubdate,
+ 'comments': to_unicode(comments),
+ 'unique_id': to_unicode(unique_id),
+ 'enclosure': enclosure,
+ 'categories': categories or (),
+ 'item_copyright': to_unicode(item_copyright),
+ 'ttl': ttl,
+ }
+ item.update(kwargs)
+ self.items.append(item)
+
+ def num_items(self):
+ return len(self.items)
+
+ def root_attributes(self):
+ """
+ Return extra attributes to place on the root (i.e. feed/channel) element.
+ Called from write().
+ """
+ return {}
+
+ def add_root_elements(self, handler):
+ """
+ Add elements in the root (i.e. feed/channel) element. Called
+ from write().
+ """
+ pass
+
+ def item_attributes(self, item):
+ """
+ Return extra attributes to place on each item (i.e. item/entry) element.
+ """
+ return {}
+
+ def add_item_elements(self, handler, item):
+ """
+ Add elements on each item (i.e. item/entry) element.
+ """
+ pass
+
+ def write(self, outfile, encoding):
+ """
+ Outputs the feed in the given encoding to outfile, which is a file-like
+ object. Subclasses should override this.
+ """
+ raise NotImplementedError
+
+ def writeString(self, encoding):
+ """
+ Returns the feed in the given encoding as a string.
+ """
+ from StringIO import StringIO
+ s = StringIO()
+ self.write(s, encoding)
+ return s.getvalue()
+
+ def latest_post_date(self):
+ """
+ Returns the latest item's pubdate. If none of them have a pubdate,
+ this returns the current date/time.
+ """
+ updates = [i['pubdate'] for i in self.items if i['pubdate'] is not None]
+ if len(updates) > 0:
+ updates.sort()
+ return updates[-1]
+ else:
+ return datetime.datetime.now()
+
+class Enclosure(object):
+ "Represents an RSS enclosure"
+ def __init__(self, url, length, mime_type):
+ "All args are expected to be Python Unicode objects"
+ self.length, self.mime_type = length, mime_type
+ self.url = iri_to_uri(url)
+
+class RssFeed(SyndicationFeed):
+ mime_type = 'application/rss+xml'
+ def write(self, outfile, encoding):
+ handler = SimplerXMLGenerator(outfile, encoding)
+ handler.startDocument()
+ handler.startElement(u"rss", self.rss_attributes())
+ handler.startElement(u"channel", self.root_attributes())
+ self.add_root_elements(handler)
+ self.write_items(handler)
+ self.endChannelElement(handler)
+ handler.endElement(u"rss")
+
+ def rss_attributes(self):
+ return {u"version": self._version}
+
+ def write_items(self, handler):
+ for item in self.items:
+ handler.startElement(u'item', self.item_attributes(item))
+ self.add_item_elements(handler, item)
+ handler.endElement(u"item")
+
+ def add_root_elements(self, handler):
+ handler.addQuickElement(u"title", self.feed['title'])
+ handler.addQuickElement(u"link", self.feed['link'])
+ handler.addQuickElement(u"description", self.feed['description'])
+ if self.feed['language'] is not None:
+ handler.addQuickElement(u"language", self.feed['language'])
+ for cat in self.feed['categories']:
+ handler.addQuickElement(u"category", cat)
+ if self.feed['feed_copyright'] is not None:
+ handler.addQuickElement(u"copyright", self.feed['feed_copyright'])
+ handler.addQuickElement(u"lastBuildDate", rfc2822_date(self.latest_post_date()).decode('utf-8'))
+ if self.feed['ttl'] is not None:
+ handler.addQuickElement(u"ttl", self.feed['ttl'])
+
+ def endChannelElement(self, handler):
+ handler.endElement(u"channel")
+
+class RssUserland091Feed(RssFeed):
+ _version = u"0.91"
+ def add_item_elements(self, handler, item):
+ handler.addQuickElement(u"title", item['title'])
+ handler.addQuickElement(u"link", item['link'])
+ if item['description'] is not None:
+ handler.addQuickElement(u"description", item['description'])
+
+class Rss201rev2Feed(RssFeed):
+ # Spec: http://blogs.law.harvard.edu/tech/rss
+ _version = u"2.0"
+ def add_item_elements(self, handler, item):
+ handler.addQuickElement(u"title", item['title'])
+ handler.addQuickElement(u"link", item['link'])
+ if item['description'] is not None:
+ handler.addQuickElement(u"description", item['description'])
+
+ # Author information.
+ if item["author_name"] and item["author_email"]:
+ handler.addQuickElement(u"author", "%s (%s)" % \
+ (item['author_email'], item['author_name']))
+ elif item["author_email"]:
+ handler.addQuickElement(u"author", item["author_email"])
+ elif item["author_name"]:
+ handler.addQuickElement(u"dc:creator", item["author_name"], {"xmlns:dc": u"http://purl.org/dc/elements/1.1/"})
+
+ if item['pubdate'] is not None:
+ handler.addQuickElement(u"pubDate", rfc2822_date(item['pubdate']).decode('utf-8'))
+ if item['comments'] is not None:
+ handler.addQuickElement(u"comments", item['comments'])
+ if item['unique_id'] is not None:
+ handler.addQuickElement(u"guid", item['unique_id'])
+ if item['ttl'] is not None:
+ handler.addQuickElement(u"ttl", item['ttl'])
+
+ # Enclosure.
+ if item['enclosure'] is not None:
+ handler.addQuickElement(u"enclosure", '',
+ {u"url": item['enclosure'].url, u"length": item['enclosure'].length,
+ u"type": item['enclosure'].mime_type})
+
+ # Categories.
+ for cat in item['categories']:
+ handler.addQuickElement(u"category", cat)
+
+class Atom1Feed(SyndicationFeed):
+ # Spec: http://atompub.org/2005/07/11/draft-ietf-atompub-format-10.html
+ mime_type = 'application/atom+xml'
+ ns = u"http://www.w3.org/2005/Atom"
+
+ def write(self, outfile, encoding):
+ handler = SimplerXMLGenerator(outfile, encoding)
+ handler.startDocument()
+ handler.startElement(u'feed', self.root_attributes())
+ self.add_root_elements(handler)
+ self.write_items(handler)
+ handler.endElement(u"feed")
+
+ def root_attributes(self):
+ if self.feed['language'] is not None:
+ return {u"xmlns": self.ns, u"xml:lang": self.feed['language']}
+ else:
+ return {u"xmlns": self.ns}
+
+ def add_root_elements(self, handler):
+ handler.addQuickElement(u"title", self.feed['title'])
+ handler.addQuickElement(u"link", "", {u"rel": u"alternate", u"href": self.feed['link']})
+ if self.feed['feed_url'] is not None:
+ handler.addQuickElement(u"link", "", {u"rel": u"self", u"href": self.feed['feed_url']})
+ handler.addQuickElement(u"id", self.feed['id'])
+ handler.addQuickElement(u"updated", rfc3339_date(self.latest_post_date()).decode('utf-8'))
+ if self.feed['author_name'] is not None:
+ handler.startElement(u"author", {})
+ handler.addQuickElement(u"name", self.feed['author_name'])
+ if self.feed['author_email'] is not None:
+ handler.addQuickElement(u"email", self.feed['author_email'])
+ if self.feed['author_link'] is not None:
+ handler.addQuickElement(u"uri", self.feed['author_link'])
+ handler.endElement(u"author")
+ if self.feed['subtitle'] is not None:
+ handler.addQuickElement(u"subtitle", self.feed['subtitle'])
+ for cat in self.feed['categories']:
+ handler.addQuickElement(u"category", "", {u"term": cat})
+ if self.feed['feed_copyright'] is not None:
+ handler.addQuickElement(u"rights", self.feed['feed_copyright'])
+
+ def write_items(self, handler):
+ for item in self.items:
+ handler.startElement(u"entry", self.item_attributes(item))
+ self.add_item_elements(handler, item)
+ handler.endElement(u"entry")
+
+ def add_item_elements(self, handler, item):
+ handler.addQuickElement(u"title", item['title'])
+ handler.addQuickElement(u"link", u"", {u"href": item['link'], u"rel": u"alternate"})
+ if item['pubdate'] is not None:
+ handler.addQuickElement(u"updated", rfc3339_date(item['pubdate']).decode('utf-8'))
+
+ # Author information.
+ if item['author_name'] is not None:
+ handler.startElement(u"author", {})
+ handler.addQuickElement(u"name", item['author_name'])
+ if item['author_email'] is not None:
+ handler.addQuickElement(u"email", item['author_email'])
+ if item['author_link'] is not None:
+ handler.addQuickElement(u"uri", item['author_link'])
+ handler.endElement(u"author")
+
+ # Unique ID.
+ if item['unique_id'] is not None:
+ unique_id = item['unique_id']
+ else:
+ unique_id = get_tag_uri(item['link'], item['pubdate'])
+ handler.addQuickElement(u"id", unique_id)
+
+ # Summary.
+ if item['description'] is not None:
+ handler.addQuickElement(u"summary", item['description'], {u"type": u"html"})
+
+ # Enclosure.
+ if item['enclosure'] is not None:
+ handler.addQuickElement(u"link", '',
+ {u"rel": u"enclosure",
+ u"href": item['enclosure'].url,
+ u"length": item['enclosure'].length,
+ u"type": item['enclosure'].mime_type})
+
+ # Categories.
+ for cat in item['categories']:
+ handler.addQuickElement(u"category", u"", {u"term": cat})
+
+ # Rights.
+ if item['item_copyright'] is not None:
+ handler.addQuickElement(u"rights", item['item_copyright'])
+
+# This isolates the decision of what the system default is, so calling code can
+# do "feedgenerator.DefaultFeed" instead of "feedgenerator.Rss201rev2Feed".
+DefaultFeed = Rss201rev2Feed
--- /dev/null
+# −*− coding: UTF−8 −*−
+from path import path
+import os
+import pickle
+"""
+A class providing dictionary access to a folder.
+cribbed from http://bitbucket.org/howthebodyworks/fsdict
+"""
+
+def get_tmp_dir():
+ import tempfile
+ return tempfile.mkdtemp()
+
+class FSDict(dict):
+ """
+ provide dictionary access to a temp dir. I don't know why i didn't just use
+ shelve. I think I forgot it existed.
+
+ N.B. the keys ordering here is FS-dependent and thus unlike to be the same as
+ with a real dict. beware.
+ """
+
+ unclean_dirs = []
+
+ def __init__(self, initval=[], work_dir=None, *args, **kwargs):
+ if work_dir is None:
+ work_dir = get_tmp_dir()
+ self.work_dir = path(work_dir)
+ if not self.work_dir.exists():
+ self.work_dir.mkdir()
+ for key, val in getattr(initval, 'iteritems', initval.__iter__)():
+ self[key] = val
+ self.unclean_dirs.append(self.work_dir)
+ super(FSDict, self).__init__(*args, **kwargs)
+
+ def __setitem__(self, key, val, *args, **kwargs):
+ pickle.dump(val, open(self.work_dir/key, 'w'))
+
+ def __getitem__(self, key, *args, **kwargs):
+ return pickle.load(open(self.work_dir/key, 'r'))
+
+ def __repr__(self):
+ """
+ a hardline list of everything in the dict. may be long.
+ """
+ return repr(dict([(k, v) for k, v in self.iteritems()]))
+
+ def __str__(self):
+ """
+ str is truncated somewhat.
+ """
+ if len(self.keys()):
+ return '{' + repr(self.keys()[0]) + ':' + repr(self[self.keys()[0]]) + ', ...'
+ else:
+ return super(FSDict, self).__str__()
+
+ def keys(self, *args, **kwargs):
+ return [key for key in self.iterkeys()]
+
+ def iterkeys(self, *args, **kwargs):
+ for f in self.work_dir.files():
+ yield str(self.work_dir.relpathto(f))
+
+ def iteritems(self):
+ for key in self.iterkeys():
+ yield key, self[key]
+
+ def itervalues(self):
+ for key in self.iterkeys():
+ yield self[key]
+
+ def __delitem__(self, key, *args, **kwargs):
+ (self.work_dir/key).unlink()
+
+ def values(self, *args, **kwargs):
+ return [self[key] for key in self.keys()]
+
+ def cleanup(self):
+ self.work_dir.rmtree()
+
+ @classmethod
+ def cleanup_all(cls):
+ for fsd in cls.unclean_dirs:
+ try:
+ fsd.rmtree()
+ except OSError:
+ pass
+
+ def move(self, new_dir):
+
+ try:
+ self.work_dir.move(new_dir)
+ except Exception, e:
+ raise
+ else:
+ self.work_dir = new_dir
+
+ def __eq__(self, other):
+ """
+ when compared to a dict, equate equal if all keys and vals are equal
+ note, this is potentially expensive.
+ """
+ #duck type our way to sanity:
+ if not hasattr(other, 'keys'): return False
+ #OK, it's a dict-ish thing
+ try:
+ return all([self[key]==other[key] for key in other]) and \
+ len(self.keys())==len(other.keys())
+ except KeyError:
+ return False
\ No newline at end of file
--- /dev/null
+""" path.py - An object representing a path to a file or directory.\r
+\r
+Example:\r
+\r
+from path import path\r
+d = path('/home/guido/bin')\r
+for f in d.files('*.py'):\r
+ f.chmod(0755)\r
+\r
+This module requires Python 2.2 or later.\r
+\r
+\r
+URL: http://www.jorendorff.com/articles/python/path\r
+Author: Jason Orendorff <jason.orendorff\x40gmail\x2ecom> (and others - see the url!)\r
+Date: 9 Mar 2007\r
+"""\r
+\r
+\r
+# TODO\r
+# - Tree-walking functions don't avoid symlink loops. Matt Harrison\r
+# sent me a patch for this.\r
+# - Bug in write_text(). It doesn't support Universal newline mode.\r
+# - Better error message in listdir() when self isn't a\r
+# directory. (On Windows, the error message really sucks.)\r
+# - Make sure everything has a good docstring.\r
+# - Add methods for regex find and replace.\r
+# - guess_content_type() method?\r
+# - Perhaps support arguments to touch().\r
+\r
+from __future__ import generators\r
+\r
+import sys, warnings, os, fnmatch, glob, shutil, codecs, md5\r
+\r
+__version__ = '2.2'\r
+__all__ = ['path']\r
+\r
+# Platform-specific support for path.owner\r
+if os.name == 'nt':\r
+ try:\r
+ import win32security\r
+ except ImportError:\r
+ win32security = None\r
+else:\r
+ try:\r
+ import pwd\r
+ except ImportError:\r
+ pwd = None\r
+\r
+# Pre-2.3 support. Are unicode filenames supported?\r
+_base = str\r
+_getcwd = os.getcwd\r
+try:\r
+ if os.path.supports_unicode_filenames:\r
+ _base = unicode\r
+ _getcwd = os.getcwdu\r
+except AttributeError:\r
+ pass\r
+\r
+# Pre-2.3 workaround for booleans\r
+try:\r
+ True, False\r
+except NameError:\r
+ True, False = 1, 0\r
+\r
+# Pre-2.3 workaround for basestring.\r
+try:\r
+ basestring\r
+except NameError:\r
+ basestring = (str, unicode)\r
+\r
+# Universal newline support\r
+_textmode = 'r'\r
+if hasattr(file, 'newlines'):\r
+ _textmode = 'U'\r
+\r
+\r
+class TreeWalkWarning(Warning):\r
+ pass\r
+\r
+class path(_base):\r
+ """ Represents a filesystem path.\r
+\r
+ For documentation on individual methods, consult their\r
+ counterparts in os.path.\r
+ """\r
+\r
+ # --- Special Python methods.\r
+\r
+ def __repr__(self):\r
+ return 'path(%s)' % _base.__repr__(self)\r
+\r
+ # Adding a path and a string yields a path.\r
+ def __add__(self, more):\r
+ try:\r
+ resultStr = _base.__add__(self, more)\r
+ except TypeError: #Python bug\r
+ resultStr = NotImplemented\r
+ if resultStr is NotImplemented:\r
+ return resultStr\r
+ return self.__class__(resultStr)\r
+\r
+ def __radd__(self, other):\r
+ if isinstance(other, basestring):\r
+ return self.__class__(other.__add__(self))\r
+ else:\r
+ return NotImplemented\r
+\r
+ # The / operator joins paths.\r
+ def __div__(self, rel):\r
+ """ fp.__div__(rel) == fp / rel == fp.joinpath(rel)\r
+\r
+ Join two path components, adding a separator character if\r
+ needed.\r
+ """\r
+ return self.__class__(os.path.join(self, rel))\r
+\r
+ # Make the / operator work even when true division is enabled.\r
+ __truediv__ = __div__\r
+\r
+ def getcwd(cls):\r
+ """ Return the current working directory as a path object. """\r
+ return cls(_getcwd())\r
+ getcwd = classmethod(getcwd)\r
+\r
+\r
+ # --- Operations on path strings.\r
+\r
+ isabs = os.path.isabs\r
+ def abspath(self): return self.__class__(os.path.abspath(self))\r
+ def normcase(self): return self.__class__(os.path.normcase(self))\r
+ def normpath(self): return self.__class__(os.path.normpath(self))\r
+ def realpath(self): return self.__class__(os.path.realpath(self))\r
+ def expanduser(self): return self.__class__(os.path.expanduser(self))\r
+ def expandvars(self): return self.__class__(os.path.expandvars(self))\r
+ def dirname(self): return self.__class__(os.path.dirname(self))\r
+ basename = os.path.basename\r
+\r
+ def expand(self):\r
+ """ Clean up a filename by calling expandvars(),\r
+ expanduser(), and normpath() on it.\r
+\r
+ This is commonly everything needed to clean up a filename\r
+ read from a configuration file, for example.\r
+ """\r
+ return self.expandvars().expanduser().normpath()\r
+\r
+ def _get_namebase(self):\r
+ base, ext = os.path.splitext(self.name)\r
+ return base\r
+\r
+ def _get_ext(self):\r
+ f, ext = os.path.splitext(_base(self))\r
+ return ext\r
+\r
+ def _get_drive(self):\r
+ drive, r = os.path.splitdrive(self)\r
+ return self.__class__(drive)\r
+\r
+ parent = property(\r
+ dirname, None, None,\r
+ """ This path's parent directory, as a new path object.\r
+\r
+ For example, path('/usr/local/lib/libpython.so').parent == path('/usr/local/lib')\r
+ """)\r
+\r
+ name = property(\r
+ basename, None, None,\r
+ """ The name of this file or directory without the full path.\r
+\r
+ For example, path('/usr/local/lib/libpython.so').name == 'libpython.so'\r
+ """)\r
+\r
+ namebase = property(\r
+ _get_namebase, None, None,\r
+ """ The same as path.name, but with one file extension stripped off.\r
+\r
+ For example, path('/home/guido/python.tar.gz').name == 'python.tar.gz',\r
+ but path('/home/guido/python.tar.gz').namebase == 'python.tar'\r
+ """)\r
+\r
+ ext = property(\r
+ _get_ext, None, None,\r
+ """ The file extension, for example '.py'. """)\r
+\r
+ drive = property(\r
+ _get_drive, None, None,\r
+ """ The drive specifier, for example 'C:'.\r
+ This is always empty on systems that don't use drive specifiers.\r
+ """)\r
+\r
+ def splitpath(self):\r
+ """ p.splitpath() -> Return (p.parent, p.name). """\r
+ parent, child = os.path.split(self)\r
+ return self.__class__(parent), child\r
+\r
+ def splitdrive(self):\r
+ """ p.splitdrive() -> Return (p.drive, <the rest of p>).\r
+\r
+ Split the drive specifier from this path. If there is\r
+ no drive specifier, p.drive is empty, so the return value\r
+ is simply (path(''), p). This is always the case on Unix.\r
+ """\r
+ drive, rel = os.path.splitdrive(self)\r
+ return self.__class__(drive), rel\r
+\r
+ def splitext(self):\r
+ """ p.splitext() -> Return (p.stripext(), p.ext).\r
+\r
+ Split the filename extension from this path and return\r
+ the two parts. Either part may be empty.\r
+\r
+ The extension is everything from '.' to the end of the\r
+ last path segment. This has the property that if\r
+ (a, b) == p.splitext(), then a + b == p.\r
+ """\r
+ filename, ext = os.path.splitext(self)\r
+ return self.__class__(filename), ext\r
+\r
+ def stripext(self):\r
+ """ p.stripext() -> Remove one file extension from the path.\r
+\r
+ For example, path('/home/guido/python.tar.gz').stripext()\r
+ returns path('/home/guido/python.tar').\r
+ """\r
+ return self.splitext()[0]\r
+\r
+ if hasattr(os.path, 'splitunc'):\r
+ def splitunc(self):\r
+ unc, rest = os.path.splitunc(self)\r
+ return self.__class__(unc), rest\r
+\r
+ def _get_uncshare(self):\r
+ unc, r = os.path.splitunc(self)\r
+ return self.__class__(unc)\r
+\r
+ uncshare = property(\r
+ _get_uncshare, None, None,\r
+ """ The UNC mount point for this path.\r
+ This is empty for paths on local drives. """)\r
+\r
+ def joinpath(self, *args):\r
+ """ Join two or more path components, adding a separator\r
+ character (os.sep) if needed. Returns a new path\r
+ object.\r
+ """\r
+ return self.__class__(os.path.join(self, *args))\r
+\r
+ def splitall(self):\r
+ r""" Return a list of the path components in this path.\r
+\r
+ The first item in the list will be a path. Its value will be\r
+ either os.curdir, os.pardir, empty, or the root directory of\r
+ this path (for example, '/' or 'C:\\'). The other items in\r
+ the list will be strings.\r
+\r
+ path.path.joinpath(*result) will yield the original path.\r
+ """\r
+ parts = []\r
+ loc = self\r
+ while loc != os.curdir and loc != os.pardir:\r
+ prev = loc\r
+ loc, child = prev.splitpath()\r
+ if loc == prev:\r
+ break\r
+ parts.append(child)\r
+ parts.append(loc)\r
+ parts.reverse()\r
+ return parts\r
+\r
+ def relpath(self):\r
+ """ Return this path as a relative path,\r
+ based from the current working directory.\r
+ """\r
+ cwd = self.__class__(os.getcwd())\r
+ return cwd.relpathto(self)\r
+\r
+ def relpathto(self, dest):\r
+ """ Return a relative path from self to dest.\r
+\r
+ If there is no relative path from self to dest, for example if\r
+ they reside on different drives in Windows, then this returns\r
+ dest.abspath().\r
+ """\r
+ origin = self.abspath()\r
+ dest = self.__class__(dest).abspath()\r
+\r
+ orig_list = origin.normcase().splitall()\r
+ # Don't normcase dest! We want to preserve the case.\r
+ dest_list = dest.splitall()\r
+\r
+ if orig_list[0] != os.path.normcase(dest_list[0]):\r
+ # Can't get here from there.\r
+ return dest\r
+\r
+ # Find the location where the two paths start to differ.\r
+ i = 0\r
+ for start_seg, dest_seg in zip(orig_list, dest_list):\r
+ if start_seg != os.path.normcase(dest_seg):\r
+ break\r
+ i += 1\r
+\r
+ # Now i is the point where the two paths diverge.\r
+ # Need a certain number of "os.pardir"s to work up\r
+ # from the origin to the point of divergence.\r
+ segments = [os.pardir] * (len(orig_list) - i)\r
+ # Need to add the diverging part of dest_list.\r
+ segments += dest_list[i:]\r
+ if len(segments) == 0:\r
+ # If they happen to be identical, use os.curdir.\r
+ relpath = os.curdir\r
+ else:\r
+ relpath = os.path.join(*segments)\r
+ return self.__class__(relpath)\r
+\r
+ # --- Listing, searching, walking, and matching\r
+\r
+ def listdir(self, pattern=None):\r
+ """ D.listdir() -> List of items in this directory.\r
+\r
+ Use D.files() or D.dirs() instead if you want a listing\r
+ of just files or just subdirectories.\r
+\r
+ The elements of the list are path objects.\r
+\r
+ With the optional 'pattern' argument, this only lists\r
+ items whose names match the given pattern.\r
+ """\r
+ names = os.listdir(self)\r
+ if pattern is not None:\r
+ names = fnmatch.filter(names, pattern)\r
+ return [self / child for child in names]\r
+\r
+ def dirs(self, pattern=None):\r
+ """ D.dirs() -> List of this directory's subdirectories.\r
+\r
+ The elements of the list are path objects.\r
+ This does not walk recursively into subdirectories\r
+ (but see path.walkdirs).\r
+\r
+ With the optional 'pattern' argument, this only lists\r
+ directories whose names match the given pattern. For\r
+ example, d.dirs('build-*').\r
+ """\r
+ return [p for p in self.listdir(pattern) if p.isdir()]\r
+\r
+ def files(self, pattern=None):\r
+ """ D.files() -> List of the files in this directory.\r
+\r
+ The elements of the list are path objects.\r
+ This does not walk into subdirectories (see path.walkfiles).\r
+\r
+ With the optional 'pattern' argument, this only lists files\r
+ whose names match the given pattern. For example,\r
+ d.files('*.pyc').\r
+ """\r
+ \r
+ return [p for p in self.listdir(pattern) if p.isfile()]\r
+\r
+ def walk(self, pattern=None, errors='strict'):\r
+ """ D.walk() -> iterator over files and subdirs, recursively.\r
+\r
+ The iterator yields path objects naming each child item of\r
+ this directory and its descendants. This requires that\r
+ D.isdir().\r
+\r
+ This performs a depth-first traversal of the directory tree.\r
+ Each directory is returned just before all its children.\r
+\r
+ The errors= keyword argument controls behavior when an\r
+ error occurs. The default is 'strict', which causes an\r
+ exception. The other allowed values are 'warn', which\r
+ reports the error via warnings.warn(), and 'ignore'.\r
+ """\r
+ if errors not in ('strict', 'warn', 'ignore'):\r
+ raise ValueError("invalid errors parameter")\r
+\r
+ try:\r
+ childList = self.listdir()\r
+ except Exception:\r
+ if errors == 'ignore':\r
+ return\r
+ elif errors == 'warn':\r
+ warnings.warn(\r
+ "Unable to list directory '%s': %s"\r
+ % (self, sys.exc_info()[1]),\r
+ TreeWalkWarning)\r
+ return\r
+ else:\r
+ raise\r
+\r
+ for child in childList:\r
+ if pattern is None or child.fnmatch(pattern):\r
+ yield child\r
+ try:\r
+ isdir = child.isdir()\r
+ except Exception:\r
+ if errors == 'ignore':\r
+ isdir = False\r
+ elif errors == 'warn':\r
+ warnings.warn(\r
+ "Unable to access '%s': %s"\r
+ % (child, sys.exc_info()[1]),\r
+ TreeWalkWarning)\r
+ isdir = False\r
+ else:\r
+ raise\r
+\r
+ if isdir:\r
+ for item in child.walk(pattern, errors):\r
+ yield item\r
+\r
+ def walkdirs(self, pattern=None, errors='strict'):\r
+ """ D.walkdirs() -> iterator over subdirs, recursively.\r
+\r
+ With the optional 'pattern' argument, this yields only\r
+ directories whose names match the given pattern. For\r
+ example, mydir.walkdirs('*test') yields only directories\r
+ with names ending in 'test'.\r
+\r
+ The errors= keyword argument controls behavior when an\r
+ error occurs. The default is 'strict', which causes an\r
+ exception. The other allowed values are 'warn', which\r
+ reports the error via warnings.warn(), and 'ignore'.\r
+ """\r
+ if errors not in ('strict', 'warn', 'ignore'):\r
+ raise ValueError("invalid errors parameter")\r
+\r
+ try:\r
+ dirs = self.dirs()\r
+ except Exception:\r
+ if errors == 'ignore':\r
+ return\r
+ elif errors == 'warn':\r
+ warnings.warn(\r
+ "Unable to list directory '%s': %s"\r
+ % (self, sys.exc_info()[1]),\r
+ TreeWalkWarning)\r
+ return\r
+ else:\r
+ raise\r
+\r
+ for child in dirs:\r
+ if pattern is None or child.fnmatch(pattern):\r
+ yield child\r
+ for subsubdir in child.walkdirs(pattern, errors):\r
+ yield subsubdir\r
+\r
+ def walkfiles(self, pattern=None, errors='strict'):\r
+ """ D.walkfiles() -> iterator over files in D, recursively.\r
+\r
+ The optional argument, pattern, limits the results to files\r
+ with names that match the pattern. For example,\r
+ mydir.walkfiles('*.tmp') yields only files with the .tmp\r
+ extension.\r
+ """\r
+ if errors not in ('strict', 'warn', 'ignore'):\r
+ raise ValueError("invalid errors parameter")\r
+\r
+ try:\r
+ childList = self.listdir()\r
+ except Exception:\r
+ if errors == 'ignore':\r
+ return\r
+ elif errors == 'warn':\r
+ warnings.warn(\r
+ "Unable to list directory '%s': %s"\r
+ % (self, sys.exc_info()[1]),\r
+ TreeWalkWarning)\r
+ return\r
+ else:\r
+ raise\r
+\r
+ for child in childList:\r
+ try:\r
+ isfile = child.isfile()\r
+ isdir = not isfile and child.isdir()\r
+ except:\r
+ if errors == 'ignore':\r
+ continue\r
+ elif errors == 'warn':\r
+ warnings.warn(\r
+ "Unable to access '%s': %s"\r
+ % (self, sys.exc_info()[1]),\r
+ TreeWalkWarning)\r
+ continue\r
+ else:\r
+ raise\r
+\r
+ if isfile:\r
+ if pattern is None or child.fnmatch(pattern):\r
+ yield child\r
+ elif isdir:\r
+ for f in child.walkfiles(pattern, errors):\r
+ yield f\r
+\r
+ def fnmatch(self, pattern):\r
+ """ Return True if self.name matches the given pattern.\r
+\r
+ pattern - A filename pattern with wildcards,\r
+ for example '*.py'.\r
+ """\r
+ return fnmatch.fnmatch(self.name, pattern)\r
+\r
+ def glob(self, pattern):\r
+ """ Return a list of path objects that match the pattern.\r
+\r
+ pattern - a path relative to this directory, with wildcards.\r
+\r
+ For example, path('/users').glob('*/bin/*') returns a list\r
+ of all the files users have in their bin directories.\r
+ """\r
+ cls = self.__class__\r
+ return [cls(s) for s in glob.glob(_base(self / pattern))]\r
+\r
+\r
+ # --- Reading or writing an entire file at once.\r
+\r
+ def open(self, mode='r'):\r
+ """ Open this file. Return a file object. """\r
+ return file(self, mode)\r
+\r
+ def bytes(self):\r
+ """ Open this file, read all bytes, return them as a string. """\r
+ f = self.open('rb')\r
+ try:\r
+ return f.read()\r
+ finally:\r
+ f.close()\r
+\r
+ def write_bytes(self, bytes, append=False):\r
+ """ Open this file and write the given bytes to it.\r
+\r
+ Default behavior is to overwrite any existing file.\r
+ Call p.write_bytes(bytes, append=True) to append instead.\r
+ """\r
+ if append:\r
+ mode = 'ab'\r
+ else:\r
+ mode = 'wb'\r
+ f = self.open(mode)\r
+ try:\r
+ f.write(bytes)\r
+ finally:\r
+ f.close()\r
+\r
+ def text(self, encoding=None, errors='strict'):\r
+ r""" Open this file, read it in, return the content as a string.\r
+\r
+ This uses 'U' mode in Python 2.3 and later, so '\r\n' and '\r'\r
+ are automatically translated to '\n'.\r
+\r
+ Optional arguments:\r
+\r
+ encoding - The Unicode encoding (or character set) of\r
+ the file. If present, the content of the file is\r
+ decoded and returned as a unicode object; otherwise\r
+ it is returned as an 8-bit str.\r
+ errors - How to handle Unicode errors; see help(str.decode)\r
+ for the options. Default is 'strict'.\r
+ """\r
+ if encoding is None:\r
+ # 8-bit\r
+ f = self.open(_textmode)\r
+ try:\r
+ return f.read()\r
+ finally:\r
+ f.close()\r
+ else:\r
+ # Unicode\r
+ f = codecs.open(self, 'r', encoding, errors)\r
+ # (Note - Can't use 'U' mode here, since codecs.open\r
+ # doesn't support 'U' mode, even in Python 2.3.)\r
+ try:\r
+ t = f.read()\r
+ finally:\r
+ f.close()\r
+ return (t.replace(u'\r\n', u'\n')\r
+ .replace(u'\r\x85', u'\n')\r
+ .replace(u'\r', u'\n')\r
+ .replace(u'\x85', u'\n')\r
+ .replace(u'\u2028', u'\n'))\r
+\r
+ def write_text(self, text, encoding=None, errors='strict', linesep=os.linesep, append=False):\r
+ r""" Write the given text to this file.\r
+\r
+ The default behavior is to overwrite any existing file;\r
+ to append instead, use the 'append=True' keyword argument.\r
+\r
+ There are two differences between path.write_text() and\r
+ path.write_bytes(): newline handling and Unicode handling.\r
+ See below.\r
+\r
+ Parameters:\r
+\r
+ - text - str/unicode - The text to be written.\r
+\r
+ - encoding - str - The Unicode encoding that will be used.\r
+ This is ignored if 'text' isn't a Unicode string.\r
+\r
+ - errors - str - How to handle Unicode encoding errors.\r
+ Default is 'strict'. See help(unicode.encode) for the\r
+ options. This is ignored if 'text' isn't a Unicode\r
+ string.\r
+\r
+ - linesep - keyword argument - str/unicode - The sequence of\r
+ characters to be used to mark end-of-line. The default is\r
+ os.linesep. You can also specify None; this means to\r
+ leave all newlines as they are in 'text'.\r
+\r
+ - append - keyword argument - bool - Specifies what to do if\r
+ the file already exists (True: append to the end of it;\r
+ False: overwrite it.) The default is False.\r
+\r
+\r
+ --- Newline handling.\r
+\r
+ write_text() converts all standard end-of-line sequences\r
+ ('\n', '\r', and '\r\n') to your platform's default end-of-line\r
+ sequence (see os.linesep; on Windows, for example, the\r
+ end-of-line marker is '\r\n').\r
+\r
+ If you don't like your platform's default, you can override it\r
+ using the 'linesep=' keyword argument. If you specifically want\r
+ write_text() to preserve the newlines as-is, use 'linesep=None'.\r
+\r
+ This applies to Unicode text the same as to 8-bit text, except\r
+ there are three additional standard Unicode end-of-line sequences:\r
+ u'\x85', u'\r\x85', and u'\u2028'.\r
+\r
+ (This is slightly different from when you open a file for\r
+ writing with fopen(filename, "w") in C or file(filename, 'w')\r
+ in Python.)\r
+\r
+\r
+ --- Unicode\r
+\r
+ If 'text' isn't Unicode, then apart from newline handling, the\r
+ bytes are written verbatim to the file. The 'encoding' and\r
+ 'errors' arguments are not used and must be omitted.\r
+\r
+ If 'text' is Unicode, it is first converted to bytes using the\r
+ specified 'encoding' (or the default encoding if 'encoding'\r
+ isn't specified). The 'errors' argument applies only to this\r
+ conversion.\r
+\r
+ """\r
+ if isinstance(text, unicode):\r
+ if linesep is not None:\r
+ # Convert all standard end-of-line sequences to\r
+ # ordinary newline characters.\r
+ text = (text.replace(u'\r\n', u'\n')\r
+ .replace(u'\r\x85', u'\n')\r
+ .replace(u'\r', u'\n')\r
+ .replace(u'\x85', u'\n')\r
+ .replace(u'\u2028', u'\n'))\r
+ text = text.replace(u'\n', linesep)\r
+ if encoding is None:\r
+ encoding = sys.getdefaultencoding()\r
+ bytes = text.encode(encoding, errors)\r
+ else:\r
+ # It is an error to specify an encoding if 'text' is\r
+ # an 8-bit string.\r
+ assert encoding is None\r
+\r
+ if linesep is not None:\r
+ text = (text.replace('\r\n', '\n')\r
+ .replace('\r', '\n'))\r
+ bytes = text.replace('\n', linesep)\r
+\r
+ self.write_bytes(bytes, append)\r
+\r
+ def lines(self, encoding=None, errors='strict', retain=True):\r
+ r""" Open this file, read all lines, return them in a list.\r
+\r
+ Optional arguments:\r
+ encoding - The Unicode encoding (or character set) of\r
+ the file. The default is None, meaning the content\r
+ of the file is read as 8-bit characters and returned\r
+ as a list of (non-Unicode) str objects.\r
+ errors - How to handle Unicode errors; see help(str.decode)\r
+ for the options. Default is 'strict'\r
+ retain - If true, retain newline characters; but all newline\r
+ character combinations ('\r', '\n', '\r\n') are\r
+ translated to '\n'. If false, newline characters are\r
+ stripped off. Default is True.\r
+\r
+ This uses 'U' mode in Python 2.3 and later.\r
+ """\r
+ if encoding is None and retain:\r
+ f = self.open(_textmode)\r
+ try:\r
+ return f.readlines()\r
+ finally:\r
+ f.close()\r
+ else:\r
+ return self.text(encoding, errors).splitlines(retain)\r
+\r
+ def write_lines(self, lines, encoding=None, errors='strict',\r
+ linesep=os.linesep, append=False):\r
+ r""" Write the given lines of text to this file.\r
+\r
+ By default this overwrites any existing file at this path.\r
+\r
+ This puts a platform-specific newline sequence on every line.\r
+ See 'linesep' below.\r
+\r
+ lines - A list of strings.\r
+\r
+ encoding - A Unicode encoding to use. This applies only if\r
+ 'lines' contains any Unicode strings.\r
+\r
+ errors - How to handle errors in Unicode encoding. This\r
+ also applies only to Unicode strings.\r
+\r
+ linesep - The desired line-ending. This line-ending is\r
+ applied to every line. If a line already has any\r
+ standard line ending ('\r', '\n', '\r\n', u'\x85',\r
+ u'\r\x85', u'\u2028'), that will be stripped off and\r
+ this will be used instead. The default is os.linesep,\r
+ which is platform-dependent ('\r\n' on Windows, '\n' on\r
+ Unix, etc.) Specify None to write the lines as-is,\r
+ like file.writelines().\r
+\r
+ Use the keyword argument append=True to append lines to the\r
+ file. The default is to overwrite the file. Warning:\r
+ When you use this with Unicode data, if the encoding of the\r
+ existing data in the file is different from the encoding\r
+ you specify with the encoding= parameter, the result is\r
+ mixed-encoding data, which can really confuse someone trying\r
+ to read the file later.\r
+ """\r
+ if append:\r
+ mode = 'ab'\r
+ else:\r
+ mode = 'wb'\r
+ f = self.open(mode)\r
+ try:\r
+ for line in lines:\r
+ isUnicode = isinstance(line, unicode)\r
+ if linesep is not None:\r
+ # Strip off any existing line-end and add the\r
+ # specified linesep string.\r
+ if isUnicode:\r
+ if line[-2:] in (u'\r\n', u'\x0d\x85'):\r
+ line = line[:-2]\r
+ elif line[-1:] in (u'\r', u'\n',\r
+ u'\x85', u'\u2028'):\r
+ line = line[:-1]\r
+ else:\r
+ if line[-2:] == '\r\n':\r
+ line = line[:-2]\r
+ elif line[-1:] in ('\r', '\n'):\r
+ line = line[:-1]\r
+ line += linesep\r
+ if isUnicode:\r
+ if encoding is None:\r
+ encoding = sys.getdefaultencoding()\r
+ line = line.encode(encoding, errors)\r
+ f.write(line)\r
+ finally:\r
+ f.close()\r
+\r
+ def read_md5(self):\r
+ """ Calculate the md5 hash for this file.\r
+\r
+ This reads through the entire file.\r
+ """\r
+ f = self.open('rb')\r
+ try:\r
+ m = md5.new()\r
+ while True:\r
+ d = f.read(8192)\r
+ if not d:\r
+ break\r
+ m.update(d)\r
+ finally:\r
+ f.close()\r
+ return m.digest()\r
+\r
+ # --- Methods for querying the filesystem.\r
+\r
+ exists = os.path.exists\r
+ isdir = os.path.isdir\r
+ isfile = os.path.isfile\r
+ islink = os.path.islink\r
+ ismount = os.path.ismount\r
+\r
+ if hasattr(os.path, 'samefile'):\r
+ samefile = os.path.samefile\r
+\r
+ getatime = os.path.getatime\r
+ atime = property(\r
+ getatime, None, None,\r
+ """ Last access time of the file. """)\r
+\r
+ getmtime = os.path.getmtime\r
+ mtime = property(\r
+ getmtime, None, None,\r
+ """ Last-modified time of the file. """)\r
+\r
+ if hasattr(os.path, 'getctime'):\r
+ getctime = os.path.getctime\r
+ ctime = property(\r
+ getctime, None, None,\r
+ """ Creation time of the file. """)\r
+\r
+ getsize = os.path.getsize\r
+ size = property(\r
+ getsize, None, None,\r
+ """ Size of the file, in bytes. """)\r
+\r
+ if hasattr(os, 'access'):\r
+ def access(self, mode):\r
+ """ Return true if current user has access to this path.\r
+\r
+ mode - One of the constants os.F_OK, os.R_OK, os.W_OK, os.X_OK\r
+ """\r
+ return os.access(self, mode)\r
+\r
+ def stat(self):\r
+ """ Perform a stat() system call on this path. """\r
+ return os.stat(self)\r
+\r
+ def lstat(self):\r
+ """ Like path.stat(), but do not follow symbolic links. """\r
+ return os.lstat(self)\r
+\r
+ def get_owner(self):\r
+ r""" Return the name of the owner of this file or directory.\r
+\r
+ This follows symbolic links.\r
+\r
+ On Windows, this returns a name of the form ur'DOMAIN\User Name'.\r
+ On Windows, a group can own a file or directory.\r
+ """\r
+ if os.name == 'nt':\r
+ if win32security is None:\r
+ raise Exception("path.owner requires win32all to be installed")\r
+ desc = win32security.GetFileSecurity(\r
+ self, win32security.OWNER_SECURITY_INFORMATION)\r
+ sid = desc.GetSecurityDescriptorOwner()\r
+ account, domain, typecode = win32security.LookupAccountSid(None, sid)\r
+ return domain + u'\\' + account\r
+ else:\r
+ if pwd is None:\r
+ raise NotImplementedError("path.owner is not implemented on this platform.")\r
+ st = self.stat()\r
+ return pwd.getpwuid(st.st_uid).pw_name\r
+\r
+ owner = property(\r
+ get_owner, None, None,\r
+ """ Name of the owner of this file or directory. """)\r
+\r
+ if hasattr(os, 'statvfs'):\r
+ def statvfs(self):\r
+ """ Perform a statvfs() system call on this path. """\r
+ return os.statvfs(self)\r
+\r
+ if hasattr(os, 'pathconf'):\r
+ def pathconf(self, name):\r
+ return os.pathconf(self, name)\r
+\r
+\r
+ # --- Modifying operations on files and directories\r
+\r
+ def utime(self, times):\r
+ """ Set the access and modified times of this file. """\r
+ os.utime(self, times)\r
+\r
+ def chmod(self, mode):\r
+ os.chmod(self, mode)\r
+\r
+ if hasattr(os, 'chown'):\r
+ def chown(self, uid, gid):\r
+ os.chown(self, uid, gid)\r
+\r
+ def rename(self, new):\r
+ os.rename(self, new)\r
+\r
+ def renames(self, new):\r
+ os.renames(self, new)\r
+\r
+\r
+ # --- Create/delete operations on directories\r
+\r
+ def mkdir(self, mode=0777):\r
+ os.mkdir(self, mode)\r
+\r
+ def makedirs(self, mode=0777):\r
+ os.makedirs(self, mode)\r
+\r
+ def rmdir(self):\r
+ os.rmdir(self)\r
+\r
+ def removedirs(self):\r
+ os.removedirs(self)\r
+\r
+\r
+ # --- Modifying operations on files\r
+\r
+ def touch(self):\r
+ """ Set the access/modified times of this file to the current time.\r
+ Create the file if it does not exist.\r
+ """\r
+ fd = os.open(self, os.O_WRONLY | os.O_CREAT, 0666)\r
+ os.close(fd)\r
+ os.utime(self, None)\r
+\r
+ def remove(self):\r
+ os.remove(self)\r
+\r
+ def unlink(self):\r
+ os.unlink(self)\r
+\r
+\r
+ # --- Links\r
+\r
+ if hasattr(os, 'link'):\r
+ def link(self, newpath):\r
+ """ Create a hard link at 'newpath', pointing to this file. """\r
+ os.link(self, newpath)\r
+\r
+ if hasattr(os, 'symlink'):\r
+ def symlink(self, newlink):\r
+ """ Create a symbolic link at 'newlink', pointing here. """\r
+ os.symlink(self, newlink)\r
+\r
+ if hasattr(os, 'readlink'):\r
+ def readlink(self):\r
+ """ Return the path to which this symbolic link points.\r
+\r
+ The result may be an absolute or a relative path.\r
+ """\r
+ return self.__class__(os.readlink(self))\r
+\r
+ def readlinkabs(self):\r
+ """ Return the path to which this symbolic link points.\r
+\r
+ The result is always an absolute path.\r
+ """\r
+ p = self.readlink()\r
+ if p.isabs():\r
+ return p\r
+ else:\r
+ return (self.parent / p).abspath()\r
+\r
+\r
+ # --- High-level functions from shutil\r
+\r
+ copyfile = shutil.copyfile\r
+ copymode = shutil.copymode\r
+ copystat = shutil.copystat\r
+ copy = shutil.copy\r
+ copy2 = shutil.copy2\r
+ copytree = shutil.copytree\r
+ if hasattr(shutil, 'move'):\r
+ move = shutil.move\r
+ rmtree = shutil.rmtree\r
+\r
+\r
+ # --- Special stuff from os\r
+\r
+ if hasattr(os, 'chroot'):\r
+ def chroot(self):\r
+ os.chroot(self)\r
+\r
+ if hasattr(os, 'startfile'):\r
+ def startfile(self):\r
+ os.startfile(self)\r
+\r