From 3266bd553ce924e29ced2acdfc220496461a99dc Mon Sep 17 00:00:00 2001 From: Julien Palard Date: Mon, 24 Apr 2023 17:39:50 +0200 Subject: [PATCH] Allowing to paste multiple files. --- paste/forms.py | 29 ---------- paste/templates/paste/index.html | 98 ++++++++++++++++++++++++++++---- paste/views.py | 70 +++++++++++++++-------- requirements.in | 1 + requirements.txt | 2 + 5 files changed, 136 insertions(+), 64 deletions(-) delete mode 100644 paste/forms.py diff --git a/paste/forms.py b/paste/forms.py deleted file mode 100644 index 035376d..0000000 --- a/paste/forms.py +++ /dev/null @@ -1,29 +0,0 @@ -from django import forms -from django.forms import CharField, ModelForm - -from webtools import settings - -from .models import Paste - - -class PasteForm(ModelForm): - """Paste model form.""" - - content = CharField( - max_length=settings.PASTE["max_characters"], strip=False, widget=forms.Textarea - ) - - class Meta: - model = Paste - fields = [ - "filename", - "content", - ] - - def save(self, commit=True): - """Overwrites save method.""" - paste = super(PasteForm, self).save(commit=False) - paste.compute_size() - if commit: - paste.save() - return paste diff --git a/paste/templates/paste/index.html b/paste/templates/paste/index.html index 998e965..9aeb426 100644 --- a/paste/templates/paste/index.html +++ b/paste/templates/paste/index.html @@ -4,29 +4,105 @@ {% load compress %} {% block content %}

Paf'Py

-

Et Paf.

+

Et PAF !

-

Ce « pastebin-like » s’utilise en ligne de commande, soit avec curl :

+

Envoyer un ou plusieurs fichiers

-
curl {{ request.build_absolute_uri }} -Fhello.py=@hello.py
+En utilisant des requêtes multipart/form-data il est possible
+d'envoyer un fichier :
+
+
curl {{ request.build_absolute_uri }} -Fmanage.py=@manage.py
+| URL                     |   size | filename  |
+|-------------------------|--------|-----------|
+| https://p.afpy.org/g3LE |    251 | manage.py |
 
-

Mais le mieux est de l'utiliser avec une fonction bash :

+ou plusieurs fichiers en même temps : -
pafpy()
+
curl {{ request.build_absolute_uri }} -Fmanage.py=@manage.py -Frequirements.txt=@requirements.txt
+| URL                     |   size | filename         |
+|-------------------------|--------|------------------|
+| https://p.afpy.org/g3LE |    251 | manage.py        |
+| https://p.afpy.org/k4oT |    547 | requirements.txt |
+
+ +C'est l'extension dans le nom du fichier qui permet de choisir la +coloration syntaxique pour chaque fichier. + + +

Envoyer dans le corps d'une requête

+ +Il est possible de coller un unique fichier via le corps d'une requête POST : + +
+$ cal | curl -XPOST --data-binary @- {{ request.build_absolute_uri }}
+| URL                     |   size | filename |
+|-------------------------|--------|----------|
+| https://p.afpy.org/mo8X |    184 | request  |
+
+
+ +Dans ce cas, il est possible de choisir la coloration syntaxique via l'entête Content-Type : + +
+$ cal | curl -XPOST -H "Content-Type: text/plain" --data-binary @- {{ request.build_absolute_uri }}
+| URL                     |   size | filename    |
+|-------------------------|--------|-------------|
+| https://p.afpy.org/dNuo |    184 | request.txt |
+
+
+ + +

Fonction bash

+ +

Pour ceux qui ne souhaitent pas rédiger des requêtes curl +toute la journée, voici une petite fonction bash :

