initial commit

This commit is contained in:
stev 2023-08-31 18:55:27 +02:00
commit 0d81f3fa3b
7 changed files with 245 additions and 0 deletions

22
README.md Normal file
View file

@ -0,0 +1,22 @@
# Gmirator
This tool was made so I could write my content only in `gmi` and publish both a
gemini capsule and a website.
## TODO
- [X] concatenate header + gmi2html + footer
- [ ] allow some special characters to start a `<p>`
- like the `*` for emphasis or `~~` for a strikethrough
- [X] write the `gen_gmi` function
- [X] make the line "---\n" of the footer only present in the code, not in the footer
file
- [X] make a config file for things like
- directories where files are supposed to be
- names of certain files
- the remote servers for the sync feature
- [X] also update the "/gemlog/index.gmi" with the list of articles
- [X] write the sync features
- [X] test the `gen gmi` and `gen html` features
- [ ] write tests for it
- [ ] move config file to `~/.config` with placeholder values

0
gmirator/__init__.py Normal file
View file

171
gmirator/generate.py Normal file
View file

@ -0,0 +1,171 @@
import typer
import re
import shutil
from io import TextIOWrapper
from pathlib import Path
import json
config_path = "config.json"
with open(config_path) as f:
config = json.load(f)
app = typer.Typer(help="generates your gmi and html files")
WORKDIR = Path(config["workdir"])
DIR_ASSETS = WORKDIR / config["dir_assets"]
DIR_SOURCE = WORKDIR / config["dir_source"]
DIR_HELPER = WORKDIR / config["dir_helper"]
DIR_O_HTML = WORKDIR / config["dir_o_html"]
@app.command("gmi")
def gen_gmi():
print("Generating GMI files...")
make_index([DIR_SOURCE / "index.gmi", DIR_SOURCE / "gemlog/index.gmi"])
make_footer(WORKDIR / "footer.gmi", DIR_SOURCE)
print("Done!")
def make_index(listing: list[Path]):
print("Populating gemlog index ...")
title, date = "", ""
files = (DIR_SOURCE / "gemlog").rglob(r'*.gmi')
files = [ \
file for file in files \
if re.search(r'\d\d\d\d.*\.gmi', file.as_posix()) \
]
articles = ""
for file in files[::-1]:
title, date = find_title_date(file)
print(f"\u2713 {date} {title.strip()}")
articles += f"=> /gemlog/{file.stem}.gmi {date} - {title}"
for path in listing:
with open(path, "r+") as file:
line = file.readline(9)
while line != "## logs\n":
line = file.readline(9)
file.seek(0, 1)
if not path.match(r"gemlog/index.gmi"):
file.write("=> /gemlog/index.gmi all gemlogs here\n")
file.write("\n" + articles)
file.truncate()
def make_footer(footer_path: Path, directory: Path, footer_str: str ="\n---\n"):
with open(footer_path) as file:
footer = file.read()
print("Adding footers...")
files = directory.rglob(r'*.gmi')
for file in files:
with open(file, "rb+") as f:
while line:= f.readline():
if line == "---\n".encode("utf-8"):
f.seek(-5, 1)
break
f.write((footer_str + footer).encode("utf-8"))
f.truncate()
print(f"\u2713 {file}")
print("Footers added !")
def find_title_date(filename: Path):
title, date = "", filename.stem[:10]
with open(filename) as file:
for line in file:
if line[:2] == "# ":
title = line[2:]
return title, date
@app.command("html")
def gen_html():
files = DIR_SOURCE.rglob('*.gmi')
for file in files:
gmi2html(file.as_posix())
print("---\nDone processing gmi files\n")
print("\nCopying static files...\n")
shutil.copytree(DIR_ASSETS, DIR_O_HTML, dirs_exist_ok=True)
def gmi2html(file: str):
new_file = file.replace(f"{DIR_SOURCE}", f"{DIR_O_HTML}")
new_file = Path(new_file).with_suffix('.html')
new_file.parent.mkdir(parents=True, exist_ok=True)
print(f"Processing: {new_file}")
new_file = open(new_file, "w")
with open(file, 'r') as gemlog:
contents, title = process_file(gemlog)
with open(DIR_HELPER / "header-part.html", "r") as header_file:
x = header_file.read()
x = re.sub("PAGETITLE", f"{title}", x)
new_file.write(x)
new_file.write(contents)
with open(DIR_HELPER / "footer-part.html", "r") as footer_file:
new_file.write(footer_file.read())
new_file.close()
def process_file(ifile: TextIOWrapper):
in_preformated = False
in_list = False
contents = ""
title = ""
for line in ifile:
if line == "---\n":
break
elif line[:2] == "# ":
title = line[2:-1]
newline = line
if in_preformated:
newline, n = re.subn(r'^```.*', "</pre>", newline)
in_preformated = False if n else True
contents += newline.lstrip()
continue
else:
newline, n = re.subn(r'^```.*', "<pre>", newline)
in_preformated = True if n else False
if re.match(r'^[a-zA-Z]', newline):
newline = "<p>" + newline.strip() + "</p>\n"
newline, in_list = process_list(newline, in_list)
newline = re.sub(r'^=> (?P<url>[^ ]*) ?(?P<text>.*)\n', repl_url, newline)
newline = re.sub(r'^(#+) (.*)', repl_heading, newline)
newline = process_inline(newline)
contents += newline
return contents, title
def process_list(line: str, in_list: bool):
if in_list:
if line[0] == "*":
line = re.sub(r'^\* (.*)', r'<li>\1</li>', line)
else:
in_list = False
line = "</ul>\n\n"
elif line[0] == "*":
line = "<ul>\n" + re.sub(r'^\* (.*)', r'<li>\1</li>', line)
in_list = True
return line, in_list
def process_inline(line: str):
repls = {
r'\*(.*?)\*': r'<em>\1</em>',
r'~~(.*?)~~': r'<s>\1</s>'
}
for pattern, repl in repls.items():
line = re.sub(pattern, repl, line)
return line
def repl_url(matchobj: re.Match):
url, text = matchobj.groupdict().values()
if not re.findall(r'=> (http|gemini)', url):
url = re.sub(r'gmi$', r'html', url)
return f'<a href="{url}">=> {text if text else url}</a><br>\n'
def repl_heading(matchobj: re.Match):
x = len(matchobj.group(1))
return f"<h{x}>{matchobj.string.strip()}</h{x}>"
if __name__ == "__main__":
app()

