Compare commits

...

45 Commits

Author SHA1 Message Date
rra 545577978a Update 'README.md' 6 days ago
decentral1se 60275a475e
Add sketchy build script 1 year ago
decentral1se e730cfec55
Boot out a 0.0.9 release 1 year ago
rra f766f67cee Merge branch 'master' of dickreckard/distribusi into master 2 years ago
dickreckard 49a521ea14 thumbnails png fix 2 years ago
dickreckard 69fa5bb7c5 thumbnails fix 2 years ago
decentral1se 5253d37411
Fix typo 2 years ago
decentral1se c0c069cead
Update version to match next release 2 years ago
rra 64f449eee8 Update 'README.md' 2 years ago
decentral1se 601d5e86e7
Ignore publishing files 2 years ago
decentral1se fd370294be
Fix back buttons when using index.html navigation 2 years ago
decentral1se cb9e19d1a2
Allow to append index.html files on menus 2 years ago
decentral1se 2a0834734f
Allow skipping hidden and sorting 2 years ago
decentral1se 63902c079a
Merge lists and announce version skip 2 years ago
rra 6a9fbe9856 files are listed alphabetically, fixes to css and html 2 years ago
rra 321405c66f update changelog 2 years ago
RRA 010020e08b added --force flag to force overwriting non-distribusi indexes, code formatting 2 years ago
RRA e21a78324f distribusi now only removes indexes that are created by distribusi 2 years ago
RRA d5335f3212 added possibility to exclude directories with --exclude-directory option 2 years ago
rra 3247286a7e added info about exiftool 2 years ago
RRA 8529d952cd clean up according to pep8 2 years ago
rra 6400b2aea2 update changelog 2 years ago
rra 89067b48f7 Merge branch 'master' of https://git.vvvvvvaria.org/varia/distribusi 2 years ago
rra bc6fa0bad8 unknown files now have icon 2 years ago
rra fc19d536df default directory is always the current one, dont print anything if not verbose 2 years ago
rra 28f8793613 images now also respect --nf 2 years ago
rra 52830ff7c4 - changed variable names for clarity 2 years ago
decentral1se 55e8fe190e
Make new release 2 years ago
decentral1se 80e8a5e4fd
Re-add pillow (still needed) and use looser bounds 2 years ago
rra b613eef36c speed up by not calling exiftools always, images now always have figcaptions, catch errors properly 2 years ago
rra dbe0e59b90 clean up argparse 2 years ago
rra 1611144f72 move mimetype mappings to separate file 2 years ago
decentral1se 9b8be7c761
Show how to upgrade 2 years ago
decentral1se cb37e6428b
Add changelog 2 years ago
decentral1se dc92a81035
Match other headers 2 years ago
decentral1se e1ef66e646
Simplify install guide, add PyPi badge and access note 2 years ago
decentral1se df71f44550
Clean up imports and formatting 2 years ago
decentral1se d5606e5325
Bump to the next version and pin python-magic 2 years ago
decentral1se 8829a84b65
Add Black and Pip files config 2 years ago
decentral1se 6212f25536
Don't use pipenv for releases 2 years ago
decentral1se b31e211498
Ignore new Pip related files 2 years ago
decentral1se 02b0e00892
Remove Pipenv stuff 2 years ago
lowrussia 415f8a5ac8 fixed style path issue 2 years ago
lowrussia afaa58d710 added arguments for including exif metadata, including external css, not including filenames of pictures 2 years ago
lowrussia 7687aa96a2 added arguments for including exif metadata, including external css, not including filenames of pictures 2 years ago
  1. 4
      .gitignore
  2. 47
      CHANGELOG.md
  3. 6
      Makefile
  4. 11
      Pipfile
  5. 169
      Pipfile.lock
  6. 57
      README.md
  7. 12
      build.sh
  8. 92
      distribusi/cli.py
  9. 287
      distribusi/distribusi.py
  10. 14
      distribusi/mappings.py
  11. 20
      distribusi/page_template.py
  12. 9
      pyproject.toml
  13. 25
      setup.py

4
.gitignore

@ -5,3 +5,7 @@ __pycache__/
build/
dist
index.html
pip-wheel-metadata/
*.pdf
*.css
*.pyz

47
CHANGELOG.md

