Browse Source

created redaktion

master
Chris 6 years ago
parent
commit
6554586a5f
  1. 6
      archetypes/default.md
  2. 75
      archetypes/post.md
  3. 6
      redaktion/.gitignore
  4. 163
      redaktion/assets/logo-inkscape.svg
  5. BIN
      redaktion/assets/logo.jpg
  6. BIN
      redaktion/assets/logo.png
  7. BIN
      redaktion/assets/logo40-dark.png
  8. BIN
      redaktion/assets/logo40-t.png
  9. BIN
      redaktion/assets/logo40.png
  10. 114
      redaktion/assets/stravainkscape.svg
  11. 3
      redaktion/backup.sh
  12. 77
      redaktion/load.sh
  13. 5
      redaktion/publish.sh
  14. 597
      redaktion/red.py
  15. 2
      redaktion/requirements.txt

6
archetypes/default.md

@ -0,0 +1,6 @@
---
title: "{{ replace .Name "-" " " | title }}"
date: {{ .Date }}
draft: true
---

75
archetypes/post.md

@ -0,0 +1,75 @@
---
# Set the correct title here
title: "2003-11-02"
# Date of the event, will be set via script. Format like "2003-12-31"
date: 2003-11-02
# Set the correct sports kind here (single value)
# It's taxonomy term: look at existing posts, to find the valid values
sports: "MTB"
# Set the correct event type here (single value)
# It's taxonomy term: look at existing posts, to find the valid values
eventtypes: "single"
# Set the correct participants here (list values)
# It's taxonomy term: lLook at existing posts, to find the valid values.
# For new participants set "FirstName LastName"
# Unknown names have to be set as "Gast"
members: [
"Peter",
"Gregor",
"Edmund",
"Gerald",
"Christian"
]
# City name of start point
# It's taxonomy term: look at existing posts, to find the valid values
# If it's a new location: Take a simple city name
locations: "Somewhere"
# false to hide it in production
draft: false
# If one of the following values are not given, delete the default value
# Set the correct value here, Example 78.3
distance_km: 0.0
# Set the correct value here, Example 3:58:59
duration_h: 0:00:00
# Set the correct value here, Example 23.2
average_speed_kmh: 0
# Set the correct value here, Example 1234
ascent_m: 0
# Set the correct value here, Example 24.2
temperature_c:
# All image paths are relative paths and have to start with "images/"
# Image for the post's header e.g. header_image: images/img123.jpg. Can be empty.
header_image:
# Image for the summary list e.g. featured_image: images/img123.jpg. Can be empty.
featured_image:
# Set captions for specific images (optional)
# A caption item has two entries: -name: "images/IMAGE_NAME" and -text: "YOUR DESCRIPTION"
# Caption names will be generated by the script, add text or let it empty.
captions:
# Should not be changed
# Be careful: src value must be unique
resources:
- src: images/**
# Links to activity on social platforms
# Example velohero_activity: https://app.velohero.com/activity/364363
# velohero_activity:
# strava_activity:
---
<!--more-->

6
redaktion/.gitignore vendored

@ -0,0 +1,6 @@
in/
.idea/
.settings
.venv/lib/python3.8/site-packages/
.venv/lib64/python3.8/site-packages/
.venv

163
redaktion/assets/logo-inkscape.svg

@ -0,0 +1,163 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="3779.5276"
height="3779.5276"
viewBox="0 0 1000 1000"
version="1.1"
id="svg8"
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"
sodipodi:docname="logo-inkscape.svg"
inkscape:export-filename="/home/chris/hugo/kollegenrunde/redaktion/logo40-dark.png"
inkscape:export-xdpi="1.02"
inkscape:export-ydpi="1.02">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.04"
inkscape:cx="-2023.3554"
inkscape:cy="-357.52586"
inkscape:document-units="mm"
inkscape:current-layer="layer2"
showgrid="false"
units="px"
inkscape:snap-global="true"
showguides="true"
inkscape:guide-bbox="true"
inkscape:snap-page="true"
inkscape:object-paths="true"
inkscape:window-width="1920"
inkscape:window-height="1015"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1">
<sodipodi:guide
position="3.7128713,563.11881"
orientation="1,0"
id="guide819"
inkscape:locked="false" />
<sodipodi:guide
position="700.49504,804.45545"
orientation="1,0"
id="guide1002"
inkscape:locked="false" />
<sodipodi:guide
position="257.42574,700.49505"
orientation="0,1"
id="guide1004"
inkscape:locked="false" />
<sodipodi:guide
position="787.1287,165.84158"
orientation="0,1"
id="guide1006"
inkscape:locked="false" />
<sodipodi:guide
position="257.42574,136.13864"
orientation="1,0"
id="guide1008"
inkscape:locked="false" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:groupmode="layer"
id="layer3"
inkscape:label="canvas"
style="display:inline"
sodipodi:insensitive="true">
<rect
ry="114.80363"
y="0"
x="-0.00013327599"
height="1000"
width="1000.0001"
id="rect947-1"
style="display:inline;opacity:1;fill:#00449e;fill-opacity:1;stroke:none;stroke-width:69.4526062;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
</g>
<g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,703)"
style="display:none;opacity:0.52100004"
sodipodi:insensitive="true">
<rect
ry="114.80363"
y="-703"
x="-8.8817842e-14"
height="1000"
width="1000.0001"
id="rect947"
style="opacity:1;fill:#e1e8ed;fill-opacity:1;stroke:none;stroke-width:69.4526062;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<circle
cy="-13.64354"
cx="273.81598"
id="circle823"
style="fill:none;fill-opacity:1;stroke:#005ec0;stroke-width:66.14583588;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
r="0" />
<circle
style="fill:none;fill-opacity:1;stroke:#005ec0;stroke-width:66.14583588;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="circle825"
cx="741.75433"
cy="-3.6059284"
r="0" />
</g>
<g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="Ebene 2"
style="display:inline;opacity:0.851">
<g
id="g1070"
style="">
<rect
ry="103.87161"
y="47.611828"
x="47.611839"
height="904.77637"
width="904.77637"
id="rect817"
style="display:inline;opacity:1;fill:none;fill-opacity:1;stroke:#5f5f5f;stroke-width:35.97515869;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<g
id="g1064"
style="">
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#5f5f5f;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:747.51794434;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 3055.6504,1045.875 c -38.4156,2.9826 -74.3936,5.8113 -116.9277,9.0566 -145.6208,11.1108 -313.7377,23.6529 -493.0997,36.9122 -72.0113,5.3234 -150.1521,11.037 -225.0605,16.5449 -73.3972,36.7278 -192.3641,77.2662 -333.9805,111.0996 -183.2066,43.7696 -398.431,76.3187 -619.1269,123.7129 v -164.668 c -156.0991,11.565 -307.1138,22.8067 -435.74416,32.6211 -72.74155,5.5501 -139.85043,10.7426 -199.89453,15.4844 -42.65048,3.3682 -76.87225,6.1978 -111.88086,9.0781 v 2023.8828 a 373.79635,373.79635 0 1 0 747.51955,0 V 2614.5625 L 2449.6797,3552.416 A 373.79635,373.79635 0 1 0 2914.252,2966.7891 L 1720.7773,2020.0098 c 111.9157,-22.6227 225.973,-46.3423 339.5,-73.4649 196.5205,-46.9504 392.0292,-103.5744 575.8555,-213.9785 174.7622,-104.9602 347.5612,-287.7609 405.0508,-525.2949 a 373.79635,373.79635 0 0 0 12.0918,-63.086 373.79635,373.79635 0 0 0 2.9531,-58.0371 c 0.2187,-13.5281 -0.063,-26.9314 -0.5781,-40.2734 z"
id="path1058"
transform="scale(0.26458333)"
inkscape:connector-curvature="0" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#5f5f5f;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:747.51794434;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 1780.334,106.60938 c -17.3218,-0.0599 -34.6822,0.15365 -52.0703,0.65039 -208.6577,5.96084 -421.286,52.75009 -619.0899,158.08984 -251.55006,133.9621 -469.85799,378.99884 -564.60349,697.92773 a 373.79635,373.79635 0 0 0 -20.93554,82.61916 373.79635,373.79635 0 0 0 -3.95704,82.6973 c 33.05448,-2.7083 64.25734,-5.2991 103.74415,-8.4161 60.16874,-4.7494 127.34976,-9.9446 200.16015,-15.5 141.98697,-10.8335 306.46287,-23.0991 480.79497,-35.9922 37.5966,-66.2966 88.4311,-107.48187 156.1601,-143.55073 89.8325,-47.83998 216.578,-73.91152 347.6348,-70.15039 18.7224,0.53733 37.5284,1.68424 56.332,3.46289 150.4285,14.22905 295.4857,70.88142 372.3496,127.53711 6.1852,4.55901 11.7923,8.82954 16.9785,12.88671 60.8726,-4.47939 125.088,-9.17146 183.9297,-13.52148 179.3152,-13.25632 347.3509,-25.79425 492.834,-36.89453 41.2124,-3.14448 75.9162,-5.87552 113.252,-8.77344 C 3000.7644,706.27308 2850.7135,509.81575 2680.3789,384.26367 2465.4185,225.81826 2206.7907,139.96854 1934.8965,114.25 c -50.9802,-4.82223 -102.5972,-7.46086 -154.5625,-7.64062 z"
id="path1014"
transform="scale(0.26458333)"
inkscape:connector-curvature="0" />
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 9.1 KiB

BIN
redaktion/assets/logo.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
redaktion/assets/logo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
redaktion/assets/logo40-dark.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
redaktion/assets/logo40-t.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
redaktion/assets/logo40.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

114
redaktion/assets/stravainkscape.svg

@ -0,0 +1,114 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
id="svg2"
sodipodi:docname="strava.svg"
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)">
<metadata
id="metadata8">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs6" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1015"
id="namedview4"
showgrid="false"
inkscape:zoom="4.72"
inkscape:cx="87.035517"
inkscape:cy="31.632792"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer2"
showguides="true"
inkscape:guide-bbox="true"
inkscape:snap-global="false">
<sodipodi:guide
position="42.584746,68.432203"
orientation="1,0"
id="guide4732"
inkscape:locked="false" />
<sodipodi:guide
position="62.288136,98.305085"
orientation="0,1"
id="guide4734"
inkscape:locked="false" />
<sodipodi:guide
position="58.050847,42.372881"
orientation="0,1"
id="guide4736"
inkscape:locked="false" />
<sodipodi:guide
position="15.042373,42.372881"
orientation="1,0"
id="guide4739"
inkscape:locked="false" />
<sodipodi:guide
position="32.415254,42.372881"
orientation="1,0"
id="guide4741"
inkscape:locked="false" />
<sodipodi:guide
position="42.584746,65.466102"
orientation="0,1"
id="guide4743"
inkscape:locked="false" />
</sodipodi:namedview>
<g
inkscape:groupmode="layer"
id="layer1"
inkscape:label="Layer 1"
sodipodi:insensitive="true"
style="display:none">
<image
y="0.97739953"
x="0.3418065"
id="image178"
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAACXBIWXMAAAsTAAALEwEAmpwYAAAD kklEQVR4nNWab2hNYRzHP3d38z8iDUuUV94opRQalmWyslqR1eRm/rQiorxQSnmhFFFSSslb5YXy AqObZZm2iGgjf6Y1f5KItvlTjhdPp11273Oe5znPeZ5zP/V7d36/8/3+7rnbub/fk8UtW4AA+OT4 vqkgC/QDV3wL8UUr4tP/AyzxrMU5WeAFogEBcNWvHPfkGDMfPgVLfQpySSXwin8bEADXfIpySRvj zYexzKMuJ1QBbyjdgOv+pLlhN6XNh7Hcm7qEmQC8JboBN3wJTJp2os2HsdKTxsSYCAyi3oAOPzKT Yx/q5sOo9aI0ASYB79BvQN6H2CQ4gL75MOo86LXKFOAD5g3odC/ZLocwNx9GvXPVlpgKfCR+A7pc C7fFYeKbD6PBsfbYTEOMuWw1oNut/PgcwZ75MBqdOojBdOAz9hvQ69JEHI6ibuoS8FDj+k0OfRgx A/iCmpnfwCKEKdUGPAIyztwYcAx1MxcL8no08pqTt2HGTOAraiZ+AQsLchsV8wLgCSl9Co6jbuJC kfxujfzNCfowYhbwDTXxP4EFRWo0KOYHwFOgIjE3BpxAXfx5SZ0ujTotCfgwYjbwHTXRP4D5klr1 inUCxF4xa92NASdRF31OoV6nRr1Wiz6MqAaGURM7CtQo1KxTrBcg9oten4JTRUSVirMadfMadXPx bZgxFxiJEBfGCDBPo/ZqxboBYs9YGduNAWc0RJ42qH9bo35bDB9G1CC+0yrihoE5BvdYpVg/QOwb q4zdFKD6KOUQ4y4VLkuurUYMTosxBNwEFivcI4P4j5CXXDMq0eGNPcg/WZ1J0K2IWgetqbZIFTCA XPhGhTpRX5f3wGS70u2xE7n4HoUadyJq7Leu2iKljswUhmwStCYidwixnks1OeQmZJOguxG5exPU bY3/j80Vi2KToHUROYOI1XxZsA25mWKToHsROe0uhNsiC/QhN1Q4CVofce0A4lhOWdGC3FThJOh+ xLW7XAq3RQXwDLmxFsS7geya11h6NfZBeIS+VPQTPUbf4Vy1RTKIP3gyg7J4iaefxzZpxrwB2z3o tU4G8fKja/45KRmU2qAJ/QZ4H5Laphd1832kbFliA52d4VZPGhNHZWeYulWZTVR2hqlbltpGtjN8 TErX5TaR7QxTe2DCNsUGH6k/MmOTtYxvQJNPQT7IM2a+bI7N2aSWsQaUzcFJ23QAD3yL8MkKYENS xf8C14RokViPyHIAAAAASUVORK5CYII= "
preserveAspectRatio="none"
height="98.257065"
width="98.257065" />
</g>
<g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="Layer 2"
style="display:inline;opacity:1">
<path
style="fill:#000001;fill-opacity:1"
d="M 43.034178,0.19789991 15.493163,56.12954 H 32.86621 L 43.034178,33.03579 53.2041,56.12954 h 17.373047 z"
id="rect4745"
inkscape:connector-curvature="0" />
<path
id="path4751"
d="M 61.439189,99.143912 83.740512,56.12954 H 69.672697 L 61.439189,73.889851 53.2041,56.12954 H 39.136284 Z"
style="fill:#000001;fill-opacity:1;stroke-width:0.78913879"
inkscape:connector-curvature="0" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.6 KiB

3
redaktion/backup.sh

@ -0,0 +1,3 @@
#!/bin/bash
rsync -avz --delete --exclude kollegenrunde/.git --exclude kollegenrunde/public/ --exclude *swp /home/chris/hugo /media/diskstation/externalAccess/rsyncs

77
redaktion/load.sh

@ -0,0 +1,77 @@
#!/bin/bash
ROOT_DST="/home/chris/hugo/kollegenrunde/redaktion/in/posts"
# case in-sensitive matching
shopt -s nocaseglob
shopt -s nullglob
if [ "$1" == '-h' ]
then
echo "Copy all images "
echo " from DIR"
echo " to $ROOT_DST/<DATE>"
echo "DIR must be a path to a DATE base directory like 2006010213_STAUFEN"
echo "Destination directory will be deleted an recreated"
echo "Usage:"
echo " load DIR"
echo ""
echo " DATE must be an existing directory"
echo " Images will be copied into $ROOT_DST/<DATE>"
echo " For instance: load /mnt/pict/20200912_STAUFEN"
exit 0
fi
if [ -z "$1" ]
then echo "Missing source directory, e. g. call 'load /mnt/pict/20200912_STAUFEN'"
exit 1
fi
DIR=$(basename "$1")
#echo dir=$DIR
DATE=${DIR:0:8}
#echo date=$DATE
if [ ! -e "$1" ]
then echo "Directory '$1' not found !"
exit 1
fi
if [ ! -e $ROOT_DST ]
then echo "Destination root directory '$ROOT_DST' not found !"
exit 1
fi
DST="$ROOT_DST/$DATE"
if [ ! -e $DST ]
then echo "Create $DST"
mkdir "$DST"
fi
echo "Processing $DATE..."
if [ -e "$DST" ]
then
echo "Delete $DST"
rm -r "$DST"
fi
echo "Create $DST"
mkdir "$DST"
# It's tricky to copy dirs with whitespaces
shopt -s extglob # turn on extended globbing
SAVEIFS=$IFS
IFS=$(echo -en "\n\b")
for f in $1?(*.jpg|*.jpeg|*.png)
do
echo "$f"
cp -v "$f" "$DST"/
done
IFS=$SAVEIFS

5
redaktion/publish.sh

@ -0,0 +1,5 @@
#!/bin/bash
echo Publish to Uberspace...
rsync -avv --delete /home/chris/hugo/kollegenrunde/public/ kollegen@despina.uberspace.de:html/kollegenrunde

597
redaktion/red.py

@ -0,0 +1,597 @@
import argparse
import re
import json
import sys
from os import path, mkdir, remove, listdir
from shutil import copyfile, rmtree
from PIL import Image
import yaml
try:
from yaml import CLoader as Loader, CDumper as Dumper
except ImportError:
from yaml import Loader, Dumper
args = None
log_switch = False
post_file_name = "index.md"
post_file_backup_name = "~index.md"
post_file_archetype = path.join("..", "archetypes", "post.md")
settings_path = ".settings"
def execute_info():
if not path.exists(settings_path):
print("No actual post.")
return
print(f"Actual post is: '{settings_get_date()}'")
exit_invalid_initialization()
def execute_update():
exit_invalid_initialization()
yml = yaml.load(get_frontmatters(), Loader=Loader)
previous_images = settings_get_images()
convert(get_image_path(yml))
remove_previous_images(previous_images, yml)
yml = cleanup_zombies(yml)
yml = merge_images(yml)
update_frontmatters(yml)
print("Done.")
def remove_previous_images(previous_images, yml):
converted_images = settings_get_images()
for image in [i for i in previous_images if i not in converted_images]:
image_path = path.join(get_image_path(yml), image)
log(f"Removing previous image '{image}'")
remove(image_path)
def init_date():
yml = yaml.load(get_frontmatters(), Loader=Loader)
yml = set_date(yml)
lines = get_frontmatters()
lines = update_date(yml, lines)
content = get_content()
with open(get_post_file_path(), "w") as f:
f.write("---\n")
f.write(lines)
f.write("---\n")
f.write(content)
def init_title():
yml = yaml.load(get_frontmatters(), Loader=Loader)
yml = set_title(yml)
lines = get_frontmatters()
lines = update_title(yml, lines)
content = get_content()
with open(get_post_file_path(), "w") as f:
f.write("---\n")
f.write(lines)
f.write("---\n")
f.write(content)
def update_frontmatters(yml):
copyfile(get_post_file_path(), get_post_backup_file_path())
lines = get_frontmatters()
lines = update_title(yml, lines)
lines = update_date(yml, lines)
lines = update_header_image(yml, lines)
lines = update_featured_image(yml, lines)
lines = update_captions(yml, lines)
content = get_content()
with open(get_post_file_path(), "w") as f:
f.write("---\n")
f.write(lines)
f.write("---\n")
f.write(content)
def set_title(yml):
yml['title'] = "Tour " + settings_get_date()[0:4] + "-" + settings_get_date()[4:6] + "-" + settings_get_date()[6:8]
return yml
def set_date(yml):
yml['date'] = settings_get_date()[0:4] + "-" + settings_get_date()[4:6] + "-" + settings_get_date()[6:8]
return yml
def get_image_path(yml):
return path.join(get_out_path(), get_image_resource_path(yml))
def get_image_resource_path(yml):
items = [i for i in yml['resources'] if 'src' in i]
ret = ""
if len(items) > 0:
path = items[0]['src']
# Should never happens
else:
path = ''
reg = re.match("(.*)/\*\*", path)
if reg is not None and len(reg.groups()) >= 1:
ret = reg.group(1)
# log(f"get_image_path={ret}")
return ret
def merge_images(yml):
files = get_image_resources(yml)
if len(files) == 0:
return yml
key = 'header_image'
if key in yml and yml[key] is None:
yml[key] = files[0]
key = 'featured_image'
if key in yml and yml[key] is None:
yml[key] = files[0]
# log(f"merge_images {yml}")
key = 'captions'
key2 = 'name'
if key in yml:
if yml[key] is None:
yml[key] = []
for file in files:
hit = False
for item in yml[key]:
if key2 in item and item[key2] == file:
hit = True
break
if not hit:
# log(f"merge_images added {file}")
yml[key].append(dict(name=file, text=""))
yml[key] = sorted(yml[key], key=lambda k: k['name'])
# log(f"merge_images {yml}")
return yml
def cleanup_zombies(yml):
files = get_image_resources(yml)
key = 'header_image'
if key in yml:
if yml[key] is not None and not yml[key] in files:
yml[key] = ""
else:
yml[key] = ""
key = 'featured_image'
if key in yml:
if yml[key] is not None and not yml[key] in files:
yml[key] = ""
else:
yml[key] = ""
# log(f"cleanup_zombies={yml}")
key = 'captions'
if key in yml:
if yml[key] is not None:
key2 = 'name'
# log(f"cleanup_zombies={yml[key]}")
items = yml[key].copy()
for item in items:
if not item[key2] in files:
# log(f"cleanup_zombies remove item {item[key2]} ")
yml[key].remove(item)
else:
yml[key] = []
# log(f"cleanup_zombies ret = {yml}")
return yml
def get_image_resources(yml):
files = get_images(get_image_path(yml))
sub = get_image_resource_path(yml)
ret = []
for name in files:
ret.append(path.join(sub, name))
return ret
def get_images(dir):
ret = [f for f in listdir(dir) if f.lower().endswith(".jpg")
or f.lower().endswith(".jpeg") or f.lower().endswith(".png")]
ret.sort()
return ret
def convert(image_path):
names = []
for f in get_images(get_in_path()):
in_path = path.join(get_in_path(), f)
out_path = path.join(image_path, f)
log(f"Converting '{in_path}' to '{out_path}'")
im = Image.open(in_path)
if im.width <= 1200 and im.height <= 1200:
copyfile(in_path, out_path)
continue
if im.width > im.height:
new_image = im.resize((1200, round(1200*im.height/im.width)))
else:
new_image = im.resize((round(1200*im.width/im.height), 1200))
if 'exif' in im.info:
new_image.save(out_path, exif=im.info['exif'], quality=80)
else:
new_image.save(out_path, quality=80)
names.append(f)
settings_save_images(names)
def update_date(yml, lines):
return re.sub("\ndate:.*\n", f"\ndate: {yml['date']}\n",lines)
def update_featured_image(yml, lines):
return re.sub("\nfeatured_image:.*\n", f"\nfeatured_image: {yml['featured_image']}\n",lines)
def update_header_image(yml, lines):
return re.sub("\nheader_image:.*\n", f"\nheader_image: {yml['header_image']}\n",lines)
def update_captions(yml, lines):
data = yml['captions']
lines = clear_captions(lines)
# log(f"update_captions lines={lines}")
if data is None:
output = ""
else:
output = yaml.dump(data, Dumper=Dumper)
# log(f"update_captions output={output}")
lines = re.sub("\ncaptions:.*\n", f"\ncaptions:\n{output}", lines)
# log(f"update_captions {lines}")
return lines
def clear_captions(lines):
arr = lines.split('\n')
captions = False
arr2 = []
for item in arr:
if item.startswith("captions:"):
captions = True
arr2.append(item)
continue
if captions:
if len(item.strip()) == 0:
captions = False
arr2.append(item)
else:
arr2.append(item)
ret = ""
for item in arr2:
if len(ret) == 0:
ret = item
else:
ret = ret + '\n' + item
return ret
def update_title(yml, lines):
return re.sub("\ntitle:.*\n", f"\ntitle: \"{yml['title']}\"\n",lines)
def get_content():
lines = ""
# log(f"get_content get_post_file_path={get_post_file_path()}")
pattern = re.compile("^---$")
cnt = 0
with open(get_post_file_path(), "r") as f:
# log(f"get_content with f={f}")
for line in f:
# log(f"get_content {line}")
if pattern.match(line):
cnt += 1
continue
if cnt < 2:
continue
lines = lines + line
# log(f"get_content return={lines}")
return lines
def get_frontmatters():
lines = ""
with open(get_post_file_path(), "r") as f:
# log("open")
pattern = re.compile("^---$")
cnt = 0
for line in f:
# log(line)
if pattern.match(line):
cnt += 1
# log(f"a: {cnt}")
continue
if cnt == 0:
# log(f"b: {cnt}")
continue
if cnt == 2:
# log(f"c: {cnt}")
break
# log(line)
lines = lines + line
return lines
def execute_cleanup():
if path.exists(settings_path):
date = settings_get_date()
else:
print("Not initialized: nothing to do.")
return
if path.exists(get_in_path()):
rmtree(get_in_path())
print(f"Removed '{get_in_path()}'")
remove(settings_path)
print(f"{date} closed.")
def settings_get_date():
with open(settings_path) as f:
settings = json.load(f)
return settings['date']
def settings_get_images():
with open(settings_path) as f:
settings = json.load(f)
return settings['images']
def settings_init(date):
with open(settings_path, 'w') as f:
json.dump(dict(date=date, images=[]), f)
def settings_save_images(images):
date = settings_get_date()
with open(settings_path, 'w') as f:
json.dump(dict(date=date, images=images), f)
def execute_init():
# date must match YYYYMMDD pattern
pattern = re.compile("^\d{8}$")
if not pattern.match(args.date):
exit_on_error("Wrong date format, must be <YYYYMMDD>")
settings_init(args.date)
# ### in ####
if path.exists(get_in_path()):
print(f"Existing in path found. Put your original images here: '{get_in_path()}'")
else:
mkdir(get_in_path())
print(f"Post directory created. Put your original images here: '{get_in_path()}'")
# ### post dir ####
if path.exists(get_out_path()):
if args.clean:
rmtree(get_out_path())
mkdir(get_out_path())
print(f"Existing post directory cleaned: '{get_out_path()}'")
else:
print(f"Existing post directory found: '{get_out_path()}'")
else:
mkdir(get_out_path())
print(f"Post' directory created: '{get_out_path()}' ")
# ### post file ####
if path.exists(get_post_file_path()):
print(f"Existing post file found: '{get_post_file_path()}'")
else:
copyfile(post_file_archetype, get_post_file_path())
init_title()
print(f"New post file created: '{get_post_file_path()}'")
yml = yaml.load(get_frontmatters(), Loader=Loader)
image_path = get_image_path(yml)
if not path.exists(image_path):
mkdir(image_path)
print(f"Image directory created: '{get_image_resource_path(yml)}' ")
init_date()
print(f"The next step would be to add your images into the '{get_in_path()}' directory. "
f"After that you can update the post directory '{get_out_path()}' and the post file '{post_file_name}': "
f"call 'update'.")
def get_post_file_path():
return path.join(get_out_path(), post_file_name)
def get_post_backup_file_path():
return path.join(get_out_path(), post_file_backup_name)
def get_in_path():
with open('.settings') as f:
settings = json.load(f)
return path.join("in", "posts", settings['date'])
def log(message):
"""
Only switched on for development
"""
if log_switch:
print(message)
def exit_invalid_initialization():
if not path.exists(settings_path):
exit_on_error("Editorial seems not to be initialized.")
if not path.exists(get_in_path()):
exit_on_error(f"Invalid in path: '{get_in_path()}'.")
if not path.exists(get_out_path()):
exit_on_error(f"Invalid post path: '{get_out_path()}'.")
if not path.exists(get_post_file_path()):
exit_on_error(f"No post file found: '{get_post_file_path()}'.")
def get_out_path():
with open(settings_path) as f:
settings = json.load(f)
return path.join("..", "content", "posts", settings['date'])
def parse_args():
global args
parser = argparse.ArgumentParser(description='Bring your content into the Hugo blog')
parser.add_argument("-v", "--verbose",
action='store_true',
help="Print more info")
sub_parsers = parser.add_subparsers()
# ######### init #########
init_parser = sub_parsers.add_parser('init',
help="Setup a new post or edit an existing one",
description=""
)
init_parser.add_argument('date', metavar='DATE', type=str,
help="Date of the post as <YYYYMMDD>, example: 'init 20201231'")
init_parser.add_argument('--clean', action="store_true",
help="Removes existing post content, if exists."
"Be careful with this option! Particularly, if there are multiple editors working "
"on this blog."
)
init_parser.set_defaults(func=execute_init)
# ######### update #########
update_parser = sub_parsers.add_parser('update',
help="Converts images into a proper size and update the post file",
description="Images must be in in/post/<DDDDYYMM>"
)
update_parser.set_defaults(func=execute_update)
# ######### cleanup #########
cleanup_parser = sub_parsers.add_parser('cleanup',
help=f"cleanup editorial by clean up the in directory",
description=f"Removes in directory (with your original images). "
)
cleanup_parser.set_defaults(func=execute_cleanup)
args = parser.parse_args()
if args.verbose:
set_log_switch(True)
if len(sys.argv) > 1:
args.func()
else:
execute_info()
def set_log_switch(value):
global log_switch
log_switch = value
def exit_on_error(message):
print(message)
exit(1)
if __name__ == '__main__':
parse_args()

2
redaktion/requirements.txt

@ -0,0 +1,2 @@
Pillow==7.2.0
PyYAML==5.3.1
Loading…
Cancel
Save