Also pretty print Markdown.

This commit is contained in:
Julien Palard 2023-04-25 09:22:03 +02:00
parent c6774a5dfc
commit 14c6436b23
Signed by: mdk
GPG Key ID: 0EFC1AC1006886F8
7 changed files with 1404 additions and 14 deletions

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,10 @@
{% extends "base.html" %}
{% block extrastyle %}
<link href="/static/css/github-markdown.css" rel="stylesheet">
{% endblock %}
{% block title %}{{ filename }}{% endblock %}
{% block content %}
<div class="markdown-body">
{{ highlighted|safe }}
</div>
{% endblock %}

132
paste/utils.py Normal file
View File

@ -0,0 +1,132 @@
from functools import lru_cache, partial
from typing import List
from urllib.parse import urlparse
import bleach
import markdown
import pygments
from django.conf import settings
from markdown.extensions.codehilite import CodeHiliteExtension
from pygments.formatters import HtmlFormatter
from pygments.lexers import get_lexer_by_name, get_lexer_for_filename
ALLOWED_TAGS = [
# Bleach Defaults
"a",
"abbr",
"acronym",
"b",
"blockquote",
"code",
"em",
"i",
"li",
"ol",
"strong",
"ul",
# Custom Additions
"br",
"caption",
"cite",
"col",
"colgroup",
"dd",
"del",
"details",
"div",
"dl",
"dt",
"h1",
"h2",
"h3",
"h4",
"h5",
"h6",
"hr",
"img",
"p",
"pre",
"span",
"sub",
"summary",
"sup",
"table",
"tbody",
"td",
"th",
"thead",
"tr",
"tt",
"kbd",
"var",
]
ALLOWED_ATTRIBUTES = {
# Bleach Defaults
"a": ["href", "title"],
"abbr": ["title"],
"acronym": ["title"],
# Custom Additions
"*": ["id"],
"hr": ["class"],
"img": ["src", "width", "height", "alt", "align", "class"],
"span": ["class"],
"div": ["class"],
"th": ["align"],
"td": ["align"],
"code": ["class"],
"p": ["align", "class"],
}
def _set_target(attrs, new=False):
link_text = attrs["_text"]
if new and not link_text.startswith(("http:", "https:")):
return None
try:
url = urlparse(attrs[(None, "href")])
except KeyError:
return attrs
if url.netloc not in settings.ALLOWED_HOSTS:
attrs[(None, "target")] = "_blank"
else:
attrs.pop((None, "target"), None)
return attrs
def markdown_to_html(text):
"""This convert markdown text to html, with two things:
- Uses bleach.clean to remove unsafe things.
"""
return bleach.sanitizer.Cleaner(
tags=getattr(settings, "ALLOWED_TAGS", ALLOWED_TAGS),
attributes=getattr(settings, "ALLOWED_ATTRIBUTES", ALLOWED_ATTRIBUTES),
filters=[
partial(
bleach.linkifier.LinkifyFilter,
callbacks=[_set_target],
skip_tags=["pre"],
parse_email=False,
),
],
).clean(
markdown.markdown(
text,
extensions=[
"fenced_code",
CodeHiliteExtension(guess_lang=False, css_class="highlight"),
"tables",
"admonition",
],
),
)
def pygmentize(filename, filecontents):
try:
lexer = get_lexer_for_filename(filename)
except pygments.util.ClassNotFound:
lexer = get_lexer_by_name(settings.PASTE["default_language"])
formatter = HtmlFormatter(style="emacs")
return pygments.highlight(filecontents, lexer, formatter)

View File

