From 16e1c2e03364b20f402be75788b68410efb5b95d Mon Sep 17 00:00:00 2001 From: Frank Duncan Date: Thu, 6 Jan 2022 11:21:05 -0600 Subject: [PATCH 1/5] Initial commit --- .candle | 8 +++ honey.asd | 10 ++++ src/main/package.lisp | 5 ++ src/main/parse.lisp | 126 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 149 insertions(+) create mode 100644 .candle create mode 100644 honey.asd create mode 100644 src/main/package.lisp create mode 100644 src/main/parse.lisp diff --git a/.candle b/.candle new file mode 100644 index 0000000..3e36ab2 --- /dev/null +++ b/.candle @@ -0,0 +1,8 @@ +(:packages :honey :wolf :sheep) +(:name :honey + :tasks + ((:name :wolf :directions + (wolf:pretty-print-check-directory "src")) + (:name :sheep :directions + (sheep:pretty-print-validate-packages :honey)))) +; vim:ft=lisp diff --git a/honey.asd b/honey.asd new file mode 100644 index 0000000..94eacaf --- /dev/null +++ b/honey.asd @@ -0,0 +1,10 @@ +(asdf:defsystem honey + :name "Markdown processor that exports to Weave html forms" + :version "0.0" + :maintainer "Frank Duncan (frank@consxy.com)" + :author "Frank Duncan (frank@consxy.com)" + :serial t + :pathname "src/main" + :components ((:file "package") + (:file "parse")) + :depends-on (:webthiteth :herbie-utility)) diff --git a/src/main/package.lisp b/src/main/package.lisp new file mode 100644 index 0000000..d1c5091 --- /dev/null +++ b/src/main/package.lisp @@ -0,0 +1,5 @@ +(defpackage #:honey (:use :common-lisp :webthiteth-html) (:export #:parse) + (:documentation "Main honey package. + +Honey is a simple markdown parse that translates to weave style html, +which can then be inserted into pages.")) diff --git a/src/main/parse.lisp b/src/main/parse.lisp new file mode 100644 index 0000000..b64cb22 --- /dev/null +++ b/src/main/parse.lisp @@ -0,0 +1,126 @@ +(in-package #:honey) + +(defparameter *line-parsers* nil) +(defparameter *inline-parsers* nil) + +(defun parse (str) + "PARSE STR => HTML + +ARGUMENTS AND VALUES: + + STR: a string, representing the markdown + HTML: a weave style html form + +DESCRIPTION: + + Parses the actual markdown, returing the html. This is the main + function for the honey package." + (let + ((lines (cl-ppcre:split "\\n" str))) + (mapcar + #'parse-texts + (parse-lines (append lines (list "")))))) + +(defun textp (x) (and (listp x) (eql 'text (car x)))) +(defun as-text (x) (list 'text x)) + +(defun parse-lines (lines &optional prev) + (if + (not lines) + (list prev) + (let* + ((line (car lines)) + (parser (getf (cadr (find-if #1'(funcall (getf $1 :checker) line) (reverse *line-parsers*) :key #'cadr)) :parser))) + (when (not parser) (error "Weird! Couldn't find a match for ~A" line)) + (multiple-value-bind (parsed-line squash-prev) (funcall (funcall parser line) prev) + (if squash-prev + (parse-lines (cdr lines) parsed-line) + (cons prev (parse-lines (cdr lines) parsed-line))))))) + +(defun parse-texts (line) + (cond + ((textp line) (parse-inline (cadr line))) + ((listp line) (mapcar #'parse-texts line)) + (t line))) + +(defun parse-inline (str) + (let + ((parser (getf (cadr (find-if #1'(funcall (getf $1 :checker) str) (reverse *inline-parsers*) :key #'cadr)) :parser))) + (if parser (funcall parser str) str))) + +(defmacro defline-parser (regex handler) + (let + ((regex (format nil "^~A$" regex))) + `(progn + (when (not (utils:strassoc ,regex *line-parsers*)) (push (list ,regex nil) *line-parsers*)) + (setf (utils:strassoc ,regex *line-parsers*) + (list + :checker (lambda (str) (cl-ppcre:scan ,regex str)) + :parser (lambda (str) + (apply + (function ,handler) + (mapcar + #'as-text + (coerce (second (multiple-value-list (cl-ppcre:scan-to-strings ,regex str))) 'list))))))))) + +(defmacro definline-parser (regex handler) + (let + ((regex (format nil "^~A$" regex))) + `(progn + (when (not (utils:strassoc ,regex *inline-parsers*)) (push (list ,regex nil) *inline-parsers*)) + (setf (utils:strassoc ,regex *inline-parsers*) + (list + :checker (lambda (str) (cl-ppcre:scan ,regex str)) + :parser (lambda (str) + (apply (function ,handler) + (mapcar + #'parse-inline + (coerce (second (multiple-value-list (cl-ppcre:scan-to-strings ,regex str))) 'list))))))))) + +; each parser function needs to return a function that takes the previous line and returns either +; +; a single element being the new element, and optionally a second values option about whether +; the previous line should be consumed + +(defun prev-h2 () + (lambda (prev) + (if (textp prev) (values (h2 prev) t) (hr)))) + +(defun prev-h1 () + (lambda (prev) + (if (textp prev) (values (h1 prev) t) (hr)))) + +(defun list-item (text) + (lambda (prev) + (let + ((li (li text))) + (if (and prev (listp prev) (eql 'ul (car prev))) + (values (append prev (list li)) t) + (ul li))))) + +(defun emptiness () + (lambda (prev) + (cond + ((textp prev) (values (p prev) t))))) + +(defun default (text) + (lambda (prev) (declare (ignore prev)) text)) + +(defline-parser "-+" prev-h2) +(defline-parser "=+" prev-h1) +(defline-parser " *\\* *(.*)" list-item) +(defline-parser " *" emptiness) +(defline-parser "(.*)" default) + +(defun strength (before during after) + (list before (span :style-font-weight "bold" during) after)) + +(defun emphasis (before during after) + (list before (span :style-font-style "italic" during) after)) + +(defun md-link (before text md-link after) + (list before (a :href md-link (parse-texts text)) after)) + +(definline-parser "(.*)\\[([^\\]]*)\\]\\(([^)]*)\\)(.*)" md-link) +(definline-parser "(.*)\\*\\*(.*)\\*\\*(.*)" strength) +(definline-parser "(.*)\\*(.*)\\*(.*)" emphasis) -- 2.25.1 From c4a9ac2f61b1ec3149ad97bcd373e59a39e4802f Mon Sep 17 00:00:00 2001 From: Frank Duncan Date: Thu, 6 Jan 2022 16:27:08 -0600 Subject: [PATCH 2/5] Add code fences --- src/main/parse.lisp | 58 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 51 insertions(+), 7 deletions(-) diff --git a/src/main/parse.lisp b/src/main/parse.lisp index b64cb22..af3fca9 100644 --- a/src/main/parse.lisp +++ b/src/main/parse.lisp @@ -32,10 +32,23 @@ DESCRIPTION: ((line (car lines)) (parser (getf (cadr (find-if #1'(funcall (getf $1 :checker) line) (reverse *line-parsers*) :key #'cadr)) :parser))) (when (not parser) (error "Weird! Couldn't find a match for ~A" line)) - (multiple-value-bind (parsed-line squash-prev) (funcall (funcall parser line) prev) - (if squash-prev - (parse-lines (cdr lines) parsed-line) - (cons prev (parse-lines (cdr lines) parsed-line))))))) + (multiple-value-bind (parsed-line squash-prev suspension) (funcall (funcall parser line) prev) + (cond + (squash-prev (parse-lines (cdr lines) parsed-line)) + (suspension + (let* + ((regex-to-find (car suspension)) + (function-to-call (cadr suspension)) + (pos + (position-if + (lambda (line) (cl-ppcre:scan regex-to-find line)) + (cdr lines)))) + (append + (list + prev + (funcall function-to-call (subseq (cdr lines) 0 (or pos (length lines))))) + (parse-lines (nthcdr (1+ pos) (cdr lines)))))) + (t (cons prev (parse-lines (cdr lines) parsed-line)))))))) (defun parse-texts (line) (cond @@ -77,10 +90,14 @@ DESCRIPTION: #'parse-inline (coerce (second (multiple-value-list (cl-ppcre:scan-to-strings ,regex str))) 'list))))))))) -; each parser function needs to return a function that takes the previous line and returns either +; each parser function needs to return a function that takes the previous line and returns ; -; a single element being the new element, and optionally a second values option about whether -; the previous line should be consumed +; the values: +; the new element +; whether the previous line should be consumed (optional) +; an optional list with two items, which suspends parsing until regex is found +; - the regex to end the suspension +; - the function to call with the list of lines (defun prev-h2 () (lambda (prev) @@ -90,6 +107,12 @@ DESCRIPTION: (lambda (prev) (if (textp prev) (values (h1 prev) t) (hr)))) +(defun inline-h1 (text) (lambda (prev) (h1 text))) +(defun inline-h2 (text) (lambda (prev) (h2 text))) +(defun inline-h3 (text) (lambda (prev) (h3 text))) +(defun inline-h4 (text) (lambda (prev) (h4 text))) +(defun inline-h5 (text) (lambda (prev) (h5 text))) + (defun list-item (text) (lambda (prev) (let @@ -106,8 +129,29 @@ DESCRIPTION: (defun default (text) (lambda (prev) (declare (ignore prev)) text)) +(defun codefence (codetype) + (lambda (prev) + (declare (ignore prev)) + (values + nil + nil + (list + "^```$" + (lambda (lines) + (pre + (code + (format nil "~{~A~%~}" lines)))))))) + (defline-parser "-+" prev-h2) (defline-parser "=+" prev-h1) + +; These need to be in reverse order so they match correctly +(defline-parser "##### *(.*)" inline-h5) +(defline-parser "#### *(.*)" inline-h4) +(defline-parser "### *(.*)" inline-h3) +(defline-parser "## *(.*)" inline-h2) +(defline-parser "# *(.*)" inline-h1) +(defline-parser "```(.*)" codefence) (defline-parser " *\\* *(.*)" list-item) (defline-parser " *" emptiness) (defline-parser "(.*)" default) -- 2.25.1 From 00c7e5bc45caa890553519610a96a0159be8e179 Mon Sep 17 00:00:00 2001 From: Frank Duncan Date: Mon, 10 Jan 2022 14:33:14 -0600 Subject: [PATCH 3/5] Remove dependency in herbie-utility --- honey.asd | 2 +- src/main/parse.lisp | 56 +++++++++++++++++++++++++++------------------ 2 files changed, 35 insertions(+), 23 deletions(-) diff --git a/honey.asd b/honey.asd index 94eacaf..ec78242 100644 --- a/honey.asd +++ b/honey.asd @@ -7,4 +7,4 @@ :pathname "src/main" :components ((:file "package") (:file "parse")) - :depends-on (:webthiteth :herbie-utility)) + :depends-on (:webthiteth)) diff --git a/src/main/parse.lisp b/src/main/parse.lisp index af3fca9..74745bc 100644 --- a/src/main/parse.lisp +++ b/src/main/parse.lisp @@ -30,10 +30,13 @@ DESCRIPTION: (list prev) (let* ((line (car lines)) - (parser (getf (cadr (find-if #1'(funcall (getf $1 :checker) line) (reverse *line-parsers*) :key #'cadr)) :parser))) + (parser + (getf + (cadr (find-if (lambda (parser) (funcall (getf parser :checker) line)) (reverse *line-parsers*) :key #'cadr)) + :parser))) (when (not parser) (error "Weird! Couldn't find a match for ~A" line)) (multiple-value-bind (parsed-line squash-prev suspension) (funcall (funcall parser line) prev) - (cond + (cond (squash-prev (parse-lines (cdr lines) parsed-line)) (suspension (let* @@ -58,37 +61,46 @@ DESCRIPTION: (defun parse-inline (str) (let - ((parser (getf (cadr (find-if #1'(funcall (getf $1 :checker) str) (reverse *inline-parsers*) :key #'cadr)) :parser))) + ((parser + (getf + (cadr (find-if (lambda (parser) (funcall (getf parser :checker) str)) (reverse *inline-parsers*) :key #'cadr)) + :parser))) (if parser (funcall parser str) str))) (defmacro defline-parser (regex handler) (let ((regex (format nil "^~A$" regex))) `(progn - (when (not (utils:strassoc ,regex *line-parsers*)) (push (list ,regex nil) *line-parsers*)) - (setf (utils:strassoc ,regex *line-parsers*) - (list - :checker (lambda (str) (cl-ppcre:scan ,regex str)) - :parser (lambda (str) - (apply - (function ,handler) - (mapcar - #'as-text - (coerce (second (multiple-value-list (cl-ppcre:scan-to-strings ,regex str))) 'list))))))))) + (when (not (find ,regex *line-parsers* :key #'car :test #'string=)) (push (list ,regex nil) *line-parsers*)) + (let + ((parser (find ,regex *line-parsers* :key #'car :test #'string=))) + (setf + (cadr parser) + (list + :checker (lambda (str) (cl-ppcre:scan ,regex str)) + :parser (lambda (str) + (apply + (function ,handler) + (mapcar + #'as-text + (coerce (second (multiple-value-list (cl-ppcre:scan-to-strings ,regex str))) 'list)))))))))) (defmacro definline-parser (regex handler) (let ((regex (format nil "^~A$" regex))) `(progn - (when (not (utils:strassoc ,regex *inline-parsers*)) (push (list ,regex nil) *inline-parsers*)) - (setf (utils:strassoc ,regex *inline-parsers*) - (list - :checker (lambda (str) (cl-ppcre:scan ,regex str)) - :parser (lambda (str) - (apply (function ,handler) - (mapcar - #'parse-inline - (coerce (second (multiple-value-list (cl-ppcre:scan-to-strings ,regex str))) 'list))))))))) + (when (not (find ,regex *inline-parsers* :key #'car :test #'string=)) (push (list ,regex nil) *inline-parsers*)) + (let + ((parser (find ,regex *inline-parsers* :key #'car :test #'string=))) + (setf + (cadr parser) + (list + :checker (lambda (str) (cl-ppcre:scan ,regex str)) + :parser (lambda (str) + (apply (function ,handler) + (mapcar + #'parse-inline + (coerce (second (multiple-value-list (cl-ppcre:scan-to-strings ,regex str))) 'list)))))))))) ; each parser function needs to return a function that takes the previous line and returns ; -- 2.25.1 From 65394a399d32af71f028dc680df1b48af87c0215 Mon Sep 17 00:00:00 2001 From: Frank Duncan Date: Mon, 10 Jan 2022 14:34:58 -0600 Subject: [PATCH 4/5] Add codefence, underscore emphasis --- src/main/parse.lisp | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/main/parse.lisp b/src/main/parse.lisp index 74745bc..61f37e8 100644 --- a/src/main/parse.lisp +++ b/src/main/parse.lisp @@ -49,8 +49,8 @@ DESCRIPTION: (append (list prev - (funcall function-to-call (subseq (cdr lines) 0 (or pos (length lines))))) - (parse-lines (nthcdr (1+ pos) (cdr lines)))))) + (funcall function-to-call (subseq (cdr lines) 0 (or pos (1- (length lines)))))) + (parse-lines (nthcdr (if pos (1+ pos) (length lines)) (cdr lines)))))) (t (cons prev (parse-lines (cdr lines) parsed-line)))))))) (defun parse-texts (line) @@ -163,7 +163,17 @@ DESCRIPTION: (defline-parser "### *(.*)" inline-h3) (defline-parser "## *(.*)" inline-h2) (defline-parser "# *(.*)" inline-h1) + +; Ignore codefence +(defline-parser "(```.*```.*)" default) + (defline-parser "```(.*)" codefence) + +; If we start with a space after the asterisk, we really do want a list +(defline-parser " *\\* (.*)" list-item) +; Ignore list-tiem +(defline-parser "(\\*\\*.*\\*\\*.*)" default) +(defline-parser "(\\*.*\\*.*)" default) (defline-parser " *\\* *(.*)" list-item) (defline-parser " *" emptiness) (defline-parser "(.*)" default) @@ -177,6 +187,11 @@ DESCRIPTION: (defun md-link (before text md-link after) (list before (a :href md-link (parse-texts text)) after)) +(defun inline-code (before during after) + (list before (code during) after)) + (definline-parser "(.*)\\[([^\\]]*)\\]\\(([^)]*)\\)(.*)" md-link) (definline-parser "(.*)\\*\\*(.*)\\*\\*(.*)" strength) (definline-parser "(.*)\\*(.*)\\*(.*)" emphasis) +(definline-parser "(.*)_(.*)_(.*)" emphasis) +(definline-parser "(.*)```(.*)```(.*)" inline-code) -- 2.25.1 From 8cc10854903eef97a9c82a0e48c3ea791fb787e6 Mon Sep 17 00:00:00 2001 From: Frank Duncan Date: Mon, 10 Jan 2022 14:44:15 -0600 Subject: [PATCH 5/5] Add documentation, scripts for releasing 0.1 --- README.md | 22 ++++++++++++++++++++++ bin/buildRelease.sh | 13 +++++++++++++ bin/generatedocs.sh | 8 ++++++++ docs/Reference.md | 24 ++++++++++++++++++++++++ honey.asd | 2 +- 5 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 README.md create mode 100755 bin/buildRelease.sh create mode 100755 bin/generatedocs.sh create mode 100644 docs/Reference.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..b9116db --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +# Honey - a simple markdown processor + +While it aims to target everything listed in the +[Github Flavored Markdown Spec](https://github.github.com/gfm/), it currently +does just a little bit of markdown that is actively used for consxy projects. + +It parser markdown into weave-html style objects which can then be turned into +strings using the weave library. + +Right now it supports a basic version of: + +* lists of one indentation +* headings of either ATX or underline style +* links +* codefences +* emphasis and strength + +## Usage + +See [the reference page](https://consxy.com/honey/reference.html) for more information, +but in general there is a ```honey:parse``` method that takes a string representing +the markdown. diff --git a/bin/buildRelease.sh b/bin/buildRelease.sh new file mode 100755 index 0000000..897d733 --- /dev/null +++ b/bin/buildRelease.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +version=$(sbcl --noinform --disable-ldb --lose-on-corruption --end-runtime-options --eval '(format t "~A" (asdf:component-version (asdf:find-system :honey)))' --eval "(quit)") + +echo -n "Building version $version, hit enter to continue" +read + +mkdir honey_$version +cp -ap src/main/* honey_$version/ +tar zcf honey_${version}.tar.gz honey_$version/ +rm -rf honey_$version + +echo "All done, it's in honey_${version}.tar.gz, you should tag it, push it up to consxy, and update the consxy releases" diff --git a/bin/generatedocs.sh b/bin/generatedocs.sh new file mode 100755 index 0000000..58585a2 --- /dev/null +++ b/bin/generatedocs.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +sbcl \ + --eval "(asdf:load-system :sheep)" \ + --eval "(asdf:load-system :honey)" \ + --eval "(format t \"----~%\")" \ + --eval "(format t \"~A\" (sheep:export-package :honey))" \ + --eval "(quit)" 2> /dev/null | sed -n '/^----$/,$p' | tail -n +2 > docs/Reference.md diff --git a/docs/Reference.md b/docs/Reference.md new file mode 100644 index 0000000..9781067 --- /dev/null +++ b/docs/Reference.md @@ -0,0 +1,24 @@ +# Package HONEY + +Main honey package. + +Honey is a simple markdown parse that translates to weave style html, which can then be inserted into pages. + +## Contents + +* **function [parse](#function-parse)** - Parses the actual markdown, returing the html. This is the main function for the honey package. + +## Function **PARSE** + +#### Syntax: + +**parse** _str_ => _html_ + +#### Arguments and Values: + +_str_---a string, representing the markdown +_html_---a weave style html form + +#### Description: + +Parses the actual markdown, returing the html. This is the main function for the honey package. diff --git a/honey.asd b/honey.asd index ec78242..7075c8f 100644 --- a/honey.asd +++ b/honey.asd @@ -1,6 +1,6 @@ (asdf:defsystem honey :name "Markdown processor that exports to Weave html forms" - :version "0.0" + :version "0.1" :maintainer "Frank Duncan (frank@consxy.com)" :author "Frank Duncan (frank@consxy.com)" :serial t -- 2.25.1