allura
Révision | eafda896802b38bc49faf895e72db41aaf562b2b (tree) |
---|---|
l'heure | 2012-06-13 01:24:08 |
Auteur | bolkimen <bolkimen@yaho...> |
Commiter | Dave Brondsema |
@@ -26,7 +26,7 @@ import ew | ||
26 | 26 | |
27 | 27 | import allura |
28 | 28 | # needed for tg.configuration to work |
29 | -from allura.lib import app_globals | |
29 | +from allura.lib import app_globals, helpers | |
30 | 30 | |
31 | 31 | log = logging.getLogger(__name__) |
32 | 32 |
@@ -62,6 +62,7 @@ class ForgeConfig(AppConfig): | ||
62 | 62 | autoescape=True, |
63 | 63 | extensions=['jinja2.ext.do', 'jinja2.ext.i18n']) |
64 | 64 | jinja2_env.install_gettext_translations(pylons.i18n) |
65 | + jinja2_env.filters['filesizeformat'] = helpers.do_filesizeformat | |
65 | 66 | config['pylons.app_globals'].jinja2_env = jinja2_env |
66 | 67 | # Jinja's unable to request c's attributes without strict_c |
67 | 68 | config['pylons.strict_c'] = True |
@@ -18,6 +18,7 @@ from ming.orm import ThreadLocalORMSession, session | ||
18 | 18 | |
19 | 19 | import allura.tasks |
20 | 20 | from allura.lib import security |
21 | +from allura.lib import utils | |
21 | 22 | from allura.lib import helpers as h |
22 | 23 | from allura.lib import widgets as w |
23 | 24 | from allura.lib.decorators import require_post |
@@ -487,8 +488,10 @@ class FileBrowser(BaseController): | ||
487 | 488 | else: |
488 | 489 | force_display = 'force' in kw |
489 | 490 | context = self._blob.context() |
491 | + stats = utils.generate_code_stats(self._blob) | |
490 | 492 | return dict( |
491 | 493 | blob=self._blob, |
494 | + stats=stats, | |
492 | 495 | prev=context.get('prev', None), |
493 | 496 | next=context.get('next', None), |
494 | 497 | force_display=force_display |
@@ -8,6 +8,7 @@ import os.path | ||
8 | 8 | import datetime |
9 | 9 | import random |
10 | 10 | import mimetypes |
11 | +import re | |
11 | 12 | from itertools import groupby |
12 | 13 | |
13 | 14 | import tg |
@@ -410,3 +411,15 @@ class LineAnchorCodeHtmlFormatter(HtmlFormatter): | ||
410 | 411 | yield (tup[0], '<div id="l%s" class="code_block">%s</div>' % (num, tup[1])) |
411 | 412 | num += 1 |
412 | 413 | yield 0, '</pre>' |
414 | + | |
415 | +def generate_code_stats(blob): | |
416 | + stats = {'line_count': 0, | |
417 | + 'code_size': 0, | |
418 | + 'data_line_count': 0} | |
419 | + code = blob.text | |
420 | + lines = code.split('\n') | |
421 | + stats['code_size'] = blob.size | |
422 | + stats['line_count'] = len(lines) | |
423 | + spaces = re.compile(r'^\s*$') | |
424 | + stats['data_line_count'] = sum([1 for l in lines if not spaces.match(l)]) | |
425 | + return stats |
@@ -108,6 +108,10 @@ class RepositoryImplementation(object): | ||
108 | 108 | '''Return a file-like object that contains the contents of the blob''' |
109 | 109 | raise NotImplementedError, 'open_blob' |
110 | 110 | |
111 | + def blob_size(self, blob): | |
112 | + '''Return a blob size in bytes''' | |
113 | + raise NotImplementedError, 'blob_size' | |
114 | + | |
111 | 115 | @classmethod |
112 | 116 | def shorthand_for_commit(cls, oid): |
113 | 117 | return '[%s]' % oid[:6] |
@@ -204,6 +208,8 @@ class Repository(Artifact): | ||
204 | 208 | return self._impl.commit_context(commit) |
205 | 209 | def open_blob(self, blob): |
206 | 210 | return self._impl.open_blob(blob) |
211 | + def blob_size(self, blob): | |
212 | + return self._impl.blob_size(blob) | |
207 | 213 | def shorthand_for_commit(self, oid): |
208 | 214 | return self._impl.shorthand_for_commit(oid) |
209 | 215 | def symbolics_for_commit(self, commit): |
@@ -1108,6 +1114,10 @@ class Blob(RepoObject): | ||
1108 | 1114 | return iter(self.open()) |
1109 | 1115 | |
1110 | 1116 | @LazyProperty |
1117 | + def size(self): | |
1118 | + return self.repo.blob_size(self) | |
1119 | + | |
1120 | + @LazyProperty | |
1111 | 1121 | def text(self): |
1112 | 1122 | return self.open().read() |
1113 | 1123 |
@@ -68,9 +68,13 @@ | ||
68 | 68 | {% elif blob.has_html_view or blob.has_pypeline_view or force_display %} |
69 | 69 | <p><a href="?format=raw">Download this file</a></p> |
70 | 70 | <div class="clip grid-19"> |
71 | - <h3><span class="ico-l"><b data-icon="{{g.icons['table'].char}}" class="ico {{g.icons['table'].css}}"></b> {{h.really_unicode(blob.name)}}</span></h3> | |
71 | + <h3> | |
72 | + <span class="ico-l"><b data-icon="{{g.icons['table'].char}}" class="ico {{g.icons['table'].css}}"></b> {{h.really_unicode(blob.name)}}</span> | |
73 | + | |
74 | + {{ stats.line_count }} lines ({{ stats.data_line_count }} with data), {{ stats.code_size|filesizeformat }} | |
75 | + </h3> | |
72 | 76 | {% if blob.has_pypeline_view %} |
73 | - {{h.render_any_markup(blob.name, blob.text, code_mode=True, linenumbers_style=h.TABLE)}} | |
77 | + {{h.render_any_markup(blob.name, blob.text, code_mode=True)}} | |
74 | 78 | {% else %} |
75 | 79 | {{g.highlight(blob.text, filename=blob.name)}} |
76 | 80 | {% endif %} |
@@ -0,0 +1,28 @@ | ||
1 | +import unittest | |
2 | +from mock import Mock | |
3 | + | |
4 | +from alluratest.controller import setup_unit_test | |
5 | +from allura.lib.utils import generate_code_stats | |
6 | + | |
7 | +class TestCodeStats(unittest.TestCase): | |
8 | + | |
9 | + def setUp(self): | |
10 | + setup_unit_test() | |
11 | + | |
12 | + def test_generate_code_stats(self): | |
13 | + blob = Mock() | |
14 | + blob.text = \ | |
15 | +"""class Person(object): | |
16 | + | |
17 | + def __init__(self, name='Alice'): | |
18 | + self.name = name | |
19 | + | |
20 | + def greetings(self): | |
21 | + print "Hello, %s" % self.name | |
22 | +\t\t""" | |
23 | + blob.size = len(blob.text) | |
24 | + | |
25 | + stats = generate_code_stats(blob) | |
26 | + assert stats['line_count'] == 8 | |
27 | + assert stats['data_line_count'] == 5 | |
28 | + assert stats['code_size'] == len(blob.text) |
@@ -272,6 +272,9 @@ class GitImplementation(M.RepositoryImplementation): | ||
272 | 272 | return _OpenedGitBlob( |
273 | 273 | self._object(blob.object_id).data_stream) |
274 | 274 | |
275 | + def blob_size(self, blob): | |
276 | + return self._object(blob.object_id).data_stream.size | |
277 | + | |
275 | 278 | def _setup_hooks(self): |
276 | 279 | 'Set up the git post-commit hook' |
277 | 280 | text = self.post_receive_template.substitute( |
@@ -257,6 +257,10 @@ class HgImplementation(M.RepositoryImplementation): | ||
257 | 257 | fctx = self._hg[blob.commit.object_id][h.really_unicode(blob.path()).encode('utf-8')[1:]] |
258 | 258 | return StringIO(fctx.data()) |
259 | 259 | |
260 | + def blob_size(self, blob): | |
261 | + fctx = self._hg[blob.commit.object_id][h.really_unicode(blob.path()).encode('utf-8')[1:]] | |
262 | + return fctx.size() | |
263 | + | |
260 | 264 | def _setup_hooks(self): |
261 | 265 | 'Set up the hg changegroup hook' |
262 | 266 | cp = ConfigParser() |
@@ -431,6 +431,24 @@ class SVNImplementation(M.RepositoryImplementation): | ||
431 | 431 | revision=self._revision(blob.commit.object_id)) |
432 | 432 | return StringIO(data) |
433 | 433 | |
434 | + def blob_size(self, blob): | |
435 | + try: | |
436 | + data = self._svn.list( | |
437 | + self._url + blob.path(), | |
438 | + revision=self._revision(blob.commit.object_id), | |
439 | + dirent_fields=pysvn.SVN_DIRENT_SIZE) | |
440 | + except pysvn.ClientError: | |
441 | + log.info('ClientError getting filesize %r %r, returning 0', blob.path(), self._repo, exc_info=True) | |
442 | + return 0 | |
443 | + | |
444 | + try: | |
445 | + size = data[0][0]['size'] | |
446 | + except (IndexError, KeyError): | |
447 | + log.info('Error getting filesize: bad data from svn client %r %r, returning 0', blob.path(), self._repo, exc_info=True) | |
448 | + size = 0 | |
449 | + | |
450 | + return size | |
451 | + | |
434 | 452 | def _setup_hooks(self): |
435 | 453 | 'Set up the post-commit and pre-revprop-change hooks' |
436 | 454 | text = self.post_receive_template.substitute( |