+ +
paf()
 {
-    curl {{request.build_absolute_uri}} -F"$1=@$1"
+    if [[ $# == 0 ]]
+    then
+        curl https://p.afpy.org/ --data-binary @- -H "Content-Type: text/plain"
+    else
+        curl https://p.afpy.org/ "${@/*/-F&=@&}"
+    fi
 }
 
-Demo : +Cette fonction est capable d'envoyer un fichier : -
$ pafpy manage.py
-https://p.afpy.org/g3LE
+
$ paf manage.py
+| URL                     |   size | filename  |
+|-------------------------|--------|-----------|
+| https://p.afpy.org/g3LE |    251 | manage.py |
 
-On peut le voir dans un navigateur (avec de la coloration syntaxique) : -https://p.afpy.org/g3LE), ou on peut le télécharger "brut" : +plusieurs fichiers : + +
$ paf *.py
+| URL                     |   size | filename              |
+|-------------------------|--------|-----------------------|
+| https://p.afpy.org/bvRV |    188 | admin.py              |
+| https://p.afpy.org/5uei |    296 | context_processors.py |
+| https://p.afpy.org/Xg5a |   1419 | models.py             |
+| https://p.afpy.org/GkGS |    309 | urls.py               |
+| https://p.afpy.org/LVXL |   2730 | views.py              |
+
+ +et même de lire sur stdin : + +
$ cal | paf
+| URL                     |   size | filename    |
+|-------------------------|--------|-------------|
+| https://p.afpy.org/dNuo |    184 | request.txt |
+
+ + +

Accès aux collages

+ +Chaque collage peut-être consulté dans un navigateur (où il est présenté avec de la coloration syntaxique : +https://p.afpy.org/g3LE), ou +être consulté en ligne de commande (où il est délivré brut) :
$ curl https://p.afpy.org/g3LE
 #!/usr/bin/env python
diff --git a/paste/views.py b/paste/views.py
index 089e858..c93fab2 100644
--- a/paste/views.py
+++ b/paste/views.py
@@ -1,5 +1,5 @@
-import json
 from functools import lru_cache
+from mimetypes import common_types, types_map
 
 import pygments
 from django.http import HttpResponse, HttpResponseRedirect
@@ -8,38 +8,60 @@ from django.template import RequestContext, loader
 from django.urls import reverse
 from django.views.decorators.csrf import csrf_exempt
 from pygments.formatters import HtmlFormatter
-from pygments.lexers import get_lexer_by_name
+from pygments.lexers import get_lexer_by_name, get_lexer_for_filename
+from tabulate import tabulate
 
-from paste.forms import PasteForm
 from paste.models import Paste
 from webtools import settings
 
+NON_STANDARD_TYPES_INV = {value: key for key, value in common_types.items()}
+STANDARD_TYPES_INV = {value: key for key, value in types_map.items()}
+HARDCODED_TYPES_INV = {"text/plain": ".txt"}
+TYPES_INV = NON_STANDARD_TYPES_INV | STANDARD_TYPES_INV | HARDCODED_TYPES_INV
+
+
+def get_files(request):
+    """Get one or multiple files from either a multipart/form-data or
+    raw request body with a Content-Type header."""
+    if request.FILES:
+        return request.FILES
+    content_type = request.headers.get("content-type", "")
+    ext = TYPES_INV.get(content_type, "")
+    return {"request" + ext: request}
+
 
 @csrf_exempt
 def index(request):
     """Displays form."""
     if request.method == "GET":
         return render(request, "paste/index.html")
-    paste = Paste()
-    paste.choose_slug()
-    if not request.FILES:
-        return HttpResponse(json.dumps({"error": "Please provide a file."}, indent=4))
-    (filename, the_file), *_ = request.FILES.items()
-    form = PasteForm(
-        {
-            "filename": filename.split("/")[-1].split("\\")[-1],
-            "content": the_file.read().decode(),
-        },
-        instance=paste,
-    )
-    if not form.is_valid():
-        return HttpResponse(json.dumps(form.errors, indent=4))
-    form.save()
-    location = request.build_absolute_uri(
-        reverse("short_paste", kwargs={"slug": paste.slug})
-    )
-    return HttpResponseRedirect(
-        location, content=location + "\n", content_type="text/plain"
+    if request.headers.get("Expect") == "100-continue":
+        return HttpResponse("")
+    pastes = []
+    for filename, the_file in get_files(request).items():
+        paste = Paste(
+            filename=filename.split("/")[-1].split("\\")[-1],
+            content=the_file.read().decode("UTF-8"),
+        )
+        paste.choose_slug()
+        paste.compute_size()
+        paste.save()
+        pastes.append(
+            (
+                request.build_absolute_uri(
+                    reverse("short_paste", kwargs={"slug": paste.slug})
+                ),
+                paste.size,
+                paste.filename,
+            )
+        )
+
+    if not pastes:
+        return HttpResponse("error: Please provide a file.")
+
+    return HttpResponse(
+        tabulate(pastes, headers=("URL", "size", "filename"), tablefmt="github") + "\n",
+        content_type="text/plain",
     )
 
 
@@ -58,7 +80,7 @@ def show(request, slug):
 @lru_cache(1024)
 def pygmentize(filename, filecontents):
     try:
-        lexer = get_lexer_by_name(filename.split(".")[-1])
+        lexer = get_lexer_for_filename(filename)
     except pygments.util.ClassNotFound:
         lexer = get_lexer_by_name(settings.PASTE["default_language"])
     formatter = HtmlFormatter(style="emacs")
diff --git a/requirements.in b/requirements.in
index 576d9a1..c7bb0de 100644
--- a/requirements.in
+++ b/requirements.in
@@ -1,4 +1,5 @@
 Django
 Pygments
 shortuuid
+tabulate
 django_compressor
diff --git a/requirements.txt b/requirements.txt
index 0769096..f874d76 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -24,3 +24,5 @@ shortuuid==1.0.11
     # via -r requirements.in
 sqlparse==0.4.4
     # via django
+tabulate==0.9.0
+    # via -r requirements.in