summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJacob Mason <jacoblmason@gmail.com>2010-07-08 21:35:19 -0500
committerJacob Mason <jacoblmason@gmail.com>2010-07-08 21:35:19 -0500
commit03056a8a31062034edb88521cafb2fa66238076f (patch)
treec5ab504e42aa111e264ecd6a7aba635265cc3196
parentac262bb012458987adcb25941c845578232bd2ca (diff)
downloadsphinx-git-03056a8a31062034edb88521cafb2fa66238076f.tar.gz
Basic comment system.
-rw-r--r--sphinx/builders/websupport.py2
-rw-r--r--sphinx/websupport/api.py37
-rw-r--r--sphinx/websupport/comments/__init__.py106
-rw-r--r--sphinx/websupport/comments/db.py53
-rw-r--r--sphinx/writers/websupport.py16
5 files changed, 204 insertions, 10 deletions
diff --git a/sphinx/builders/websupport.py b/sphinx/builders/websupport.py
index ddf525c4b..e2caeccd1 100644
--- a/sphinx/builders/websupport.py
+++ b/sphinx/builders/websupport.py
@@ -28,7 +28,7 @@ class WebSupportBuilder(StandaloneHTMLBuilder):
def write_doc(self, docname, doctree):
# The translator needs the docname to generate ids.
- self.docname = docname
+ self.cur_docname = docname
StandaloneHTMLBuilder.write_doc(self, docname, doctree)
def get_target_uri(self, docname, typ=None):
diff --git a/sphinx/websupport/api.py b/sphinx/websupport/api.py
index 37f590971..b8274f52c 100644
--- a/sphinx/websupport/api.py
+++ b/sphinx/websupport/api.py
@@ -11,26 +11,46 @@
import cPickle as pickle
from os import path
+from datetime import datetime
from jinja2 import Environment, FileSystemLoader
from sphinx.application import Sphinx
+from sphinx.util.osutil import ensuredir
from sphinx.websupport.search import search_adapters
+from sphinx.websupport import comments as sphinxcomments
class WebSupportApp(Sphinx):
def __init__(self, *args, **kwargs):
self.search = kwargs.pop('search', None)
+ self.comments = kwargs.pop('comments', None)
Sphinx.__init__(self, *args, **kwargs)
class WebSupport(object):
- def __init__(self, srcdir='', outdir='', search=None):
+ def __init__(self, srcdir='', outdir='', search=None,
+ comments=None):
self.srcdir = srcdir
self.outdir = outdir or path.join(self.srcdir, '_build',
'websupport')
- self.init_templating()
+ self.init_templating()
if search is not None:
self.init_search(search)
+ self.init_comments(comments)
+
+ def init_comments(self, comments):
+ if isinstance(comments, sphinxcomments.CommentBackend):
+ self.comments = comments
+ elif comments is not None:
+ # If a CommentBackend isn't provided, use the default
+ # SQLAlchemy backend with an SQLite db.
+ from sphinx.websupport.comments import SQLAlchemyComments
+ from sqlalchemy import create_engine
+ db_path = path.join(self.outdir, 'comments', 'comments.db')
+ ensuredir(path.dirname(db_path))
+ engine = create_engine('sqlite:///%s' % db_path)
+ self.comments = SQLAlchemyComments(engine)
+
def init_templating(self):
import sphinx
template_path = path.join(path.dirname(sphinx.__file__),
@@ -52,8 +72,11 @@ class WebSupport(object):
path.join(self.outdir, 'doctrees'))
app = WebSupportApp(self.srcdir, self.srcdir,
self.outdir, doctreedir, 'websupport',
- search=self.search)
+ search=self.search,
+ comments=self.comments)
+ self.comments.pre_build()
app.build()
+ self.comments.post_build()
def get_document(self, docname):
infilename = path.join(self.outdir, docname + '.fpickle')
@@ -70,3 +93,11 @@ class WebSupport(object):
document['body'] = self.results_template.render(ctx)
document['title'] = 'Search Results'
return document
+
+ def get_comments(self, node_id):
+ return self.comments.get_comments(node_id)
+
+ def add_comment(self, parent_id, text, displayed=True, user_id=None,
+ rating=0, time=None):
+ return self.comments.add_comment(parent_id, text, displayed, user_id,
+ rating, time)
diff --git a/sphinx/websupport/comments/__init__.py b/sphinx/websupport/comments/__init__.py
new file mode 100644
index 000000000..1876d4343
--- /dev/null
+++ b/sphinx/websupport/comments/__init__.py
@@ -0,0 +1,106 @@
+from datetime import datetime
+
+from sqlalchemy.orm import sessionmaker
+
+from sphinx.websupport.comments.db import Base, Node, Comment
+
+Session = sessionmaker()
+
+class CommentBackend(object):
+ def pre_build(self):
+ pass
+
+ def add_node(self, document, line, source, treeloc):
+ raise NotImplemented
+
+ def post_build(self):
+ pass
+
+ def add_comment(self, parent_id, text, displayed, user_id, rating, time):
+ raise NotImplemented
+
+ def get_comments(self, parent_id):
+ raise NotImplemented
+
+
+class SQLAlchemyComments(CommentBackend):
+ def __init__(self, engine):
+ self.engine = engine
+ Base.metadata.bind = engine
+ Base.metadata.create_all()
+ Session.configure(bind=engine)
+ self.session = Session()
+
+ def add_node(self, document, line, source, treeloc):
+ node = Node(document, line, source, treeloc)
+ self.session.add(node)
+ return node.id
+
+ def post_build(self):
+ self.session.commit()
+
+ def add_comment(self, parent_id, text, displayed, user_id, rating, time):
+ time = time or datetime.now()
+
+ id = parent_id[1:]
+ if parent_id[0] == 's':
+ node = self.session.query(Node).filter(Node.id == id).first()
+ comment = Comment(text, displayed, user_id, rating,
+ time, node=node)
+ elif parent_id[0] == 'c':
+ parent = self.session.query(Comment).filter(Comment.id == id).first()
+ comment = Comment(text, displayed, user_id, rating,
+ time, parent=parent)
+
+ self.session.add(comment)
+ self.session.commit()
+ return self.serializable(comment)
+
+ def get_comments(self, parent_id):
+ parent_id = parent_id[1:]
+ node = self.session.query(Node).filter(Node.id == parent_id).first()
+ comments = []
+ for comment in node.comments:
+ comments.append(self.serializable(comment))
+
+ return comments
+
+ def serializable(self, comment):
+ time = {'year': comment.time.year,
+ 'month': comment.time.month,
+ 'day': comment.time.day,
+ 'hour': comment.time.hour,
+ 'minute': comment.time.minute,
+ 'second': comment.time.second,
+ 'iso': comment.time.isoformat(),
+ 'delta': self.pretty_delta(comment)}
+
+ return {'text': comment.text,
+ 'user_id': comment.user_id,
+ 'id': comment.id,
+ 'rating': self.pretty_rating(comment),
+ 'time': time,
+ 'node': comment.node.id if comment.node else None,
+ 'parent': comment.parent.id if comment.parent else None,
+ 'children': [self.serializable(child)
+ for child in comment.children]}
+
+ def pretty_rating(self, comment):
+ if comment.rating == 1:
+ return '%s point' % comment.rating
+ else:
+ return '%s points' % comment.rating
+
+ def pretty_delta(self, comment):
+ delta = datetime.now() - comment.time
+ days = delta.days
+ seconds = delta.seconds
+ hours = seconds / 3600
+ minutes = seconds / 60
+
+ if days == 0:
+ dt = (minutes, 'minute') if hours == 0 else (hours, 'hour')
+ else:
+ dt = (days, 'day')
+
+ return '%s %s ago' % dt if dt[0] == 1 else '%s %ss ago' % dt
diff --git a/sphinx/websupport/comments/db.py b/sphinx/websupport/comments/db.py
new file mode 100644
index 000000000..ff731a9f1
--- /dev/null
+++ b/sphinx/websupport/comments/db.py
@@ -0,0 +1,53 @@
+from sqlalchemy import Column, Integer, Text, String, Boolean, ForeignKey,\
+DateTime
+from sqlalchemy.ext.declarative import declarative_base
+from sqlalchemy.orm import relation
+
+Base = declarative_base()
+
+db_prefix = 'sphinx_'
+
+class Node(Base):
+ """Data about a Node in a doctree."""
+ __tablename__ = db_prefix + 'nodes'
+
+ id = Column(Integer, primary_key=True)
+ document = Column(String(256), nullable=False)
+ line = Column(Integer)
+ source = Column(Text, nullable=False)
+ treeloc = Column(String(32), nullable=False)
+
+ def __init__(self, document, line, source, treeloc):
+ self.document = document
+ self.line = line
+ self.source = source
+ self.treeloc = treeloc
+
+ def __repr__(self):
+ return '<Node %s#%s>' % (document, treeloc)
+
+class Comment(Base):
+ __tablename__ = db_prefix + 'comments'
+
+ id = Column(Integer, primary_key=True)
+ rating = Column(Integer, nullable=False)
+ time = Column(DateTime, nullable=False)
+ text = Column(Text, nullable=False)
+ displayed = Column(Boolean, index=True, default=False)
+ user_id = Column(String(50), nullable=True)
+
+ node_id = Column(Integer, ForeignKey(db_prefix + 'nodes.id'))
+ node = relation(Node, backref='comments')
+
+ parent_id = Column(Integer, ForeignKey(db_prefix + 'comments.id'))
+ parent = relation('Comment', backref='children', remote_side=[id])
+
+ def __init__(self, text, displayed, user_id, rating, time,
+ node=None, parent=None):
+ self.text = text
+ self.displayed = displayed
+ self.user_id = user_id
+ self.rating = rating
+ self.time = time
+ self.node = node
+ self.parent = parent
diff --git a/sphinx/writers/websupport.py b/sphinx/writers/websupport.py
index 4afc3ecbd..18c0807d6 100644
--- a/sphinx/writers/websupport.py
+++ b/sphinx/writers/websupport.py
@@ -41,14 +41,14 @@ class WebSupportTranslator(HTMLTranslator):
# node will not be commented.
if not self.in_commentable:
self.in_commentable = True
- id = self.create_id(node)
+ node_id = self.add_db_node(node)
# We will place the node in the HTML id attribute. If the node
- # already has another id (for indexing purposes) put an empty
+ # already has an id (for indexing purposes) put an empty
# span with the existing id directly before this node's HTML.
if node.attributes['ids']:
self.body.append('<span id="%s"></span>'
% node.attributes['ids'][0])
- node.attributes['ids'] = [id]
+ node.attributes['ids'] = ['s%s' % node_id]
node.attributes['classes'].append(self.comment_class)
def handle_depart_commentable(self, node):
@@ -56,6 +56,10 @@ class WebSupportTranslator(HTMLTranslator):
if self.comment_class in node.attributes['classes']:
self.in_commentable = False
- def create_id(self, node):
- self.current_id += 1
- return '%s_%s' % (node.__class__.__name__, self.current_id)
+ def add_db_node(self, node):
+ comments = self.builder.app.comments
+ db_node_id = comments.add_node(document=self.builder.cur_docname,
+ line=node.line,
+ source=node.rawsource,
+ treeloc='???')
+ return db_node_id