aboutsummaryrefslogtreecommitdiff
path: root/blog_experiment/blog
diff options
context:
space:
mode:
authoralex <alex@pdp7.net>2023-10-13 16:11:25 +0200
committeralex <alex@pdp7.net>2023-10-13 16:11:25 +0200
commit250201b433c0a99f6cabcb2596bfe43f1a5a3968 (patch)
tree8dc5de16ed4315b3ab4fcc001b7dbd74d475c4b3 /blog_experiment/blog
parent1a80ac63854ba4ea28f81194ad15314771e979d5 (diff)
Moving to prod!
Diffstat (limited to 'blog_experiment/blog')
-rw-r--r--blog_experiment/blog/__init__.py54
-rw-r--r--blog_experiment/blog/__main__.py36
-rw-r--r--blog_experiment/blog/blog_pages.py139
-rw-r--r--blog_experiment/blog/gemtext.py223
-rw-r--r--blog_experiment/blog/html.py101
-rw-r--r--blog_experiment/blog/meta.py13
-rw-r--r--blog_experiment/blog/page.py37
-rw-r--r--blog_experiment/blog/pretty.py5
8 files changed, 0 insertions, 608 deletions
diff --git a/blog_experiment/blog/__init__.py b/blog_experiment/blog/__init__.py
deleted file mode 100644
index 81332ddd..00000000
--- a/blog_experiment/blog/__init__.py
+++ /dev/null
@@ -1,54 +0,0 @@
-import pathlib
-import re
-
-import bicephalus
-
-import htmlgenerator as h
-
-from blog import blog_pages, page, html, pretty, gemtext
-
-
-class SimplePage(page.BasePage):
- def __init__(self, request, url, title):
- super().__init__(request)
- self.url = url
- self.title = title
-
- def get_gemini_content(self):
- return (
- bicephalus.Status.OK,
- "text/gemini",
- pathlib.Path(f"static{self.url}index.gmi").read_text(),
- )
-
- def get_http_content(self):
- return (
- bicephalus.Status.OK,
- "text/html",
- pretty.pretty_html(h.render(
- h.HTML(
- h.HEAD(
- h.TITLE(self.title),
- ),
- h.BODY(*html.gemini_to_html(gemtext.parse(self.get_gemini_content()[2])))
- ), {})),
- )
-
-
-def handler(request: bicephalus.Request) -> bicephalus.Response:
- if request.path == "/":
- return blog_pages.Root(request).response()
- if re.match(r"/\d{4}/\d{2}/.*/", request.path):
- blog_file = pathlib.Path("content") / (request.path[1:-1] + ".gmi")
- if blog_file.exists():
- return blog_pages.EntryPage(request, blog_file).response()
- if request.path == "/feed/" and request.proto == bicephalus.Proto.HTTP:
- return blog_pages.Root(request).feed()
- if request.path == "/about/":
- return SimplePage(request, request.path, "About Álex Córcoles").response()
- if request.path == "/laspelis/":
- return SimplePage(request, request.path, "laspelis").response()
- if re.match(r"/laspelis/\d+/?", request.path):
- return SimplePage(request, request.path.removesuffix("/") + "/", request.path).response()
-
- return page.NotFound(request).response()
diff --git a/blog_experiment/blog/__main__.py b/blog_experiment/blog/__main__.py
deleted file mode 100644
index 21033902..00000000
--- a/blog_experiment/blog/__main__.py
+++ /dev/null
@@ -1,36 +0,0 @@
-import argparse
-import logging
-import sys
-
-from bicephalus import main as bicephalus_main
-from bicephalus import otel
-from bicephalus import ssl
-
-import blog
-
-from blog import meta
-
-
-def main():
- otel.configure_logging(logging.INFO)
-
- parser = argparse.ArgumentParser()
- parser.add_argument("--key-cert", nargs=2, metavar=("KEY", "CERT",), help="Path to a key and a file")
- parser.add_argument("schema")
- parser.add_argument("host")
- args = parser.parse_args()
- meta.SCHEMA = args.schema
- meta.HOST = args.host
-
- if args.key_cert:
- key, cert = args.key_cert
- with ssl.ssl_context_from_files(cert, key) as ssl_context:
- bicephalus_main.main(blog.handler, ssl_context, 8000)
- sys.exit(0)
-
- with ssl.temporary_ssl_context("localhost") as ssl_context:
- bicephalus_main.main(blog.handler, ssl_context, 8000)
- sys.exit(0)
-
-if __name__ == "__main__":
- main()
diff --git a/blog_experiment/blog/blog_pages.py b/blog_experiment/blog/blog_pages.py
deleted file mode 100644
index 7802c00b..00000000
--- a/blog_experiment/blog/blog_pages.py
+++ /dev/null
@@ -1,139 +0,0 @@
-import datetime
-import itertools
-import pathlib
-import textwrap
-
-import bicephalus
-
-import htmlgenerator as h
-
-from feedgen import feed
-
-from blog import html, page, gemtext, meta, pretty
-
-
-def gemini_links():
- return "\n".join([f"=> {url} {text}" for text, url in meta.LINKS])
-
-
-class Entry:
- def __init__(self, path: pathlib.Path):
- assert path.is_relative_to(pathlib.Path("content")), f"bad path {path}"
- self.path = path
- self.content = path.read_text()
-
- @property
- def title(self):
- return self.content.splitlines()[0][2:]
-
- @property
- def posted(self):
- return datetime.date.fromisoformat(self.content.splitlines()[1])
-
- @property
- def uri(self):
- return f"/{self.path.parts[1]}/{self.path.parts[2]}/{self.path.stem}/"
-
- @property
- def edit_url(self):
- return f"https://github.com/alexpdp7/gemini_blog/edit/master/content{self.uri[:-1]}.gmi"
-
- def html(self):
- parsed = gemtext.parse(self.content)
-
- assert isinstance(parsed[0], gemtext.Header)
- assert parsed[0].level == 1
- assert isinstance(parsed[1], gemtext.Line)
- assert parsed[2] == gemtext.Line("")
-
- result = html.gemini_to_html(parsed[3:])
- result.append(h.P(h.A("Editar", href=self.edit_url)))
- return result
-
-
-class Root(page.BasePage):
- def entries(self):
- entries = map(Entry, pathlib.Path("content").glob("*/*/*.gmi"))
- return sorted(entries, key=lambda e: e.posted, reverse=True)
-
- def get_gemini_content(self):
- posts = "\n".join([f"=> {e.uri} {e.posted} {e.title}" for e in self.entries()])
- content = (
- textwrap.dedent(
- f"""\
- # {meta.TITLE}
-
- ## {meta.SUBTITLE}
-
- """
- )
- + gemini_links()
- + f"\n{meta.EMAIL_TEXT}\n"
- + "\n"
- + posts
- )
- return bicephalus.Status.OK, "text/gemini", content
-
- def get_http_content(self):
- posts = [
- (h.H3(h.A(f"{e.title} ({e.posted})", href=e.uri))) for e in self.entries()
- ]
- return (
- bicephalus.Status.OK,
- "text/html",
- html.html_template(*itertools.chain(posts)),
- )
-
- def feed(self):
- fg = feed.FeedGenerator()
- fg.title(meta.TITLE)
- fg.subtitle(meta.SUBTITLE)
- fg.link(href=f"{meta.SCHEMA}://{meta.HOST}", rel="self")
-
- for entry in self.entries()[0:10]:
- fe = fg.add_entry()
- url = f"{meta.SCHEMA}://{meta.HOST}/{entry.uri}"
- fe.link(href=url)
- fe.published(datetime.datetime.combine(entry.posted, datetime.datetime.min.time(), tzinfo=datetime.timezone.utc))
- fe.title(entry.title)
- html = h.render(h.BaseElement(*entry.html()), {})
- html = pretty.pretty_html(html)
- fe.content(html, type="html")
-
- return bicephalus.Response(
- status=bicephalus.Status.OK,
- content_type="application/rss+xml",
- content=fg.rss_str(pretty=True),
- )
-
-
-class EntryPage(page.BasePage):
- def __init__(self, request, path):
- super().__init__(request)
- self.path = path
- self.entry = Entry(path)
-
- def get_gemini_content(self):
- content = (
- textwrap.dedent(f"""\
- => gemini://{meta.HOST} alex.corcoles.net
- {meta.EMAIL_TEXT}
-
- """) +
- self.entry.content +
- textwrap.dedent(f"""\
- => {self.entry.edit_url} Editar
- """)
- )
-
- return bicephalus.Status.OK, "text/gemini", content
-
- def get_http_content(self):
- return (
- bicephalus.Status.OK,
- "text/html",
- html.html_template(
- *self.entry.html(),
- page_title=f"{self.entry.title} - {self.entry.posted}",
- ),
- )
diff --git a/blog_experiment/blog/gemtext.py b/blog_experiment/blog/gemtext.py
deleted file mode 100644
index 66298e3f..00000000
--- a/blog_experiment/blog/gemtext.py
+++ /dev/null
@@ -1,223 +0,0 @@
-import dataclasses
-import re
-import typing
-
-
-def parse(s):
- """
- >>> parse('''# Header 1
- ...
- ... ## Header 2
- ...
- ... ### Header 3
- ...
- ... * List 1
- ... * List 2
- ...
- ... > First line quote.
- ... > Second line of quote.
- ...
- ... ```
- ... Fenced
- ... Lines
- ... ```
- ...
- ... Paragraph.
- ...
- ... Another paragraph.
- ... ''')
- [Header(level=1, text='Header 1'),
- Line(text=''),
- Header(level=2, text='Header 2'),
- Line(text=''),
- Header(level=3, text='Header 3'),
- Line(text=''),
- List(items=[ListItem(text='List 1'),
- ListItem(text='List 2')]),
- Line(text=''),
- BlockQuote(lines=[BlockQuoteLine(text='First line quote.'),
- BlockQuoteLine(text='Second line of quote.')]),
- Line(text=''),
- Pre(content='Fenced\\nLines\\n'),
- Line(text=''),
- Line(text='Paragraph.'),
- Line(text=''),
- Line(text='Another paragraph.')]
- """
-
- lines = s.splitlines()
-
- i = 0
- gem = []
-
- while i < len(lines):
- line = parse_line(lines[i])
-
- if isinstance(line, Link):
- gem.append(line)
- i = i + 1
- continue
-
- if isinstance(line, Header):
- gem.append(line)
- i = i + 1
- continue
-
- if isinstance(line, ListItem):
- items = []
- while i < len(lines) and isinstance(parse_line(lines[i]), ListItem):
- items.append(parse_line(lines[i]))
- i = i + 1
- gem.append(List(items))
- continue
-
- if isinstance(line, BlockQuoteLine):
- quotes = []
- while i < len(lines) and isinstance(parse_line(lines[i]), BlockQuoteLine):
- quotes.append(parse_line(lines[i]))
- i = i + 1
- gem.append(BlockQuote(quotes))
- continue
-
- if isinstance(line, PreFence):
- content = ""
- i = i + 1
- while i < len(lines) and not isinstance(parse_line(lines[i]), PreFence):
- content += lines[i]
- content += "\n"
- i = i + 1
- gem.append(Pre(content))
- i = i + 1
- continue
-
- gem.append(line)
- i = i + 1
-
- return gem
-
-
-def parse_line(l):
- if Link.is_link(l):
- return Link(l)
- if Header.is_header(l):
- return Header(l)
- if ListItem.is_list_item(l):
- return ListItem(l)
- if BlockQuoteLine.is_block_quote_line(l):
- return BlockQuoteLine(l)
- if PreFence.is_pre_fence(l):
- return PreFence()
- return Line(l)
-
-
-@dataclasses.dataclass
-class Link:
- """
- >>> Link("=> http://example.com")
- Link(url='http://example.com', text=None)
-
- >>> Link("=> http://example.com Example text")
- Link(url='http://example.com', text='Example text')
- """
-
- url: str
- text: typing.Optional[str]
-
- def __init__(self, line: str):
- assert Link.is_link(line)
- parts = line.split(None, 2)
- self.url = parts[1]
- self.text = parts[2] if len(parts) > 2 else None
-
- @staticmethod
- def is_link(line: str):
- return line.startswith("=>")
-
-@dataclasses.dataclass
-class Header:
- """
- >>> Header("# Level one")
- Header(level=1, text='Level one')
-
- >>> Header("## Level two")
- Header(level=2, text='Level two')
-
- >>> Header("### Level three")
- Header(level=3, text='Level three')
- """
-
- level: int
- text: str
-
- def __init__(self, line: str):
- assert Header.is_header(line)
- hashes, self.text = line.split(None, 1)
- self.level = len(hashes)
-
- @staticmethod
- def is_header(line: str):
- return re.match("#{1,3} .*", line)
-
-@dataclasses.dataclass
-class ListItem:
- """
- >>> ListItem("* foo")
- ListItem(text='foo')
- """
-
- text: str
-
- def __init__(self, line: str):
- assert ListItem.is_list_item(line)
- self.text = line[2:]
-
- @staticmethod
- def is_list_item(line: str):
- return line.startswith("* ")
-
-
-@dataclasses.dataclass
-class BlockQuoteLine:
- """
- >>> BlockQuoteLine("> foo")
- BlockQuoteLine(text='foo')
-
- >>> BlockQuoteLine(">foo")
- BlockQuoteLine(text='foo')
- """
-
- text: str
-
- def __init__(self, line: str):
- assert BlockQuoteLine.is_block_quote_line(line)
- self.text = line[2:] if line.startswith("> ") else line[1:]
-
- @staticmethod
- def is_block_quote_line(line: str):
- return line.startswith(">")
-
-
-class PreFence:
- @staticmethod
- def is_pre_fence(line: str):
- return line == "```"
-
-
-@dataclasses.dataclass
-class Line:
- text: str
-
-
-@dataclasses.dataclass
-class List:
- items: typing.List[ListItem]
-
-
-@dataclasses.dataclass
-class BlockQuote:
- lines: typing.List[BlockQuoteLine]
-
-
-@dataclasses.dataclass
-class Pre:
- content: str
diff --git a/blog_experiment/blog/html.py b/blog_experiment/blog/html.py
deleted file mode 100644
index 8aa92f4a..00000000
--- a/blog_experiment/blog/html.py
+++ /dev/null
@@ -1,101 +0,0 @@
-import itertools
-
-import htmlgenerator as h
-
-from blog import meta, pretty, gemtext
-
-
-def html_template(*content, page_title=None):
- title = [h.A(meta.TITLE, href=f"{meta.SCHEMA}://{meta.HOST}")]
- if page_title:
- title += f" - {page_title}"
-
- title = h.BaseElement(*title)
-
- links = list(itertools.chain(*[(h.A(text, href=href), ", ") for text, href in meta.LINKS]))
-
- links += h.BaseElement(f" {meta.EMAIL_TEXT}")
-
- return pretty.pretty_html(h.render(
- h.HTML(
- h.HEAD(
- h.TITLE(meta.TITLE + (f" - {page_title}" if page_title else "")),
- h.LINK(rel="alternate", type="application/rss+xml", title=meta.TITLE, href=f"{meta.SCHEMA}://{meta.HOST}/feed/"),
- ),
- h.BODY(
- h.H1(title),
- h.H2(meta.SUBTITLE),
- h.P(*links),
- *content,
- ),
- doctype="html",
- ),
- {},
- ))
-
-
-def gemini_to_html(parsed):
- i = 0
- result = []
- while i < len(parsed):
- gem_element = parsed[i]
-
- if isinstance(gem_element, gemtext.Header):
- header = [h.H1, h.H2, h.H3, h.H4, h.H5, h.H6][gem_element.level - 1]
- result.append(header(gem_element.text))
- i = i + 1
- continue
-
- if isinstance(gem_element, gemtext.List):
- result.append(h.UL(*[h.LI(i.text) for i in gem_element.items]))
- i = i + 1
- continue
-
- if isinstance(gem_element, gemtext.Link):
- url = gem_element.url
- if url.startswith("gemini://"):
- if url.startswith("gemini://alex.corcoles.net/"):
- url = url.replace("gemini://alex.corcoles.net/", f"{meta.SCHEMA}://{meta.HOST}/")
- else:
- url = url.replace("gemini://", "https://portal.mozz.us/gemini/")
-
- result.append(h.P(h.A(gem_element.text or gem_element.url, href=url)))
- i = i + 1
- continue
-
- if gem_element == gemtext.Line(""):
- i = i + 1
- continue
-
- if isinstance(gem_element, gemtext.BlockQuote):
- content = []
- for line in gem_element.lines:
- if line.text:
- content.append(line.text)
- content.append(h.BR())
- result.append(h.BLOCKQUOTE(*content))
- i = i + 1
- continue
-
- if isinstance(gem_element, gemtext.Line):
- paragraph = [gem_element.text]
- i = i + 1
- while i < len(parsed):
- gem_element = parsed[i]
- if isinstance(gem_element, gemtext.Line) and gem_element.text != "":
- paragraph.append(h.BR())
- paragraph.append(gem_element.text)
- i = i + 1
- else:
- break
- result.append(h.P(*paragraph))
- continue
-
- if isinstance(gem_element, gemtext.Pre):
- result.append(h.PRE(gem_element.content))
- i = i + 1
- continue
-
- assert False, f"unknown element {gem_element}"
-
- return result
diff --git a/blog_experiment/blog/meta.py b/blog_experiment/blog/meta.py
deleted file mode 100644
index e92b2acb..00000000
--- a/blog_experiment/blog/meta.py
+++ /dev/null
@@ -1,13 +0,0 @@
-TITLE = "El blog es mío"
-SUBTITLE = "Hay otros como él, pero este es el mío"
-HOST = None
-SCHEMA = None
-
-LINKS = (
- ("GitHub", "https://github.com/alexpdp7/"),
- ("LinkedIn", "https://es.linkedin.com/in/alexcorcoles"),
- ("Project Euler", "https://projecteuler.net/profile/koalillo.png"),
- ("Stack Exchange", "https://stackexchange.com/users/13361/alex"),
-)
-
-EMAIL_TEXT = "escríbeme cogiendo el dominio de esta web y cambiando el primer punto por una arroba"
diff --git a/blog_experiment/blog/page.py b/blog_experiment/blog/page.py
deleted file mode 100644
index fcc4841a..00000000
--- a/blog_experiment/blog/page.py
+++ /dev/null
@@ -1,37 +0,0 @@
-import bicephalus
-
-
-class BasePage:
- def __init__(self, request):
- self.request = request
-
- def response(self):
- if self.request.proto == bicephalus.Proto.GEMINI:
- status, content_type, content = self.get_gemini_content()
- elif self.request.proto == bicephalus.Proto.HTTP:
- status, content_type, content = self.get_http_content()
- else:
- assert False, f"unknown protocol {self.request.proto}"
-
- return bicephalus.Response(
- content=content.encode("utf8"),
- content_type=content_type,
- status=bicephalus.Status.OK,
- )
-
-
-class NotFound(BasePage):
- def get_gemini_content(self):
- # TODO: does not work!
- return (
- bicephalus.Status.NOT_FOUND,
- "text/gemini",
- f"{self.request.path} not found",
- )
-
- def get_http_content(self):
- return (
- bicephalus.Status.NOT_FOUND,
- "text/html",
- f"{self.request.path} not found",
- )
diff --git a/blog_experiment/blog/pretty.py b/blog_experiment/blog/pretty.py
deleted file mode 100644
index 2ae916a7..00000000
--- a/blog_experiment/blog/pretty.py
+++ /dev/null
@@ -1,5 +0,0 @@
-from lxml import etree, html
-
-
-def pretty_html(s):
- return etree.tostring(html.fromstring(s), pretty_print=True).decode("utf8")