~sotirisp/qute-gemini

7d9c106c54344c6fc4e79e2ffa3bd12f918ab559 — Sotiris Papatheodorou 9 months ago 8d2e699
Restructure code and add comments
1 files changed, 62 insertions(+), 55 deletions(-)

M qute-gemini
M qute-gemini => qute-gemini +62 -55
@@ 72,6 72,10 @@ def qute_fifo() -> str:
    return os.environ["QUTE_FIFO"]


def html_href(url: str, description: str) -> str:
    return "".join(['<a href="', url, '">', description, "</a>"])


def qute_gemini_css_path() -> str:
    """Return the path where the custom CSS file is expected to be."""
    try:


@@ 81,17 85,17 @@ def qute_gemini_css_path() -> str:
    return os.path.join(base_dir, "qutebrowser/userscripts/qute-gemini.css")


def gemini_absolutise_url(base: str, relative: str) -> str:
def gemini_absolutise_url(base_url: str, relative_url: str) -> str:
    """Absolutise relative gemini URLs.

    Adapted from gcat: https://github.com/aaronjanse/gcat
    """
    if "://" not in relative:
    if "://" not in relative_url:
        # Python's URL tools somehow only work with known schemes?
        base = base.replace("gemini://", "http://")
        relative = urllib.parse.urljoin(base, relative)
        relative = relative.replace("http://", "gemini://")
    return relative
        base_url = base_url.replace("gemini://", "http://")
        relative_url = urllib.parse.urljoin(base_url, relative_url)
        relative_url = relative_url.replace("http://", "gemini://")
    return relative_url


def gemini_fetch_url(url: str) -> Tuple[str, str, str, str, str]:


