Compare commits
16 Commits
Author | SHA1 | Date | |
---|---|---|---|
4654ced5e8 | |||
e93a3e3b28 | |||
eec6016737 | |||
48f1889ee9 | |||
8146cb4831 | |||
e5f3be254b | |||
b6a2c7687d | |||
442c38e45a | |||
1efb44aaad | |||
0ac1f2ef60 | |||
1db3682210 | |||
3eefd6e5ca | |||
5934122c6e | |||
c0b40c7d30 | |||
4cb03d1131 | |||
e291e7497e |
48
README.md
48
README.md
@ -8,6 +8,15 @@ index functions featured in several web servers. It works by traversing the
|
|||||||
file system and directory hierarchy to automatically list all the files in the
|
file system and directory hierarchy to automatically list all the files in the
|
||||||
directory and providing them with html classes and tags for easy styling.
|
directory and providing them with html classes and tags for easy styling.
|
||||||
|
|
||||||
|
## This distribusi fork powers distribusi-verse
|
||||||
|
|
||||||
|
If you want to run distribusi on its own go here: [original](https://git.vvvvvvaria.org/varia/distribusi)
|
||||||
|
This version is used to power [distribusi-verse](https://git.vvvvvvaria.org/Toolsheds/distribusi-verse) which folds this tool in a webapplication.
|
||||||
|
To make this work a little easier this version has certain options removed that are handy
|
||||||
|
and available in the standalone version.
|
||||||
|
|
||||||
|
For other distribusi like softwares also checkout [distribusi-go](https://git.coopcloud.tech/decentral1se/distribusi-go)
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
While a Pip install will pull in Python dependencies, you might need system
|
While a Pip install will pull in Python dependencies, you might need system
|
||||||
@ -18,13 +27,6 @@ packages:
|
|||||||
* [github.com/threatstack/libmagic](https://github.com/threatstack/libmagic)
|
* [github.com/threatstack/libmagic](https://github.com/threatstack/libmagic)
|
||||||
* [pillow.readthedocs.io](https://pillow.readthedocs.io/en/5.3.x/installation.html#external-libraries)
|
* [pillow.readthedocs.io](https://pillow.readthedocs.io/en/5.3.x/installation.html#external-libraries)
|
||||||
|
|
||||||
### Optional requirements
|
|
||||||
|
|
||||||
If you wish to use the `--caption` flag to add image captions read from EXIF comment metadata you will need a utility called `exiftool`.
|
|
||||||
|
|
||||||
You can install it via your package manager. For other options please consult the website: [https://www.sno.phy.queensu.ca/~phil/exiftool/](https://www.sno.phy.queensu.ca/~phil/exiftool/)
|
|
||||||
|
|
||||||
|
|
||||||
## Install It
|
## Install It
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@ -62,7 +64,7 @@ Create a quick gallery for the web:
|
|||||||
$ distribusi -d /path/to/my/photos -t
|
$ distribusi -d /path/to/my/photos -t
|
||||||
```
|
```
|
||||||
|
|
||||||
This creates an `index.html` with `base64` encoded thumbnails.
|
This creates an `index.html` with accompanying `_thumbnail.jpg` files
|
||||||
|
|
||||||
Generate verbose output:
|
Generate verbose output:
|
||||||
|
|
||||||
@ -81,40 +83,14 @@ $ distribusi -d /var/www/archive/my_event -t -v
|
|||||||
|
|
||||||
## History
|
## History
|
||||||
|
|
||||||
Distribusi was first conceptualized as a tool which supported a contribution by Dennis de Bel, Danny van der Kleij and Roel Roscam Abbing to the [ruru house](http://ruruhuis.nl/) organized by [Reinaart Vanhoe](http://vanhoe.org/) and the [ruangrupa](http://ruru.ruangrupa.org/) collective during 2016 Sonsbeek Biennale in Arnhem. During the biennale time the ruru house was a lively meeting place with a programme of discussions, workshops, lectures, culinary activities, performances, pop-up markets and even karaoke evenings, where curators and Arnhemmers met.
|
Distribusi was first conceptualized as a tool which supported a contribution by Dennis de Bel, Danny van der Kleij and Roel Roscam Abbing to the [ruru house](http://ruruhuis.nl/) organized by [Reinaart Vanhoe](http://vanhoe.org/) and the [ruangrupa](http://ruru.ruangrupa.org/) collective during 2016 Sonsbeek Biennale in Arnhem. During the biennale time the ruru house was a lively meeting place with a programme of discussions, workshops, lectures, culinary activities, performances, pop-up markets and even karaoke evenings, where curators and Arnhemmers met.
|
||||||
|
|
||||||
The contribution consisted of setting up distribusi.ruruhuis.nl (distribusi is bahasa Indonesian for 'distribution') which was a website connected to a server in the space. Rather than a hidden administrative interface, the server was present and visible and an invitation was extended to visitors to use it to publish material online. This was done by inserting a USB-drive into any of the ports. The distribusi script would then turn the contents of that stick it into a website. Once the USB-drive was removed that website was no longer on-line. Over time distribusi.ruruhuis.nl hosted photos, books and movies. The website is now off-line but the tool that was used to make it is still used in Varia
|
The contribution consisted of setting up distribusi.ruruhuis.nl (distribusi is bahasa Indonesian for 'distribution') which was a website connected to a server in the space. Rather than a hidden administrative interface, the server was present and visible and an invitation was extended to visitors to use it to publish material online. This was done by inserting a USB-drive into any of the ports. The distribusi script would then turn the contents of that stick it into a website. Once the USB-drive was removed that website was no longer on-line. Over time distribusi.ruruhuis.nl hosted photos, books and movies. The website is now off-line but the tool that was used to make it is still used in Varia
|
||||||
|
|
||||||
## Uses
|
## Uses
|
||||||
|
|
||||||
### 디스트리붓시
|
### 디스트리붓시
|
||||||
[Dianaband](http://www.dianaband.info/) used distribusi for https://fragments1444.ink/.
|
[Dianaband](http://www.dianaband.info/) used distribusi for https://fragments1444.ink/.
|
||||||
|
|
||||||
> "Individuals collecting fragments each have their own folder. When they put a story, picture, audio, or video file inside a folder, each fragment is assigned a serial number, and gets accumulated in the fragments of hospitality website.The fragments connect us. We hope that we can choose the “nature and attitude” of the medium that mediates our connection."
|
> "Individuals collecting fragments each have their own folder. When they put a story, picture, audio, or video file inside a folder, each fragment is assigned a serial number, and gets accumulated in the fragments of hospitality website.The fragments connect us. We hope that we can choose the “nature and attitude” of the medium that mediates our connection."
|
||||||
[https://fragments1444.ink/about.html](source)
|
[https://fragments1444.ink/about.html](source)
|
||||||
|
|
||||||
## Change It
|
|
||||||
|
|
||||||
You'll need to get a copy of the repository and then do an [editable] install:
|
|
||||||
|
|
||||||
[editable]: https://setuptools.readthedocs.io/en/latest/setuptools.html#development-mode
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ git clone https://git.vvvvvvaria.org/varia/distribusi.git && cd distribusi
|
|
||||||
$ python3 -m venv .venv && source .venv/bin/activate
|
|
||||||
$ pip install -e .
|
|
||||||
```
|
|
||||||
|
|
||||||
You're then ready to make your changes and experiment with them.
|
|
||||||
|
|
||||||
## Release It
|
|
||||||
|
|
||||||
You'll need a [PyPi](https://pypi.org/) account and to be added as a maintainer.
|
|
||||||
|
|
||||||
Please ask around @ Varia for who has PyPi access.
|
|
||||||
|
|
||||||
```
|
|
||||||
$ # ... change the version number in setup.py ... #
|
|
||||||
$ pip install twine wheel
|
|
||||||
$ make publish
|
|
||||||
```
|
|
||||||
|
@ -6,83 +6,79 @@ from distribusi.distribusi import distribusify
|
|||||||
|
|
||||||
def build_argparser():
|
def build_argparser():
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
"""
|
"""
|
||||||
distribusi is a content management system for the web that produces static
|
distribusi is a content management system for the web that produces static
|
||||||
index pages based on folders in the files system. It is inspired by the
|
index pages based on folders in the files system. It is inspired by the
|
||||||
automatic index functions featured in several popular web servers.
|
automatic index functions featured in several popular web servers.
|
||||||
distribusi works by traversing the file system and directory hierarchy to
|
distribusi works by traversing the file system and directory hierarchy to
|
||||||
automatically list all the files in the directory, detect the file types
|
automatically list all the files in the directory, detect the file types
|
||||||
and providing them with relevant html classes and tags for easy styling.
|
and providing them with relevant html classes and tags for easy styling.
|
||||||
""")
|
"""
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
'-d', '--directory', help="Select which directory to distribute", default="."
|
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-s', '--style', help="Select a CSS style sheet to include"
|
"-d",
|
||||||
|
"--directory",
|
||||||
|
help="Select which directory to distribute",
|
||||||
|
default=".",
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-v', '--verbose', help="Print verbose debug output", action="store_true"
|
"-s", "--style", help="Select a CSS style sheet to include"
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-t',
|
"-v",
|
||||||
'--thumbnail',
|
"--verbose",
|
||||||
|
help="Print verbose debug output",
|
||||||
|
action="store_true",
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"-t",
|
||||||
|
"--thumbnail",
|
||||||
help="Generate 450x450 thumbnails for images",
|
help="Generate 450x450 thumbnails for images",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-n',
|
"-a",
|
||||||
'--no-template',
|
"--alttexts",
|
||||||
help="Don't use the template to output html",
|
help="Adds file alttext based on same named files",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-nf',
|
"-r",
|
||||||
'--no-filenames',
|
"--remove-index",
|
||||||
help="Don't add file names to listing",
|
|
||||||
action="store_true",
|
|
||||||
)
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
'-c',
|
|
||||||
'--captions',
|
|
||||||
help="Adds image captions based on EXIF metadata, requires 'exiftool'",
|
|
||||||
action="store_true",
|
|
||||||
)
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
'-r',
|
|
||||||
'--remove-index',
|
|
||||||
help="Recursively removes all instances of index.html that have been previously made by distribusi",
|
help="Recursively removes all instances of index.html that have been previously made by distribusi",
|
||||||
action="store_true")
|
action="store_true",
|
||||||
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-e',
|
"-e",
|
||||||
'--exclude-directory',
|
"--exclude-directory",
|
||||||
help="Exclude one or multiple directories from indexing",
|
help="Exclude one or multiple directories from indexing",
|
||||||
nargs="*",
|
nargs="*",
|
||||||
metavar='DIR')
|
metavar="DIR",
|
||||||
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-f',
|
"-f",
|
||||||
'--force',
|
"--force",
|
||||||
help="Force whether distribusi overwrites or removes instances of index.html not generated by distribusi, use at own risk!",
|
help="Force whether distribusi overwrites or removes instances of index.html not generated by distribusi, use at own risk!",
|
||||||
action="store_true")
|
action="store_true",
|
||||||
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--no-hidden',
|
"--no-hidden", help="Exclude hidden directories", action="store_true"
|
||||||
help="Exclude hidden directories",
|
)
|
||||||
action="store_true")
|
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--menu-with-index',
|
"--menu-with-index",
|
||||||
help="Append index.html to menu items to aid navigation",
|
help="Append index.html to menu items to aid navigation",
|
||||||
action="store_true")
|
action="store_true",
|
||||||
|
)
|
||||||
|
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
@ -1,75 +1,85 @@
|
|||||||
import base64
|
|
||||||
import os
|
import os
|
||||||
import subprocess
|
|
||||||
from io import BytesIO
|
|
||||||
|
|
||||||
import magic
|
import magic
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
from distribusi.templates.page_template import html_footer, html_head
|
||||||
from distribusi.page_template import html_footer, html_head
|
from distribusi.templates.image_templates import (
|
||||||
|
image_no_description,
|
||||||
|
image_with_description,
|
||||||
|
image_with_alttext,
|
||||||
|
image_full_figure,
|
||||||
|
)
|
||||||
from distribusi.mappings import CODE_TYPES, FILE_TYPES, SUB_TYPES
|
from distribusi.mappings import CODE_TYPES, FILE_TYPES, SUB_TYPES
|
||||||
|
|
||||||
MIME_TYPE = magic.Magic(mime=True)
|
MIME_TYPE = magic.Magic(mime=True)
|
||||||
|
|
||||||
|
|
||||||
def caption(image):
|
def add_alttext(full_path_image):
|
||||||
try:
|
try:
|
||||||
process = subprocess.Popen(
|
image_filename_no_ext = os.path.splitext(full_path_image)[0]
|
||||||
['exiftool', '-Comment', image], stdout=subprocess.PIPE)
|
alttext_filename = f"{image_filename_no_ext}_alttext.txt"
|
||||||
out, err = process.communicate()
|
return _read_matching_text_file(
|
||||||
|
image_filename_no_ext, alttext_filename
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(e)
|
print(f"exception {e} raised while making alttext")
|
||||||
print('Do you have exiftool installed?')
|
return
|
||||||
try:
|
|
||||||
caption = out.decode("utf-8").split(": ", 1)[1]
|
|
||||||
except Exception as e:
|
|
||||||
caption = ''
|
|
||||||
print(e)
|
|
||||||
return caption
|
|
||||||
|
|
||||||
|
|
||||||
def thumbnail(image, name, args):
|
def add_description(full_path_image):
|
||||||
|
try:
|
||||||
|
image_filename_no_ext = os.path.splitext(full_path_image)[0]
|
||||||
|
description_filename = f"{image_filename_no_ext}_dv_description.txt"
|
||||||
|
return _read_matching_text_file(
|
||||||
|
image_filename_no_ext, description_filename
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"exception {e} raised while adding description")
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def _read_matching_text_file(image_filename_no_ext, filename):
|
||||||
|
if not os.path.isfile(filename):
|
||||||
|
return
|
||||||
|
print(f"{image_filename_no_ext} has {filename}")
|
||||||
|
with open(filename, "r") as text_file:
|
||||||
|
return text_file.read()
|
||||||
|
|
||||||
|
|
||||||
|
def thumbnail(full_path_image, name, args):
|
||||||
|
if full_path_image.endswith("_thumbnail.jpg"):
|
||||||
|
return
|
||||||
try:
|
try:
|
||||||
size = (450, 450)
|
size = (450, 450)
|
||||||
im = Image.open(image)
|
thumbnail_image = Image.open(full_path_image)
|
||||||
im.thumbnail(size)
|
thumbnail_image.thumbnail(size)
|
||||||
|
|
||||||
if (im.mode == 'RGBA'):
|
if thumbnail_image.mode == "RGBA":
|
||||||
bg = Image.new('RGBA', im.size, (255,255,255))
|
bg = Image.new("RGBA", thumbnail_image.size, (255, 255, 255))
|
||||||
composite = Image.alpha_composite(bg, im)
|
composite = Image.alpha_composite(bg, thumbnail_image)
|
||||||
im=composite.convert('RGB')
|
thumbnail_image = composite.convert("RGB")
|
||||||
|
|
||||||
output = BytesIO()
|
image_filename_no_ext = os.path.splitext(full_path_image)[0]
|
||||||
im.save(output, format='JPEG')
|
thumbnail_filename = f"{image_filename_no_ext}_thumbnail.jpg"
|
||||||
im_data = output.getvalue()
|
thumbnail_image.save(thumbnail_filename, format="JPEG")
|
||||||
data_url = base64.b64encode(im_data).decode()
|
return os.path.basename(thumbnail_filename)
|
||||||
if args.captions:
|
|
||||||
cap = caption(image)
|
|
||||||
else:
|
|
||||||
cap = name
|
|
||||||
return (
|
|
||||||
"<figure><a href='{}'><img class='thumbnail' src='data:image/jpg;base64,{}'></a><figcaption>{}</figcaption></figure>"
|
|
||||||
).format(name, data_url, cap)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print('Thumbnailer:', e)
|
print("Thumbnailer:", e)
|
||||||
return "<figure><a href='{}'><img src='{}'></a><figcaption>{}</figcaption></figure>".format(name, name, name)
|
return
|
||||||
|
|
||||||
|
|
||||||
def div(args, type_, subtype, tag, name):
|
def format_div(args, type_, subtype, tag, name):
|
||||||
id_name = name.split('.')[0].replace(' ', '_')
|
id_name = name.split(".")[0].replace(" ", "_")
|
||||||
if args.no_filenames:
|
filename = f'<span class="filename">{name}</span>'
|
||||||
filename = ''
|
|
||||||
else:
|
|
||||||
filename = '<span class="filename">{}</span>'.format(name)
|
|
||||||
|
|
||||||
if 'image' in type_:
|
if "image" in type_:
|
||||||
html = '<div id="{}" class="{}">{}</div>'
|
html = '<div id="{}" class="{}">{}</div>'
|
||||||
elif 'pdf' in subtype:
|
elif "pdf" in subtype:
|
||||||
html = '<div id="{}" class="{}">{}' + filename + '</div>'
|
html = '<div id="{}" class="{}">{}' + filename + "</div>"
|
||||||
elif 'dir' in type_ or 'html' in subtype or 'unkown-file' in subtype:
|
elif "dir" in type_ or "html" in subtype or "unkown-file" in subtype:
|
||||||
html = '<div id="{}" class="{}">{}</div>'
|
html = '<div id="{}" class="{}">{}</div>'
|
||||||
else:
|
else:
|
||||||
html = '<div id="{}" class="{}">{}' + filename + '</div>'
|
html = '<div id="{}" class="{}">{}' + filename + "</div>"
|
||||||
|
|
||||||
return html.format(id_name, subtype, tag)
|
return html.format(id_name, subtype, tag)
|
||||||
|
|
||||||
@ -78,142 +88,188 @@ def check_distribusi_index(args, index):
|
|||||||
"""
|
"""
|
||||||
check whether a index.html file is generated by distribusi
|
check whether a index.html file is generated by distribusi
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not args.force:
|
if not args.force:
|
||||||
with open(index, 'r') as f:
|
with open(index, "r") as f:
|
||||||
if '<meta name="generator" content="distribusi" />' in f.read():
|
if '<meta name="generator" content="distribusi" />' in f.read():
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
if args.verbose:
|
if args.verbose:
|
||||||
print(index, 'not generated by distribusi, skipping')
|
print(index, "not generated by distribusi, skipping")
|
||||||
return False
|
return False
|
||||||
elif args.force:
|
elif args.force:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def write_index(args,index, html, html_head, html_footer):
|
|
||||||
with open(index, 'w') as f:
|
|
||||||
if not args.no_template:
|
|
||||||
if args.style:
|
|
||||||
fs = open(args.style, "r")
|
|
||||||
style = fs.read()
|
|
||||||
styled_html_head = html_head % style
|
|
||||||
else:
|
|
||||||
styled_html_head = html_head % ''
|
|
||||||
f.write(styled_html_head)
|
|
||||||
|
|
||||||
|
def write_index_html(args, index, html):
|
||||||
|
with open(index, "w") as index_file:
|
||||||
|
styled_html_head = html_head.format(cssfile=args.style)
|
||||||
|
index_file.write(styled_html_head)
|
||||||
for line in html:
|
for line in html:
|
||||||
f.write(line + '\n')
|
index_file.write(line + "\n")
|
||||||
|
index_file.write(html_footer)
|
||||||
|
|
||||||
if not args.no_template:
|
|
||||||
f.write(html_footer)
|
def handle_text_files(name, full_path, subtype):
|
||||||
|
if name.endswith(".html") or subtype == "html":
|
||||||
|
subtype = "html"
|
||||||
|
# what types of text files to expand
|
||||||
|
tag = '<section id="{}">{}</section>'.format(
|
||||||
|
name, open(full_path).read()
|
||||||
|
)
|
||||||
|
elif subtype in CODE_TYPES or name.endswith(".txt"):
|
||||||
|
# if the plain text is code,
|
||||||
|
# which types do we wrap in pre-tags?
|
||||||
|
tag = "<pre>" + open(full_path).read() + "</pre>"
|
||||||
|
else:
|
||||||
|
subtype = subtype + " unkown-file"
|
||||||
|
tag = "<a href='{}'>{}</a>"
|
||||||
|
# a = FILE_TYPES[type_]
|
||||||
|
return subtype, tag
|
||||||
|
|
||||||
|
|
||||||
|
def handle_image_files(name, full_path, args):
|
||||||
|
if args.thumbnail:
|
||||||
|
thumbnail_filename = thumbnail(full_path, name, args)
|
||||||
|
if thumbnail_filename is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
image_alttext = add_alttext(full_path)
|
||||||
|
image_description = add_description(full_path)
|
||||||
|
|
||||||
|
if not image_alttext and not image_description:
|
||||||
|
return image_no_description.format(
|
||||||
|
name=name, thumbnail_filename=thumbnail_filename
|
||||||
|
)
|
||||||
|
if not image_alttext:
|
||||||
|
return image_with_description.format(
|
||||||
|
name=name,
|
||||||
|
thumbnail_filename=thumbnail_filename,
|
||||||
|
image_description=image_description,
|
||||||
|
)
|
||||||
|
if not image_description:
|
||||||
|
return image_with_alttext.format(
|
||||||
|
name=name,
|
||||||
|
thumbnail_filename=thumbnail_filename,
|
||||||
|
image_alttext=image_alttext,
|
||||||
|
)
|
||||||
|
return image_full_figure.format(
|
||||||
|
name=name,
|
||||||
|
thumbnail_filename=thumbnail_filename,
|
||||||
|
image_alttext=image_alttext,
|
||||||
|
image_description=image_description,
|
||||||
|
)
|
||||||
|
return FILE_TYPES["image"].format(name, image_alttext)
|
||||||
|
|
||||||
|
|
||||||
|
def remove_index_html(root, files, args):
|
||||||
|
index = os.path.join(root, "index.html")
|
||||||
|
if "index.html" in files:
|
||||||
|
try:
|
||||||
|
if check_distribusi_index(args, index):
|
||||||
|
if args.verbose:
|
||||||
|
print("Removing index.html from", root)
|
||||||
|
os.remove(index)
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def remove_hidden_files_and_folders(dirs, files):
|
||||||
|
dirs = list(filter(lambda d: not d.startswith("."), dirs))
|
||||||
|
files = list(filter(lambda f: not f.startswith("."), files))
|
||||||
|
return dirs, files
|
||||||
|
|
||||||
|
|
||||||
|
def is_skipped_file(name, args):
|
||||||
|
if "index.html" in name:
|
||||||
|
return True
|
||||||
|
|
||||||
|
if name.endswith("_thumbnail.jpg"):
|
||||||
|
return True
|
||||||
|
|
||||||
|
if name.endswith("_alttext.txt"):
|
||||||
|
return True
|
||||||
|
|
||||||
|
if name.endswith("_dv_description.txt"):
|
||||||
|
return True
|
||||||
|
|
||||||
|
if args.style in name:
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def distribusify(args, directory): # noqa
|
def distribusify(args, directory): # noqa
|
||||||
for root, dirs, files in os.walk(directory):
|
for root, dirs, files in os.walk(directory):
|
||||||
|
html = []
|
||||||
|
|
||||||
if args.exclude_directory:
|
if args.exclude_directory:
|
||||||
if args.verbose:
|
if args.verbose:
|
||||||
print('Excluding directory:', ", ".join(args.exclude_directory))
|
print(
|
||||||
|
"Excluding directory:", ", ".join(args.exclude_directory)
|
||||||
|
)
|
||||||
dirs[:] = [d for d in dirs if d not in args.exclude_directory]
|
dirs[:] = [d for d in dirs if d not in args.exclude_directory]
|
||||||
|
|
||||||
if args.no_hidden:
|
if args.no_hidden:
|
||||||
dirs = list(filter(lambda d: not d.startswith('.'), dirs))
|
dirs, files = remove_hidden_files_and_folders(dirs, files)
|
||||||
files = list(filter(lambda f: not f.startswith('.'), files))
|
|
||||||
|
|
||||||
dirs.sort()
|
|
||||||
files.sort()
|
|
||||||
|
|
||||||
if not args.remove_index:
|
|
||||||
html = []
|
|
||||||
|
|
||||||
if args.verbose:
|
|
||||||
print('Generating directory listing for', root)
|
|
||||||
|
|
||||||
for name in sorted(files):
|
|
||||||
|
|
||||||
if 'index.html' not in name:
|
|
||||||
full_path = os.path.join(root, name)
|
|
||||||
mime = MIME_TYPE.from_file(full_path)
|
|
||||||
# example: MIME plain/text becomes 'type' plain 'subtype' text
|
|
||||||
type_, subtype = mime.split('/')
|
|
||||||
|
|
||||||
caption = name
|
|
||||||
|
|
||||||
if args.verbose:
|
|
||||||
print('Found', name, 'as', mime)
|
|
||||||
|
|
||||||
if type_ in FILE_TYPES:
|
|
||||||
|
|
||||||
a = FILE_TYPES[type_].format(name, caption)
|
|
||||||
|
|
||||||
# expansion for different kind of text files
|
|
||||||
if type_ == 'text':
|
|
||||||
if name.endswith('.html') or subtype == 'html':
|
|
||||||
subtype = 'html'
|
|
||||||
# what types of text files to expand
|
|
||||||
a = '<section id="{}">{}</section>'.format(name, open(full_path).read())
|
|
||||||
elif subtype in CODE_TYPES or name.endswith('.txt'):
|
|
||||||
# if the plain text is code,
|
|
||||||
# which types do we wrap in pre-tags?
|
|
||||||
a = "<pre>" + open(full_path).read() + "</pre>"
|
|
||||||
else:
|
|
||||||
subtype = subtype+' unkown-file'
|
|
||||||
a = "<a href='{}'>{}</a>"
|
|
||||||
# a = FILE_TYPES[type_]
|
|
||||||
|
|
||||||
if type_ == 'image':
|
|
||||||
if args.thumbnail:
|
|
||||||
a = thumbnail(full_path, name, args)
|
|
||||||
if args.no_filenames:
|
|
||||||
caption = ""
|
|
||||||
a = FILE_TYPES[type_].format(name, caption)
|
|
||||||
if args.captions:
|
|
||||||
caption = caption(full_path)
|
|
||||||
a = FILE_TYPES[type_].format(name, caption)
|
|
||||||
|
|
||||||
if subtype in SUB_TYPES:
|
|
||||||
a = SUB_TYPES[subtype]
|
|
||||||
|
|
||||||
if type_ not in FILE_TYPES and subtype not in SUB_TYPES:
|
|
||||||
# catch exceptions not yet defined in FILE_TYPES or SUB_TYPES
|
|
||||||
a = "<a href='{}'>{}</a>"
|
|
||||||
if args.verbose:
|
|
||||||
message = 'not in list of file types, adding as plain href: \n'
|
|
||||||
print(type_, subtype, message, name)
|
|
||||||
subtype = subtype + ' unkown-file'
|
|
||||||
|
|
||||||
a = a.replace('{}', name)
|
|
||||||
|
|
||||||
html.append(div(args, type_, subtype, a, name))
|
|
||||||
|
|
||||||
if root != directory:
|
|
||||||
if args.menu_with_index:
|
|
||||||
html.append('<a href="../index.html">../</a>')
|
|
||||||
else:
|
|
||||||
html.append('<a href="../">../</a>')
|
|
||||||
|
|
||||||
for name in dirs:
|
|
||||||
if args.menu_with_index:
|
|
||||||
a = "<a href='{}/index.html'>{}</a>".replace('{}', name)
|
|
||||||
else:
|
|
||||||
a = "<a href='{}'>{}/</a>".replace('{}', name)
|
|
||||||
|
|
||||||
html.insert(0, div(args, 'dir', 'dir', a, 'folder'))
|
|
||||||
|
|
||||||
index = os.path.join(root, 'index.html')
|
|
||||||
if os.path.exists(index):
|
|
||||||
if check_distribusi_index(args, index):
|
|
||||||
write_index(args,index,html, html_head, html_footer)
|
|
||||||
elif not os.path.exists(index):
|
|
||||||
write_index(args,index,html, html_head, html_footer)
|
|
||||||
|
|
||||||
if args.remove_index:
|
if args.remove_index:
|
||||||
index = os.path.join(root, 'index.html')
|
remove_index_html(root, files, args)
|
||||||
if 'index.html' in files:
|
|
||||||
try:
|
if args.verbose:
|
||||||
if check_distribusi_index(args, index):
|
print("Generating directory listing for", root)
|
||||||
if args.verbose:
|
|
||||||
print('Removing index.html from', root)
|
for name in sorted(files):
|
||||||
os.remove(index)
|
if is_skipped_file(name, args):
|
||||||
except Exception as e:
|
continue
|
||||||
print(e)
|
|
||||||
|
full_path = os.path.join(root, name)
|
||||||
|
mime = MIME_TYPE.from_file(full_path)
|
||||||
|
type_, subtype = mime.split("/")
|
||||||
|
alttext = name
|
||||||
|
|
||||||
|
if args.verbose:
|
||||||
|
print("Found", name, "as", mime)
|
||||||
|
if type_ in FILE_TYPES:
|
||||||
|
match type_:
|
||||||
|
case "text":
|
||||||
|
subtype, tag = handle_text_files(
|
||||||
|
name, full_path, subtype
|
||||||
|
)
|
||||||
|
case "image":
|
||||||
|
tag = handle_image_files(name, full_path, args)
|
||||||
|
if tag is None:
|
||||||
|
continue
|
||||||
|
case _:
|
||||||
|
tag = FILE_TYPES[type_].format(name, alttext)
|
||||||
|
|
||||||
|
if subtype in SUB_TYPES:
|
||||||
|
tag = SUB_TYPES[subtype]
|
||||||
|
|
||||||
|
if type_ not in FILE_TYPES and subtype not in SUB_TYPES:
|
||||||
|
# catch exceptions not yet defined in FILE_TYPES or SUB_TYPES
|
||||||
|
tag = "<a href='{}'>{}</a>"
|
||||||
|
if args.verbose:
|
||||||
|
message = (
|
||||||
|
"not in list of file types, adding as plain href: \n"
|
||||||
|
)
|
||||||
|
print(type_, subtype, message, name)
|
||||||
|
subtype = subtype + " unkown-file"
|
||||||
|
|
||||||
|
tag = tag.replace("{}", name)
|
||||||
|
html.append(format_div(args, type_, subtype, tag, name))
|
||||||
|
|
||||||
|
if root != directory:
|
||||||
|
if args.menu_with_index:
|
||||||
|
html.append('<a href="../index.html">../</a>')
|
||||||
|
else:
|
||||||
|
html.append('<a href="../">../</a>')
|
||||||
|
|
||||||
|
for name in sorted(dirs):
|
||||||
|
if args.menu_with_index:
|
||||||
|
tag = "<a href='{}/index.html'>{}</a>".replace("{}", name)
|
||||||
|
else:
|
||||||
|
tag = "<a href='{}'>{}/</a>".replace("{}", name)
|
||||||
|
|
||||||
|
html.insert(0, format_div(args, "dir", "dir", tag, "folder"))
|
||||||
|
|
||||||
|
index = os.path.join(root, "index.html")
|
||||||
|
write_index_html(args, index, html)
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
CODE_TYPES = ['x-c', 'x-shellscript', 'x-python']
|
CODE_TYPES = ["x-c", "x-shellscript", "x-python"]
|
||||||
|
|
||||||
FILE_TYPES = {
|
FILE_TYPES = {
|
||||||
'image': '<figure><img class="image" src="{}"><figcaption>{}</figcaption></figure>',
|
"image": '<img class="image" src="{}" alt="{}">',
|
||||||
'text': '<a href="{}" class="text">{}</a>',
|
"text": '<a href="{}" class="text">{}</a>',
|
||||||
'video': ('<video controls>' '<source src="{}"></video>'),
|
"video": ("<video controls>" '<source src="{}"></video>'),
|
||||||
'audio': ('<audio controls class="audio">' '<source src="{}"></audio>'),
|
"audio": ('<audio controls class="audio">' '<source src="{}"></audio>'),
|
||||||
}
|
}
|
||||||
|
|
||||||
SUB_TYPES = {
|
SUB_TYPES = {
|
||||||
'pdf': (
|
"pdf": (
|
||||||
'<object data="{}" class="pdf" type="application/pdf">'
|
'<object data="{}" class="pdf" type="application/pdf">'
|
||||||
'<embed src="{}" type="application/pdf" /></object>')
|
'<embed src="{}" type="application/pdf" /></object>'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
html_head = """
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<!-- Generated with distribusi https://git.vvvvvvaria.org/varia/distribusi -->
|
|
||||||
<meta name="generator" content="distribusi" />
|
|
||||||
<meta http-equiv="content-type" content="text/html; charset=utf-8">
|
|
||||||
<style>
|
|
||||||
.image{max-width: 100%%;}
|
|
||||||
.pdf object{width:640px;height: 640px;}
|
|
||||||
.dir::before{content:"📁 ";font-size:18px;}
|
|
||||||
.filename{display:block;font-family:mono;}
|
|
||||||
.unkown-file::before{content:"📄 ";font-size:18px;}
|
|
||||||
div{max-width: 640px;display:inline-block;vertical-align:top;margin:1em;padding:1em;}
|
|
||||||
video {width:640px;max-height:640px;}
|
|
||||||
%s
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
"""
|
|
||||||
|
|
||||||
html_footer = """
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
"""
|
|
0
distribusi/templates/__init__.py
Normal file
0
distribusi/templates/__init__.py
Normal file
4
distribusi/templates/image_templates.py
Normal file
4
distribusi/templates/image_templates.py
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
image_no_description = '<a href="{name}"><img class="thumbnail" src="{thumbnail_filename}" alt="{name}"></a>'
|
||||||
|
image_with_description = '<figure><a href="{name}"><img class="thumbnail" src="{thumbnail_filename}"></a><figcaption>{image_description}</figcaption></figure>'
|
||||||
|
image_with_alttext = '<a href="{name}"><img class="thumbnail" src="{thumbnail_filename}" alt="{image_alttext}"></a>'
|
||||||
|
image_full_figure = '<figure><a href="{name}"><img class="thumbnail" src="{thumbnail_filename}" alt="{image_alttext}"></a><figcaption>{image_description}</figcaption></figure>'
|
16
distribusi/templates/page_template.py
Normal file
16
distribusi/templates/page_template.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
html_head = '''\
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<!-- Generated with distribusi https://git.vvvvvvaria.org/crunk/distribusi -->
|
||||||
|
<meta name="generator" content="distribusi" />
|
||||||
|
<meta http-equiv="content-type" content="text/html; charset=utf-8">
|
||||||
|
<link rel="stylesheet" href="{cssfile}" crossorigin="anonymous">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
'''
|
||||||
|
|
||||||
|
html_footer ='''\
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
'''
|
@ -7,3 +7,53 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[tool.black]
|
[tool.black]
|
||||||
skip-string-normalization = true
|
skip-string-normalization = true
|
||||||
|
|
||||||
|
[tool.ruff]
|
||||||
|
line-length = 79
|
||||||
|
target-version = "py311"
|
||||||
|
#include = '\.pyi?$'
|
||||||
|
exclude = [
|
||||||
|
".bzr",
|
||||||
|
".direnv",
|
||||||
|
".eggs",
|
||||||
|
".git",
|
||||||
|
".git-rewrite",
|
||||||
|
".hg",
|
||||||
|
".ipynb_checkpoints",
|
||||||
|
".mypy_cache",
|
||||||
|
".nox",
|
||||||
|
".pants.d",
|
||||||
|
".pyenv",
|
||||||
|
".pytest_cache",
|
||||||
|
".pytype",
|
||||||
|
".ruff_cache",
|
||||||
|
".svn",
|
||||||
|
".tox",
|
||||||
|
".venv",
|
||||||
|
".vscode",
|
||||||
|
"__pypackages__",
|
||||||
|
"_build",
|
||||||
|
"buck-out",
|
||||||
|
"build",
|
||||||
|
"dist",
|
||||||
|
"node_modules",
|
||||||
|
"site-packages",
|
||||||
|
"venv",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.ruff.lint]
|
||||||
|
# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default.
|
||||||
|
select = ["E4", "E7", "E9", "F"]
|
||||||
|
ignore = []
|
||||||
|
# Allow fix for all enabled rules (when `--fix`) is provided.
|
||||||
|
fixable = ["ALL"]
|
||||||
|
unfixable = []
|
||||||
|
# Allow unused variables when underscore-prefixed.
|
||||||
|
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
|
||||||
|
|
||||||
|
[tool.ruff.format]
|
||||||
|
quote-style = "double"
|
||||||
|
indent-style = "space"
|
||||||
|
docstring-code-format = true
|
||||||
|
line-ending = "auto"
|
||||||
|
skip-magic-trailing-comma = false
|
||||||
|
38
setup.py
38
setup.py
@ -1,28 +1,36 @@
|
|||||||
from setuptools import find_packages, setup
|
from setuptools import find_packages, setup
|
||||||
|
|
||||||
dependencies = ['pillow >= 6.1.0, < 7.0', 'python-magic >= 0.4.15, < 1.0']
|
dependencies = [
|
||||||
|
"pillow >= 10.3.0",
|
||||||
|
"python-magic >= 0.4.15, < 1.0",
|
||||||
|
]
|
||||||
|
|
||||||
with open('README.md', 'r') as handle:
|
with open("README.md", "r") as handle:
|
||||||
long_description = handle.read()
|
long_description = handle.read()
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='distribusi',
|
name="distribusi",
|
||||||
version='0.0.10',
|
version="0.0.12",
|
||||||
url='https://git.vvvvvvaria.org/varia/distribusi',
|
url="https://git.vvvvvvaria.org/crunk/distribusi",
|
||||||
license='GPLv3',
|
license="GPLv3",
|
||||||
author='Varia',
|
author="Varia",
|
||||||
description=(
|
description=(
|
||||||
'Distribusi is a content management system for '
|
"Distribusi is a content management system for "
|
||||||
'the web that produces static pages based on '
|
"the web that produces static pages based on "
|
||||||
'the file system.'
|
"the file system."
|
||||||
),
|
),
|
||||||
long_description=long_description,
|
long_description=long_description,
|
||||||
long_description_content_type='text/markdown',
|
long_description_content_type="text/markdown",
|
||||||
packages=find_packages(exclude=['tests']),
|
packages=find_packages(exclude=["tests"]),
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
zip_safe=False,
|
zip_safe=False,
|
||||||
platforms='any',
|
platforms="any",
|
||||||
install_requires=dependencies,
|
install_requires=dependencies,
|
||||||
entry_points={'console_scripts': ['distribusi = distribusi.cli:cli_entrypoint']},
|
entry_points={
|
||||||
classifiers=['Programming Language :: Python :: 3', 'Environment :: Console'],
|
"console_scripts": ["distribusi = distribusi.cli:cli_entrypoint"]
|
||||||
|
},
|
||||||
|
classifiers=[
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
"Environment :: Console",
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user