Compare commits
No commits in common. "master" and "package-it-up" have entirely different histories.
master
...
package-it
4
.gitignore
vendored
4
.gitignore
vendored
@ -5,7 +5,3 @@ __pycache__/
|
|||||||
build/
|
build/
|
||||||
dist
|
dist
|
||||||
index.html
|
index.html
|
||||||
pip-wheel-metadata/
|
|
||||||
*.pdf
|
|
||||||
*.css
|
|
||||||
*.pyz
|
|
||||||
|
51
CHANGELOG.md
51
CHANGELOG.md
@ -1,51 +0,0 @@
|
|||||||
# Changelog
|
|
||||||
|
|
||||||
The changelog was only added at version 0.0.4.
|
|
||||||
|
|
||||||
## 0.0.10
|
|
||||||
|
|
||||||
- Fix `-nf` functionality ([#5](https://git.vvvvvvaria.org/varia/distribusi/pulls/5)) (thanks @losttra8n!)
|
|
||||||
|
|
||||||
## 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
6
Makefile
@ -1,4 +1,6 @@
|
|||||||
|
PIPENV := pipenv run
|
||||||
|
|
||||||
publish:
|
publish:
|
||||||
@rm -rf dist
|
@rm -rf dist
|
||||||
@python setup.py bdist_wheel --universal
|
@$(PIPENV) python setup.py bdist_wheel --universal
|
||||||
@twine upload dist/*
|
@$(PIPENV) twine upload dist/*
|
||||||
|
11
Pipfile
Normal file
11
Pipfile
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
[[source]]
|
||||||
|
name = "pypi"
|
||||||
|
url = "https://pypi.org/simple"
|
||||||
|
verify_ssl = true
|
||||||
|
|
||||||
|
[dev-packages]
|
||||||
|
pillow = "*"
|
||||||
|
python-magic = "*"
|
||||||
|
twine = "*"
|
||||||
|
|
||||||
|
[packages]
|
169
Pipfile.lock
generated
Normal file
169
Pipfile.lock
generated
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
{
|
||||||
|
"_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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
70
README.md
70
README.md
@ -1,7 +1,5 @@
|
|||||||
# Distribusi CMS
|
# 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
|
`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 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
|
index functions featured in several web servers. It works by traversing the
|
||||||
@ -15,32 +13,22 @@ dependencies. This package requires two underlying packages. Those are
|
|||||||
`python-magic`, and `pillow`. Here are the installation documentation for those
|
`python-magic`, and `pillow`. Here are the installation documentation for those
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
* [github.com/threatstack/libmagic](https://github.com/threatstack/libmagic)
|
* https://github.com/threatstack/libmagic
|
||||||
* [pillow.readthedocs.io](https://pillow.readthedocs.io/en/5.3.x/installation.html#external-libraries)
|
* https://pillow.readthedocs.io/en/5.3.x/installation.html#external-libraries
|
||||||
|
|
||||||
### Optional requirements
|
## Installation
|
||||||
|
|
||||||
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
|
```bash
|
||||||
$ export PATH=$PATH:$HOME/.local/bin
|
|
||||||
$ pip install --user distribusi
|
$ pip install --user distribusi
|
||||||
```
|
```
|
||||||
|
|
||||||
## Upgrade It
|
## Usage
|
||||||
|
|
||||||
If you already have it, you can upgrade with:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ pip install -U distribusi
|
|
||||||
```
|
|
||||||
|
|
||||||
## Use It
|
|
||||||
|
|
||||||
Get help with:
|
Get help with:
|
||||||
|
|
||||||
@ -78,53 +66,21 @@ $ 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)
|
|
||||||
|
|
||||||
### Distribusi verse
|
|
||||||
|
|
||||||
> An attempt to make distribusi into a web interface that can be operated remotely without any knowlegde of CLI. Trying to somehow combine the ideas of distribusi with the ideas of a [tildeverse](https://tildeverse.org/) or [Tilde club](https://tilde.club), but also be neither of these ideas. This project is made for Autonomous Practices at the WDKA in Rotterdam.
|
|
||||||
|
|
||||||
See [`crunk/distribusi-verse`](https://git.vvvvvvaria.org/crunk/distribusi-verse) for more.
|
|
||||||
|
|
||||||
### `distribusi-go`
|
|
||||||
|
|
||||||
Inspired a re-implementation in Go, see [`decentral1se/distribusi-go`](https://git.vvvvvvaria.org/decentral1se/distribusi-go) for more.
|
|
||||||
|
|
||||||
## Change It
|
## Change It
|
||||||
|
|
||||||
You'll need to get a copy of the repository and then do an [editable] install:
|
Install [Pipenv] and then run:
|
||||||
|
|
||||||
[editable]: https://setuptools.readthedocs.io/en/latest/setuptools.html#development-mode
|
[Pipenv]: https://pipenv.readthedocs.io/en/latest/install/#installing-pipenv
|
||||||
|
|
||||||
```bash
|
```
|
||||||
$ git clone https://git.vvvvvvaria.org/varia/distribusi.git && cd distribusi
|
$ pipenv install --dev
|
||||||
$ python3 -m venv .venv && source .venv/bin/activate
|
$ pipenv run pip install -e .
|
||||||
$ pip install -e .
|
$ pipenv run distribusi --help
|
||||||
```
|
```
|
||||||
|
|
||||||
You're then ready to make your changes and experiment with them.
|
|
||||||
|
|
||||||
## Release It
|
## 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
|
$ make publish
|
||||||
```
|
```
|
||||||
|
12
build.sh
12
build.sh
@ -1,12 +0,0 @@
|
|||||||
#!/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 \
|
|
||||||
.
|
|
@ -1,93 +1,59 @@
|
|||||||
import argparse
|
import argparse
|
||||||
import os
|
|
||||||
|
|
||||||
from distribusi.distribusi import distribusify
|
from distribusi.distribusi import distribusify
|
||||||
|
|
||||||
|
|
||||||
def build_argparser():
|
def build_argparser():
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser("""
|
||||||
"""
|
distbusi 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 filesystem. 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 web servers. It works by
|
||||||
automatic index functions featured in several popular web servers.
|
traversing the file system and directory hierarchy to automatically list
|
||||||
distribusi works by traversing the file system and directory hierarchy to
|
all the files in the directory and providing them with html classes and
|
||||||
automatically list all the files in the directory, detect the file types
|
tags for easy styling.
|
||||||
and providing them with relevant html classes and tags for easy styling.
|
|
||||||
""")
|
""")
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-d', '--directory', help="Select which directory to distribute", default="."
|
'-d',
|
||||||
|
'--directory',
|
||||||
|
help="Select which directory to distribute"
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-s', '--style', help="Select a CSS style sheet to include"
|
'-v',
|
||||||
)
|
'--verbose',
|
||||||
|
help="Print verbose debug output",
|
||||||
parser.add_argument(
|
action="store_true"
|
||||||
'-v', '--verbose', help="Print verbose debug output", action="store_true"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-t',
|
'-t',
|
||||||
'--thumbnail',
|
'--thumbnail',
|
||||||
help="Generate 450x450 thumbnails for images",
|
help="Generate 150x150 thumbnails for images",
|
||||||
action="store_true",
|
action="store_true"
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-n',
|
'-n',
|
||||||
'--no-template',
|
'--no-template',
|
||||||
help="Don't use the template to output html",
|
help="Don't use the template to ouput html",
|
||||||
action="store_true",
|
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
|
return parser
|
||||||
|
|
||||||
|
|
||||||
def cli_entrypoint():
|
def cli_entrypoint():
|
||||||
parser = build_argparser()
|
parser = build_argparser()
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
distribusify(args, args.directory)
|
|
||||||
|
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)
|
||||||
|
@ -1,219 +1,127 @@
|
|||||||
import base64
|
import base64
|
||||||
import os
|
import os
|
||||||
import subprocess
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
import magic
|
import magic
|
||||||
|
from distribusi.page_template import html_footer, html_head
|
||||||
from PIL import Image
|
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):
|
def thumbnail(image, name):
|
||||||
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)
|
size = (450, 450)
|
||||||
im = Image.open(image)
|
im = Image.open(image)
|
||||||
im.thumbnail(size)
|
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()
|
output = BytesIO()
|
||||||
im.save(output, format='JPEG')
|
im.save(output, format='JPEG')
|
||||||
im_data = output.getvalue()
|
im_data = output.getvalue()
|
||||||
data_url = base64.b64encode(im_data).decode()
|
data_url = base64.b64encode(im_data).decode()
|
||||||
if args.captions:
|
|
||||||
cap = caption(image)
|
|
||||||
else:
|
|
||||||
cap = name
|
|
||||||
return (
|
return (
|
||||||
"<figure><a href='{}'><img class='thumbnail' src='data:image/jpg;base64,{}'></a><figcaption>{}</figcaption></figure>"
|
"<a href='{}'><img class='thumbnail' "
|
||||||
).format(name, data_url, cap)
|
"src='data:image/jpg;base64,{}'></a>"
|
||||||
except Exception as e:
|
).format(name, data_url)
|
||||||
print('Thumbnailer:', e)
|
|
||||||
return "<figure><a href='{}'><img src='{}'></a><figcaption>{}</figcaption></figure>".format(name, name, name)
|
|
||||||
|
|
||||||
|
|
||||||
def div(args, type_, subtype, tag, name):
|
def div(mime, tag, *values):
|
||||||
id_name = name.split('.')[0].replace(' ', '_')
|
id_name = values[0].split('.')[0].replace(' ', '_')
|
||||||
if args.no_filenames:
|
|
||||||
filename = ''
|
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:
|
else:
|
||||||
filename = '<span class="filename">{}</span>'.format(name)
|
html = '<div id="{}">{}</div>'
|
||||||
|
|
||||||
if 'image' in type_:
|
return html.format(id_name, tag, values[0])
|
||||||
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(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)
|
|
||||||
|
|
||||||
for line in html:
|
|
||||||
f.write(line + '\n')
|
|
||||||
|
|
||||||
if not args.no_template:
|
|
||||||
f.write(html_footer)
|
|
||||||
|
|
||||||
|
|
||||||
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):
|
||||||
|
|
||||||
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 = list(filter(lambda d: not d.startswith('.'), dirs))
|
|
||||||
files = list(filter(lambda f: not f.startswith('.'), files))
|
|
||||||
|
|
||||||
dirs.sort()
|
|
||||||
files.sort()
|
|
||||||
|
|
||||||
if not args.remove_index:
|
|
||||||
html = []
|
html = []
|
||||||
|
|
||||||
if args.verbose:
|
if args.verbose:
|
||||||
print('Generating directory listing for', root)
|
print('Listing', root)
|
||||||
|
|
||||||
for name in sorted(files):
|
|
||||||
|
|
||||||
|
for name in files:
|
||||||
|
if args.verbose:
|
||||||
|
print('Adding', name)
|
||||||
if 'index.html' not in name:
|
if 'index.html' not in name:
|
||||||
full_path = os.path.join(root, name)
|
full_path = os.path.join(root, name)
|
||||||
mime = MIME_TYPE.from_file(full_path)
|
mime = MIME_TYPE.from_file(full_path)
|
||||||
# example: MIME plain/text becomes 'type' plain 'subtype' text
|
mime, format = mime.split('/') # example: plain text
|
||||||
type_, subtype = mime.split('/')
|
|
||||||
|
|
||||||
caption = name
|
|
||||||
|
|
||||||
if args.verbose:
|
if args.verbose:
|
||||||
print('Found', name, 'as', mime)
|
print(mime, format)
|
||||||
|
|
||||||
if type_ in FILE_TYPES:
|
if mime in FILE_TYPES:
|
||||||
|
# expansion for different kind of textfiles
|
||||||
a = FILE_TYPES[type_].format(name, caption)
|
if mime == 'text':
|
||||||
|
if name.endswith('.html') or name.endswith('.txt'):
|
||||||
# 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
|
# what types of text files to expand
|
||||||
a = '<section id="{}">{}</section>'.format(name, open(full_path).read())
|
a = open(full_path).read()
|
||||||
elif subtype in CODE_TYPES or name.endswith('.txt'):
|
elif format in CODE_TYPES:
|
||||||
# if the plain text is code,
|
# if the plain text is code,
|
||||||
# which types do we wrap in pre-tags?
|
# which types do we wrap in pre-tags?
|
||||||
a = "<pre>" + open(full_path).read() + "</pre>"
|
a = "<pre>"+open(full_path).read()+"</pre>"
|
||||||
else:
|
else:
|
||||||
subtype = subtype+' unkown-file'
|
a = FILE_TYPES[mime]
|
||||||
a = "<a href='{}'>{}</a>"
|
|
||||||
# a = FILE_TYPES[type_]
|
|
||||||
|
|
||||||
if type_ == 'image':
|
if mime == 'image' and args.thumbnail:
|
||||||
if args.thumbnail:
|
a = thumbnail(full_path, name)
|
||||||
a = thumbnail(full_path, name, args)
|
else:
|
||||||
if args.no_filenames:
|
a = FILE_TYPES[mime]
|
||||||
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:
|
if format in FILE_TYPES:
|
||||||
a = SUB_TYPES[subtype]
|
a = FILE_TYPES[format]
|
||||||
|
|
||||||
if type_ not in FILE_TYPES and subtype not in SUB_TYPES:
|
if mime not in FILE_TYPES and format not in FILE_TYPES:
|
||||||
# catch exceptions not yet defined in FILE_TYPES or SUB_TYPES
|
# catch exceptions not defined in FILE_TYPES before
|
||||||
a = "<a href='{}'>{}</a>"
|
a = "<a href='{}'>{}</a>"
|
||||||
if args.verbose:
|
if args.verbose:
|
||||||
message = 'not in list of file types, adding as plain href: \n'
|
message = 'mime-type not in list, adding as href: \n'
|
||||||
print(type_, subtype, message, name)
|
print(message, mime, format, name)
|
||||||
subtype = subtype + ' unkown-file'
|
|
||||||
|
|
||||||
a = a.replace('{}', name)
|
a = a.replace('{}', name)
|
||||||
|
html.append(div(mime, a, name))
|
||||||
html.append(div(args, type_, subtype, a, name))
|
|
||||||
|
|
||||||
if root != directory:
|
if root != directory:
|
||||||
if args.menu_with_index:
|
|
||||||
html.append('<a href="../index.html">../</a>')
|
|
||||||
else:
|
|
||||||
html.append('<a href="../">../</a>')
|
html.append('<a href="../">../</a>')
|
||||||
|
|
||||||
for name in dirs:
|
for name in dirs:
|
||||||
if args.menu_with_index:
|
a = "<a href='{}' class='dir'>{}/</a>".replace('{}', name)
|
||||||
a = "<a href='{}/index.html'>{}</a>".replace('{}', name)
|
html.append(div('dir', a, 'folder'))
|
||||||
else:
|
|
||||||
a = "<a href='{}'>{}/</a>".replace('{}', name)
|
|
||||||
|
|
||||||
html.insert(0, div(args, 'dir', 'dir', a, 'folder'))
|
with open(os.path.join(root, 'index.html'), 'w') as f:
|
||||||
|
if not args.no_template:
|
||||||
|
f.write(html_head)
|
||||||
|
|
||||||
index = os.path.join(root, 'index.html')
|
for line in html:
|
||||||
if os.path.exists(index):
|
f.write(line+'\n')
|
||||||
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 not args.no_template:
|
||||||
index = os.path.join(root, 'index.html')
|
f.write(html_footer)
|
||||||
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)
|
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
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>')
|
|
||||||
}
|
|
@ -2,18 +2,14 @@ html_head = """
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<!-- Generated with distribusi https://git.vvvvvvaria.org/varia/distribusi -->
|
<!-- Generated with distribusi https://git.vvvvvvaria.org/rra/distribusi -->
|
||||||
<meta name="generator" content="distribusi" />
|
|
||||||
<meta http-equiv="content-type" content="text/html; charset=utf-8">
|
<meta http-equiv="content-type" content="text/html; charset=utf-8">
|
||||||
<style>
|
<style>
|
||||||
.image{max-width: 100%%;}
|
.image{max-width: 100%;}
|
||||||
.pdf object{width:640px;height: 640px;}
|
.pdf {width:100%;}
|
||||||
.dir::before{content:"📁 ";font-size:18px;}
|
div{width: 640px;float:left;padding:1em;}
|
||||||
.filename{display:block;font-family:mono;}
|
video {width:640px;}
|
||||||
.unkown-file::before{content:"📄 ";font-size:18px;}
|
.dir::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>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
[build-system]
|
|
||||||
requires = [
|
|
||||||
"setuptools >= 41.0.0",
|
|
||||||
"wheel",
|
|
||||||
]
|
|
||||||
build-backend = "setuptools.build_meta"
|
|
||||||
|
|
||||||
[tool.black]
|
|
||||||
skip-string-normalization = true
|
|
25
setup.py
25
setup.py
@ -1,18 +1,22 @@
|
|||||||
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 = [
|
||||||
|
# pinned because https://github.com/python-pillow/Pillow/issues/2609
|
||||||
|
'pillow==4.1',
|
||||||
|
'python-magic',
|
||||||
|
]
|
||||||
|
|
||||||
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.3',
|
||||||
url='https://git.vvvvvvaria.org/varia/distribusi',
|
url='https://git.vvvvvvaria.org/rra/distribusi',
|
||||||
license='GPLv3',
|
license='GPLv3',
|
||||||
author='Varia',
|
author='rra',
|
||||||
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.'
|
||||||
),
|
),
|
||||||
@ -23,6 +27,13 @@ setup(
|
|||||||
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