@ -1,23 +1,20 @@
from functools import lru_cache
from mimetypes import common_types, types_map
import pygments
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
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, get_lexer_for_filename
from tabulate import tabulate
from paste.models import Paste
from paste.utils import markdown_to_html, pygmentize
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
TYPES_INV = NON_STANDARD_TYPES_INV | STANDARD_TYPES_INV
def get_files(request):
@ -72,19 +69,18 @@ def show(request, slug):
paste.incr_viewcount()
if "html" in request.headers["accept"]:
return HttpResponse(pygmentize(paste.filename, paste.content))
return HttpResponse(paste_to_html(paste.filename, paste.content))
else:
return HttpResponse(paste.content, content_type="text/plain")
@lru_cache(1024)
def pygmentize(filename, filecontents):
try:
lexer = get_lexer_for_filename(filename)
except pygments.util.ClassNotFound:
lexer = get_lexer_by_name(settings.PASTE["default_language"])
formatter = HtmlFormatter(style="emacs")
def paste_to_html(filename, filecontents):
data = {}
data["filename"] = filename
data["highlighted"] = pygments.highlight(filecontents, lexer, formatter)
return loader.render_to_string("paste/show-pygments.html", data)
if filename.endswith(".md") or filename.endswith(".txt") or "." not in filename:
data["highlighted"] = markdown_to_html(filecontents)
return loader.render_to_string("paste/show-markdown.html", data)
else:
data["highlighted"] = pygmentize(filename, filecontents)
return loader.render_to_string("paste/show-pygments.html", data)

View File

@ -1,3 +1,5 @@
bleach
markdown
Django
Pygments
shortuuid

View File

@ -6,6 +6,8 @@
#
asgiref==3.6.0
# via django
bleach==6.0.0
# via -r requirements.in
django==4.2
# via
# -r requirements.in
@ -14,6 +16,8 @@ django-appconf==1.0.5
# via django-compressor
django-compressor==4.3.1
# via -r requirements.in
markdown==3.4.3
# via -r requirements.in
pygments==2.15.1
# via -r requirements.in
rcssmin==1.1.1
@ -22,7 +26,11 @@ rjsmin==1.2.1
# via django-compressor
shortuuid==1.0.11
# via -r requirements.in
six==1.16.0
# via bleach
sqlparse==0.4.4
# via django
tabulate==0.9.0
# via -r requirements.in
webencodings==0.5.1
# via bleach

141
using.fr.md Normal file
View File

@ -0,0 +1,141 @@
# Paf'Py
Et PAF !
## Envoyer un ou plusieurs fichiers
En utilisant des requêtes `multipart/form-data` il est possible d'envoyer un fichier :
```bash
$ curl http://localhost:8000/ -Fmanage.py=@manage.py
| URL | size | filename |
|-------------------------|--------|-----------|
| https://p.afpy.org/g3LE | 251 | manage.py |
```
ou plusieurs fichiers en même temps :
```bash
$ curl http://localhost:8000/ -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 :
```bash
$ cal | curl -XPOST --data-binary @- http://localhost:8000/
| 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` :
```bash
$ cal | curl -XPOST -H "Content-Type: text/plain" --data-binary @- http://localhost:8000/
| 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 :
```bash
paf()
{
if [[ $# == 0 ]]
then
curl https://p.afpy.org/ --data-binary @- -H "Content-Type: text/plain"
else
curl https://p.afpy.org/ "${@/*/-F&=@&}"
fi
}
```
Cette fonction est capable d'envoyer un fichier :
```bash
$ paf manage.py
| URL | size | filename |
|-------------------------|--------|-----------|
| https://p.afpy.org/g3LE | 251 | manage.py |
```
plusieurs fichiers :
```bash
$ 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 |
```
Dernière démo, puisque le résultat d'un envoi est un tableau de toutes les URL, il est tentant de le partager lui aussi :
```
$ paf *.py | paf
| URL | size | filename |
|-------------------------|--------|-------------|
| https://p.afpy.org/L5pc | 488 | request.txt |
```
Ce qui donne :
```
$ curl https://p.afpy.org/L5pc
| URL | size | filename |
|-------------------------|--------|-----------------------|
| https://p.afpy.org/7rFj | 188 | admin.py |
| https://p.afpy.org/DLfp | 296 | context_processors.py |
| https://p.afpy.org/9o33 | 0 | __init__.py |
| https://p.afpy.org/YdvG | 1419 | models.py |
| https://p.afpy.org/97fG | 309 | urls.py |
| https://p.afpy.org/oPRr | 2974 | views.py |
```
pratique pour partager tout un dossier.
## 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
import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "webtools.settings")
from django.core.management import execute_from_command_line
execute_from_command_line(sys.argv)
```