@ -0,0 +1,47 @@
# Changelog
The changelog was only added at version 0.0.4.
## 0.0.9
- Fix thumbnail generation (thanks @dickreckard!)
- Adjust formatting for usage output
## 0.0.8
- Allow to ignore hidden directories with `--no-hidden`
- Files and directories are now sorted during distribusification.
- Allow to append `index.html` to the menu items with `--menu-with-index`
## 0.0.7
Let's think a bit about safety and robustness:
- distribusi only overwrites (or removes) indexes that have been created by distribusi itself
- override the above behaviour with `--force`
- `--exclude` now allows you to exclude folder names from being listed, this behaviour is not influenced by `--force`
And also some refactoring and niceties:
- Distribusi only prints when called with `--verbose`
- Restyled `--verbose` output
- Generated indexes can be removed with `--remove-indexes`
- .html and .txt files are now expanded and included as snippets in the index file
- code rewrite for clarity
- HTML output is more precisely styleable
## 0.0.6
Woops, we missed that one.
## 0.0.5
- Use loose bounds for dependencies
- Don't call exiftools on every execution
- If `PILLOW` can't thumbnail an image it is included as a link instead
## 0.0.4
- Add captions from EXIF metadata
- Custom stylesheet usage
- Ability to hide filenames

6
Makefile

