Commits

Anonymous committed fe17687

Serve logs as HTML pages with anchors.

  • Participants
  • Parent commits 0d4bc09

Comments (0)

Files changed (6)

       padding: 0.5em;
       vertical-align: top;
   }
+
+pre {
+      white-space: normal;
+      font-size: 12px;
+}
+
+.seelog { font-size: 9px; }

templates/easy_installability.html

 
         <td>
           #for release in $not_easy_installable_releases
-            <a href="score/$release[0]/$release[1]">$release[0]-$release[1]</a></em><br />
+            <a href="score/$release[0]/$release[1]">$release[0]-$release[1]</a></em>
+            #if $not_installable_because_of_download($release)
+              <a href="../log/$last_logname_of[$release]#download">
+            #elif $not_installable_because_of_install($release)
+              <a href="../log/$last_logname_of[$release]#install">
+            #else
+              <a href="../log/$last_logname_of[$release]">
+            #end if
+            <span class="seelog">[see log]</span></a><br />
           #end for
         </td>
       </tr>

templates/log.html

+#encoding utf-8
+<html>
+  <head>
+    <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
+    <title>Cheesecake service: Log for $package-$version</title>
+    <link rel="stylesheet" href="../style.css" type="text/css" />
+  </head>
+
+  <body>
+
+    <h2>Contents of a logfile <em>$logname</em></h2>
+
+    #include raw source=$content
+
+  </body>
+</html>

tests/_test_case_with_spec.py

+import re
+import unittest
+
+def remove_leading(needle, haystack):
+    """Remove leading needle string (if exists).
+
+    >>> remove_leading('Test', 'TestThisAndThat')
+    'ThisAndThat'
+    >>> remove_leading('Test', 'ArbitraryName')
+    'ArbitraryName'
+    """
+    if haystack[:len(needle)] == needle:
+        return haystack[len(needle):]
+    return haystack
+
+def camel2word(string):
+    """Covert name from CamelCase to "Normal case".
+
+    >>> camel2word('CamelCase')
+    'Camel case'
+    >>> camel2word('CaseWithSpec')
+    'Case with spec'
+    """
+    def wordize(match):
+        return ' ' + match.group(1).lower()
+
+    return string[0] + re.sub(r'([A-Z])', wordize, string[1:])
+
+def complete_english(string):
+    """
+    >>> complete_english('dont do this')
+    "don't do this"
+    >>> complete_english('doesnt is matched as well')
+    "doesn't is matched as well"
+    """
+    return string.replace('dont', "don't").replace('doesnt', "doesn't")
+
+def underscore2word(string):
+    return string.replace('_', ' ')
+
+class TestCaseWithSpec(unittest.TestCase):
+    def shortDescription(self):
+        return "%s %s" % (getattr(self, 'context', None) or camel2word(remove_leading('Test', self.__class__.__name__)),
+                          complete_english(underscore2word(remove_leading('test_', getattr(self, '_TestCase__testMethodName')))))

tests/test_webui.py

