gminick / sphinx-gsoc2009
A support for per-paragraph comments and user/developer interface for submitting/committing fixes.
$ hg clone http://bitbucket.org/gminick/sphinx-gsoc2009/
| commit 1619: | d16335626c05 |
| parent 1618: | f9d4256eb2e0 |
| branch: | default |
Changed (Δ2.1 KB):
sphinx/builders/webapp/templates/webapp.conf (2 lines added, 0 lines removed)
sphinx/builders/webapp/webapp.py (12 lines added, 11 lines removed)
sphinx/config.py (2 lines added, 1 lines removed)
sphinx/quickstart.py (2 lines added, 1 lines removed)
sphinx/themes/basic/static/comments.js (9 lines added, 8 lines removed)
sphinx/web/middleware/appserver.py (45 lines added, 7 lines removed)
sphinx/web/singlebuilder.py (3 lines added, 3 lines removed)
sphinx/web/webconfig.py (2 lines added, 0 lines removed)
Up to file-list sphinx/builders/webapp/templates/webapp.conf:
| … | … | @@ -3,5 +3,7 @@ licence={{ licence }} |
3 |
3 |
|
4 |
4 |
[Repository] |
5 |
5 |
repodir={{ repodir }} |
6 |
reporoot={{ reporoot }} |
|
7 |
repopath={{ reporoot }}/{{ repodir }} |
|
6 |
8 |
reposums={{ reposums }} |
7 |
9 |
piddbfile={{ piddbfile }} |
Up to file-list sphinx/builders/webapp/webapp.py:
| … | … | @@ -50,7 +50,7 @@ class WebAppBuilder(StandaloneHTMLBuilde |
50 |
50 |
self.public_dir = path.join(self.outdir, 'public') |
51 |
51 |
|
52 |
52 |
# _build/webapp/repo - reST source files as a repository |
53 |
self.repo |
|
53 |
self.reporoot = path.join(self.outdir, 'repo') |
|
54 |
54 |
|
55 |
55 |
# _build/webapp/html - HTML templates for webapp/middleware |
56 |
56 |
self.html_dir = path.join(self.outdir, 'html') |
| … | … | @@ -111,7 +111,7 @@ class WebAppBuilder(StandaloneHTMLBuilde |
111 |
111 |
repo = self.detect_repo() |
112 |
112 |
if repo: |
113 |
113 |
# currently - only mercurial |
114 |
commands.clone(self.ui, repo.base_path, self.repo |
|
114 |
commands.clone(self.ui, repo.base_path, self.reporoot) |
|
115 |
115 |
else: |
116 |
116 |
self.new_repo() |
117 |
117 |
except Exception, err: |
| … | … | @@ -121,8 +121,8 @@ class WebAppBuilder(StandaloneHTMLBuilde |
121 |
121 |
return get_workdir_manager_for_path(self.srcdir) |
122 |
122 |
|
123 |
123 |
def new_repo(self): |
124 |
commands.init(self.ui, self.repodir) |
|
125 |
repo = repository(self.ui, self.repodir) |
|
124 |
commands.init(self.ui, self.reporoot) |
|
125 |
repo = repository(self.ui, self.reporoot) |
|
126 |
126 |
builddir = path.join(self.srcdir, '_build') |
127 |
127 |
def callback(arg, directory, files): |
128 |
128 |
srclen = len(self.srcdir) |
| … | … | @@ -131,7 +131,7 @@ class WebAppBuilder(StandaloneHTMLBuilde |
131 |
131 |
for elem in files: |
132 |
132 |
srcfile = path.join(directory, elem) |
133 |
133 |
subdir = directory[srclen+1:] |
134 |
dstfile = path.join(path.join(self.repo |
|
134 |
dstfile = path.join(path.join(self.reporoot, subdir), elem) |
|
135 |
135 |
if path.isdir(srcfile): |
136 |
136 |
os.mkdir(dstfile) |
137 |
137 |
elif not path.exists(dstfile): |
| … | … | @@ -141,7 +141,7 @@ class WebAppBuilder(StandaloneHTMLBuilde |
141 |
141 |
|
142 |
142 |
def add_pids_to_paragraphs(self): |
143 |
143 |
def add_pids_to_lines(filename): |
144 |
dirbase = path.join(self.orig_outdir, self.webconfig.repo |
|
144 |
dirbase = path.join(self.orig_outdir, self.webconfig.repopath) |
|
145 |
145 |
fullpath = path.join(dirbase, filename) |
146 |
146 |
try: |
147 |
147 |
w = codecs.open(fullpath, 'r', self.encoding) |
| … | … | @@ -213,6 +213,7 @@ class WebAppBuilder(StandaloneHTMLBuilde |
213 |
213 |
{ 'licence': self.config.licence, |
214 |
214 |
'piddbfile': self.config.piddbfile, |
215 |
215 |
'repodir': self.config.repodir, |
216 |
'reporoot': self.config.reporoot, |
|
216 |
217 |
'reposums': self.config.reposums }) |
217 |
218 |
conf_dst = path.join(self.outdir, 'webapp.conf') |
218 |
219 |
w = codecs.open(conf_dst, 'w', self.encoding) |
| … | … | @@ -285,7 +286,7 @@ def cutcwd(fpath): |
285 |
286 |
|
286 |
287 |
class WebappHTMLTranslator(HTMLTranslator): |
287 |
288 |
licence = None |
288 |
repo |
|
289 |
repopath = None |
|
289 |
290 |
piddbfile = None |
290 |
291 |
def __init__(self, *args, **kwds): |
291 |
292 |
self.templ_env = None # templates environment |
| … | … | @@ -294,8 +295,8 @@ class WebappHTMLTranslator(HTMLTranslato |
294 |
295 |
self.piddb = PidDb(self.piddbfile) |
295 |
296 |
HTMLTranslator.__init__(self, *args, **kwds) |
296 |
297 |
|
297 |
def init_attributes(self, repodir, piddbfile, licence): |
|
298 |
self.repodir = repodir |
|
298 |
def init_attributes(self, repopath, piddbfile, licence): |
|
299 |
self.repopath = repopath |
|
299 |
300 |
self.piddbfile = piddbfile |
300 |
301 |
self.licence = licence |
301 |
302 |
|
| … | … | @@ -323,8 +324,8 @@ class WebappHTMLTranslator(HTMLTranslato |
323 |
324 |
HTMLTranslator.depart_paragraph(self, node) |
324 |
325 |
if not self.should_be_compact_paragraph(node) and node.source: |
325 |
326 |
nodename = cutcwd(node.source) |
326 |
if self.repodir: |
|
327 |
nodename = nodename[len(self.repodir)+1:] |
|
327 |
if self.repopath: |
|
328 |
nodename = nodename[len(self.repopath)+1:] |
|
328 |
329 |
self.body.append(comm_template.render({ 'licence': self.licence, |
329 |
330 |
'node': nodename, |
330 |
331 |
'paragraph_id': self.pid, |
Up to file-list sphinx/config.py:
| … | … | @@ -120,7 +120,8 @@ class Config(object): |
120 |
120 |
|
121 |
121 |
# Web application options |
122 |
122 |
xapian_db = ('xapian_db', 'webapp'), |
123 |
repodir = (' |
|
123 |
repodir = ('doc', 'webapp'), |
|
124 |
reporoot = ('repo', 'webapp'), |
|
124 |
125 |
reposums = ('reposums.pkl', None), |
125 |
126 |
piddbfile = ('piddb.pkl', None), |
126 |
127 |
licence = ('BSD', 'webapp'), |
Up to file-list sphinx/quickstart.py:
| … | … | @@ -227,7 +227,8 @@ latex_documents = [ |
227 |
227 |
# -- Options for WebApp -------------------------------------------------------- |
228 |
228 |
# configuration options for Xapian: |
229 |
229 |
xapian_db = 'xapian_db' |
230 |
repo |
|
230 |
reporoot = 'repo' |
|
231 |
repodir = 'doc' |
|
231 |
232 |
reposums = 'reposums.pkl' |
232 |
233 |
piddbfile = 'piddb.pkl' |
233 |
234 |
licence = 'BSD' |
Up to file-list sphinx/themes/basic/static/comments.js:
106 |
106 |
* located in 'id' file under |
107 |
107 |
* 'comment_no' index |
108 |
108 |
* |
109 |
* commit_fix(id, fix_no) -> commit a fix located in 'id' file under |
|
110 |
* 'fix_no' index to the repository |
|
109 |
* commit_fix(node, id, fix_no) -> commit a fix located in 'id' file under |
|
110 |
* 'fix_no' index to the repository |
|
111 |
111 |
* |
112 |
112 |
** |
113 |
113 |
*** general-use functions ************************************************ |
| … | … | @@ -205,6 +205,7 @@ function print_generic(what, id, data) { |
205 |
205 |
var c_flag = (what == 'comments') ? true : false; |
206 |
206 |
var singular = (what == 'comments') ? 'comment' : 'fix'; |
207 |
207 |
var isdev = is_developer(); |
208 |
var node = $('div[class=submitFixFields_' + id + ']').attr('value'); |
|
208 |
209 |
|
209 |
210 |
if(what == 'comments') { |
210 |
211 |
empty_comments(id); |
| … | … | @@ -247,8 +248,8 @@ function print_generic(what, id, data) { |
247 |
248 |
return '<a href="#" onclick="delete_comment(\'' + db + '\', \'' + id + '\', \'' + comment_no + '\')">delete</a>' |
248 |
249 |
} |
249 |
250 |
|
250 |
function commit_button(id, fix_no) { |
|
251 |
return '<a href="#" onclick="commit_fix(\'' + id + '\', \'' |
|
251 |
function commit_button(node, id, fix_no) { |
|
252 |
return '<a href="#" onclick="commit_fix(\'' + node + '\', \'' + id + '\', \'' |
|
252 |
253 |
+ fix_no + '\')">commit</a>' |
253 |
254 |
} |
254 |
255 |
|
| … | … | @@ -258,7 +259,7 @@ function print_generic(what, id, data) { |
258 |
259 |
+ 'Admin actions: ' |
259 |
260 |
+ delete_comment_button(what, id, data[i].comment_no) |
260 |
261 |
+ ', ' |
261 |
+ commit_button( |
|
262 |
+ commit_button(node, id, data[i].comment_no) |
|
262 |
263 |
+ '.' |
263 |
264 |
+ '</td>' |
264 |
265 |
+ '</tr>' |
| … | … | @@ -290,7 +291,7 @@ function print_generic(what, id, data) { |
290 |
291 |
+ '</tr>' |
291 |
292 |
+ (isdev ? developers_actions(what, id, data, i) : '') |
292 |
293 |
+ '<tr><td> </td></tr>' |
293 |
+ proposed_fix(c_flag, data[i].paragraph |
|
294 |
+ proposed_fix(c_flag, data[i].paragraph_diff) |
|
294 |
295 |
+ "<tr>" |
295 |
296 |
+ "<td>" |
296 |
297 |
+ 'Comment: ' + data[i].comment |
| … | … | @@ -507,11 +508,11 @@ function delete_comment(db, id, comment_ |
507 |
508 |
}); |
508 |
509 |
} |
509 |
510 |
|
510 |
function commit_fix( |
|
511 |
function commit_fix(node, id, fix_no) { |
|
511 |
512 |
var c_path = location.protocol + "//" + location.host + "/commit_fix"; |
512 |
513 |
$.ajax({ url: c_path, |
513 |
514 |
type: 'GET', |
514 |
data: " |
|
515 |
data: "node=" + node + "&id=" + id + "&fix_no=" + fix_no, |
|
515 |
516 |
success: function() { load_func(id)} |
516 |
517 |
}); |
517 |
518 |
} |
Up to file-list sphinx/web/middleware/appserver.py:
| … | … | @@ -26,6 +26,7 @@ from os import path |
26 |
26 |
from difflib import Differ |
27 |
27 |
from hashlib import md5 |
28 |
28 |
|
29 |
from anyvc import workdir |
|
29 |
30 |
from jinja2 import Environment, FileSystemLoader, Template |
30 |
31 |
from webob import Request, Response |
31 |
32 |
|
| … | … | @@ -173,9 +174,10 @@ class AppServer(object): |
173 |
174 |
self.piddb = PidDb(self.webconfig.piddbfile) |
174 |
175 |
self.webauth = WebAuth() |
175 |
176 |
self.webauth.developers_load('developers.txt') |
177 |
self.repo = workdir.get_workdir_manager_for_path(self.webconfig.repopath) |
|
176 |
178 |
self.reposums = RepoSums(self.webconfig.reposums) |
177 |
179 |
self.htmlbuilder = SinglefileHTMLBuilder(self.www_dir, |
178 |
self.webconfig.repo |
|
180 |
self.webconfig.repopath, |
|
179 |
181 |
self.webconfig.piddbfile, |
180 |
182 |
self.webconfig.licence) |
181 |
183 |
self.init_templates() |
| … | … | @@ -344,11 +346,46 @@ class AppServer(object): |
344 |
346 |
|
345 |
347 |
def doCommitFix(self, req, resp): |
346 |
348 |
"""Commit fix""" |
347 |
|
|
349 |
def new_file_to_commit(file_path, record): |
|
350 |
"""Open a file 'file_path' and in its content change a paragraph |
|
351 |
'paragraph_orig' with 'paragraph_submitted' from the 'record' |
|
352 |
dict. Then overwrite the 'file_path' file with a new version |
|
353 |
of a file and commit it to the repository.""" |
|
354 |
try: |
|
355 |
w = open(file_path) |
|
356 |
file_contents = w.read() |
|
357 |
w.close() |
|
358 |
except Exception, err: |
|
359 |
raise SphinxError(err) |
|
360 |
||
361 |
filesub = re.sub(re.escape(record['paragraph_orig']), |
|
362 |
record['paragraph_submitted'], |
|
363 |
file_contents) |
|
364 |
||
365 |
try: |
|
366 |
w = open(file_path, 'w') |
|
367 |
w.write(filesub) |
|
368 |
w.close() |
|
369 |
except Exception, err: |
|
370 |
raise SphinxError(err) |
|
371 |
||
372 |
olddir = os.getcwd() |
|
373 |
os.chdir(path.join(olddir, self.webconfig.reporoot)) |
|
374 |
self.repo.commit(message='need to add textarea for '\ |
|
375 |
'message in the post form', |
|
376 |
paths=(path.join('doc', path.basename(file_path)),)) |
|
377 |
os.chdir(olddir) |
|
378 |
||
379 |
node, p_id, fix_no = req.GET['node'], req.GET['id'], int(req.GET['fix_no']) |
|
348 |
380 |
fixes_db_path = get_fixes_db_path(p_id) |
349 |
381 |
data = load_comments(fixes_db_path) |
350 |
fix_index = index_by_key_val(data, 'comment_no', int(fix_no)) |
|
351 |
print '>>>', p_id, fix_no, data[fix_index]['paragraph_orig'] |
|
382 |
fix_index = index_by_key_val(data, 'comment_no', fix_no) |
|
383 |
file_path = path.join(self.webconfig.repopath, node) |
|
384 |
new_file_to_commit(file_path, data[fix_index]) |
|
385 |
delete_comment(fixes_db_path, p_id, fix_no) |
|
386 |
self.piddb.add_record(node, p_id, data[fix_index]['paragraph_submitted'], |
|
387 |
save=True) |
|
388 |
# need to reload the page to see the changes |
|
352 |
389 |
return resp |
353 |
390 |
|
354 |
391 |
def doAddComment(self, req, resp): |
| … | … | @@ -392,8 +429,9 @@ class AppServer(object): |
392 |
429 |
|
393 |
430 |
d = Differ() |
394 |
431 |
pdiff = list(d.compare(p2, p1)) |
395 |
db_rec['paragraph'] = modify_diff(pdiff) |
|
396 |
db_rec['paragraph_orig'] = pdiff |
|
432 |
db_rec['paragraph_submitted'] = req.POST.get(p_str) |
|
433 |
db_rec['paragraph_diff'] = modify_diff(pdiff) |
|
434 |
db_rec['paragraph_orig'] = req.POST.get(p_orig_str) |
|
397 |
435 |
db_path = get_fixes_db_path(p_id) |
398 |
436 |
else: |
399 |
437 |
db_path = get_comments_db_path(p_id) |
| … | … | @@ -465,7 +503,7 @@ class AppServer(object): |
465 |
503 |
resp.charset = 'utf8' |
466 |
504 |
file_path = self.constructPath(req) |
467 |
505 |
|
468 |
repo_file = path.join(self.webconfig.repo |
|
506 |
repo_file = path.join(self.webconfig.repopath,file_path[:-5] + '.rst') |
|
469 |
507 |
if check_rst_sum(repo_file): |
470 |
508 |
self.htmlbuilder.render_page(file_path[:-5]) |
471 |
509 |
Up to file-list sphinx/web/singlebuilder.py:
| … | … | @@ -23,9 +23,9 @@ from sphinx.errors import SphinxError |
23 |
23 |
class SinglefileHTMLBuilder(StandaloneHTMLBuilder, Sphinx): |
24 |
24 |
"""Render a file when requested using data from a pickled archive. |
25 |
25 |
Handle theming.""" |
26 |
def __init__(self, www_dir, repo |
|
26 |
def __init__(self, www_dir, repopath, piddbfile, licence): |
|
27 |
27 |
self.outdir = www_dir |
28 |
self.srcdir = repo |
|
28 |
self.srcdir = repopath |
|
29 |
29 |
self.piddbfile = piddbfile |
30 |
30 |
self.licence = licence |
31 |
31 |
self.cwd = os.getcwd() |
| … | … | @@ -67,7 +67,7 @@ class SinglefileHTMLBuilder(StandaloneHT |
67 |
67 |
def init_translator_class(self): |
68 |
68 |
WebappHTMLTranslator.licence = self.licence |
69 |
69 |
WebappHTMLTranslator.piddbfile = self.piddbfile |
70 |
WebappHTMLTranslator.repo |
|
70 |
WebappHTMLTranslator.repopath = self.srcdir |
|
71 |
71 |
self.translator_class = WebappHTMLTranslator |
72 |
72 |
|
73 |
73 |
def render_page(self, docname): |
Up to file-list sphinx/web/webconfig.py:
| … | … | @@ -23,5 +23,7 @@ class WebConfig(object): |
23 |
23 |
|
24 |
24 |
self.piddbfile = self.conf.get('Repository', 'piddbfile') |
25 |
25 |
self.repodir = self.conf.get('Repository', 'repodir') |
26 |
self.reporoot = self.conf.get('Repository', 'reporoot') |
|
27 |
self.repopath = self.conf.get('Repository', 'repopath') |
|
26 |
28 |
self.reposums = self.conf.get('Repository', 'reposums') |
27 |
29 |