14
gmirator/main.py Normal file
View file

@ -0,0 +1,14 @@
import typer
import generate
import synchronise
app = typer.Typer(
add_completion=False,
help="gmirator helps you publish in both gmi and html"
)
app.add_typer(generate.app, name='gen')
app.add_typer(synchronise.app, name='sync')
if __name__ == "__main__":
app()

33
gmirator/synchronise.py Normal file
View file

@ -0,0 +1,33 @@
import typer
import subprocess
from pathlib import Path
import shlex
import json
config_path = "config.json"
with open(config_path) as f:
config = json.load(f)
WORKDIR = Path(config["workdir"])
RPORT = config["port"]
app = typer.Typer(help="syncs your files with configured remote")
@app.command("gmi")
def sync_gmi():
print("Synchronising GMI content with remote...")
LGMI = WORKDIR / "content"
RGMI = config["rem_gmi"]
subprocess.call(
shlex.split(f'rsync -aP -e "ssh -p {RPORT}" {LGMI} {RGMI}')
)
print("Done!")
@app.command("html")
def sync_html():
print("Synchronising HTML content with remote...")
LHTML = WORKDIR / config["dir_o_html"]
RHTML = config["rem_html"]
subprocess.call(
shlex.split(f'rsync -aP -e "ssh -p {RPORT}" {LHTML} {RHTML}')
)
print("Done!")

View file

View file

@ -0,0 +1,5 @@
from gmirator.generate import process_inline
def test_process_inline():
assert process_inline("*Hello* ~~world~~!") == "<em>Hello</em> <s>world</s>!"