+from _test_case_with_spec import TestCaseWithSpec
+import _path_setup
+
+from webui import put_anchors
+
+
+class TestMethodPutAnchors(TestCaseWithSpec):
+    context = "Method put_anchors()"
+
+    def test_doesnt_insert_any_anchor_for_empty_log(self):
+        assert '' == put_anchors('')
+
+    def test_recognizes_download_section(self):
+        assert '<a name="download"></a>' in put_anchors(log_contents)
+
+    def test_recognizes_install_section(self):
+        assert '<a name="install"></a>' in put_anchors(log_contents)
+
+
+log_contents = """[cheesecake:logfile] Profile requirements: classes, dirs_list, distance_from_pypi, docformat_cnt, docstring_cnt, doctests_count, download_url, files_list, files_list, files_list, files_list, files_list, found_locally, found_on_cheeseshop, installed, methods, object_cnt, object_cnt, original_package_name, package, package_dir, package_dir, package_dir, pylint_max_execution_time, sandbox_install_dir, unittests_count, unpack_dir, unpacked.
+[cheesecake:logfile] Trying to download package nose from PyPI using setuptools utilities
+[cheesecake:logfile] Looking for source package on PyPI... found!
+[cheesecake:logfile] Downloaded package nose-0.9.2.tar.gz from http://somethingaboutorange.com/mrl/projects/nose/nose-0.9.2.tar.gz
+[cheesecake:logfile] Package name: nose-0.9.2
+[cheesecake:logfile] Package type: tar.gz
+[cheesecake:logfile:codeparser] Inspecting file: /tmp/cheesecake9CQrcI/nose-0.9.2/nose/__init__.py
+[cheesecake:logfile:codeparser] modules: __init__
+[cheesecake:logfile:codeparser] classes:
+[cheesecake:logfile:codeparser] methods:
+[cheesecake:logfile:codeparser] functions:
+[cheesecake:logfile:codeparser] docstrings: {'reST': ['__init__'], 'epytext': ['__init__'], 'javadoc': []}
+[cheesecake:logfile:codeparser] number of doctests: 0
+[cheesecake:logfile:codeparser] Inspecting file: /tmp/cheesecake9CQrcI/nose-0.9.2/nose/case.py
+[cheesecake:logfile:codeparser] modules: case
+[cheesecake:logfile:codeparser] classes: case.FunctionTestCase,case.MethodTestCase
+[cheesecake:logfile:codeparser] methods: case.FunctionTestCase.__init__,case.FunctionTestCase.id,case.FunctionTestCase.runTest,case.FunctionTestCase.setUp,case.FunctionTestCase.tearDown,case.FunctionTestCase.__str__,case.FunctionTestCase.shortDescription,case.MethodTestCase.__init__,case.MethodTestCase.__str__,case.MethodTestCase.desc,case.MethodTestCase.id,case.MethodTestCase.setUp,case.MethodTestCase.runTest,case.MethodTestCase.tearDown,case.MethodTestCase.shortDescription
+[cheesecake:logfile:codeparser] functions:
+[cheesecake:logfile:codeparser] docstrings: {'reST': ['case.FunctionTestCase'], 'epytext': [], 'javadoc': []}
+[cheesecake:logfile:codeparser] number of doctests: 0
+[cheesecake:logfile] Found 92 files: AUTHORS, CHANGELOG, ez_setup.py, index.html.tpl, lgpl.txt, MANIFEST.in, NEWS, NEWS_0.9, nosetests.1, PKG-INFO, README.txt, setup.cfg, setup.py, tickets.csv, tickets.rss, TODO_0_9, examples/attrib_plugin.py, examples/html_plugin/htmlplug.py, examples/html_plugin/setup.py, examples/plugin/plug.py, examples/plugin/setup.py, nose/__init__.py, nose/case.py, nose/commands.py, nose/config.py, nose/core.py, nose/exc.py, nose/importer.py, nose/inspector.py, nose/loader.py, nose/proxy.py, nose/result.py, nose/selector.py, nose/suite.py, nose/tools.py, nose/twistedtools.py, nose/util.py, nose/plugins/__init__.py, nose/plugins/attrib.py, nose/plugins/base.py, nose/plugins/cover.py, nose/plugins/doctests.py, nose/plugins/isolate.py, nose/plugins/missed.py, nose/plugins/prof.py, nose.egg-info/dependency_links.txt, nose.egg-info/entry_points.txt, nose.egg-info/PKG-INFO, nose.egg-info/SOURCES.txt, nose.egg-info/top_level.txt, scripts/mkindex.py, scripts/mkrelease.py, scripts/mkwiki.py, scripts/rst2wiki.py, unit_tests/helpers.py, unit_tests/mock.py, unit_tests/test_bug105.py, unit_tests/test_cases.py, unit_tests/test_collector.py, unit_tests/test_config.py, unit_tests/test_core.py, unit_tests/test_importer.py, unit_tests/test_inspector.py, unit_tests/test_isolation_plugin.py, unit_tests/test_issue_006.py, unit_tests/test_lazy_suite.py, unit_tests/test_loader.py, unit_tests/test_logging.py, unit_tests/test_plugin_interfaces.py, unit_tests/test_plugins.py, unit_tests/test_proxy.py, unit_tests/test_result.py, unit_tests/test_selector.py, unit_tests/test_selector_plugins.py, unit_tests/test_tools.py, unit_tests/test_twisted.py, unit_tests/test_utils.py, unit_tests/support/script.py, unit_tests/support/test.py, unit_tests/support/bug101/tests.py, unit_tests/support/bug105/tests.py, unit_tests/support/foo/__init__.py, unit_tests/support/foo/doctests.txt, unit_tests/support/foo/test_foo.py, unit_tests/support/foo/bar/__init__.py, unit_tests/support/foo/bar/buz.py, unit_tests/support/foo/tests/dir_test_file.py, unit_tests/support/issue006/tests.py, unit_tests/support/other/file.txt, unit_tests/support/pkgorg/lib/modernity.py, unit_tests/support/pkgorg/tests/test_mod.py, unit_tests/support/test-dir/test.py.
+[cheesecake:logfile] Found 20 directories: examples, nose, nose.egg-info, scripts, unit_tests, examples/html_plugin, examples/plugin, nose/plugins, unit_tests/support, unit_tests/support/bug101, unit_tests/support/bug105, unit_tests/support/foo, unit_tests/support/issue006, unit_tests/support/other, unit_tests/support/pkgorg, unit_tests/support/test-dir, unit_tests/support/foo/bar, unit_tests/support/foo/tests, unit_tests/support/pkgorg/lib, unit_tests/support/pkgorg/tests.
+[cheesecake:logfile] Trying to install package nose
+[cheesecake:logfile] Installation into /tmp/cheesecake9CQrcI/tmp_install_nose-0.9.2 successful.
+[cheesecake:logfile] Index IndexPyPIDownload computed in 0.00 seconds.
+[cheesecake:logfile] Index IndexUnpack computed in 0.00 seconds.
+[cheesecake:logfile] Index IndexUnpackDir computed in 0.00 seconds.
+[cheesecake:logfile] 25 points entry found: setup.py (setup.py)
+[cheesecake:logfile] Index setup.py computed in 0.01 seconds.
+[cheesecake:logfile] Index IndexInstall computed in 0.00 seconds.
+[cheesecake:logfile] Index IndexGeneratedFiles computed in 0.01 seconds.
+[cheesecake:logfile] Index INSTALLABILITY computed in 0.02 seconds.
+[cheesecake:logfile] 10 points entry found: AUTHORS (authors/authors.html/authors.txt)
+[cheesecake:logfile] 20 points entry found: CHANGELOG (announce/announce.html/announce.txt/changelog/changelog.html/changelog.txt/changes/changes.html/changes.txt)
+[cheesecake:logfile] 10 points entry found: NEWS (news/news.html/news.txt)
+[cheesecake:logfile] 30 points entry found: README.txt (readme/readme.html/readme.txt)
+[cheesecake:logfile] 10 points entry found: examples (demo/example/examples)
+[cheesecake:logfile] 30 points entry found: tests (test/tests)
+[cheesecake:logfile] Index IndexRequiredFiles computed in 0.05 seconds.
+[cheesecake:logfile] Index IndexDocstrings computed in 0.00 seconds.
+[cheesecake:logfile] Index IndexFormattedDocstrings computed in 0.00 seconds.
+[cheesecake:logfile] Index DOCUMENTATION computed in 0.06 seconds.
+[cheesecake:logfile] Index IndexUnitTested computed in 0.00 seconds.
+[cheesecake:logfile] Running pylint on files: nose/case.py nose/commands.py nose/config.py nose/core.py nose/exc.py nose/importer.py nose/inspector.py nose/loader.py nose/proxy.py nose/result.py nose/selector.py nose/suite.py nose/tools.py nose/twistedtools.py nose/util.py nose/plugins/attrib.py nose/plugins/base.py nose/plugins/cover.py nose/plugins/doctests.py nose/plugins/isolate.py nose/plugins/missed.py nose/plugins/prof.py scripts/mkindex.py scripts/mkrelease.py scripts/mkwiki.py scripts/rst2wiki.py unit_tests/helpers.py unit_tests/mock.py unit_tests/support/script.py unit_tests/support/test.py unit_tests/support/bug101/tests.py unit_tests/support/bug105/tests.py unit_tests/support/foo/bar/buz.py unit_tests/support/issue006/tests.py unit_tests/support/pkgorg/lib/modernity.py unit_tests/support/test-dir/test.py.
+[cheesecake:logfile] Index pylint computed in 16.87 seconds.
+[cheesecake:logfile] Index CODE KWALITEE computed in 16.87 seconds.
+[cheesecake:logfile] Index Cheesecake computed in 16.95 seconds.
+[cheesecake:logfile] A given package can currently reach a MAXIMUM number of 595 points
+[cheesecake:logfile] Starting computation of Cheesecake index for package 'nose-0.9.2.tar.gz'
+[cheesecake:logfile] Removing file /tmp/cheesecake9CQrcI/nose-0.9.2.tar.gz
+[cheesecake:logfile] Removing directory /tmp/cheesecake9CQrcI
+"""
 from store import Store
 from config import LOG_DIRECTORY, TIMESTAMP_FILE, DB_NAME, DB_USER, DB_PASSWORD, STATIC_FILES_DIRECTORY, TEMPLATES_DIRECTORY
 
