Also pretty print Markdown.
This commit is contained in:
parent
c6774a5dfc
commit
14c6436b23
File diff suppressed because it is too large
Load Diff
|
@ -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 %}
|
|
@ -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)
|
|
@ -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)
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
bleach
|
||||
markdown
|
||||
Django
|
||||
Pygments
|
||||
shortuuid
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
```
|
Loading…
Reference in New Issue