initial commit
This commit is contained in:
commit
0d81f3fa3b
7 changed files with 245 additions and 0 deletions
22
README.md
Normal file
22
README.md
Normal 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
0
gmirator/__init__.py
Normal file
171
gmirator/generate.py
Normal file
171
gmirator/generate.py
Normal 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
14
gmirator/main.py
Normal 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
33
gmirator/synchronise.py
Normal 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!")
|
0
gmirator/test/__init__.py
Normal file
0
gmirator/test/__init__.py
Normal file
5
gmirator/test/test_generate.py
Normal file
5
gmirator/test/test_generate.py
Normal 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>!"
|
Loading…
Reference in a new issue