+################################################################################
+## Helpers.
+################################################################################
+
+def read_file_contents(path):
+    fd = file(path)
+    contents = fd.read()
+    fd.close()
+    return contents
+
+def put_anchors(log_contents):
+    """Put anchors between logfile lines to divide it into sections.
+    """
+    mapping = {
+        'Trying to download package': '<a name="download"></a>Trying to download package',
+        'Trying to install package': '<a name="install"></a>Trying to install package',
+    }
+
+    for pattern, replacement in mapping.iteritems():
+        log_contents = log_contents.replace(pattern, replacement)
+
+    return log_contents
+
+################################################################################
+## Controllers.
+################################################################################
 
 urls = (
     '/', 'index',
     def _GET(self):
         easy_installable_releases = []
         not_easy_installable_releases = []
+        last_logname_of = {}
+        _not_installable_because_of_download = []
+        _not_installable_because_of_install = []
 
         for release, score in self.store.score.iteritems():
             if score.can_be_installed():
                 easy_installable_releases.append(release)
             else:
                 not_easy_installable_releases.append(release)
+                last_logname_of[release] = self.store.logs_for_release(*release)[-1]
+                _not_installable_because_of_install.append(release)
+
+        # Append all failed runs as well.
+        for run in self.store._runs_failure:
+            release = (run.package, run.version)
+
+            not_easy_installable_releases.append(release)
+            last_logname_of[release] = run.logname()
+            _not_installable_because_of_download.append(release)
 
         easy_installable_releases.sort()
         not_easy_installable_releases.sort()
 
+        not_installable_because_of_download = lambda r: r in _not_installable_because_of_download
+        not_installable_because_of_install = lambda r: r in _not_installable_because_of_install
+
         web.render('easy_installability.html')
 
 class StaticHandler(object):
+    def outputter(self, content, _):
+        sys.stdout.write(content)
+
     def GET(self, filename):
         if not filename:
             web.notfound()
             if not path.startswith(base):
                 raise ValueError
 
-            fd = file(path)
-            sys.stdout.write(fd.read())
-            fd.close()
+            self.outputter(read_file_contents(path), filename)
         except (IOError, ValueError):
             web.notfound()
 
 class log(StaticHandler):
     top_directory = LOG_DIRECTORY
 
+    def outputter(self, content, logname):
+        package = ''
+        version = ''
+        content = '<pre>%s</pre>' % put_anchors(web.htmlquote(content).replace('\n', '<br />'))
+        web.render('log.html')
+
 
 web.db_parameters = dict(dbn='postgres', db=DB_NAME, user=DB_USER, pw=DB_PASSWORD)
 main = web.wsgifunc(web.webpyfunc(urls))