You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
612 lines
15 KiB
612 lines
15 KiB
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_and_years(): |
|
|
|
yml = yaml.load(get_frontmatters(), Loader=Loader) |
|
|
|
yml = set_date(yml) |
|
|
|
yml = set_years(yml) |
|
|
|
lines = get_frontmatters() |
|
|
|
lines = update_date(yml, lines) |
|
|
|
lines = update_years(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 set_years(yml): |
|
yml['years'] = settings_get_date()[0:4] |
|
|
|
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_years(yml, lines): |
|
return re.sub("\nyears:.*\n", f"\nyears: {yml['years']}\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, allow_unicode=True) |
|
|
|
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 |
|
|
|
# log(lines) |
|
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_and_years() |
|
|
|
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()
|
|
|