From 3bc2eb0c8441f518b88a6f33d8b1bbb065418b9c Mon Sep 17 00:00:00 2001 From: Martin Mahner Date: Mon, 9 Jun 2014 20:18:08 +0200 Subject: [PATCH] Add support to set a lexer with a given filename. Closes issue #63. This allows third-party API utils to set the lexer automatically based on the open filename. --- docs/api.rst | 26 ++++++++++++++++++++ dpaste/tests/test_api.py | 52 +++++++++++++++++++++++++++++++++++++++- dpaste/views.py | 30 +++++++++++++++++++---- 3 files changed, 102 insertions(+), 6 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index 4e4ef34..3d15c99 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -62,6 +62,32 @@ values. Default: ``2592000``. In the default configuration valid values are: * 604800 * 2592000 +``filename`` (optional) +~~~~~~~~~~~~~~~~~~~~~~~ + +Can also be set via GET. A filename which we use to determine a lexer, if +``lexer`` is not set. In case we can't determine a file, the lexer will fallback +to ``plain`` code (no highlighting). A given ``lexer`` will overwrite any +filename! Example:: + + { + "url": "https://dpaste.de/xsWd", + "lexer": "", + "filename": "python", + "conent": "The text body of the snippet." + } + +This will create a ``python`` highlighted snippet. However in this example:: + + { + "url": "https://dpaste.de/xsWd", + "lexer": "php", + "filename": "python", + "conent": "The text body of the snippet." + } + +Since the lexer is set too, we will create a ``php`` highlighted snippet. + .. hint:: You need to adjust the setting ``DPASTE_BASE_URL`` which is used to generate the full qualified URL in the API response. See :doc:`settings`. diff --git a/dpaste/tests/test_api.py b/dpaste/tests/test_api.py index e5e56ad..b9dff5b 100644 --- a/dpaste/tests/test_api.py +++ b/dpaste/tests/test_api.py @@ -3,9 +3,9 @@ from django.core.urlresolvers import reverse from django.test.client import Client from django.test import TestCase -from django.test.utils import override_settings from ..models import Snippet +from ..highlight import PLAIN_CODE class SnippetAPITestCase(TestCase): @@ -188,3 +188,53 @@ class SnippetAPITestCase(TestCase): self.assertEqual(response.status_code, 200) self.assertEqual(Snippet.objects.count(), 1) self.assertTrue(Snippet.objects.all()[0].expires) + + def test_filename_not_given(self): + """ + No lexer and no filename given returns a BadRequest. + """ + response = self.client.post(self.api_url, { + 'content': u"Hello Wörld.\n\tGood Bye", + 'lexer': '', + 'filename': '' + }) + self.assertEqual(response.status_code, 400) + + def test_filename_given(self): + """ + No lexer and a Python filename will set a 'python' lexer. + """ + response = self.client.post(self.api_url, { + 'content': u"Hello Wörld.\n\tGood Bye", + 'lexer': '', + 'filename': 'helloworld.py' + }) + self.assertEqual(response.status_code, 200) + self.assertEqual(Snippet.objects.count(), 1) + self.assertEqual(Snippet.objects.all()[0].lexer, 'python') + + def test_awkward_filename_given(self): + """ + A unknown filename will create a 'plain' code snippet. + """ + response = self.client.post(self.api_url, { + 'content': u"Hello Wörld.\n\tGood Bye", + 'lexer': '', + 'filename': 'helloworld.helloworld' + }) + self.assertEqual(response.status_code, 200) + self.assertEqual(Snippet.objects.count(), 1) + self.assertEqual(Snippet.objects.all()[0].lexer, PLAIN_CODE) + + def test_filename_and_lexer_given(self): + """ + A given lexer will overwrite whats the filename guessing. + """ + response = self.client.post(self.api_url, { + 'content': u"Hello Wörld.\n\tGood Bye", + 'lexer': 'php', + 'filename': 'helloworld.py' + }) + self.assertEqual(response.status_code, 200) + self.assertEqual(Snippet.objects.count(), 1) + self.assertEqual(Snippet.objects.all()[0].lexer, 'php') diff --git a/dpaste/views.py b/dpaste/views.py index 5854e56..6d1a478 100644 --- a/dpaste/views.py +++ b/dpaste/views.py @@ -16,10 +16,13 @@ from django.views.defaults import (page_not_found as django_page_not_found, server_error as django_server_error) from django.views.decorators.csrf import csrf_exempt +from pygments.lexers import get_lexer_for_filename +from pygments.util import ClassNotFound + from dpaste.forms import SnippetForm, get_expire_values, EXPIRE_CHOICES from dpaste.models import Snippet, ONETIME_LIMIT -from dpaste.highlight import LEXER_WORDWRAP, LEXER_LIST -from dpaste.highlight import LEXER_DEFAULT, LEXER_KEYS +from dpaste.highlight import (LEXER_DEFAULT, LEXER_KEYS, LEXER_WORDWRAP, + LEXER_LIST, PLAIN_CODE) # ----------------------------------------------------------------------------- # Snippet Handling @@ -285,15 +288,32 @@ FORMAT_MAPPING = { def snippet_api(request): content = request.POST.get('content', '').strip() lexer = request.REQUEST.get('lexer', LEXER_DEFAULT).strip() + filename = request.REQUEST.get('filename', '').strip() expires = request.REQUEST.get('expires', '').strip() format = request.REQUEST.get('format', 'default').strip() if not content: return HttpResponseBadRequest('No content given') - if not lexer in LEXER_KEYS: - return HttpResponseBadRequest('Invalid lexer given. Valid lexers are: %s' % - ', '.join(LEXER_KEYS)) + # We need at least a lexer or a filename + if not lexer and not filename: + return HttpResponseBadRequest('No lexer or filename given. Unable to ' + 'determine a highlight. Valid lexers are: %s' % ', '.join(LEXER_KEYS)) + + # A lexer is given, check if its valid at all + if lexer and lexer not in LEXER_KEYS: + return HttpResponseBadRequest('Invalid lexer "%s" given. Valid lexers are: %s' % ( + lexer, ', '.join(LEXER_KEYS))) + + # No lexer is given, but we have a filename, try to get the lexer out of it. + # In case Pygments cannot determine the lexer of the filename, we fallback + # to 'plain' code. + if not lexer and filename: + try: + lexer_cls = get_lexer_for_filename(filename) + lexer = lexer_cls.aliases[0] + except (ClassNotFound, IndexError): + lexer = PLAIN_CODE if expires: expire_options = [str(i) for i in dict(EXPIRE_CHOICES).keys()]