@@ 103,6 107,7 @@ def gemini_fetch_url(url: str) -> Tuple[str, str, str, str, str]:

    Adapted from gcat: https://github.com/aaronjanse/gcat
    """
    # Parse the URL to get the hostname and port
    parsed_url = urllib.parse.urlparse(url)
    if not parsed_url.scheme:
        url = "gemini://" + url


@@ 113,20 118,21 @@ def gemini_fetch_url(url: str) -> Tuple[str, str, str, str, str]:
        useport = parsed_url.port
    else:
        useport = 1965
    # Do the Gemini transaction
    # Do the Gemini transaction, looping for redirects
    redirects = 0
    while True:
        # Send the request
        s = socket.create_connection((parsed_url.hostname, useport))
        context = ssl.SSLContext(ssl.PROTOCOL_TLS)
        context.check_hostname = False
        context.verify_mode = ssl.CERT_NONE
        s = context.wrap_socket(s, server_hostname = parsed_url.netloc)
        s.sendall((url + "\r\n").encode("UTF-8"))
        # Get header and check for redirects
        # Get the status code and meta
        fp = s.makefile("rb")
        header = fp.readline().decode("UTF-8").strip()
        status, meta = header.split()[:2]
        # Follow redirects
        # Follow up to 5 redirects
        if status.startswith("3"):
            url = gemini_absolutise_url(url, meta)
            parsed_url = urllib.parse.urlparse(url)


@@ 142,18 148,17 @@ def gemini_fetch_url(url: str) -> Tuple[str, str, str, str, str]:
    error_msg = ""
    # 2x Success
    if status.startswith("2"):
        # Decode according to declared charset
        media_type, media_type_opts = cgi.parse_header(meta)
        # Decode according to declared charset defaulting to UTF-8
        if meta.startswith("text/gemini"):
            content = fp.read()
            content = content.decode(media_type_opts.get("charset", "UTF-8"))
            charset = media_type_opts.get("charset", "UTF-8")
            content = fp.read().decode(charset)
        else:
            error_msg = "Expected media type text/gemini but received " \
                + media_type
    # Handle errors
    else:
        # Try matching a 2-digit status code before trying to match a 1-digit
        # one
        # Try matching a 2-digit and then a 1-digit status code
        try:
            error_msg = _status_code_desc[status[0:2]]
        except KeyError:


@@ 161,6 166,7 @@ def gemini_fetch_url(url: str) -> Tuple[str, str, str, str, str]:
                error_msg = _status_code_desc[status[0]]
            except KeyError:
                error_msg = "The server sent back something weird."
        # Substitute the contents of meta into the error message if needed
        error_msg = error_msg.replace("META", meta)
    return content, url, status, meta, error_msg



@@ 170,23 176,24 @@ def gemtext_to_html(gemtext: str, url: str, original_url: str,
    """Convert gemtext to HTML.

    title: Used as the document title.
    url:    The URL the gemtext was received from. Used to resolve relative
            URLs in the gemtext content.
    status: The Gemini status code returned by the server.
    meta:   The meta returned by the server.
    url:          The URL the gemtext was received from. Used to resolve
                  relative URLs in the gemtext content.
    original_url: The URL the original request was made at.
    status:       The Gemini status code returned by the server.
    meta:         The meta returned by the server.
    Returns the HTML representation as a string.
    """
    # Accumulate converted gemtext lines
    lines = ['<?xml version="1.0" encoding="UTF-8"?>',
            '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">',
            "\t<head>",
            "\t\t<title>" + html.escape(url) + "</title>",
            "\t\t<style>",
            get_css(),
            "\t\t</style>",
            "\t</head>",
            "\t<body>",
            "\t<article>"]
             '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">',
             "\t<head>",
             "\t\t<title>" + html.escape(url) + "</title>",
             "\t\t<style>",
             get_css(),
             "\t\t</style>",
             "\t</head>",
             "\t<body>",
             "\t<article>"]
    in_pre = False
    in_list = False
    # Add an extra newline to ensure list tags are closed properly


@@ 195,9 202,8 @@ def gemtext_to_html(gemtext: str, url: str, original_url: str,
        if not line.startswith("*") and in_list:
            lines.append("\t\t</ul>")
            in_list = False
        # Blank line
        # Blank line, ignore
        if not line:
            # Ignore
            pass
        # Link
        elif line.startswith("=>"):


@@ 209,7 215,7 @@ def gemtext_to_html(gemtext: str, url: str, original_url: str,
            l[1] = html.escape(l[1])
            # Resolve relative URLs
            l[0] = gemini_absolutise_url(url, l[0])
            lines.append("".join(['\t\t<p><a href="', l[0], '">', l[1], "</a></p>"]))
            lines.append("\t\t<p>" + html_href(l[0], l[1]) + "</p>")
        # Preformated toggle
        elif line.startswith("```"):
            if in_pre:


@@ 235,31 241,33 @@ def gemtext_to_html(gemtext: str, url: str, original_url: str,
            lines.append("\t\t\t<li>" + html.escape(line[1:].strip()) + "</li>")
        # Quote
        elif line.startswith(">"):
            lines.append("\t\t<blockquote>\n\t\t\t<p>" + line[1:].strip() + "</p>\n\t\t</blockquote>")
            lines.extend(["\t\t<blockquote>",
                          "\t\t\t<p>" + line[1:].strip() + "</p>",
                          "\t\t</blockquote>"])
        # Normal text
        else:
            lines.append("\t\t<p>" + html.escape(line.strip()) + "</p>")
    lines.append("")
    url_html = '<a href="' + url + '">' + html.escape(url) + "</a>"
    original_url_html = '<a href="' + original_url + '">' + html.escape(url) + "</a>"
    lines.append("\t</article>")
    lines.append("\t<details>")
    lines.append("\t\t<summary>")
    lines.append("\t\t\tContent from " + url_html)
    lines.append("\t\t</summary>")
    lines.append("\t\t<dl>")
    lines.append("\t\t\t<dt>Original URL</dt>")
    lines.append("\t\t\t<dd>" + original_url_html + "</dd>")
    lines.append("\t\t\t<dt>Status</dt>")
    lines.append("\t\t\t<dd>" + status + "</dd>")
    lines.append("\t\t\t<dt>Meta</dt>")
    lines.append("\t\t\t<dd>" + meta + "</dd>")
    lines.append("\t\t\t<dt>Fetched by</dt>")
    lines.append('\t\t\t<dd><a href="https://git.sr.ht/~sotirisp/qute-gemini">qute-gemini ' + str(_version) + "</a></dd>")
    lines.append("\t\t</dl>")
    lines.append("\t</details>")
    lines.append("\t</body>\n")
    lines.append("</html>")
    url_html = html_href(url, html.escape(url))
    original_url_html = html_href(original_url, html.escape(original_url))
    lines.extend(["",
                  "\t</article>",
                  "\t<details>",
                  "\t\t<summary>",
                  "\t\t\tContent from " + url_html,
                  "\t\t</summary>",
                  "\t\t<dl>",
                  "\t\t\t<dt>Original URL</dt>",
                  "\t\t\t<dd>" + original_url_html + "</dd>",
                  "\t\t\t<dt>Status</dt>",
                  "\t\t\t<dd>" + status + "</dd>",
                  "\t\t\t<dt>Meta</dt>",
                  "\t\t\t<dd>" + meta + "</dd>",
                  "\t\t\t<dt>Fetched by</dt>",
                  '\t\t\t<dd><a href="https://git.sr.ht/~sotirisp/qute-gemini">qute-gemini ' + str(_version) + "</a></dd>",
                  "\t\t</dl>",
                  "\t</details>",
                  "\t</body>",
                  "</html>"])
    return "\n".join(lines)




@@ 296,16 304,16 @@ def open_gemini(url: str, open_args: str) -> None:
    # Get the Gemini content
    content, content_url, status, meta, error_msg = gemini_fetch_url(url)
    if error_msg:
        # Generate an error page in a data URI
        open_url = qute_error_page(url, error_msg)
    else:
        # Convert to HTML in a temporary file
        # Success, convert to HTML in a temporary file
        tmpf = tempfile.NamedTemporaryFile("w", suffix=".html", delete=False)
        tmp_filename = tmpf.name
        tmpf.close()
        with open(tmp_filename, "w") as f:
            f.write(gemtext_to_html(content, content_url, url, status, meta))
        open_url = " file://" + tmp_filename

    # Open the HTML file in qutebrowser
    with open(qute_fifo(), "w") as qfifo:
        qfifo.write("open " + open_args + open_url)


@@ 323,7 331,6 @@ if __name__ == "__main__":
        open_args = "-t"
    else:
        open_args = ""

    # Select how to open the URL depending on its scheme
    url = qute_url()
    parsed_url = urllib.parse.urlparse(url)