@ -1,6 +1,4 @@
PIPENV := pipenv run
publish:
@rm -rf dist
@$(PIPENV) python setup.py bdist_wheel --universal
@$(PIPENV) twine upload dist/*
@python setup.py bdist_wheel --universal
@twine upload dist/*

11
Pipfile

@ -1,11 +0,0 @@
[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true
[dev-packages]
pillow = "*"
python-magic = "*"
twine = "*"
[packages]

169
Pipfile.lock

@ -1,169 +0,0 @@
{
"_meta": {
"hash": {
"sha256": "84f37d28add02d81cc62fe51ee634dcc491c25ebdd9e0076e67f43039cd58c56"
},
"pipfile-spec": 6,
"requires": {},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {},
"develop": {
"bleach": {
"hashes": [
"sha256:48d39675b80a75f6d1c3bdbffec791cf0bbbab665cf01e20da701c77de278718",
"sha256:73d26f018af5d5adcdabf5c1c974add4361a9c76af215fe32fdec8a6fc5fb9b9"
],
"version": "==3.0.2"
},
"certifi": {
"hashes": [
"sha256:339dc09518b07e2fa7eda5450740925974815557727d6bd35d319c1524a04a4c",
"sha256:6d58c986d22b038c8c0df30d639f23a3e6d172a05c3583e766f4c0b785c0986a"
],
"version": "==2018.10.15"
},
"chardet": {
"hashes": [
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
],
"version": "==3.0.4"
},
"docutils": {
"hashes": [
"sha256:02aec4bd92ab067f6ff27a38a38a41173bf01bed8f89157768c1573f53e474a6",
"sha256:51e64ef2ebfb29cae1faa133b3710143496eca21c530f3f71424d77687764274",
"sha256:7a4bd47eaf6596e1295ecb11361139febe29b084a87bf005bf899f9a42edc3c6"
],
"version": "==0.14"
},
"idna": {
"hashes": [
"sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e",
"sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16"
],
"version": "==2.7"
},
"pillow": {
"hashes": [
"sha256:00203f406818c3f45d47bb8fe7e67d3feddb8dcbbd45a289a1de7dd789226360",
"sha256:0616f800f348664e694dddb0b0c88d26761dd5e9f34e1ed7b7a7d2da14b40cb7",
"sha256:1f7908aab90c92ad85af9d2fec5fc79456a89b3adcc26314d2cde0e238bd789e",
"sha256:2ea3517cd5779843de8a759c2349a3cd8d3893e03ab47053b66d5ec6f8bc4f93",
"sha256:48a9f0538c91fc136b3a576bee0e7cd174773dc9920b310c21dcb5519722e82c",
"sha256:5280ebc42641a1283b7b1f2c20e5b936692198b9dd9995527c18b794850be1a8",
"sha256:5e34e4b5764af65551647f5cc67cf5198c1d05621781d5173b342e5e55bf023b",
"sha256:63b120421ab85cad909792583f83b6ca3584610c2fe70751e23f606a3c2e87f0",
"sha256:696b5e0109fe368d0057f484e2e91717b49a03f1e310f857f133a4acec9f91dd",
"sha256:870ed021a42b1b02b5fe4a739ea735f671a84128c0a666c705db2cb9abd528eb",
"sha256:916da1c19e4012d06a372127d7140dae894806fad67ef44330e5600d77833581",
"sha256:9303a289fa0811e1c6abd9ddebfc770556d7c3311cb2b32eff72164ddc49bc64",
"sha256:9577888ecc0ad7d06c3746afaba339c94d62b59da16f7a5d1cff9e491f23dace",
"sha256:987e1c94a33c93d9b209315bfda9faa54b8edfce6438a1e93ae866ba20de5956",
"sha256:99a3bbdbb844f4fb5d6dd59fac836a40749781c1fa63c563bc216c27aef63f60",
"sha256:99db8dc3097ceafbcff9cb2bff384b974795edeb11d167d391a02c7bfeeb6e16",
"sha256:a5a96cf49eb580756a44ecf12949e52f211e20bffbf5a95760ac14b1e499cd37",
"sha256:aa6ca3eb56704cdc0d876fc6047ffd5ee960caad52452fbee0f99908a141a0ae",
"sha256:aade5e66795c94e4a2b2624affeea8979648d1b0ae3fcee17e74e2c647fc4a8a",
"sha256:b78905860336c1d292409e3df6ad39cc1f1c7f0964e66844bbc2ebfca434d073",
"sha256:b92f521cdc4e4a3041cc343625b699f20b0b5f976793fb45681aac1efda565f8",
"sha256:bfde84bbd6ae5f782206d454b67b7ee8f7f818c29b99fd02bf022fd33bab14cb",
"sha256:c2b62d3df80e694c0e4a0ed47754c9480521e25642251b3ab1dff050a4e60409",
"sha256:c5e2be6c263b64f6f7656e23e18a4a9980cffc671442795682e8c4e4f815dd9f",
"sha256:c99aa3c63104e0818ec566f8ff3942fb7c7a8f35f9912cb63fd8e12318b214b2",
"sha256:dae06620d3978da346375ebf88b9e2dd7d151335ba668c995aea9ed07af7add4",
"sha256:db5499d0710823fa4fb88206050d46544e8f0e0136a9a5f5570b026584c8fd74",
"sha256:f36baafd82119c4a114b9518202f2a983819101dcc14b26e43fc12cbefdce00e",
"sha256:f52b79c8796d81391ab295b04e520bda6feed54d54931708872e8f9ae9db0ea1",
"sha256:ff8cff01582fa1a7e533cb97f628531c4014af4b5f38e33cdcfe5eec29b6d888"
],
"index": "pypi",
"version": "==5.3.0"
},
"pkginfo": {
"hashes": [
"sha256:5878d542a4b3f237e359926384f1dde4e099c9f5525d236b1840cf704fa8d474",
"sha256:a39076cb3eb34c333a0dd390b568e9e1e881c7bf2cc0aee12120636816f55aee"
],
"version": "==1.4.2"
},
"pygments": {
"hashes": [
"sha256:78f3f434bcc5d6ee09020f92ba487f95ba50f1e3ef83ae96b9d5ffa1bab25c5d",
"sha256:dbae1046def0efb574852fab9e90209b23f556367b5a320c0bcb871c77c3e8cc"
],
"version": "==2.2.0"
},
"python-magic": {
"hashes": [
"sha256:f2674dcfad52ae6c49d4803fa027809540b130db1dec928cfbb9240316831375",
"sha256:f3765c0f582d2dfc72c15f3b5a82aecfae9498bd29ca840d72f37d7bd38bfcd5"
],
"index": "pypi",
"version": "==0.4.15"
},
"readme-renderer": {
"hashes": [
"sha256:bb16f55b259f27f75f640acf5e00cf897845a8b3e4731b5c1a436e4b8529202f",
"sha256:c8532b79afc0375a85f10433eca157d6b50f7d6990f337fa498c96cd4bfc203d"
],
"version": "==24.0"
},
"requests": {
"hashes": [
"sha256:65b3a120e4329e33c9889db89c80976c5272f56ea92d3e74da8a463992e3ff54",
"sha256:ea881206e59f41dbd0bd445437d792e43906703fff75ca8ff43ccdb11f33f263"
],
"version": "==2.20.1"
},
"requests-toolbelt": {
"hashes": [
"sha256:42c9c170abc2cacb78b8ab23ac957945c7716249206f90874651971a4acff237",
"sha256:f6a531936c6fa4c6cfce1b9c10d5c4f498d16528d2a54a22ca00011205a187b5"
],
"version": "==0.8.0"
},
"six": {
"hashes": [
"sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9",
"sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"
],
"version": "==1.11.0"
},
"tqdm": {
"hashes": [
"sha256:3c4d4a5a41ef162dd61f1edb86b0e1c7859054ab656b2e7c7b77e7fbf6d9f392",
"sha256:5b4d5549984503050883bc126280b386f5f4ca87e6c023c5d015655ad75bdebb"
],
"version": "==4.28.1"
},
"twine": {
"hashes": [
"sha256:7d89bc6acafb31d124e6e5b295ef26ac77030bf098960c2a4c4e058335827c5c",
"sha256:fad6f1251195f7ddd1460cb76d6ea106c93adb4e56c41e0da79658e56e547d2c"
],
"index": "pypi",
"version": "==1.12.1"
},
"urllib3": {
"hashes": [
"sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39",
"sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22"
],
"version": "==1.24.1"
},
"webencodings": {
"hashes": [
"sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"
],
"version": "==0.5.1"
}
}
}

57
README.md

@ -1,5 +1,7 @@
# Distribusi CMS
[![PyPI version](https://badge.fury.io/py/distribusi.svg)](https://badge.fury.io/py/distribusi)
`distribusi` is a content management system for the web that produces static
index pages based on folders in the filesystem. It is inspired by the automatic
index functions featured in several web servers. It works by traversing the
@ -16,29 +18,29 @@ packages:
* [github.com/threatstack/libmagic](https://github.com/threatstack/libmagic)
* [pillow.readthedocs.io](https://pillow.readthedocs.io/en/5.3.x/installation.html#external-libraries)
## Installation
### 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`.
Using [--user] or [a virtual environment] is recommended:
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/)
[--user]: https://packaging.python.org/tutorials/installing-packages/#installing-to-the-user-site
[a virtual environment]: https://packaging.python.org/tutorials/installing-packages/#creating-virtual-environments
## Install It
```bash
$ export PATH=$PATH:$HOME/.local/bin
$ pip install --user distribusi
```
Note: check if the path of your local bin is added to your shell path (otherwise you cannot run distribusi from the shell directly).
To check where distribusi is installed:
$ find * | grep distribusi
## Upgrade It
Add local bin to the $PATH variable:
If you already have it, you can upgrade with:
$ PATH=$PATH:/home/USERNAME/.local/bin/
```bash
$ pip install -U distribusi
```
## Usage
## Use It
Get help with:
@ -76,23 +78,42 @@ $ distribusi -d /var/www/archive/my_event -t -v
# ✌
## 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.
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
### 디스트리붓시
[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."
[https://fragments1444.ink/about.html](source)
## Change It
Install [Pipenv] and then run:
You'll need to get a copy of the repository and then do an [editable] install:
[Pipenv]: https://pipenv.readthedocs.io/en/latest/install/#installing-pipenv
[editable]: https://setuptools.readthedocs.io/en/latest/setuptools.html#development-mode
```
$ pipenv install --dev
$ pipenv run pip install -e .
$ pipenv run distribusi --help
```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.
```
$ pip install twine
$ make publish
```

12
build.sh

@ -0,0 +1,12 @@
#!/bin/bash
# Ultra wild west single binary compiling
# https://github.com/linkedin/shiv
shiv --output-file=distribusi.pyz \
--site-packages=.venv/lib/python3.7/site-packages/ \
--entry-point=distribusi.cli.cli_entrypoint \
--python="/usr/bin/env python3" \
--compressed \
--compile-pyc \
.

92
distribusi/cli.py

@ -1,59 +1,93 @@
import argparse
import os
from distribusi.distribusi import distribusify
def build_argparser():
parser = argparse.ArgumentParser("""
distbusi is a content management system for the web that produces static
index pages based on folders in the filesystem. It is inspired by the
automatic 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 directory and providing them with html classes and
tags for easy styling.
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"
'-d', '--directory', help="Select which directory to distribute", default="."
)
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(
'-v', '--verbose', help="Print verbose debug output", action="store_true"
)
parser.add_argument(
'-t',
'--thumbnail',
help="Generate 150x150 thumbnails for images",
action="store_true"
help="Generate 450x450 thumbnails for images",
action="store_true",
)
parser.add_argument(
'-n',
'--no-template',
help="Don't use the template to ouput html",
action="store_true"
help="Don't use the template to output html",
action="store_true",
)
parser.add_argument(
'-nf',
'--no-filenames',
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",
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()
if args.directory:
if args.verbose:
print('Generating directory listing for', args.directory)
if args.thumbnail:
print('Making thumbnails')
directory = args.directory
else:
directory = '.'
distribusify(args, directory)
distribusify(args, args.directory)

287
distribusi/distribusi.py

@ -1,127 +1,218 @@
import base64
import os
import subprocess
from io import BytesIO
import magic
from distribusi.page_template import html_footer, html_head
from PIL import Image
from distribusi.page_template import html_footer, html_head
from distribusi.mappings import CODE_TYPES, FILE_TYPES, SUB_TYPES
CODE_TYPES = [
'x-c',
'html'
]
FILE_TYPES = {
'image': '<img class="image" src="{}">',
'pdf': (
'<object data="{}" class="pdf" type="application/pdf">'
'<embed src="{}" type="application/pdf" /></object>'
),
'text': '<a href="{}" class="text">{}</a>',
'video': (
'<video class="video" controls>'
'<source src="{}"></source></video>'
),
'audio': (
'<audio controls class="audio">'
'<source src="{}"></source></audio>'
),
}
MIME_TYPE = magic.Magic(mime=True)
MIME_TYPE = magic.Magic(mime=True)
def caption(image):
try:
process = subprocess.Popen(
['exiftool', '-Comment', image], stdout=subprocess.PIPE)
out, err = process.communicate()
except Exception as e:
print(e)
print('Do you have exiftool installed?')
try:
caption = out.decode("utf-8").split(": ", 1)[1]
except Exception as e:
caption = ''
print(e)
return caption
def thumbnail(image, name, args):
try:
size = (450, 450)
im = Image.open(image)
im.thumbnail(size)
if (im.mode == 'RGBA'):
bg = Image.new('RGBA', im.size, (255,255,255))
composite = Image.alpha_composite(bg, im)
im=composite.convert('RGB')
output = BytesIO()
im.save(output, format='JPEG')
im_data = output.getvalue()
data_url = base64.b64encode(im_data).decode()
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:
print('Thumbnailer:', e)
return "<figure><a href='{}'><img src='{}'></a><figcaption>{}</figcaption></figure>".format(name, name, name)
def div(args, type_, subtype, tag, name):
id_name = name.split('.')[0].replace(' ', '_')
if args.no_filenames:
filename = ''
else:
filename = '<span class="filename">{}</span>'.format(name)
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 thumbnail(image, name):
size = (450, 450)
im = Image.open(image)
im.thumbnail(size)
output = BytesIO()
im.save(output, format='JPEG')
im_data = output.getvalue()
data_url = base64.b64encode(im_data).decode()
return (
"<a href='{}'><img class='thumbnail' "
"src='data:image/jpg;base64,{}'></a>"
).format(name, data_url)
def check_distribusi_index(args, index):
"""
check whether a index.html file is generated by distribusi
"""
def div(mime, tag, *values):
id_name = values[0].split('.')[0].replace(' ', '_')
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
if 'image' in mime:
html = '<div id="{}">{}<br><span class="filename">{}</span></div>'
elif 'pdf' in mime:
html = '<div id="{}">{}<br><class="filename">{}</span></div>'
else:
html = '<div id="{}">{}</div>'
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)
return html.format(id_name, tag, values[0])
for line in html:
f.write(line + '\n')
if not args.no_template:
f.write(html_footer)
def distribusify(args, directory): # noqa
for root, dirs, files in os.walk(directory):
html = []
if args.verbose:
print('Listing', root)
for name in files:
if args.exclude_directory:
if args.verbose:
print('Adding', name)
if 'index.html' not in name:
full_path = os.path.join(root, name)
mime = MIME_TYPE.from_file(full_path)
mime, format = mime.split('/') # example: plain text
print('Excluding directory:', ", ".join(args.exclude_directory))
dirs[:] = [d for d in dirs if d not in args.exclude_directory]
if args.verbose:
print(mime, format)
if mime in FILE_TYPES:
# expansion for different kind of textfiles
if mime == 'text':
if name.endswith('.html') or name.endswith('.txt'):
# what types of text files to expand
a = open(full_path).read()
elif format in CODE_TYPES:
# if the plain text is code,
# which types do we wrap in pre-tags?
a = "<pre>"+open(full_path).read()+"</pre>"
else:
a = FILE_TYPES[mime]
if mime == 'image' and args.thumbnail:
a = thumbnail(full_path, name)
else:
a = FILE_TYPES[mime]
if format in FILE_TYPES:
a = FILE_TYPES[format]
if mime not in FILE_TYPES and format not in FILE_TYPES:
# catch exceptions not defined in FILE_TYPES before
a = "<a href='{}'>{}</a>"
if args.verbose:
message = 'mime-type not in list, adding as href: \n'
print(message, mime, format, name)
if args.no_hidden:
dirs = list(filter(lambda d: not d.startswith('.'), dirs))
files = list(filter(lambda f: not f.startswith('.'), files))
a = a.replace('{}', name)
html.append(div(mime, a, name))
dirs.sort()
files.sort()
if root != directory:
html.append('<a href="../">../</a>')
if not args.remove_index:
html = []
for name in dirs:
a = "<a href='{}' class='dir'>{}/</a>".replace('{}', name)
html.append(div('dir', a, 'folder'))
if args.verbose:
print('Generating directory listing for', root)
for name in sorted(files):
with open(os.path.join(root, 'index.html'), 'w') as f:
if not args.no_template:
f.write(html_head)
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('/')
for line in html:
f.write(line+'\n')
caption = name
if not args.no_template:
f.write(html_footer)
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 = ""
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:
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)

14
distribusi/mappings.py

@ -0,0 +1,14 @@
CODE_TYPES = ['x-c', 'x-shellscript', 'x-python']
FILE_TYPES = {
'image': '<figure><img class="image" src="{}"><figcaption>{}</figcaption></figure>',
'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>')
}

20
distribusi/page_template.py

@ -2,15 +2,19 @@ html_head = """
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Generated with distribusi https://git.vvvvvvaria.org/rra/distribusi -->
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<!-- 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 {width:100%;}
div{width: 640px;float:left;padding:1em;}
video {width:640px;}
.dir::before{content:"📁";font-size:18px;}
</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>
"""

9
pyproject.toml

@ -0,0 +1,9 @@
[build-system]
requires = [
"setuptools >= 41.0.0",
"wheel",
]
build-backend = "setuptools.build_meta"
[tool.black]
skip-string-normalization = true

25
setup.py

@ -1,22 +1,18 @@
from setuptools import find_packages, setup
dependencies = [
# pinned because https://github.com/python-pillow/Pillow/issues/2609
'pillow==4.1',
'python-magic',
]
dependencies = ['pillow >= 6.1.0, < 7.0', 'python-magic >= 0.4.15, < 1.0']
with open('README.md', 'r') as handle:
long_description = handle.read()
setup(
name='distribusi',
version='0.0.3',
url='https://git.vvvvvvaria.org/rra/distribusi',
version='0.0.9',
url='https://git.vvvvvvaria.org/varia/distribusi',
license='GPLv3',
author='rra',
author='Varia',
description=(
'distribusi is a content management system for '
'Distribusi is a content management system for '
'the web that produces static pages based on '
'the file system.'
),
@ -27,13 +23,6 @@ setup(
zip_safe=False,
platforms='any',
install_requires=dependencies,
entry_points={
'console_scripts': [
'distribusi = distribusi.cli:cli_entrypoint',
],
},
classifiers=[
'Programming Language :: Python :: 3',
'Environment :: Console',
],
entry_points={'console_scripts': ['distribusi = distribusi.cli:cli_entrypoint']},
classifiers=['Programming Language :: Python :: 3', 'Environment :: Console'],
)
Loading…
Cancel
Save