2020-09-06 11:32:08 +02:00
|
|
|
;; tinmop: an humble gemini and pleroma client
|
2020-06-14 16:46:28 +02:00
|
|
|
;; Copyright (C) 2020 cage
|
|
|
|
|
|
|
|
;; This program is free software: you can redistribute it and/or modify
|
|
|
|
;; it under the terms of the GNU General Public License as published by
|
|
|
|
;; the Free Software Foundation, either version 3 of the License, or
|
|
|
|
;; (at your option) any later version.
|
|
|
|
|
|
|
|
;; This program is distributed in the hope that it will be useful,
|
|
|
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
;; GNU General Public License for more details.
|
|
|
|
|
|
|
|
;; You should have received a copy of the GNU General Public License
|
|
|
|
;; along with this program.
|
|
|
|
;; If not, see [[http://www.gnu.org/licenses/][http://www.gnu.org/licenses/]].
|
|
|
|
|
|
|
|
(in-package :gemini-parser)
|
|
|
|
|
2021-04-08 16:32:34 +02:00
|
|
|
(defparameter *raw-mode-data* nil)
|
2020-07-26 12:04:46 +02:00
|
|
|
|
2021-01-09 16:27:40 +01:00
|
|
|
(define-constant +h1-prefix+ "#" :test #'string=)
|
|
|
|
|
|
|
|
(define-constant +h2-prefix+ "##" :test #'string=)
|
|
|
|
|
|
|
|
(define-constant +h3-prefix+ "###" :test #'string=)
|
|
|
|
|
|
|
|
(define-constant +list-bullet-prefix+ "* " :test #'string=)
|
|
|
|
|
|
|
|
(define-constant +quote-prefix+ ">" :test #'string=)
|
|
|
|
|
|
|
|
(define-constant +preformatted-prefix+ "```" :test #'string=)
|
|
|
|
|
|
|
|
(define-constant +link-prefix+ "=>" :test #'string=)
|
|
|
|
|
|
|
|
(defmacro gen-geminize-line (name prefix)
|
|
|
|
`(defun ,(format-fn-symbol t "geminize-~a" name) (text)
|
|
|
|
(strcat ,prefix text)))
|
|
|
|
|
|
|
|
(gen-geminize-line h1 +h1-prefix+)
|
|
|
|
|
|
|
|
(gen-geminize-line h2 +h2-prefix+)
|
|
|
|
|
|
|
|
(gen-geminize-line h3 +h3-prefix+)
|
|
|
|
|
|
|
|
(gen-geminize-line list +list-bullet-prefix+)
|
|
|
|
|
|
|
|
(gen-geminize-line quote +quote-prefix+)
|
|
|
|
|
|
|
|
(gen-geminize-line link +link-prefix+)
|
|
|
|
|
2021-04-01 17:08:42 +02:00
|
|
|
(defun geminize-preformatted (text)
|
|
|
|
(format nil "~a~%~a~a~%"
|
|
|
|
+preformatted-prefix+
|
|
|
|
text
|
|
|
|
+preformatted-prefix+))
|
|
|
|
|
2021-01-09 16:27:40 +01:00
|
|
|
(defun make-gemini-link (url title)
|
|
|
|
(format nil "~a ~a"
|
|
|
|
(geminize-link url)
|
|
|
|
title))
|
|
|
|
|
2020-06-14 16:46:28 +02:00
|
|
|
(defrule space (or #\Space #\Tab)
|
|
|
|
(:constant nil))
|
|
|
|
|
|
|
|
(defrule new-line #\Newline
|
|
|
|
(:constant nil))
|
|
|
|
|
|
|
|
(defrule carriage-return #\Return
|
|
|
|
(:constant nil))
|
|
|
|
|
|
|
|
(defrule cr-lf (and (? carriage-return) new-line)
|
2020-06-19 11:40:20 +02:00
|
|
|
(:constant nil))
|
2020-06-14 16:46:28 +02:00
|
|
|
|
|
|
|
(defrule h1-prefix "#"
|
|
|
|
(:constant :h1))
|
|
|
|
|
|
|
|
(defrule h2-prefix "##"
|
|
|
|
(:constant :h2))
|
|
|
|
|
|
|
|
(defrule h3-prefix "###"
|
|
|
|
(:constant :h3))
|
|
|
|
|
|
|
|
(defrule list-bullet "* "
|
|
|
|
(:constant :li))
|
|
|
|
|
|
|
|
(defrule quote-prefix ">"
|
|
|
|
(:constant :quote))
|
|
|
|
|
|
|
|
(defrule preformatted-text-tag (and "```"
|
|
|
|
(* (not cr-lf))
|
|
|
|
cr-lf)
|
|
|
|
(:function (lambda (a)
|
2021-04-08 16:32:34 +02:00
|
|
|
(let ((saved-raw-mode *raw-mode-data*)
|
|
|
|
(alt-text (coerce (second a) 'string)))
|
|
|
|
(if *raw-mode-data*
|
|
|
|
(setf *raw-mode-data* nil)
|
|
|
|
(setf *raw-mode-data* alt-text))
|
2021-04-05 12:01:30 +02:00
|
|
|
(if (not saved-raw-mode)
|
|
|
|
(list :pre
|
2021-04-08 16:32:34 +02:00
|
|
|
(list (list :alt alt-text)))
|
2021-04-05 12:01:30 +02:00
|
|
|
(list :pre-end () ""))))))
|
2020-06-14 16:46:28 +02:00
|
|
|
|
|
|
|
(defrule link-prefix (and "=>"
|
|
|
|
(* space))
|
|
|
|
(:constant :a))
|
|
|
|
|
|
|
|
(defrule text-line (and (+ (not cr-lf)) cr-lf)
|
2020-06-19 11:40:20 +02:00
|
|
|
(:function (lambda (a)
|
2021-04-08 16:32:34 +02:00
|
|
|
(list :text
|
2020-06-19 11:40:20 +02:00
|
|
|
nil
|
|
|
|
(coerce (first a) 'string)))))
|
2020-06-14 16:46:28 +02:00
|
|
|
|
|
|
|
(defrule link-url (+ (not (or space
|
|
|
|
cr-lf)))
|
|
|
|
(:text t))
|
|
|
|
|
|
|
|
(defrule link-name (+ (not cr-lf))
|
|
|
|
(:text t))
|
|
|
|
|
|
|
|
(defrule link (and link-prefix
|
|
|
|
link-url
|
|
|
|
(? (and space
|
2020-07-15 15:18:08 +02:00
|
|
|
(? link-name)))
|
2020-06-14 16:46:28 +02:00
|
|
|
cr-lf)
|
|
|
|
(:function (lambda (a)
|
|
|
|
(list (first a)
|
|
|
|
(list (list :href (second a)))
|
2021-01-24 12:18:29 +01:00
|
|
|
(text-utils:trim-blanks (second (third a)))))))
|
2020-06-14 16:46:28 +02:00
|
|
|
|
|
|
|
(defrule h1 (and h1-prefix
|
|
|
|
text-line)
|
|
|
|
(:function (lambda (a)
|
|
|
|
(list (first a)
|
|
|
|
nil
|
2020-06-19 11:40:20 +02:00
|
|
|
(tag-value (second a))))))
|
2020-06-14 16:46:28 +02:00
|
|
|
|
|
|
|
(defrule h2 (and h2-prefix
|
|
|
|
text-line)
|
|
|
|
(:function (lambda (a)
|
|
|
|
(list (first a)
|
|
|
|
nil
|
2020-06-19 11:40:20 +02:00
|
|
|
(tag-value (second a))))))
|
2020-06-14 16:46:28 +02:00
|
|
|
|
|
|
|
(defrule h3 (and h3-prefix
|
|
|
|
text-line)
|
|
|
|
(:function (lambda (a)
|
|
|
|
(list (first a)
|
|
|
|
nil
|
2020-06-19 11:40:20 +02:00
|
|
|
(tag-value (second a))))))
|
2020-06-14 16:46:28 +02:00
|
|
|
|
|
|
|
(defrule list-item (and list-bullet
|
|
|
|
text-line)
|
|
|
|
(:function (lambda (a)
|
|
|
|
(list (first a)
|
|
|
|
nil
|
2020-06-19 11:40:20 +02:00
|
|
|
(tag-value (second a))))))
|
2020-06-14 16:46:28 +02:00
|
|
|
|
|
|
|
(defrule preformatted-text (and preformatted-text-tag
|
|
|
|
(* (not preformatted-text-tag))
|
|
|
|
preformatted-text-tag)
|
|
|
|
(:function (lambda (a) (append (first a)
|
|
|
|
(list (coerce (second a) 'string))))))
|
|
|
|
|
|
|
|
(defrule quote-line (and quote-prefix
|
|
|
|
text-line)
|
|
|
|
(:function (lambda (a) (list (first a)
|
|
|
|
nil
|
2020-06-19 11:40:20 +02:00
|
|
|
(tag-value (second a))))))
|
2020-06-14 16:46:28 +02:00
|
|
|
|
|
|
|
(defrule gemini-file (* (or h3
|
|
|
|
h2
|
|
|
|
h1
|
2020-07-26 12:04:46 +02:00
|
|
|
preformatted-text-tag
|
2020-06-14 16:46:28 +02:00
|
|
|
link
|
|
|
|
list-item
|
|
|
|
quote-line
|
|
|
|
text-line
|
2021-04-05 15:37:59 +02:00
|
|
|
cr-lf))
|
|
|
|
(:function first))
|
2020-06-14 16:46:28 +02:00
|
|
|
|
|
|
|
(define-constant +h1-underline+ #\━ :test #'char=)
|
|
|
|
|
|
|
|
(define-constant +h2-underline+ #\─ :test #'char=)
|
|
|
|
|
|
|
|
(define-constant +h3-underline+ #\- :test #'char=)
|
|
|
|
|
|
|
|
(define-constant +quote-line-prefix+ #\> :test #'char=)
|
|
|
|
|
|
|
|
(define-constant +bullet-line-prefix+ #\• :test #'char=)
|
|
|
|
|
2020-06-19 11:40:20 +02:00
|
|
|
(defclass gemini-link ()
|
|
|
|
((target
|
|
|
|
:initform nil
|
|
|
|
:initarg :target
|
|
|
|
:accessor target)
|
|
|
|
(name
|
|
|
|
:initform nil
|
|
|
|
:initarg :name
|
|
|
|
:accessor name)))
|
|
|
|
|
|
|
|
(defmethod print-object ((object gemini-link) stream)
|
|
|
|
(print-unreadable-object (object stream :type t :identity t)
|
|
|
|
(with-accessors ((target target)
|
|
|
|
(name name)) object
|
|
|
|
(format stream "target: ~s name: ~s" target name))))
|
|
|
|
|
|
|
|
(defun tag-value (node)
|
|
|
|
(first (html-utils:children node)))
|
|
|
|
|
2020-06-22 13:58:04 +02:00
|
|
|
(defun path-last-dir (path)
|
|
|
|
(if (char= (last-elt path) #\/)
|
|
|
|
path
|
|
|
|
(fs:parent-dir-path path)))
|
|
|
|
|
|
|
|
(defun absolutize-link (link-value original-host original-port original-path)
|
2020-12-14 13:57:29 +01:00
|
|
|
(let ((parsed (or (ignore-errors (iri:iri-parse link-value))
|
|
|
|
(iri:make-iri nil nil nil nil link-value nil nil))))
|
2020-06-22 13:58:04 +02:00
|
|
|
(cond
|
2020-12-13 15:29:04 +01:00
|
|
|
((null (uri:host parsed))
|
2020-06-22 13:58:04 +02:00
|
|
|
(let* ((absolute-path-p (string-starts-with-p "/" link-value))
|
|
|
|
(path (if absolute-path-p
|
2020-12-25 15:03:39 +01:00
|
|
|
(uri:path parsed)
|
2020-07-02 20:26:40 +02:00
|
|
|
(strcat (if original-path
|
|
|
|
(path-last-dir original-path)
|
|
|
|
"/")
|
2020-12-25 15:03:39 +01:00
|
|
|
(uri:path parsed)))))
|
2020-12-17 13:56:07 +01:00
|
|
|
(make-gemini-iri original-host
|
2020-10-25 19:58:05 +01:00
|
|
|
(uri:normalize-path path)
|
2020-12-25 15:03:39 +01:00
|
|
|
:query (uri:query parsed)
|
|
|
|
:port original-port
|
|
|
|
:fragment (uri:fragment parsed))))
|
2020-12-13 15:29:04 +01:00
|
|
|
((null (uri:scheme parsed))
|
2020-06-22 13:58:04 +02:00
|
|
|
(strcat +gemini-scheme+ ":"
|
2020-10-25 19:58:05 +01:00
|
|
|
(to-s (uri:normalize-path parsed))))
|
2020-06-22 13:58:04 +02:00
|
|
|
(t
|
2020-10-25 19:58:05 +01:00
|
|
|
(to-s (uri:normalize-path parsed))))))
|
|
|
|
|
2020-12-25 15:03:39 +01:00
|
|
|
(defun make-gemini-iri (host path &key
|
|
|
|
(query nil)
|
|
|
|
(port +gemini-default-port+)
|
2021-03-27 09:19:13 +01:00
|
|
|
(fragment nil)
|
|
|
|
(scheme +gemini-scheme+))
|
2020-10-25 19:58:05 +01:00
|
|
|
(let* ((actual-path (if (string-starts-with-p "/" path)
|
|
|
|
(subseq path 1)
|
|
|
|
path))
|
|
|
|
(actual-port (if port
|
|
|
|
(to-s port)
|
|
|
|
(to-s +gemini-default-port+)))
|
2021-03-27 09:19:13 +01:00
|
|
|
(iri (strcat scheme "://"
|
2020-10-25 19:58:05 +01:00
|
|
|
host ":"
|
|
|
|
actual-port "/"
|
|
|
|
actual-path)))
|
|
|
|
(when query
|
2020-12-17 13:56:07 +01:00
|
|
|
(setf iri (strcat iri "?" query)))
|
2020-12-25 15:03:39 +01:00
|
|
|
(when fragment
|
|
|
|
(setf iri (strcat iri "#" fragment)))
|
2020-12-17 13:56:07 +01:00
|
|
|
iri))
|
2020-06-22 13:58:04 +02:00
|
|
|
|
2021-03-27 09:19:13 +01:00
|
|
|
(defun sexp->links (parsed-gemini original-host original-port original-path
|
|
|
|
&key (comes-from-local-file nil))
|
|
|
|
(loop
|
|
|
|
for node in parsed-gemini
|
|
|
|
when (html-utils:tag= :a node)
|
|
|
|
collect
|
|
|
|
(let* ((link-value (html-utils:node->link node))
|
|
|
|
(absolute-p (iri:absolute-url-p link-value))
|
|
|
|
(rendered-link (cond
|
|
|
|
(absolute-p
|
|
|
|
link-value)
|
|
|
|
(comes-from-local-file
|
|
|
|
(strcat original-path
|
|
|
|
iri:+segment-separator+
|
|
|
|
link-value))
|
|
|
|
(t
|
|
|
|
(absolutize-link link-value
|
|
|
|
original-host
|
|
|
|
original-port
|
|
|
|
original-path)))))
|
|
|
|
(make-instance 'gemini-link
|
|
|
|
:target rendered-link
|
|
|
|
:name (tag-value node)))))
|
2020-06-19 11:40:20 +02:00
|
|
|
|
2020-12-17 13:56:07 +01:00
|
|
|
(defun gemini-link-iri-p (iri)
|
2020-07-08 18:34:48 +02:00
|
|
|
(conditions:with-default-on-error (nil)
|
2020-12-17 13:56:07 +01:00
|
|
|
(or (text-utils:string-starts-with-p +gemini-scheme+ iri)
|
|
|
|
(null (uri:scheme (iri:iri-parse iri))))))
|
2020-07-08 18:34:48 +02:00
|
|
|
|
|
|
|
(defclass gemini-page-theme ()
|
|
|
|
((link-prefix-gemini
|
|
|
|
:initarg :link-prefix-gemini
|
|
|
|
:initform "-> "
|
|
|
|
:accessor link-prefix-gemini)
|
|
|
|
(link-prefix-other
|
|
|
|
:initarg :link-prefix-other
|
|
|
|
:initform "^ "
|
|
|
|
:accessor link-prefix-other)
|
|
|
|
(h1-prefix
|
|
|
|
:initarg :h1-prefix
|
|
|
|
:initform "+ "
|
|
|
|
:accessor h1-prefix)
|
|
|
|
(h2-prefix
|
|
|
|
:initarg :h2-prefix
|
|
|
|
:initform "+ "
|
|
|
|
:accessor h2-prefix)
|
|
|
|
(h3-prefix
|
|
|
|
:initarg :h3-prefix
|
|
|
|
:initform "+ "
|
|
|
|
:accessor h3-prefix)
|
|
|
|
(quote-prefix
|
|
|
|
:initarg :quote-prefix
|
2021-04-05 12:01:30 +02:00
|
|
|
:initform +quote-prefix+
|
2020-07-08 18:34:48 +02:00
|
|
|
:accessor quote-prefix)
|
|
|
|
(bullet-prefix
|
|
|
|
:initarg :bullet-prefix
|
2021-04-05 12:01:30 +02:00
|
|
|
:initform "@ "
|
2021-04-03 15:03:24 +02:00
|
|
|
:accessor bullet-prefix)
|
2021-04-05 12:01:30 +02:00
|
|
|
(preformatted-fg
|
|
|
|
:initarg :preformatted-fg
|
|
|
|
:initform :red
|
|
|
|
:accessor preformatted-fg)
|
2021-04-03 15:03:24 +02:00
|
|
|
(viewport
|
|
|
|
:initarg :viewport
|
|
|
|
:initform nil
|
|
|
|
:accessor viewport)))
|
2020-07-08 18:34:48 +02:00
|
|
|
|
2021-04-08 16:32:34 +02:00
|
|
|
(defclass with-group-id ()
|
|
|
|
((group-id
|
|
|
|
:initform nil
|
|
|
|
:initarg :group-id
|
|
|
|
:accessor group-id)))
|
|
|
|
|
|
|
|
(defclass with-lines ()
|
|
|
|
((lines
|
|
|
|
:initform ()
|
|
|
|
:initarg :lines
|
|
|
|
:accessor lines)))
|
|
|
|
|
|
|
|
(defclass with-alt-text ()
|
2021-04-05 12:01:30 +02:00
|
|
|
((alt-text
|
|
|
|
:initform nil
|
|
|
|
:initarg :alt-text
|
|
|
|
:accessor alt-text)))
|
|
|
|
|
2021-04-08 16:32:34 +02:00
|
|
|
(defclass pre-start (with-group-id with-alt-text) ())
|
|
|
|
|
|
|
|
(defmethod print-object ((object pre-start) stream)
|
|
|
|
(print-unreadable-object (object stream :type t :identity t)
|
|
|
|
(format stream "gid: ~a alt ~a" (group-id object) (alt-text object))))
|
|
|
|
|
|
|
|
(defun make-pre-start (alt-text group-id)
|
|
|
|
(make-instance 'pre-start :alt-text alt-text :group-id group-id))
|
2021-04-05 12:01:30 +02:00
|
|
|
|
|
|
|
(defclass pre-end () ())
|
|
|
|
|
|
|
|
(defun make-pre-end ()
|
|
|
|
(make-instance 'pre-end))
|
|
|
|
|
2021-04-08 16:32:34 +02:00
|
|
|
(defclass quoted-lines (with-lines) ())
|
2021-04-05 14:47:57 +02:00
|
|
|
|
|
|
|
(defun make-quoted-lines (text-lines)
|
|
|
|
(make-instance 'quoted-lines
|
|
|
|
:lines (split-lines text-lines)))
|
|
|
|
|
2021-04-08 16:32:34 +02:00
|
|
|
(defclass pre-line (with-group-id with-lines with-alt-text) ())
|
|
|
|
|
|
|
|
(defmethod print-object ((object pre-line) stream)
|
|
|
|
(print-unreadable-object (object stream :type t)
|
|
|
|
(format stream
|
|
|
|
"gid: ~a alt ~a lines ~a"
|
|
|
|
(group-id object)
|
|
|
|
(alt-text object)
|
|
|
|
(lines object))))
|
|
|
|
|
|
|
|
(defun make-pre-line (lines group-id alt-text)
|
|
|
|
(make-instance 'pre-line
|
|
|
|
:lines lines
|
|
|
|
:group-id group-id
|
|
|
|
:alt-text alt-text))
|
|
|
|
|
2021-04-10 13:52:56 +02:00
|
|
|
(defclass vertical-space ()
|
|
|
|
((size
|
|
|
|
:initform 1
|
|
|
|
:initarg :size
|
|
|
|
:accessor size)))
|
|
|
|
|
2021-04-13 17:01:55 +02:00
|
|
|
(defclass header-line (with-group-id with-lines)
|
|
|
|
((level
|
|
|
|
:initform nil
|
|
|
|
:initarg :level
|
|
|
|
:accessor level)))
|
|
|
|
|
|
|
|
(defun make-header-line (text gid level)
|
|
|
|
(make-instance 'header-line
|
|
|
|
:lines (list text)
|
|
|
|
:group-id gid
|
|
|
|
:level level))
|
|
|
|
|
2021-04-13 17:29:25 +02:00
|
|
|
(defclass unordered-list-line (with-lines) ())
|
|
|
|
|
|
|
|
(defun make-unordered-list-line (text)
|
|
|
|
(make-instance 'unordered-list-line
|
|
|
|
:lines (list text)))
|
|
|
|
|
|
|
|
(defclass link-line (with-lines)
|
|
|
|
((link-name
|
|
|
|
:initarg :link-name
|
|
|
|
:initform nil
|
|
|
|
:accessor link-name)
|
|
|
|
(link-value
|
|
|
|
:initarg :link-value
|
|
|
|
:initform nil
|
|
|
|
:accessor link-value)))
|
|
|
|
|
|
|
|
(defun make-link-line (text link-name link-value)
|
|
|
|
(make-instance 'link-line
|
|
|
|
:lines (list text)
|
|
|
|
:link-name link-name
|
|
|
|
:link-value link-value))
|
|
|
|
|
2021-04-05 12:01:30 +02:00
|
|
|
(defun sexp->text-rows (parsed-gemini theme)
|
2021-04-13 17:01:55 +02:00
|
|
|
(let ((win-width (message-window:viewport-width (viewport theme)))
|
|
|
|
(pre-group-id -1)
|
|
|
|
(header-group-id -1)
|
2021-04-13 17:29:25 +02:00
|
|
|
(pre-alt-text ""))
|
2021-04-13 17:01:55 +02:00
|
|
|
(labels ((header-prefix (prefix header)
|
|
|
|
(strcat prefix header))
|
|
|
|
(header-prefix-h1 (header)
|
|
|
|
(header-prefix (h1-prefix theme) header))
|
|
|
|
(header-prefix-h2 (header)
|
|
|
|
(header-prefix (h2-prefix theme) header))
|
|
|
|
(header-prefix-h3 (header)
|
|
|
|
(header-prefix (h3-prefix theme) header))
|
|
|
|
(build-underline (text underline-char)
|
|
|
|
(let* ((size (length text))
|
|
|
|
(underline (build-string size underline-char)))
|
|
|
|
underline))
|
|
|
|
(make-header (level text underline-char)
|
|
|
|
(let ((underline (build-underline text underline-char)))
|
|
|
|
(incf header-group-id)
|
|
|
|
(list (make-header-line text header-group-id level)
|
|
|
|
(make-header-line underline header-group-id level))))
|
|
|
|
(trim (a)
|
|
|
|
(trim-blanks a))
|
|
|
|
(text-value (node &key (trim t))
|
|
|
|
(let ((text (first (html-utils:children node))))
|
|
|
|
(if trim
|
|
|
|
(trim text)
|
|
|
|
text)))
|
|
|
|
(linkify (link-name link-value)
|
|
|
|
(if (gemini-link-iri-p link-value)
|
|
|
|
(format nil "~a~a~%" (link-prefix-gemini theme) link-name)
|
|
|
|
(format nil "~a~a~%" (link-prefix-other theme) link-name)))
|
|
|
|
(fit-quote-lines (line win-width)
|
|
|
|
(let* ((justified (flush-left-mono-text (split-words line)
|
|
|
|
(- win-width
|
|
|
|
(length (quote-prefix theme)))))
|
|
|
|
(lines (mapcar (lambda (a) (strcat (quote-prefix theme) a))
|
|
|
|
justified)))
|
|
|
|
(make-quoted-lines (join-with-strings lines (format nil "~%")))))
|
|
|
|
(pre-alt-text (node)
|
|
|
|
(trim (html-utils:attribute-value (html-utils:find-attribute :alt node))))
|
|
|
|
(build-rows ()
|
|
|
|
(loop for node in parsed-gemini
|
|
|
|
collect
|
|
|
|
(cond
|
|
|
|
((null node)
|
|
|
|
(make-instance 'vertical-space)) ;(format nil "~%"))
|
2021-04-13 17:29:25 +02:00
|
|
|
((html-utils:tag= :as-is node)
|
|
|
|
(let* ((truncated-line (safe-subseq (text-value node) 0 (1- win-width)))
|
|
|
|
(fg (preformatted-fg theme))
|
|
|
|
(line (tui:make-tui-string (format nil "~a" truncated-line)
|
|
|
|
:fgcolor fg)))
|
|
|
|
(make-pre-line (list line) pre-group-id pre-alt-text)))
|
|
|
|
((html-utils:tag= :text node)
|
|
|
|
(format nil "~a~%" (text-value node)))
|
|
|
|
((html-utils:tag= :h1 node)
|
|
|
|
(make-header 1
|
|
|
|
(header-prefix-h1 (text-value node))
|
|
|
|
+h1-underline+))
|
|
|
|
((html-utils:tag= :h2 node)
|
|
|
|
(make-header 2
|
|
|
|
(header-prefix-h2 (text-value node))
|
|
|
|
+h2-underline+))
|
|
|
|
((html-utils:tag= :h3 node)
|
|
|
|
(make-header 3
|
|
|
|
(header-prefix-h3 (text-value node))
|
|
|
|
+h3-underline+))
|
|
|
|
((html-utils:tag= :li node)
|
|
|
|
(let ((text (format nil
|
|
|
|
"~a ~a"
|
|
|
|
(bullet-prefix theme)
|
|
|
|
(text-value node))))
|
|
|
|
(make-unordered-list-line text)))
|
|
|
|
((html-utils:tag= :quote node)
|
|
|
|
(fit-quote-lines (text-value node :trim nil)
|
|
|
|
win-width))
|
|
|
|
((html-utils:tag= :pre node)
|
|
|
|
(let ((current-alt-text (pre-alt-text node)))
|
|
|
|
(incf pre-group-id)
|
|
|
|
(setf pre-alt-text current-alt-text)
|
|
|
|
(make-pre-start current-alt-text pre-group-id)))
|
|
|
|
((html-utils:tag= :pre-end node)
|
|
|
|
(make-pre-end))
|
|
|
|
((html-utils:tag= :a node)
|
|
|
|
(let* ((link-name (text-value node :trim nil))
|
|
|
|
(link-value (html-utils:attribute-value
|
|
|
|
(html-utils:find-attribute :href
|
|
|
|
node)))
|
|
|
|
(link-text (if link-name
|
|
|
|
(linkify link-name link-value)
|
|
|
|
(linkify link-value link-value))))
|
|
|
|
(make-link-line link-text link-name link-value)))))))
|
2021-04-13 17:01:55 +02:00
|
|
|
(flatten (build-rows)))))
|
2021-04-05 12:01:30 +02:00
|
|
|
|
2020-07-08 18:34:48 +02:00
|
|
|
(defun sexp->text (parsed-gemini theme)
|
|
|
|
(labels ((header-prefix (prefix header)
|
|
|
|
(strcat prefix header))
|
|
|
|
(header-prefix-h1 (header)
|
|
|
|
(header-prefix (h1-prefix theme) header))
|
|
|
|
(header-prefix-h2 (header)
|
2021-04-03 15:03:24 +02:00
|
|
|
(header-prefix (h2-prefix theme) header))
|
2020-07-08 18:34:48 +02:00
|
|
|
(header-prefix-h3 (header)
|
2021-04-03 15:03:24 +02:00
|
|
|
(header-prefix (h3-prefix theme) header))
|
2020-07-08 18:34:48 +02:00
|
|
|
(underlineize (stream text underline-char)
|
2020-06-14 16:46:28 +02:00
|
|
|
(let* ((size (length text))
|
|
|
|
(underline (build-string size underline-char)))
|
|
|
|
(format stream "~a~%~a~%" text underline)))
|
|
|
|
(trim (a)
|
|
|
|
(string-trim '(#\Newline #\Return) a))
|
|
|
|
(text-value (node &key (trim t))
|
|
|
|
(let ((text (first (html-utils:children node))))
|
|
|
|
(if trim
|
|
|
|
(trim text)
|
2020-07-08 18:34:48 +02:00
|
|
|
text)))
|
|
|
|
(linkify (link-name link-value)
|
2020-12-17 13:56:07 +01:00
|
|
|
(if (gemini-link-iri-p link-value)
|
2020-07-08 18:34:48 +02:00
|
|
|
(format nil "~a~a~%" (link-prefix-gemini theme) link-name)
|
2021-04-03 15:03:24 +02:00
|
|
|
(format nil "~a~a~%" (link-prefix-other theme) link-name)))
|
|
|
|
(fit-quote-lines (line win-width)
|
|
|
|
(join-with-strings (mapcar (lambda (a) (strcat (quote-prefix theme) a))
|
|
|
|
(flush-left-mono-text (split-words line)
|
|
|
|
(- win-width
|
|
|
|
(length (quote-prefix theme)))))
|
|
|
|
(format nil "~%"))))
|
|
|
|
(let ((win-width (message-window:viewport-width (viewport theme))))
|
|
|
|
(with-output-to-string (stream)
|
|
|
|
(loop for node in parsed-gemini do
|
|
|
|
(cond
|
|
|
|
((null node)
|
|
|
|
(format stream "~%"))
|
|
|
|
((html-utils:tag= :as-is node)
|
2021-04-05 12:01:30 +02:00
|
|
|
(let ((truncated-line (safe-subseq (text-value node) 0 win-width)))
|
|
|
|
(format stream "~a~%" truncated-line)))
|
2021-04-03 15:03:24 +02:00
|
|
|
((html-utils:tag= :text node)
|
|
|
|
(format stream "~a~%" (text-value node)))
|
|
|
|
((html-utils:tag= :h1 node)
|
|
|
|
(underlineize stream
|
|
|
|
(header-prefix-h1 (text-value node))
|
|
|
|
+h1-underline+))
|
|
|
|
((html-utils:tag= :h2 node)
|
|
|
|
(underlineize stream
|
|
|
|
(header-prefix-h2 (text-value node))
|
|
|
|
+h2-underline+))
|
|
|
|
((html-utils:tag= :h3 node)
|
|
|
|
(underlineize stream
|
|
|
|
(header-prefix-h3 (text-value node))
|
|
|
|
+h3-underline+))
|
|
|
|
((html-utils:tag= :li node)
|
|
|
|
(format stream
|
|
|
|
"~a ~a~%"
|
|
|
|
(bullet-prefix theme)
|
|
|
|
(text-value node)))
|
|
|
|
((html-utils:tag= :quote node)
|
|
|
|
(write-sequence (fit-quote-lines (text-value node :trim nil)
|
|
|
|
win-width)
|
|
|
|
stream))
|
2021-04-05 12:01:30 +02:00
|
|
|
;; ((html-utils:tag= :pre node)
|
|
|
|
;; (write-sequence (text-value node :trim nil) stream))
|
2021-04-03 15:03:24 +02:00
|
|
|
((html-utils:tag= :a node)
|
|
|
|
(let ((link-name (text-value node :trim nil))
|
|
|
|
(link-value (html-utils:attribute-value (html-utils:find-attribute :href
|
|
|
|
node))))
|
|
|
|
(if link-name
|
|
|
|
(write-string (linkify link-name link-value) stream)
|
|
|
|
(write-string (linkify link-value link-value) stream))))))))))
|
2020-06-14 16:46:28 +02:00
|
|
|
|
2020-06-19 11:40:20 +02:00
|
|
|
(defun parse-gemini-file (data)
|
2021-04-05 15:37:59 +02:00
|
|
|
(let* ((lines (if (string= (format nil "~%") data)
|
|
|
|
(list (format nil "~%"))
|
|
|
|
(mapcar (lambda (a)
|
|
|
|
(strcat a (string #\Newline)))
|
|
|
|
(split-lines data)))))
|
|
|
|
(loop for line in lines
|
|
|
|
collect
|
2021-04-08 16:32:34 +02:00
|
|
|
(let ((was-raw-mode *raw-mode-data*)
|
2021-04-05 15:37:59 +02:00
|
|
|
(parsed-line (parse 'gemini-file line :junk-allowed t)))
|
|
|
|
(if was-raw-mode
|
2021-04-08 16:32:34 +02:00
|
|
|
(if *raw-mode-data*
|
|
|
|
(html-utils:make-tag-node :as-is
|
|
|
|
(list (list :alt *raw-mode-data*))
|
|
|
|
line)
|
2021-04-05 15:37:59 +02:00
|
|
|
parsed-line)
|
|
|
|
parsed-line)))))
|
2020-06-19 11:40:20 +02:00
|
|
|
|
|
|
|
;; response header
|
|
|
|
|
|
|
|
(define-constant +max-meta-length+ 1024 :test #'=)
|
|
|
|
|
|
|
|
(defrule response-first-digit (or "1" "2" "3" "4" "5" "6")
|
|
|
|
(:text t))
|
|
|
|
|
|
|
|
(defrule response-second-digit (or "0" "1" "2" "3" "4" "5" "6" "7" "8" "9")
|
|
|
|
(:text t))
|
|
|
|
|
|
|
|
(defrule meta (+ (not carriage-return))
|
|
|
|
(:text t))
|
|
|
|
|
|
|
|
(defclass gemini-response ()
|
|
|
|
((status-code
|
|
|
|
:initform nil
|
|
|
|
:initarg :status-code
|
|
|
|
:accessor status-code)
|
|
|
|
(meta
|
|
|
|
:initarg :meta
|
|
|
|
:accessor meta)))
|
|
|
|
|
|
|
|
(defmethod print-object ((object gemini-response) stream)
|
|
|
|
(print-unreadable-object (object stream :type t :identity t)
|
|
|
|
(with-accessors ((status-code status-code)
|
|
|
|
(meta meta)) object
|
|
|
|
(format stream "status: ~a meta: ~a" status-code meta))))
|
|
|
|
|
|
|
|
(defrule response (and response-first-digit
|
|
|
|
response-second-digit
|
|
|
|
space
|
|
|
|
meta
|
|
|
|
carriage-return
|
|
|
|
new-line)
|
|
|
|
(:function (lambda (a)
|
|
|
|
(make-instance 'gemini-response
|
|
|
|
:status-code (parse-integer (strcat (first a)
|
|
|
|
(second a)))
|
|
|
|
:meta (fourth a)))))
|
|
|
|
|
|
|
|
(defun parse-gemini-response-header (data)
|
|
|
|
(let ((parsed (parse 'response data)))
|
|
|
|
(if (> (length (meta parsed))
|
|
|
|
+max-meta-length+)
|
|
|
|
(error 'conditions:length-error
|
|
|
|
:seq (meta parsed)
|
|
|
|
:text (format nil
|
|
|
|
" is too long. Maximum allowed length is ~a"
|
|
|
|
+max-meta-length+))
|
|
|
|
parsed)))
|
2020-06-28 12:36:59 +02:00
|
|
|
|
2020-12-17 13:56:07 +01:00
|
|
|
(defun gemini-iri-p (maybe-iri)
|
2020-06-28 12:36:59 +02:00
|
|
|
(conditions:with-default-on-error (nil)
|
2020-12-17 13:56:07 +01:00
|
|
|
(let ((parsed (iri:iri-parse maybe-iri)))
|
2020-06-28 12:36:59 +02:00
|
|
|
(and parsed
|
|
|
|
(string-equal +gemini-scheme+
|
2020-12-13 15:29:04 +01:00
|
|
|
(uri:scheme parsed))
|
|
|
|
(uri:host parsed)))))
|