forked from crunk/distribusi-verse
removed distribusi git submodule
This commit is contained in:
parent
4d0d5bd767
commit
c8862dfb59
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -1,3 +0,0 @@
|
||||
[submodule "distribusi"]
|
||||
path = distribusi
|
||||
url = https://git.vvvvvvaria.org/crunk/distribusi
|
@ -1 +0,0 @@
|
||||
Subproject commit e291e7497e40211c2ebd54ca32a1f4bdaed71230
|
0
distribusi/__init__.py
Normal file
0
distribusi/__init__.py
Normal file
89
distribusi/cli.py
Normal file
89
distribusi/cli.py
Normal file
@ -0,0 +1,89 @@
|
||||
import argparse
|
||||
import os
|
||||
|
||||
from distribusi.distribusi import distribusify
|
||||
|
||||
|
||||
def build_argparser():
|
||||
parser = argparse.ArgumentParser(
|
||||
"""
|
||||
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
|
||||
automatic index functions featured in several popular web servers.
|
||||
distribusi works by traversing the file system and directory hierarchy to
|
||||
automatically list all the files in the directory, detect the file types
|
||||
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(
|
||||
"-s", "--style", help="Select a CSS style sheet to include"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"-v",
|
||||
"--verbose",
|
||||
help="Print verbose debug output",
|
||||
action="store_true",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"-t",
|
||||
"--thumbnail",
|
||||
help="Generate 450x450 thumbnails for images",
|
||||
action="store_true",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"-a",
|
||||
"--alttexts",
|
||||
help="Adds file alttext based on same named files",
|
||||
action="store_true",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"-r",
|
||||
"--remove-index",
|
||||
help="Recursively removes all instances of index.html that have been previously made by distribusi",
|
||||
action="store_true",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"-e",
|
||||
"--exclude-directory",
|
||||
help="Exclude one or multiple directories from indexing",
|
||||
nargs="*",
|
||||
metavar="DIR",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"-f",
|
||||
"--force",
|
||||
help="Force whether distribusi overwrites or removes instances of index.html not generated by distribusi, use at own risk!",
|
||||
action="store_true",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--no-hidden", help="Exclude hidden directories", action="store_true"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--menu-with-index",
|
||||
help="Append index.html to menu items to aid navigation",
|
||||
action="store_true",
|
||||
)
|
||||
|
||||
return parser
|
||||
|
||||
|
||||
def cli_entrypoint():
|
||||
parser = build_argparser()
|
||||
args = parser.parse_args()
|
||||
distribusify(args, args.directory)
|
275
distribusi/distribusi.py
Normal file
275
distribusi/distribusi.py
Normal file
@ -0,0 +1,275 @@
|
||||
import os
|
||||
|
||||
import magic
|
||||
from PIL import Image
|
||||
from distribusi.templates.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
|
||||
|
||||
MIME_TYPE = magic.Magic(mime=True)
|
||||
|
||||
|
||||
def add_alttext(full_path_image):
|
||||
try:
|
||||
image_filename_no_ext = os.path.splitext(full_path_image)[0]
|
||||
alttext_filename = f"{image_filename_no_ext}_alttext.txt"
|
||||
return _read_matching_text_file(
|
||||
image_filename_no_ext, alttext_filename
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"exception {e} raised while making alttext")
|
||||
return
|
||||
|
||||
|
||||
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:
|
||||
size = (450, 450)
|
||||
thumbnail_image = Image.open(full_path_image)
|
||||
thumbnail_image.thumbnail(size)
|
||||
|
||||
if thumbnail_image.mode == "RGBA":
|
||||
bg = Image.new("RGBA", thumbnail_image.size, (255, 255, 255))
|
||||
composite = Image.alpha_composite(bg, thumbnail_image)
|
||||
thumbnail_image = composite.convert("RGB")
|
||||
|
||||
image_filename_no_ext = os.path.splitext(full_path_image)[0]
|
||||
thumbnail_filename = f"{image_filename_no_ext}_thumbnail.jpg"
|
||||
thumbnail_image.save(thumbnail_filename, format="JPEG")
|
||||
return os.path.basename(thumbnail_filename)
|
||||
except Exception as e:
|
||||
print("Thumbnailer:", e)
|
||||
return
|
||||
|
||||
|
||||
def format_div(args, type_, subtype, tag, name):
|
||||
id_name = name.split(".")[0].replace(" ", "_")
|
||||
filename = f'<span class="filename">{name}</span>'
|
||||
|
||||
if "image" in type_:
|
||||
html = '<div id="{}" class="{}">{}</div>'
|
||||
elif "pdf" in subtype:
|
||||
html = '<div id="{}" class="{}">{}' + filename + "</div>"
|
||||
elif "dir" in type_ or "html" in subtype or "unkown-file" in subtype:
|
||||
html = '<div id="{}" class="{}">{}</div>'
|
||||
else:
|
||||
html = '<div id="{}" class="{}">{}' + filename + "</div>"
|
||||
|
||||
return html.format(id_name, subtype, tag)
|
||||
|
||||
|
||||
def check_distribusi_index(args, index):
|
||||
"""
|
||||
check whether a index.html file is generated by distribusi
|
||||
"""
|
||||
if not args.force:
|
||||
with open(index, "r") as f:
|
||||
if '<meta name="generator" content="distribusi" />' in f.read():
|
||||
return True
|
||||
else:
|
||||
if args.verbose:
|
||||
print(index, "not generated by distribusi, skipping")
|
||||
return False
|
||||
elif args.force:
|
||||
return True
|
||||
|
||||
|
||||
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:
|
||||
index_file.write(line + "\n")
|
||||
index_file.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
|
||||
for root, dirs, files in os.walk(directory):
|
||||
html = []
|
||||
|
||||
if args.exclude_directory:
|
||||
if args.verbose:
|
||||
print(
|
||||
"Excluding directory:", ", ".join(args.exclude_directory)
|
||||
)
|
||||
dirs[:] = [d for d in dirs if d not in args.exclude_directory]
|
||||
|
||||
if args.no_hidden:
|
||||
dirs, files = remove_hidden_files_and_folders(dirs, files)
|
||||
|
||||
if args.remove_index:
|
||||
remove_index_html(root, files, args)
|
||||
|
||||
if args.verbose:
|
||||
print("Generating directory listing for", root)
|
||||
|
||||
for name in sorted(files):
|
||||
if is_skipped_file(name, args):
|
||||
continue
|
||||
|
||||
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)
|
15
distribusi/mappings.py
Normal file
15
distribusi/mappings.py
Normal file
@ -0,0 +1,15 @@
|
||||
CODE_TYPES = ["x-c", "x-shellscript", "x-python"]
|
||||
|
||||
FILE_TYPES = {
|
||||
"image": '<img class="image" src="{}" alt="{}">',
|
||||
"text": '<a href="{}" class="text">{}</a>',
|
||||
"video": ("<video controls>" '<source src="{}"></video>'),
|
||||
"audio": ('<audio controls class="audio">' '<source src="{}"></audio>'),
|
||||
}
|
||||
|
||||
SUB_TYPES = {
|
||||
"pdf": (
|
||||
'<object data="{}" class="pdf" type="application/pdf">'
|
||||
'<embed src="{}" type="application/pdf" /></object>'
|
||||
)
|
||||
}
|
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>
|
||||
'''
|
@ -8,7 +8,6 @@ bleach-allowlist==1.0.3
|
||||
blinker==1.7.0
|
||||
cffi
|
||||
click==8.1.7
|
||||
distribusi @ git+https://git.vvvvvvaria.org/crunk/distribusi@4654ced5e8dfe803b62bc6163fa3d88a73a9010a
|
||||
dnspython==2.1.0
|
||||
email-validator==1.1.3
|
||||
Flask==3.0.3
|
||||
|
Loading…
Reference in New Issue
Block a user