├── website ├── source │ ├── resources │ │ ├── navigation.md │ │ ├── ug-navigation.md │ │ ├── ug-footer.md │ │ ├── ug-header.md │ │ ├── header.md │ │ └── footer.md │ ├── style.css │ ├── user-guide.css │ ├── index.md │ └── user-guide.md └── website.tmproj ├── unit-tests ├── markdown-tests │ ├── Nested blockquotes.text │ ├── Tidyness.text │ ├── Blockquotes with code blocks.text │ ├── Strong and em together.text │ ├── Literal quotes in titles.text │ ├── Inline HTML (Advanced).text │ ├── Links, inline style.text │ ├── Inline HTML comments.text │ ├── bullets-and-numbers-1.text │ ├── Hard-wrapped paragraphs with list-like lines.text │ ├── Auto links.text │ ├── Tabs.text │ ├── Links, reference style.text │ ├── Amps and angle encoding.text │ ├── Horizontal rules.text │ ├── Inline HTML (Simple).text │ ├── Backslash escapes.text │ ├── Ordered and unordered lists.text │ ├── style.css │ └── Markdown Documentation - Basics.text ├── development-tests │ ├── paragraphs-1.text │ └── paragraphs-2.text ├── test-strippers.lisp ├── test-anchors.lisp ├── test-headers.lisp ├── brackets-with-empty-lines.lisp ├── test-extensions.lisp ├── framework.lisp ├── utilities.lisp ├── package.lisp ├── test-utilities.lisp ├── test-regexes.lisp ├── test-brackets-and-includes.lisp ├── test-dl.lisp ├── test-links.lisp ├── test-spans.lisp ├── test-markdown.lisp ├── test-snippets.lisp ├── comparison.lisp └── test-chunkers.lisp ├── dev ├── epilogue.lisp ├── notes.text ├── definitions.lisp ├── package.lisp ├── api.lisp ├── plain.lisp ├── macros.lisp ├── class-defs.lisp ├── footnotes.lisp ├── extension-mechanisms.lisp ├── dead-code │ └── lml2.lisp ├── multiple-documents.lisp ├── extensions.lisp ├── regexes.lisp ├── spans.lisp └── reports.lisp ├── .gitignore ├── resources └── markdown-report-styles.css ├── .boring ├── lift-standard.config ├── cl-markdown-comparisons.asd ├── cl-markdown-test.asd ├── COPYING └── cl-markdown.asd /website/source/resources/navigation.md: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /unit-tests/markdown-tests/Nested blockquotes.text: -------------------------------------------------------------------------------- 1 | > foo 2 | > 3 | > > bar 4 | > 5 | > foo 6 | -------------------------------------------------------------------------------- /website/source/resources/ug-navigation.md: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /unit-tests/markdown-tests/Tidyness.text: -------------------------------------------------------------------------------- 1 | > A list within a blockquote: 2 | > 3 | > * asterisk 1 4 | > * asterisk 2 5 | > * asterisk 3 6 | -------------------------------------------------------------------------------- /unit-tests/development-tests/paragraphs-1.text: -------------------------------------------------------------------------------- 1 | Hello there 2 | my name is bob 3 | there is bunny over here 4 | ======== 5 | 6 | What not 7 | -------------------------------------------------------------------------------- /unit-tests/markdown-tests/Blockquotes with code blocks.text: -------------------------------------------------------------------------------- 1 | > Example: 2 | > 3 | > sub status { 4 | > print "working"; 5 | > } 6 | > 7 | -------------------------------------------------------------------------------- /dev/epilogue.lisp: -------------------------------------------------------------------------------- 1 | ;;; the last file to be loaded... 2 | 3 | (in-package #:cl-markdown) 4 | 5 | (setf *parsing-environment* (make-instance 'parsing-environment)) 6 | -------------------------------------------------------------------------------- /unit-tests/markdown-tests/Strong and em together.text: -------------------------------------------------------------------------------- 1 | ***This is strong and em.*** 2 | 3 | So is ***this*** word. 4 | 5 | ___This is strong and em.___ 6 | 7 | So is ___this___ word. 8 | -------------------------------------------------------------------------------- /unit-tests/markdown-tests/Literal quotes in titles.text: -------------------------------------------------------------------------------- 1 | Foo [bar][]. 2 | 3 | Foo [bar](/url/ "Title with "quotes" inside"). 4 | 5 | 6 | [bar]: /url/ "Title with "quotes" inside" 7 | 8 | -------------------------------------------------------------------------------- /unit-tests/development-tests/paragraphs-2.text: -------------------------------------------------------------------------------- 1 | this is 2 | paragraph number one. 3 | 4 | this is paragraph number two. 5 | 6 | 7 | 8 | 9 | and this 10 | is 11 | paragraph number 12 | three. -------------------------------------------------------------------------------- /unit-tests/markdown-tests/Inline HTML (Advanced).text: -------------------------------------------------------------------------------- 1 | Simple block on one line: 2 | 3 |
foo
4 | 5 | And nested without indentation: 6 | 7 |
8 |
9 |
10 | foo 11 |
12 |
13 |
bar
14 |
15 | -------------------------------------------------------------------------------- /unit-tests/markdown-tests/Links, inline style.text: -------------------------------------------------------------------------------- 1 | Just a [URL](/url/). 2 | 3 | [URL and title](/url/ "title"). 4 | 5 | [URL and title](/url/ "title preceded by two spaces"). 6 | 7 | [URL and title](/url/ "title preceded by a tab"). 8 | 9 | [Empty](). 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # really this is private to my build process 2 | make/ 3 | common-lisp.net 4 | .vcs 5 | GNUmakefile 6 | init-lisp.lisp 7 | project-init.lisp 8 | 9 | log5.tar.gz 10 | website/output/ 11 | test-results/ 12 | lift-local.config 13 | *.dribble 14 | *.fasl 15 | -------------------------------------------------------------------------------- /unit-tests/markdown-tests/Inline HTML comments.text: -------------------------------------------------------------------------------- 1 | Paragraph one. 2 | 3 | 4 | 5 | 8 | 9 | Paragraph two. 10 | 11 | 12 | 13 | The end. 14 | -------------------------------------------------------------------------------- /unit-tests/markdown-tests/bullets-and-numbers-1.text: -------------------------------------------------------------------------------- 1 | First **paragraph** 2 | 3 | Second 4 | 5 | Third 6 | 7 | * bullet 1 8 | 9 | 1. Is __it__ true? 10 | 11 | * what it [is][why] 12 | 13 | Last paragraph 14 | 15 | 16 | [why]: http://www.butter.com/ 17 | 18 | 19 | -------------------------------------------------------------------------------- /unit-tests/markdown-tests/Hard-wrapped paragraphs with list-like lines.text: -------------------------------------------------------------------------------- 1 | In Markdown 1.0.0 and earlier. Version 2 | 8. This line turns into a list item. 3 | Because a hard-wrapped line in the 4 | middle of a paragraph looked like a 5 | list item. 6 | 7 | Here's one with a bullet. 8 | * criminey. 9 | -------------------------------------------------------------------------------- /website/source/resources/ug-footer.md: -------------------------------------------------------------------------------- 1 | 11 | -------------------------------------------------------------------------------- /unit-tests/markdown-tests/Auto links.text: -------------------------------------------------------------------------------- 1 | Link: . 2 | 3 | With an ampersand: 4 | 5 | * In a list? 6 | * 7 | * It should. 8 | 9 | > Blockquoted: 10 | 11 | Auto-links should not occur here: `` 12 | 13 | or here: -------------------------------------------------------------------------------- /unit-tests/markdown-tests/Tabs.text: -------------------------------------------------------------------------------- 1 | + this is a list item 2 | indented with tabs 3 | 4 | + this is a list item 5 | indented with spaces 6 | 7 | Code: 8 | 9 | this code block is indented by one tab 10 | 11 | And: 12 | 13 | this code block is indented by two tabs 14 | 15 | And: 16 | 17 | + this is an example list item 18 | indented with tabs 19 | 20 | + this is an example list item 21 | indented with spaces 22 | -------------------------------------------------------------------------------- /unit-tests/markdown-tests/Links, reference style.text: -------------------------------------------------------------------------------- 1 | Foo [bar] [1]. 2 | 3 | Foo [bar][1]. 4 | 5 | Foo [bar] 6 | [1]. 7 | 8 | [1]: /url/ "Title" 9 | 10 | 11 | With [embedded [brackets]] [b]. 12 | 13 | 14 | Indented [once][]. 15 | 16 | Indented [twice][]. 17 | 18 | Indented [thrice][]. 19 | 20 | Indented [four][] times. 21 | 22 | [once]: /url 23 | 24 | [twice]: /url 25 | 26 | [thrice]: /url 27 | 28 | [four]: /url 29 | 30 | 31 | [b]: /url/ 32 | -------------------------------------------------------------------------------- /website/source/resources/ug-header.md: -------------------------------------------------------------------------------- 1 | {set-property html yes} 2 | {set-property style-sheet user-guide} 3 | 4 | [darcs]: http://www.darcs.net/ 5 | [asdf-install]: http://common-lisp.net/project/asdf-install 6 | [tarball]: http://common-lisp.net/project/cl-markdown/cl-markdown_latest.tar.gz 7 | [gwking]: http://www.metabang.com/ 8 | [cl-markdown-cliki]: http://www.cliki.net/cl-markdown 9 | [user-guide]: user-guide.html 10 | 11 | 14 | -------------------------------------------------------------------------------- /unit-tests/markdown-tests/Amps and angle encoding.text: -------------------------------------------------------------------------------- 1 | AT&T has an ampersand in their name. 2 | 3 | AT&T is another way to write it. 4 | 5 | This & that. 6 | 7 | 4 < 5. 8 | 9 | 6 > 5. 10 | 11 | Here's a [link] [1] with an ampersand in the URL. 12 | 13 | Here's a link with an amersand in the link text: [AT&T] [2]. 14 | 15 | Here's an inline [link](/script?foo=1&bar=2). 16 | 17 | Here's an inline [link](). 18 | 19 | 20 | [1]: http://example.com/?foo=1&bar=2 21 | [2]: http://att.com/ "AT&T" -------------------------------------------------------------------------------- /unit-tests/markdown-tests/Horizontal rules.text: -------------------------------------------------------------------------------- 1 | Dashes: 2 | 3 | --- 4 | 5 | --- 6 | 7 | --- 8 | 9 | --- 10 | 11 | --- 12 | 13 | - - - 14 | 15 | - - - 16 | 17 | - - - 18 | 19 | - - - 20 | 21 | - - - 22 | 23 | 24 | Asterisks: 25 | 26 | *** 27 | 28 | *** 29 | 30 | *** 31 | 32 | *** 33 | 34 | *** 35 | 36 | * * * 37 | 38 | * * * 39 | 40 | * * * 41 | 42 | * * * 43 | 44 | * * * 45 | 46 | 47 | Underscores: 48 | 49 | ___ 50 | 51 | ___ 52 | 53 | ___ 54 | 55 | ___ 56 | 57 | ___ 58 | 59 | _ _ _ 60 | 61 | _ _ _ 62 | 63 | _ _ _ 64 | 65 | _ _ _ 66 | 67 | _ _ _ 68 | -------------------------------------------------------------------------------- /unit-tests/test-strippers.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:cl-markdown-test) 2 | 3 | (deftestsuite test-strippers () 4 | ()) 5 | 6 | (deftestsuite test-one-tab-stripper (test-strippers) 7 | () 8 | (:equality-test 'samep)) 9 | 10 | (addtest 11 | test-spaces-1 12 | (ensure-same (one-tab-stripper " hello") (values "hello" t))) 13 | 14 | (addtest 15 | test-tabs-1 16 | (ensure-same (one-tab-stripper (concatenate 'string (string #\Tab) "hello")) 17 | (values "hello" t))) 18 | 19 | (addtest 20 | test-tabs-2 21 | (ensure-same (one-tab-stripper (concatenate 'string (string #\Tab) " hello")) 22 | (values " hello" t))) 23 | 24 | (addtest 25 | test-spaces-2 26 | (ensure-same (one-tab-stripper " hello") (values " hello" nil))) -------------------------------------------------------------------------------- /resources/markdown-report-styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Georgia, "Times New Roman", Times, serif; 3 | margin-right: 0.5in; 4 | margin-left: 0.5in; 5 | margin-bottom: 0.25px; 6 | } 7 | 8 | .markdown-report { 9 | margin-left: 4em; 10 | } 11 | 12 | #main-content { 13 | float: left; 14 | width: 80%; 15 | margin-bottom: 2em; 16 | line-height: 1.4; 17 | font-size: 80%; 18 | } 19 | 20 | #footer { 21 | margin-top: 2em; 22 | margin-bottom: 2em; 23 | padding-top: 0.25em; 24 | border-top-style: inset; 25 | border-top-width: 3px; 26 | clear: both; 27 | width: 80%; 28 | } 29 | 30 | div.markdown-report h1,h2 { 31 | margin-left: -2em; 32 | clear: both; 33 | margin-bottom: 0.5em; 34 | } 35 | 36 | .markdown-report span { 37 | float: left; 38 | display: block; 39 | width: 2.5in; 40 | } -------------------------------------------------------------------------------- /.boring: -------------------------------------------------------------------------------- 1 | # Boring file regexps: 2 | \.hi$ 3 | \.o$ 4 | \.o\.cmd$ 5 | # *.ko files aren't boring by default because they might 6 | # be Korean translations rather than kernel modules. 7 | # \.ko$ 8 | \.ko\.cmd$ 9 | \.mod\.c$ 10 | (^|/)\.tmp_versions($|/) 11 | (^|/)CVS($|/) 12 | (^|/)RCS($|/) 13 | ~$ 14 | #(^|/)\.[^/] 15 | (^|/)_darcs($|/) 16 | \.bak$ 17 | \.BAK$ 18 | \.orig$ 19 | (^|/)vssver\.scc$ 20 | \.swp$ 21 | (^|/)MT($|/) 22 | (^|/)\{arch\}($|/) 23 | (^|/).arch-ids($|/) 24 | (^|/), 25 | \.class$ 26 | \.prof$ 27 | (^|/)\.DS_Store$ 28 | (^|/)BitKeeper($|/) 29 | (^|/)ChangeSet($|/) 30 | (^|/)\.svn($|/) 31 | \.py[co]$ 32 | \# 33 | \.cvsignore$ 34 | (^|/)Thumbs\.db$ 35 | (^|/)autom4te\.cache($|/) 36 | (^|/)scratch($|/) 37 | (^|/)two words($|/) 38 | (^|/)test-results($|/) 39 | \.dribble 40 | (^|/)make($|/) 41 | -------------------------------------------------------------------------------- /website/source/resources/header.md: -------------------------------------------------------------------------------- 1 | {include shared-links.md} 2 | 3 | {set-property html yes} 4 | {set-property style-sheet "http://common-lisp.net/project/cl-containers/shared/style.css"} 5 | {set-property author "Gary Warren King"} 6 | 7 | [devel-list]: http://common-lisp.net/cgi-bin/mailman/listinfo/cl-markdown-devel 8 | [cliki-home]: http://www.cliki.net/cl-markdown 9 | [tarball]: http://common-lisp.net/project/cl-markdown/cl-markdown.tar.gz 10 | 11 |
12 | 13 | 14 | ## CL-Markdown 15 | 16 | #### Finally, text mucking fun the whole family can enjoy 17 | 18 |
19 | -------------------------------------------------------------------------------- /unit-tests/test-anchors.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:cl-markdown-test) 2 | 3 | (deftestsuite test-anchors (test-snippets) 4 | () 5 | (:dynamic-variables 6 | (cl-markdown::*default-properties* 7 | '((:omit-initial-paragraph t) 8 | (:omit-final-paragraph t))))) 9 | 10 | (addtest (test-anchors) 11 | just-a-name 12 | (check-html-output 13 | "@(foo)" 14 | "")) 15 | 16 | (addtest (test-anchors) 17 | name-with-text 18 | (check-html-output 19 | "@[bar](foo)" 20 | "bar")) 21 | 22 | #| 23 | (markdown 24 | " 25 | hi 26 | @(foo) 27 | @[bar](foo) 28 | " :format :html) 29 | 30 | (markdown "hi @(foo)" :stream nil) 31 | (shell-tidy "hi ") 32 | (markdown "@(foo)" 33 | :properties '((:omit-initial-paragraph t) 34 | (:omit-final-paragraph t))) 35 | (markdown "@[bar](foo)") 36 | |# -------------------------------------------------------------------------------- /unit-tests/markdown-tests/Inline HTML (Simple).text: -------------------------------------------------------------------------------- 1 | Here's a simple block: 2 | 3 |
4 | foo 5 |
6 | 7 | This should be a code block, though: 8 | 9 |
10 | foo 11 |
12 | 13 | As should this: 14 | 15 |
foo
16 | 17 | Now, nested: 18 | 19 |
20 |
21 |
22 | foo 23 |
24 |
25 |
26 | 27 | This should just be an HTML comment: 28 | 29 | 30 | 31 | Multiline: 32 | 33 | 37 | 38 | Code block: 39 | 40 | 41 | 42 | Just plain comment, with trailing spaces on the line: 43 | 44 | 45 | 46 | Code: 47 | 48 |
49 | 50 | Hr's: 51 | 52 |
53 | 54 |
55 | 56 |
57 | 58 |
59 | 60 |
61 | 62 |
63 | 64 |
65 | 66 |
67 | 68 |
69 | 70 | -------------------------------------------------------------------------------- /lift-standard.config: -------------------------------------------------------------------------------- 1 | ;;; configuration for LIFT tests 2 | 3 | ;; settings 4 | (:if-dribble-exists :supersede) 5 | (:dribble "lift.dribble") 6 | (:print-length 10) 7 | (:print-level 5) 8 | (:print-test-case-names t) 9 | 10 | ;; suites to run 11 | (cl-markdown-test) 12 | 13 | ;; report properties 14 | (:report-property :title "CL-Markdown | Test results") 15 | (:report-property :relative-to cl-markdown-test) 16 | 17 | 18 | 19 | (:report-property :style-sheet "test-style.css") 20 | (:report-property :if-exists :supersede) 21 | (:report-property :format :html) 22 | (:report-property :full-pathname "test-results/test-report") 23 | (:report-property :unique-name t) 24 | (:build-report) 25 | 26 | (:report-property :format :save) 27 | (:report-property :full-pathname "test-results/test-report.sav") 28 | (:build-report) 29 | 30 | (:report-property :format :describe) 31 | (:report-property :full-pathname *standard-output*) 32 | (:build-report) 33 | -------------------------------------------------------------------------------- /cl-markdown-comparisons.asd: -------------------------------------------------------------------------------- 1 | (in-package #:common-lisp-user) 2 | (defpackage #:cl-markdown-test-system (:use #:cl #:asdf)) 3 | (in-package #:cl-markdown-test-system) 4 | 5 | (defsystem cl-markdown-comparisons 6 | :version "0.1" 7 | :author "Gary Warren King " 8 | :maintainer "Gary Warren King " 9 | :licence "MIT Style License" 10 | :components ((:module "unit-tests" 11 | :components ((:file "package") 12 | (:file "framework" 13 | :depends-on ("package")) 14 | (:file "comparison" 15 | :depends-on ("framework")) 16 | ))) 17 | :depends-on (:cl-markdown :lml2 18 | :cl-html-diff :html-encode :trivial-shell 19 | ;; probably not needed if we rearranged more... 20 | :lift)) 21 | 22 | -------------------------------------------------------------------------------- /unit-tests/test-headers.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:cl-markdown-test) 2 | 3 | #+(or) 4 | (run-tests :suite 'test-headers) 5 | 6 | (deftestsuite test-headers (test-snippets) 7 | () 8 | (:documentation "Case 272")) 9 | 10 | (addtest (test-headers) 11 | one-dash 12 | (check-output "asdf 13 | -")) 14 | 15 | (addtest (test-headers) 16 | two-dash 17 | (check-output "asdf 18 | --")) 19 | 20 | (addtest (test-headers) 21 | three-dash 22 | (check-output "asdf 23 | ---")) 24 | 25 | (addtest (test-headers) 26 | three-dash-with-whitespace 27 | (check-output "asdf 28 | --- ")) 29 | 30 | (addtest (test-headers) 31 | four-dash 32 | (check-output "asdf 33 | ----")) 34 | 35 | (addtest (test-headers) 36 | five-dash 37 | (check-output "asdf 38 | -----")) 39 | 40 | (addtest (test-headers) 41 | six-dash 42 | (check-output "asdf 43 | ------")) 44 | 45 | (addtest (test-headers) 46 | starts-with-dashes 47 | (check-output "asdf 48 | -- it's the bomb")) 49 | 50 | (addtest (test-headers) 51 | really-an-hr 52 | (check-output " 53 | ---")) 54 | -------------------------------------------------------------------------------- /cl-markdown-test.asd: -------------------------------------------------------------------------------- 1 | ;;; -*- Mode: Lisp; package: CL-USER; Syntax: Common-lisp; Base: 10 -*- 2 | 3 | (in-package #:common-lisp-user) 4 | (defpackage #:cl-markdown-test-system (:use #:cl #:asdf)) 5 | (in-package #:cl-markdown-test-system) 6 | 7 | (defsystem cl-markdown-test 8 | :author "Gary Warren King " 9 | :maintainer "Gary Warren King " 10 | :licence "MIT Style License" 11 | :components ((:module 12 | "setup" 13 | :pathname "unit-tests/" 14 | :components 15 | ((:file "package") 16 | (:file "utilities" 17 | :depends-on ("package")) 18 | (:file "test-markdown" 19 | :depends-on ("package")))) 20 | (:module 21 | "unit-tests" 22 | :depends-on ("setup") 23 | :components ((:file "test-chunkers") 24 | (:file "test-snippets") 25 | (:file "test-links") 26 | (:file "test-brackets-and-includes") 27 | (:file "brackets-with-empty-lines") 28 | (:file "test-headers") 29 | (:file "test-dl") 30 | (:file "test-anchors")))) 31 | :depends-on (:cl-markdown 32 | :lift 33 | :trivial-shell)) 34 | 35 | 36 | -------------------------------------------------------------------------------- /dev/notes.text: -------------------------------------------------------------------------------- 1 | ### Notes 2 | 3 | - We go from md to document to html 4 | 5 | We can't really :check (:compare) or :remove unless we 6 | have a way to go from html to document or html to md. 7 | 8 | - How to handle multi-line responses. 9 | 10 | from doctest (documentation)[http://docs.python.org/lib/doctest-finding-examples.html] 11 | 12 | > Any expected output must immediately follow the final '>>> ' or '... ' line containing the code, and the expected output (if any) extends to the next '>>> ' or all-whitespace line. 13 | 14 | (there are lots of other good ideas in this document). 15 | 16 | Well known properties 17 | 18 | :search-locations 19 | :title 20 | :style-sheet 21 | :docs-package 22 | html 23 | style-sheets 24 | author 25 | docs-space-entity 26 | 27 | (document-property :xmlns "http://www.w3.org/1999/xhtml") 28 | (document-property :xmllang "en") 29 | (document-property :lang "en")) 30 | (document-property :header-comment) 31 | 32 | 33 | 34 | ### Problems 35 | 36 | This causes an error 37 | 38 | 41 | 42 | {today} causing an error in footnote 43 | 44 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 2006 - 2007 Gary Warren King (gwking@metabang.com) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a 4 | copy of this software and associated documentation files (the "Software"), 5 | to deal in the Software without restriction, including without limitation 6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | and/or sell copies of the Software, and to permit persons to whom the 8 | Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 16 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | DEALINGS IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /dev/definitions.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:cl-markdown) 2 | 3 | (defparameter *spaces-per-tab* 4) 4 | (defparameter *parsing-environment* nil) 5 | (defparameter *chunk-parsing-environments* 6 | (make-container 'simple-associative-container)) 7 | (defparameter *spanner-parsing-environments* 8 | (make-container 'simple-associative-container :test #'equal)) 9 | (defparameter *horizontal-rule-count-threshold* 3) 10 | 11 | 12 | (defparameter *default-stream* *standard-output*) 13 | (defparameter *default-format* :html) 14 | 15 | (defvar *output-stream* nil) 16 | (defvar *current-indentation-level* 0) 17 | 18 | (defparameter *current-document* nil) 19 | 20 | (defparameter *current-chunk* nil) 21 | 22 | (defparameter *current-format* nil) 23 | 24 | (defparameter *render-active-functions* 25 | '(table-of-contents property set-property anchor footnote footnotes 26 | today now include include-if comment)) 27 | 28 | (defparameter *parse-active-functions* 29 | '(table-of-contents property set-property anchor footnote footnotes 30 | include include-if comment)) 31 | 32 | (defparameter *block-level-html-tags* 33 | '(address blockquote div fieldset 34 | h1 h2 h3 h4 h5 h6 35 | hr legend p pre ul ol li dl dd)) 36 | 37 | (defparameter *default-properties* nil) -------------------------------------------------------------------------------- /unit-tests/brackets-with-empty-lines.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:cl-markdown-test) 2 | 3 | (deftestsuite brackets-with-empty-lines (test-bracket-processing) 4 | ()) 5 | 6 | (addtest (brackets-with-empty-lines) 7 | linefeed-in-bracket 8 | (ensure (search "guide for test 3.0" 9 | (nth-value 1 10 | (markdown "{set-property test \"3.0\"} 11 | 12 | This is the guide for test {property 13 | test}. It rocks." :stream nil)) 14 | :test 'char=))) 15 | 16 | (addtest (brackets-with-empty-lines) 17 | no-linefeeds 18 | (ensure (search "footnoteBacklink" 19 | (nth-value 1 (markdown " 20 | Hi there 21 | this is a footnote{footnote \"Actualy, this is\"}. Nice. 22 | 23 | {footnotes}" :stream nil)) :test #'char=))) 24 | 25 | (addtest (brackets-with-empty-lines) 26 | one-linefeed 27 | (ensure (search "footnoteBacklink" 28 | (nth-value 1 (markdown " 29 | Hi there 30 | this is a footnote{footnote \"Actualy, 31 | this is\"}. Nice. 32 | 33 | {footnotes}" :stream nil)) :test #'char=))) 34 | 35 | 36 | (addtest (brackets-with-empty-lines) 37 | two-linefeeds 38 | (ensure (search "footnoteBacklink" 39 | (nth-value 1 (markdown " 40 | Hi there 41 | this is a footnote{footnote \"Actualy, 42 | 43 | this is\"}. Nice. 44 | 45 | {footnotes}" :stream nil)) :test #'char=))) 46 | -------------------------------------------------------------------------------- /unit-tests/test-extensions.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:cl-markdown-test) 2 | 3 | (deftestsuite test-extensions-basic (cl-markdown-test) 4 | ()) 5 | 6 | (deftestsuite test-properties (test-extensions-basic) 7 | ()) 8 | 9 | (addtest (test-properties) 10 | basic-get/set 11 | (ensure-same 12 | (cl-markdown::strip-whitespace 13 | (strip-html 14 | (nth-value 1 15 | (markdown "{set-property _my-prop_ \"hello there\"} 16 | I say '{property _my-prop_}'." :stream nil)))) 17 | "I say 'hello there'.")) 18 | 19 | (addtest (test-properties) 20 | set/get-embedded-markdown 21 | (let ((markdown (nth-value 1 22 | (markdown "{set-property version \"**3.1**\"} 23 | I say '{property version}'." :stream nil)))) 24 | (ensure (search "" markdown :test #'char=)) 25 | (ensure-same 26 | (cl-markdown::strip-whitespace 27 | (strip-html markdown)) 28 | "I say '3.1'."))) 29 | 30 | ;; fails because the embedded {} aren't properly parsed 31 | (addtest (test-properties) 32 | set/get-embedded-property 33 | (ensure-same 34 | (cl-markdown::strip-whitespace 35 | (strip-html 36 | (nth-value 1 37 | (markdown "{set-property version \"3.1\"} 38 | {set-property version-name \"version-{version}\"} 39 | I say '{property version-name}'." :stream nil)))) 40 | "I say 'version-3.1'.")) 41 | 42 | 43 | -------------------------------------------------------------------------------- /unit-tests/framework.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:cl-markdown-test) 2 | 3 | ;;; from ASDF-Install 4 | 5 | #-:digitool 6 | (defun system-namestring (pathname) 7 | (namestring (truename pathname))) 8 | 9 | #+:digitool 10 | (defvar *start-up-volume* 11 | (second (pathname-directory (truename "ccl:")))) 12 | 13 | #+:digitool 14 | (defun system-namestring (pathname) 15 | ;; this tries to adjust the root directory to eliminate the spurious 16 | ;; volume name for the boot file system; it also avoids use of 17 | ;; TRUENAME as some applications are for not yet existent files 18 | (let ((truename (probe-file pathname))) 19 | (unless truename 20 | (setf truename 21 | (translate-logical-pathname 22 | (merge-pathnames pathname *default-pathname-defaults*)))) 23 | (let ((directory (pathname-directory truename))) 24 | (flet ((string-or-nil (value) (when (stringp value) value)) 25 | (absolute-p (directory) (eq (first directory) :absolute)) 26 | (root-volume-p (directory) 27 | (equal *start-up-volume* (second directory)))) 28 | (format nil "~:[~;/~]~{~a/~}~@[~a~]~@[.~a~]" 29 | (absolute-p directory) 30 | (if (root-volume-p directory) (cddr directory) (cdr directory)) 31 | (string-or-nil (pathname-name truename)) 32 | (string-or-nil (pathname-type truename))))))) 33 | 34 | -------------------------------------------------------------------------------- /unit-tests/markdown-tests/Backslash escapes.text: -------------------------------------------------------------------------------- 1 | These should all get escaped: 2 | 3 | Backslash: \\ 4 | 5 | Backtick: \` 6 | 7 | Asterisk: \* 8 | 9 | Underscore: \_ 10 | 11 | Left brace: \{ 12 | 13 | Right brace: \} 14 | 15 | Left bracket: \[ 16 | 17 | Right bracket: \] 18 | 19 | Left paren: \( 20 | 21 | Right paren: \) 22 | 23 | Greater-than: \> 24 | 25 | Hash: \# 26 | 27 | Period: \. 28 | 29 | Bang: \! 30 | 31 | Plus: \+ 32 | 33 | Minus: \- 34 | 35 | 36 | 37 | These should not, because they occur within a code block: 38 | 39 | Backslash: \\ 40 | 41 | Backtick: \` 42 | 43 | Asterisk: \* 44 | 45 | Underscore: \_ 46 | 47 | Left brace: \{ 48 | 49 | Right brace: \} 50 | 51 | Left bracket: \[ 52 | 53 | Right bracket: \] 54 | 55 | Left paren: \( 56 | 57 | Right paren: \) 58 | 59 | Greater-than: \> 60 | 61 | Hash: \# 62 | 63 | Period: \. 64 | 65 | Bang: \! 66 | 67 | Plus: \+ 68 | 69 | Minus: \- 70 | 71 | 72 | Nor should these, which occur in code spans: 73 | 74 | Backslash: `\\` 75 | 76 | Backtick: `` \` `` 77 | 78 | Asterisk: `\*` 79 | 80 | Underscore: `\_` 81 | 82 | Left brace: `\{` 83 | 84 | Right brace: `\}` 85 | 86 | Left bracket: `\[` 87 | 88 | Right bracket: `\]` 89 | 90 | Left paren: `\(` 91 | 92 | Right paren: `\)` 93 | 94 | Greater-than: `\>` 95 | 96 | Hash: `\#` 97 | 98 | Period: `\.` 99 | 100 | Bang: `\!` 101 | 102 | Plus: `\+` 103 | 104 | Minus: `\-` 105 | -------------------------------------------------------------------------------- /unit-tests/markdown-tests/Ordered and unordered lists.text: -------------------------------------------------------------------------------- 1 | ## Unordered 2 | 3 | Asterisks tight: 4 | 5 | * asterisk 1 6 | * asterisk 2 7 | * asterisk 3 8 | 9 | 10 | Asterisks loose: 11 | 12 | * asterisk 1 13 | 14 | * asterisk 2 15 | 16 | * asterisk 3 17 | 18 | * * * 19 | 20 | Pluses tight: 21 | 22 | + Plus 1 23 | + Plus 2 24 | + Plus 3 25 | 26 | 27 | Pluses loose: 28 | 29 | + Plus 1 30 | 31 | + Plus 2 32 | 33 | + Plus 3 34 | 35 | * * * 36 | 37 | 38 | Minuses tight: 39 | 40 | - Minus 1 41 | - Minus 2 42 | - Minus 3 43 | 44 | 45 | Minuses loose: 46 | 47 | - Minus 1 48 | 49 | - Minus 2 50 | 51 | - Minus 3 52 | 53 | 54 | ## Ordered 55 | 56 | Tight: 57 | 58 | 1. First 59 | 2. Second 60 | 3. Third 61 | 62 | and: 63 | 64 | 1. One 65 | 2. Two 66 | 3. Three 67 | 68 | 69 | Loose using tabs: 70 | 71 | 1. First 72 | 73 | 2. Second 74 | 75 | 3. Third 76 | 77 | and using spaces: 78 | 79 | 1. One 80 | 81 | 2. Two 82 | 83 | 3. Three 84 | 85 | Multiple paragraphs: 86 | 87 | 1. Item 1, graf one. 88 | 89 | Item 2. graf two. The quick brown fox jumped over the lazy dog's 90 | back. 91 | 92 | 2. Item 2. 93 | 94 | 3. Item 3. 95 | 96 | 97 | 98 | ## Nested 99 | 100 | * Tab 101 | * Tab 102 | * Tab 103 | 104 | Here's another: 105 | 106 | 1. First 107 | 2. Second: 108 | * Fee 109 | * Fie 110 | * Foe 111 | 3. Third 112 | 113 | Same thing but with paragraphs: 114 | 115 | 1. First 116 | 117 | 2. Second: 118 | * Fee 119 | * Fie 120 | * Foe 121 | 122 | 3. Third 123 | -------------------------------------------------------------------------------- /website/source/resources/footer.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /unit-tests/markdown-tests/style.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | body { 4 | margin: 2em; 5 | background: #000033; 6 | } 7 | 8 | #contents { 9 | margin: 002em; 10 | background: #9999cc; 11 | padding: 1em; 12 | border: 3px outset #9999cc; 13 | } 14 | 15 | h1 { 16 | font-size: 120%; 17 | } 18 | 19 | #footer { 20 | clear: both; 21 | padding-top: 2em; 22 | text-align: left; 23 | font-style: italic; 24 | } 25 | 26 | .section-contents { 27 | margin-left: 2em; 28 | background: #ccccff; 29 | padding: 0.5em; 30 | } 31 | 32 | .diff { 33 | 34 | } 35 | 36 | ins { 37 | color: green; 38 | } 39 | 40 | del { 41 | color: red; 42 | text-decoration: line-through; 43 | } 44 | 45 | #cl-markdown-output { 46 | float: left; 47 | width: 50%; 48 | overflow: auto; 49 | } 50 | 51 | #notes { 52 | font-size: 80%; 53 | clear: both; 54 | padding-top: 1em; 55 | padding-right: 1em; 56 | padding-left: 1em; 57 | } 58 | 59 | #markdown-output { 60 | float: left; 61 | overflow: auto; 62 | width: 50%; 63 | } 64 | 65 | #diff-output { 66 | float: left; 67 | clear: both; 68 | overflow: auto; 69 | } 70 | 71 | #cl-markdown-html { 72 | float: left; 73 | overflow: auto; 74 | } 75 | 76 | #original-source { 77 | float: left; 78 | clear: both; 79 | overflow: auto; 80 | } 81 | 82 | .index-entry { 83 | float: left; 84 | clear: both; 85 | text-decoration: none; 86 | line-height: 21pt; 87 | } 88 | 89 | .index-entry a { 90 | text-decoration: none; 91 | } 92 | 93 | .error a { 94 | color: red; 95 | } 96 | 97 | .error { 98 | color: red; 99 | } 100 | 101 | .good a { 102 | color: #330099; 103 | } 104 | 105 | .good { 106 | color: #330099; 107 | } 108 | 109 | -------------------------------------------------------------------------------- /dev/package.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:common-lisp-user) 2 | 3 | (defpackage #:cl-markdown 4 | (:use #:common-lisp #:metatilities #:cl-containers #:cl-ppcre 5 | #:metabang-bind #:anaphora) 6 | (:export 7 | #:handle-spans 8 | #:markdown 9 | #:markdown-many 10 | #:render-to-stream 11 | #:*current-document* 12 | #:*output-stream* 13 | #:document-property) 14 | (:nicknames #:markdown) 15 | (:export 16 | #:*render-active-functions* 17 | #:*parse-active-functions* 18 | #:anchor 19 | #:table-of-contents 20 | #:property 21 | #:set-property 22 | #:render 23 | #:render-documentation) 24 | ;; handy (?) regular expressions 25 | (:export 26 | #:emphasis-1 #:emphasis-2 27 | #:strong-1 #:strong-2 28 | #:backtick 29 | #:auto-link #:auto-mail 30 | #:html #:entity 31 | #:hostname-char #:hostname 32 | #:pathname-char #:url-pathname 33 | #:url #:url-no-registers 34 | #:bracketed #:link+title 35 | #:reference-link #:inline-link #:link-label) 36 | (:export 37 | #:footnote 38 | #:footnotes 39 | #:find-documentation 40 | #:add-documentation-strategy 41 | #:defextension 42 | #:defsimple-extension) 43 | (:export 44 | ;; ugh 45 | #:phase 46 | #:args 47 | #:result 48 | #:*current-format* 49 | ;; for docudown::remove-non-html-entities 50 | #:+first-name-characters+ 51 | #:+name-characters+)) 52 | 53 | (defpackage #:cl-markdown-user 54 | (:use #:common-lisp #:metatilities #:cl-markdown) 55 | (:import-from #:cl-markdown 56 | #:footnote 57 | #:footnotes 58 | #:defextension 59 | #:defsimple-extension 60 | ) 61 | (:export 62 | #:footnote 63 | #:footnotes 64 | #:find-documentation 65 | #:add-documentation-strategy 66 | #:defextension 67 | #:defsimple-extension 68 | )) 69 | 70 | -------------------------------------------------------------------------------- /unit-tests/utilities.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:cl-markdown-test) 2 | 3 | (defun strip-html (string) 4 | (with-output-to-string (out) 5 | (flet ((emit (ch) 6 | (write-char ch out))) 7 | (let ((quote? nil) 8 | (bracket? nil) 9 | (index 0)) 10 | (loop while (< index (length string)) do 11 | (let ((ch (aref string index))) 12 | (cond ((char= ch #\\) 13 | (emit ch) 14 | (emit (aref string (incf index)))) 15 | ((char= ch #\") 16 | (setf quote? (not quote?)) 17 | (emit ch)) 18 | ((and (not quote?) (char= ch #\<)) 19 | (setf bracket? t)) 20 | ((and bracket? (char= ch #\>)) 21 | (setf bracket? nil)) 22 | (bracket? 23 | ;; skip it 24 | ) 25 | (t 26 | (emit ch))) 27 | (incf index))))))) 28 | 29 | (defun compare-line-by-line (a b &key (key 'identity) (test 'string=)) 30 | (setf key (coerce key 'function)) 31 | (setf test (coerce test 'function)) 32 | (let ((ia (make-iterator a :treat-contents-as :lines)) 33 | (ib (make-iterator b :treat-contents-as :lines))) 34 | (map-containers 35 | (lambda (la lb) 36 | (unless (funcall test (funcall key la) (funcall key lb)) 37 | (return-from compare-line-by-line nil))) 38 | ia ib) 39 | (and (null (move-forward-p ia)) (null (move-forward-p ib))))) 40 | 41 | #+(or) 42 | (defun compare-line-by-line (a b &key (key 'identity) (test 'string=)) 43 | (setf key (coerce key 'function)) 44 | (setf test (coerce test 'function)) 45 | (with-input-from-string (sa a) 46 | (with-input-from-string (sb b) 47 | (loop for la = (read-line sa nil nil) 48 | for lb = (read-line sb nil nil) 49 | when (and (not la) (not lb)) do (return t) 50 | when (or (not la) (not lb) 51 | (not (funcall test (funcall key la) (funcall key lb)))) do 52 | (return nil) 53 | finally (return t))))) 54 | 55 | #+(or) 56 | (compare-line-by-line 57 | "a 58 | b 59 | c" 60 | "a 61 | b 62 | c" 63 | ) 64 | -------------------------------------------------------------------------------- /dev/api.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:cl-markdown) 2 | 3 | 4 | (defgeneric reset (thing) 5 | ) 6 | 7 | 8 | (defgeneric (setf document-property) (value name)) 9 | 10 | (defgeneric render-to-stream (document style stream-specifier) 11 | ) 12 | 13 | (defgeneric main-parent (document) 14 | ) 15 | 16 | (defgeneric handle-spans (document) 17 | ) 18 | 19 | (defgeneric scan-one-span (what scanner-name scanner scanners) 20 | ) 21 | 22 | (defgeneric process-span-in-span-p (sub-span current-span) 23 | ) 24 | 25 | (defgeneric unconvert-escapes (what) 26 | ) 27 | 28 | (defgeneric render (document style stream) 29 | ) 30 | 31 | (defgeneric it-starts-with-block-level-html-p (chunk) 32 | ) 33 | 34 | (defgeneric markup-class-mergable-p (what) 35 | ) 36 | 37 | (defgeneric merge-lines-in-chunks (what) 38 | ) 39 | 40 | (defgeneric can-merge-lines-p (first second) 41 | ) 42 | 43 | (defgeneric handle-paragraph-eval-interactions (what) 44 | ) 45 | 46 | (defgeneric encode-html (what encoding-method &rest codes) 47 | ) 48 | 49 | (defgeneric markup-class-for-html (what) 50 | ) 51 | 52 | (defgeneric render-span-to-html (kind body encoding-method) 53 | ) 54 | 55 | (defgeneric generate-link-output (link-info text) 56 | ) 57 | 58 | (defgeneric add-html-header-p (document) 59 | ) 60 | 61 | (defgeneric render-plain (what) 62 | ) 63 | 64 | (defgeneric render-span-plain (kind body) 65 | ) 66 | 67 | (defgeneric process-span-for (kind command args) 68 | ) 69 | 70 | (defgeneric generate-link-output-for-kind (kind link-info text) 71 | ) 72 | 73 | (defgeneric process-span (name registers) 74 | (:documentation "Called during span processing on each match of name in the 75 | document. Registers a list of the registers captured by names regular expression. 76 | Returns a possibly new set of registers.") 77 | (:method ((name t) (registers t)) 78 | (values registers))) 79 | 80 | (defgeneric print-html-markup (markup stream) 81 | ) 82 | -------------------------------------------------------------------------------- /unit-tests/package.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:common-lisp-user) 2 | 3 | (defpackage #:cl-markdown-test 4 | (:use #:common-lisp #:lift #:metatilities #:cl-containers 5 | #:cl-ppcre #:cl-markdown #:trivial-shell 6 | #:metabang-bind) 7 | (:shadowing-import-from #:lift 8 | #:with-timeout) 9 | (:shadowing-import-from #:metatilities 10 | #:copy-file) 11 | (:import-from #:trivial-shell 12 | #:shell-command) 13 | (:import-from #:cl-markdown 14 | #:scan-lines-with-scanners 15 | #:atx-header-markup-class 16 | #:blockquote-stripper 17 | #:chunk-source 18 | #:line-could-be-link-reference-title-p 19 | #:line-indentation 20 | #:line-is-blockquote-p 21 | #:line-is-code-p 22 | #:line-is-empty-p 23 | #:line-is-horizontal-rule-p 24 | #:line-starts-with-bullet-p 25 | #:line-starts-with-number-p 26 | #:it-starts-with-block-level-html-p 27 | #:markdown 28 | #:one-tab-stripper 29 | #:remove-atx-header 30 | #:remove-marker 31 | #:remove-number 32 | #:strippers 33 | #:reset 34 | 35 | #:handle-setext-headers 36 | #:lines 37 | #:indentation 38 | #:maybe-strip-line 39 | #:chunks 40 | #:markup-class 41 | #:paragraph? 42 | #:link-info 43 | #:id 44 | #:properties 45 | #:title 46 | 47 | #:header1 48 | #:header2 49 | #:header3 50 | #:header4 51 | #:header5 52 | #:header6 53 | 54 | #:*spaces-per-tab* 55 | #:*parsing-environment* 56 | 57 | #:system-relative-pathname)) 58 | -------------------------------------------------------------------------------- /website/source/style.css: -------------------------------------------------------------------------------- 1 | pre { padding:5px; background-color:#e0e0e0; 2 | overflow: auto; 3 | } 4 | 5 | body { 6 | background: white; 7 | margin: 2px; 8 | padding-left: 3em; 9 | padding-right: 3em; 10 | } 11 | 12 | .note { 13 | border: 2px inset gray; 14 | padding: 0.5em; 15 | margin-right: 2em; 16 | margin-left: 2em; 17 | } 18 | 19 | #footer { 20 | margin-top: 2em; 21 | border-top-style: inset; 22 | border-top-width: 2px; 23 | } 24 | 25 | #header { 26 | text-align: left; 27 | border-top: 1px none black; 28 | border-collapse: collapse; 29 | border-bottom: 1px dotted black; 30 | margin-bottom: 1em; 31 | } 32 | 33 | #navigation li { 34 | display: inline; 35 | border-right-style: dotted; 36 | border-right-width: 1px; 37 | border-left-style: dotted; 38 | border-left-width: 1px; 39 | border-collapse: collapse; 40 | padding-right: 0.25em; 41 | padding-left: 0.25em; 42 | margin-right: 1em; 43 | } 44 | 45 | #navigation { 46 | text-align: center; 47 | } 48 | 49 | #timestamp { 50 | font-size: 80%; 51 | text-align: right; 52 | } 53 | a.none { text-decoration: none; color:black } 54 | a.none:visited { text-decoration: none; color:black } 55 | a.none:active { text-decoration: none; color:black } 56 | a.none:hover { text-decoration: none; color:black } 57 | a { text-decoration: none; } 58 | a:visited { text-decoration: none; } 59 | a:active { text-decoration: underline; } 60 | a:hover { text-decoration: underline; } 61 | 62 | .note { 63 | 64 | } 65 | 66 | .windows { 67 | 68 | } 69 | 70 | /* @group toc */ 71 | 72 | .table-of-contents { 73 | font-size: 90%; 74 | } 75 | 76 | .table-of-contents h1, h2, h3, h4, h5, h6, h7 { 77 | font-size: inherit; 78 | } 79 | 80 | .table-of-contents h2 { 81 | position: relative; 82 | left: 2em; 83 | } 84 | 85 | .table-of-contents h3 { 86 | position: relative; 87 | left: 4em; 88 | } 89 | 90 | .table-of-contents h4 { 91 | position: relative; 92 | left: 6em; 93 | } 94 | 95 | .table-of-contents h5 { 96 | position: relative; 97 | left: 8px; 98 | } 99 | 100 | /* @end */ 101 | -------------------------------------------------------------------------------- /website/source/user-guide.css: -------------------------------------------------------------------------------- 1 | /* @override file:///Users/gwking/darcs/log5/website/output/style.css */ 2 | 3 | pre { background-color:#e0e0e0; 4 | overflow: auto; 5 | margin-right: 1em; 6 | margin-left: 1em; 7 | padding: 5px; 8 | } 9 | 10 | body { 11 | background: white; 12 | margin: 2px; 13 | padding-left: 6em; 14 | padding-right: 6em; 15 | } 16 | 17 | .note { 18 | border: 2px inset gray; 19 | padding: 0.5em; 20 | margin-right: 2em; 21 | margin-left: 2em; 22 | } 23 | 24 | #footer { 25 | margin-top: 2em; 26 | border-top-style: inset; 27 | border-top-width: 2px; 28 | } 29 | 30 | #header { 31 | text-align: left; 32 | border-top: 1px none black; 33 | border-collapse: collapse; 34 | border-bottom: 1px dotted black; 35 | margin-bottom: 1em; 36 | } 37 | 38 | #navigation li { 39 | display: inline; 40 | border-right-style: dotted; 41 | border-right-width: 1px; 42 | border-left-style: dotted; 43 | border-left-width: 1px; 44 | border-collapse: collapse; 45 | padding-right: 0.25em; 46 | padding-left: 0.25em; 47 | margin-right: 1em; 48 | } 49 | 50 | #navigation { 51 | text-align: center; 52 | } 53 | 54 | #timestamp { 55 | font-size: 80%; 56 | text-align: right; 57 | } 58 | a.none { text-decoration: none; color:black } 59 | a.none:visited { text-decoration: none; color:black } 60 | a.none:active { text-decoration: none; color:black } 61 | a.none:hover { text-decoration: none; color:black } 62 | a { text-decoration: none; } 63 | a:visited { text-decoration: none; } 64 | a:active { text-decoration: underline; } 65 | a:hover { text-decoration: underline; } 66 | 67 | .note { 68 | 69 | } 70 | 71 | .windows { 72 | 73 | } 74 | 75 | /* @group toc */ 76 | 77 | .table-of-contents { 78 | font-size: 90%; 79 | } 80 | 81 | .table-of-contents h1, h2, h3, h4, h5, h6, h7 { 82 | font-size: inherit; 83 | margin-top: -0.5em; 84 | } 85 | 86 | .table-of-contents h2 { 87 | position: relative; 88 | left: 2em; 89 | } 90 | 91 | .table-of-contents h3 { 92 | position: relative; 93 | left: 4em; 94 | } 95 | 96 | .table-of-contents h4 { 97 | position: relative; 98 | left: 6em; 99 | } 100 | 101 | .table-of-contents h5 { 102 | position: relative; 103 | left: 8px; 104 | } 105 | 106 | /* @end */ 107 | -------------------------------------------------------------------------------- /dev/plain.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:cl-markdown) 2 | 3 | #| 4 | (markdown "`test` blue **beans**" :format :plain) 5 | (markdown "Eta 6 | 7 | * beta 8 | * data 9 | 10 | Eta" :format :plain) 11 | |# 12 | 13 | (defmethod render ((document abstract-document) (style (eql :plain)) stream) 14 | (declare (ignore stream)) 15 | (render-plain document)) 16 | 17 | (defmethod render-plain ((document abstract-document)) 18 | (bind ((current-chunk nil)) 19 | (labels ((render-block (block level markup inner?) 20 | (declare (ignore markup)) 21 | (let ((add-markup? (not (eq (first block) current-chunk)))) 22 | (cond ((or (length-1-list-p block)) 23 | (render-plain (first block))) 24 | ((not add-markup?) 25 | (render-plain (first block)) 26 | (do-it (rest block) level)) 27 | (t 28 | (setf current-chunk (and inner? (first block))) 29 | (do-it block level))))) 30 | (do-it (chunks level) 31 | (loop for rest = chunks then (rest rest) 32 | for chunk = (first rest) then (first rest) 33 | while chunk 34 | for new-level = (and chunk (level chunk)) 35 | when (= level new-level) do 36 | (let ((index (inner-block rest)) 37 | (inner-markup (html-inner-block-markup chunk))) 38 | (render-block (subseq rest 0 index) 39 | level inner-markup t) 40 | (setf rest (nthcdr (1- index) rest))) 41 | when (< level new-level) do 42 | (multiple-value-bind (block remaining method) 43 | (next-block rest new-level) 44 | (declare (ignore method)) 45 | (render-block 46 | block new-level (html-block-markup chunk) nil) 47 | (setf rest remaining))))) 48 | (do-it (collect-elements (chunks document)) (level document))))) 49 | 50 | (defmethod render-plain ((chunk chunk)) 51 | (bind ((paragraph? (paragraph? chunk))) 52 | (iterate-elements 53 | (lines chunk) 54 | (lambda (line) 55 | (render-plain line))) 56 | (when paragraph? 57 | (fresh-line *output-stream*)))) 58 | 59 | (defmethod render-plain ((line string)) 60 | (format *output-stream* "~a" line)) 61 | 62 | (defmethod render-plain ((chunk list)) 63 | (render-span-plain (first chunk) (rest chunk))) 64 | 65 | (defmethod render-span-plain ((code t) body) 66 | (format *output-stream* "~a" (first body))) 67 | 68 | (defmethod render-span-plain ((code (eql 'eval)) body) 69 | (render-handle-eval body)) 70 | 71 | (defmethod render-span-plain ((code (eql 'code-eval)) body) 72 | (render-handle-eval body)) 73 | -------------------------------------------------------------------------------- /unit-tests/test-utilities.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:cl-markdown-test) 2 | 3 | (deftestsuite test-utilities (cl-markdown-test-all) ()) 4 | 5 | (deftestsuite list-depth (test-utilities) () 6 | (:test ((ensure-same (list-depth '(((a)))) 3))) 7 | (:test ((ensure-same (list-depth '(((a) b))) 3))) 8 | (:test ((ensure-same (list-depth '(((a b)))) 3))) 9 | (:test ((ensure-same (list-depth '(a)) 1))) 10 | (:test ((ensure-same (list-depth nil) 0)))) 11 | 12 | (deftestsuite test-list->tree (test-utilities) 13 | () 14 | (:test ((ensure-same (list->tree '((a 1) (b 1) (c 1)) :key #'first :depth-fn #'second) 15 | '(a b c) :test 'equal))) 16 | (:test ((ensure-same (list->tree '((a 1) (b 1) (c 2)) :key #'first :depth-fn #'second) 17 | '(a b (c)) :test 'equal))) 18 | (:test ((ensure-same (list->tree '((a 2) (b 1) (c 1)) :key #'first :depth-fn #'second) 19 | '((a) b c) :test 'equal))) 20 | (:test ((ensure-same (list->tree '((a 1) (b 2) (c 1)) :key #'first :depth-fn #'second) 21 | '(a (b) c) :test 'equal))) 22 | (:test ((ensure-same (list->tree '((a 1) (b 2) (c 3)) :key #'first :depth-fn #'second) 23 | '(a (b (c))) :test 'equal))) 24 | (:test ((ensure-same (list->tree '((a 1) (b 2) (c 3) (d 3)) :key #'first :depth-fn #'second) 25 | '(a (b (c d))) :test 'equal))) 26 | (:test ((ensure-same (list->tree '((a 3) (b 2) (c 1)) :key #'first :depth-fn #'second) 27 | '(((a) b) c) :test 'equal))) 28 | (:test ((ensure-same (list->tree '((a 1) (b 2) (c 3) (d 3) (e 1)) :key #'first :depth-fn #'second) 29 | '(a (b (c d)) e) :test 'equal))) 30 | (:test ((ensure-same (list->tree '((a 1) (b 2 :q) (c 3 :q) (d 3) (e 1)) 31 | :key #'first :depth-fn #'second 32 | :marker #'third) 33 | '(a (:q b (:q c d)) e) :test 'equal)))) 34 | 35 | (deftestsuite test-merge-atom-with-list-at-depth (test-utilities) ()) 36 | (addtest (ensure-same (merge-atom-with-list-at-depth 'a '((b) c) 2) '((a b) c) 37 | :test 'tree-equal)) 38 | (addtest (ensure-same (merge-atom-with-list-at-depth 'a '((b) c) 3) '(((a)) (b) c) 39 | :test 'tree-equal)) 40 | (addtest (ensure-same (merge-atom-with-list-at-depth 'a '((b) c) 1) '(a (b) c) 41 | :test 'tree-equal)) 42 | (addtest (ensure-same (merge-atom-with-list-at-depth 'b '(((c d)) e) 2) '((b (c d)) e) 43 | :test 'tree-equal)) 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /cl-markdown.asd: -------------------------------------------------------------------------------- 1 | (in-package #:common-lisp-user) 2 | 3 | (defpackage #:cl-markdown-system (:use #:cl #:asdf)) 4 | (in-package #:cl-markdown-system) 5 | 6 | ;; Load asdf-system-connections if available 7 | (unless (member :asdf-system-connections *features*) 8 | (if (asdf:find-system "asdf-system-connections" nil) 9 | (asdf:load-system "asdf-system-connections") 10 | (warn "The CL-Markdown system would enjoy having ~ 11 | asdf-system-connections around. See 12 | http://www.cliki.net/asdf-system-connections for details and download 13 | instructions."))) 14 | 15 | (defsystem cl-markdown 16 | :version "0.10.6" 17 | :author "Gary Warren King " 18 | :maintainer "Gary Warren King " 19 | :licence "MIT Style License" 20 | :components 21 | ((:static-file "COPYING") 22 | (:module "setup" 23 | :pathname "dev/" 24 | :components 25 | ((:file "package") 26 | (:file "api" 27 | :depends-on ("package")))) 28 | (:module "dev" 29 | :depends-on ("setup") 30 | :components 31 | ((:file "definitions") 32 | (:file "macros") 33 | (:file "class-defs" 34 | :depends-on ("definitions")) 35 | (:file "utilities" 36 | :depends-on ("macros" "definitions" "class-defs")) 37 | (:file "spans" 38 | :depends-on ("regexes" "class-defs")) 39 | (:file "regexes") 40 | (:file "markdown" 41 | :depends-on ("utilities" "class-defs" 42 | "spans" "definitions")) 43 | (:file "html" 44 | :depends-on ("utilities" "class-defs" "spans")) 45 | (:file "plain" 46 | :depends-on ("utilities" "class-defs" "spans")) 47 | (:file "multiple-documents" 48 | :depends-on ("definitions")) 49 | (:file "epilogue" 50 | :depends-on ("markdown")) 51 | (:static-file "notes.text"))) 52 | 53 | (:module "extensions" 54 | :pathname #.(make-pathname :directory '(:relative "dev")) 55 | :components 56 | ((:file "extension-mechanisms") 57 | (:file "extensions" :depends-on ("extension-mechanisms")) 58 | (:file "footnotes" :depends-on ("extension-mechanisms"))) 59 | :depends-on ("dev")) 60 | 61 | (:module "website" 62 | :components 63 | ((:module "source" 64 | :components ((:static-file "index.md")))))) 65 | 66 | :in-order-to ((test-op (load-op cl-markdown-test))) 67 | :perform (test-op :after (op c) 68 | (funcall 69 | (intern (symbol-name '#:run-tests) :lift) 70 | :config :generic)) 71 | :depends-on ((:version :metatilities-base "0.6.0") 72 | :metabang-bind 73 | ;; ugh, the order matters here. Add more duct tape 74 | #-asdf-system-connections :container-dynamic-classes 75 | (:version :cl-containers "0.11.5") 76 | :dynamic-classes 77 | :anaphora 78 | :cl-ppcre)) 79 | 80 | (defmethod operation-done-p 81 | ((o test-op) (c (eql (find-system '#:cl-markdown)))) 82 | (values nil)) 83 | 84 | -------------------------------------------------------------------------------- /unit-tests/test-regexes.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:cl-markdown-test) 2 | 3 | (deftestsuite test-regexes (cl-markdown-test-all) ()) 4 | 5 | (deftestsuite test-url (test-regexes) ()) 6 | (addtest (test-url) 7 | test-1 8 | (ensure-same 9 | (scan-to-strings 10 | '(:sequence url) "My page is at http://www.metabang.com/~gwking/public.") 11 | (values "http://www.metabang.com/~gwking/public" 12 | #("www.metabang.com" "~gwking/public")) 13 | :test 'equalp)) 14 | 15 | ;;; --------------------------------------------------------------------------- 16 | 17 | (deftestsuite test-link-label (test-regexes) ()) 18 | (addtest (test-link-label) 19 | test-link 20 | (bind (((values nil registers) 21 | (scan-to-strings '(:sequence link-label) " [aa]: http://foo.bar"))) 22 | (ensure-same (aref registers 0) "aa") 23 | (ensure-same (aref registers 1) "http://foo.bar") 24 | (ensure-same (aref registers 2) nil))) 25 | 26 | (addtest (test-link-label) 27 | test-link-with-title 28 | (bind (((values nil registers) 29 | (scan-to-strings '(:sequence link-label) 30 | " [aa]: http://foo.bar \"best foos\""))) 31 | (ensure-same (aref registers 0) "aa") 32 | (ensure-same (aref registers 1) "http://foo.bar") 33 | (ensure-same (aref registers 2) "best foos"))) 34 | 35 | ;;; --------------------------------------------------------------------------- 36 | 37 | (deftestsuite test-inline-links (test-regexes) ()) 38 | (addtest (test-inline-links) 39 | test-1 40 | (ensure-same 41 | (nth-value 1 42 | (scan-to-strings 43 | '(:sequence inline-link) 44 | "This is an [in-line](http://www.google.com/ \"Link to Google\") link")) 45 | #("in-line" "http://www.google.com/" "Link to Google") 46 | :test 'equalp)) 47 | 48 | (addtest (test-inline-links) 49 | test-2 50 | (ensure-same 51 | (nth-value 1 52 | (scan-to-strings 53 | '(:sequence inline-link) 54 | "This is an [in-line](http://www.google.com/) link with no title")) 55 | #("in-line" "http://www.google.com/" nil) 56 | :test 'equalp)) 57 | 58 | (addtest (test-inline-links) 59 | test-2 60 | (ensure-same 61 | (scan-to-strings 62 | '(:sequence inline-link) 63 | "This is not an (in-line)(http://www.google.com/) link with no title") nil)) 64 | 65 | ;;; --------------------------------------------------------------------------- 66 | 67 | (deftestsuite test-reference-links (test-regexes) ()) 68 | 69 | (addtest (test-reference-links) 70 | test-1 71 | (ensure-same 72 | (nth-value 1 73 | (scan-to-strings 74 | '(:sequence reference-link) 75 | "This is an [in-line][id] link")) 76 | #("in-line" "id") 77 | :test 'equalp)) 78 | 79 | (addtest (test-reference-links) 80 | test-2 81 | (ensure-same 82 | (nth-value 1 83 | (scan-to-strings 84 | '(:sequence reference-link) 85 | "This is an [in-line] [id] link with no title")) 86 | #("in-line" "id") 87 | :test 'equalp)) 88 | 89 | -------------------------------------------------------------------------------- /unit-tests/test-brackets-and-includes.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:cl-markdown-test) 2 | 3 | (deftestsuite brackets-and-includes (cl-markdown-test) 4 | ((temporary-directory "/tmp/")) 5 | (:setup 6 | (with-new-file (out (relative-pathname temporary-directory "bandi-1.md")) 7 | (format out " 8 | {set-property slush \"1234-simple\"} 9 | This is true.\{footnote \"technically, this is true\"}. Did you: 10 | 11 | * like it? 12 | * love it? 13 | * find it irrelevant? 14 | 15 | ")) 16 | (with-new-file (out (relative-pathname temporary-directory "bandi-2.md")) 17 | (format out " 18 | {set-property slush \"1234-complex\"} 19 | This is true.\{footnote \"actually it's not 20 | only false but also 21 | 22 | 1. misleading 23 | 2. incorrect 24 | 3. overly optimistic. 25 | 26 | Let you conscience by your guide.\"}")))) 27 | 28 | (addtest (brackets-and-includes) 29 | include-simple 30 | (let ((output 31 | (nth-value 1 32 | (markdown 33 | (concatenate 'string 34 | "Including bandi-1.md 35 | 36 | {include " (namestring (relative-pathname temporary-directory "bandi-1.md")) "} 37 | 38 | slush: {property slush} 39 | 40 | Lets show the footnotes: 41 | 42 | {footnotes} 43 | 44 | All done.") 45 | :stream nil)))) 46 | (ensure (search "like it?" output :test 'char=) 47 | :report "footnote not found") 48 | (ensure (search "1234-simple" output :test 'char=) 49 | :report "property not found"))) 50 | 51 | (addtest (brackets-and-includes) 52 | include-complex 53 | (let ((output 54 | (nth-value 1 55 | (markdown 56 | (concatenate 'string 57 | "Including bandi-2.md 58 | 59 | {include " (namestring (relative-pathname temporary-directory "bandi-2.md")) "} 60 | 61 | slush: {property slush} 62 | 63 | Lets show the footnotes: 64 | 65 | {footnotes} 66 | 67 | All done.") 68 | :stream nil)))) 69 | (ensure (search "misleading" output :test 'char=) 70 | :report "footnote not found") 71 | (ensure (search "1234-complex" output :test 'char=) 72 | :report "property not found"))) 73 | 74 | (deftestsuite include-if (cl-markdown-test) 75 | ((temporary-directory "/tmp/")) 76 | (:setup 77 | (with-new-file (out (relative-pathname temporary-directory "bandi-1.md")) 78 | (format out " 79 | {set-property slush \"1234-simple\"} 80 | This is true.\{footnote \"technically, this is true\"}. Did you: 81 | 82 | * like it? 83 | * love it? 84 | * find it irrelevant? 85 | 86 | ")))) 87 | 88 | (addtest (include-if) 89 | property-not-set 90 | (let ((text 91 | (nth-value 92 | 1 (markdown 93 | (concatenate 94 | 'string "# Title 95 | 96 | {include-if test-prop " 97 | (namestring (relative-pathname temporary-directory "bandi-1.md")) 98 | "} 99 | 100 | paragraph") :stream nil :properties `((test-prop . nil)))))) 101 | (ensure-null (search "This is true" text :test 'char=)))) 102 | 103 | (addtest (include-if) 104 | property-set 105 | (let ((text 106 | (nth-value 107 | 1 (markdown 108 | (concatenate 109 | 'string "# Title 110 | 111 | {include-if test-prop " 112 | (namestring (relative-pathname temporary-directory "bandi-1.md")) 113 | "} 114 | 115 | paragraph") :stream nil :properties `((test-prop . t)))))) 116 | (ensure (search "This is true" text :test 'char=)))) -------------------------------------------------------------------------------- /unit-tests/test-dl.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:cl-markdown-test) 2 | 3 | (deftestsuite test-definition-list (test-snippets) 4 | ()) 5 | 6 | #| 7 | Term one 8 | : Definition one 9 | * More stuff in def one 10 | * (a list!) 11 | 12 | Term two 13 | 14 | : Definition one for term two 15 | : Definition two for term two 16 | 17 | Term three 18 | Another term three 19 | : definition for term three 20 | 21 | (markdown " 22 | My things 23 | 24 | Green 25 | : beautiful 26 | ") 27 | 28 | (markdown " 29 | My things 30 | 31 | Green 32 | : beautiful 33 | : toasty 34 | ") 35 | 36 | (markdown " 37 | My things 38 | 39 | Red 40 | Green 41 | : beautiful 42 | : toasty 43 | ") 44 | 45 | (markdown " 46 | My things 47 | 48 | Green 49 | : beautiful 50 | 51 | : toasty 52 | ") 53 | 54 | (markdown " 55 | My things 56 | 57 | Green 58 | : beautiful 59 | * one 60 | * two 61 | : toasty 62 | ") 63 | 64 | (markdown " 65 | My things 66 | 67 | Green 68 | : beautiful 69 | 70 | one 71 | 72 | two 73 | 74 | three 75 | 76 | : toasty 77 | ") 78 | 79 | 80 | (markdown "Term 1 81 | 82 | : This is a definition with two paragraphs. Lorem ipsum 83 | dolor sit amet, consectetuer adipiscing elit. Aliquam 84 | hendrerit mi posuere lectus. 85 | 86 | Vestibulum enim wisi, viverra nec, fringilla in, laoreet 87 | vitae, risus. 88 | 89 | : Second definition for term 1, also wrapped in a paragraph 90 | because of the blank line preceding it. 91 | 92 | Term 2 93 | 94 | : This definition has a code block, a blockquote and a list. 95 | 96 | code block. 97 | 98 | > block quote 99 | > on two lines. 100 | 101 | 1. first list item 102 | 2. second list item" 103 | 104 | ) 105 | 106 | |# 107 | 108 | (addtest (test-definition-list) 109 | one-term-two-descriptions 110 | (check-html-output 111 | "Punt 112 | : Kick a ball 113 | : Take a bet" 114 | "
115 |
Punt 116 |
Kick a ball
117 |
Take a bet
118 |
")) 119 | 120 | (addtest (test-definition-list) 121 | two-terms-one-description 122 | (check-html-output 123 | "Punt 124 | Dance 125 | : Take a bet" 126 | "
127 |
Punt
128 |
Dance
129 |
Take a bet
130 |
")) 131 | 132 | (addtest (test-definition-list) 133 | two-simple-entries 134 | (check-html-output 135 | "Punt 136 | : Take a bet 137 | 138 | Dance 139 | : Shake a jig" 140 | "
141 |
Punt
142 |
Take a bet
143 |
Dance
144 |
Shake a jig
145 |
")) 146 | 147 | #+(or) 148 | (addtest (test-definition-list) 149 | complex 150 | (check-html-output 151 | "Term 1 152 | 153 | : This is a definition with two paragraphs. Lorem ipsum 154 | dolor sit amet, consectetuer adipiscing elit. Aliquam 155 | hendrerit mi posuere lectus. 156 | 157 | Vestibulum enim wisi, viverra nec, fringilla in, laoreet 158 | vitae, risus. 159 | 160 | : Second definition for term 1, also wrapped in a paragraph 161 | because of the blank line preceding it. 162 | 163 | More stuff 164 | 165 | Term 2 166 | 167 | : This definition has a code block, a blockquote and a list. 168 | 169 | code block. 170 | 171 | > block quote 172 | > on two lines. 173 | 174 | 1. first list item 175 | 2. second list item" 176 | " 177 | ")) 178 | 179 | (addtest (test-definition-list) 180 | mulit-line-definition 181 | (check-html-output 182 | "Punt 183 | : Take a bet 184 | 185 | that is right." 186 | "
187 |
Punt
188 |
Take a bet 189 | 190 |

that is right.

191 |
")) 192 | -------------------------------------------------------------------------------- /dev/macros.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:cl-markdown) 2 | 3 | (defmacro defsimple-extension (name &body body) 4 | "Create an extension (a function named `name`) with no arguments that 5 | does not depend on the markdown phase and which does not use the result. 6 | These are handy for simple text substitutions." 7 | (with-gensyms (phase arguments result) 8 | `(progn 9 | (pushnew (list ',name t) *extensions* :key #'car) 10 | (defun ,name (,phase ,arguments ,result) 11 | (declare (ignore ,phase ,arguments ,result)) 12 | ,@body) 13 | ,@(%import/export-symbol name)))) 14 | 15 | (defun %validate-defextension-arguments (arguments) 16 | (loop for argument in (ensure-list arguments) do 17 | (cond ((atom argument) 18 | (when (eq (symbol-package argument) #.(find-package :keyword)) 19 | (error "Argument names may not be keywords and ~s is not" 20 | argument))) 21 | (t 22 | (unless (every (lambda (facet) 23 | (member facet '(:required :keyword :whole))) 24 | (rest argument)) 25 | (error "Invalid argument facets in ~s" (rest argument))))))) 26 | 27 | (defun %collect-arguments (arguments kind) 28 | (loop for argument in (ensure-list arguments) 29 | when (and (consp argument) 30 | (member kind (rest argument))) collect 31 | (first argument))) 32 | 33 | (defun %collect-positionals (arguments) 34 | (loop for argument in (ensure-list arguments) 35 | when (or (atom argument) 36 | (and (consp argument) 37 | (not (member :keyword (rest argument))))) collect 38 | (first (ensure-list argument)))) 39 | 40 | (defparameter *extensions* nil) 41 | 42 | (defmacro defextension ((name &key arguments (insertp nil) (exportp t)) 43 | &body body) 44 | (%validate-defextension-arguments arguments) 45 | (bind ((keywords (%collect-arguments arguments :keyword)) 46 | (requires (%collect-arguments arguments :required)) 47 | (whole (%collect-arguments arguments :whole)) 48 | (positionals (%collect-positionals arguments))) 49 | (assert (<= (length whole) 1) 50 | nil "At most one :whole argument is allowed.") 51 | (assert (null (intersection whole keywords)) 52 | nil "Keyword arguments cannot be wholes") 53 | `(progn 54 | (setf *extensions* (remove ',name *extensions* :key #'first)) 55 | (push (list ',name ,insertp) *extensions*) 56 | (defun ,name (phase args result) 57 | (declare (ignorable phase args result)) 58 | (bind (,@(loop for positional in positionals 59 | unless (member positional whole) collect 60 | `(,positional (pop args))) 61 | ,@(loop for keyword in keywords collect 62 | `(,keyword 63 | (getf args ,(intern (symbol-name keyword) :keyword) 64 | nil))) 65 | ,@(when whole 66 | `((,(first whole) 67 | ;; remove keywords from args 68 | (progn 69 | ,@(loop for keyword in keywords collect 70 | `(,keyword 71 | (remf args 72 | ,(intern (symbol-name keyword) :keyword)))) 73 | (if (length-1-list-p args) (first args) args)))))) 74 | ,@(loop for require in requires collect 75 | `(assert ,require nil ,(format nil "~s is required" require))) 76 | ,@body 77 | ,@(unless insertp nil))) 78 | ,@(when exportp 79 | (%import/export-symbol name))))) 80 | 81 | (defun %import/export-symbol (name) 82 | `((eval-when (:compile-toplevel :load-toplevel :execute) 83 | (import ',name ,(find-package :cl-markdown-user)) 84 | (export ',name ,(find-package :cl-markdown-user))))) 85 | 86 | (defmacro aand+ (&rest args) 87 | "Anaphoric nested AND. 88 | 89 | Binds the symbol `it' to the value of the preceding `arg.'" 90 | (cond ((null args) t) 91 | ((null (cdr args)) (car args)) 92 | (t `(aif ,(car args) (aand ,@(cdr args)))))) 93 | -------------------------------------------------------------------------------- /unit-tests/test-links.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:cl-markdown-test) 2 | 3 | (deftestsuite test-reference-links (test-snippets) 4 | ()) 5 | 6 | (addtest (test-reference-links) 7 | title-only-1 8 | (let ((doc 9 | (cl-markdown:markdown 10 | "I like [beans][]. Do you? 11 | 12 | [beans]: http://www.beans.com \"foo\" 13 | " :stream :none))) 14 | (ensure-same (properties (first-element (link-info doc))) nil) 15 | (ensure-same (title (first-element (link-info doc))) 16 | "foo" :test 'string=))) 17 | 18 | (addtest (test-reference-links) 19 | title-only-2-a 20 | (let ((doc 21 | (cl-markdown:markdown 22 | "I like [beans][]. Do you? 23 | 24 | [beans]: http://www.beans.com \(foo is a bean\) 25 | " :stream :none))) 26 | (ensure-same (properties (first-element (link-info doc))) nil) 27 | (ensure-same (title (first-element (link-info doc))) 28 | "foo is a bean" :test 'string=))) 29 | 30 | (addtest (test-reference-links) 31 | title-only-2-b 32 | (let ((doc 33 | (cl-markdown:markdown 34 | "I like [beans][]. Do you? 35 | 36 | [beans]: http://www.beans.com \"foo is a bean\" 37 | " :stream :none))) 38 | (ensure-same (properties (first-element (link-info doc))) nil) 39 | (ensure-same (title (first-element (link-info doc))) 40 | "foo is a bean" :test 'string=))) 41 | 42 | (addtest (test-reference-links) 43 | properties-only-1 44 | (let ((doc 45 | (cl-markdown:markdown 46 | "I like [beans][]. Do you? 47 | 48 | [beans]: http://www.beans.com target new class \"external link\" 49 | " :stream :none))) 50 | (ensure-same (properties (first-element (link-info doc))) 51 | '((:target . "new") (:class . "external link")) :test 'equalp) 52 | (ensure-null (title (first-element (link-info doc)))))) 53 | 54 | (addtest (test-reference-links) 55 | title-and-properties-1 56 | (let ((doc 57 | (cl-markdown:markdown 58 | "I like [beans][]. Do you? 59 | 60 | [beans]: http://www.beans.com \"beans are the new black\" target new 61 | " :stream :none))) 62 | (ensure-same (properties (first-element (link-info doc))) 63 | '((:target . "new")) :test 'equalp) 64 | (ensure-same (title (first-element (link-info doc))) 65 | "beans are the new black" :test 'string=))) 66 | 67 | (addtest (test-reference-links) 68 | title-and-properties-2 69 | (let ((doc 70 | (cl-markdown:markdown 71 | "I like [beans][]. Do you? 72 | 73 | [beans]: http://www.beans.com \"beans are the new black\" target new class external 74 | " :stream :none))) 75 | (ensure-same (properties (first-element (link-info doc))) 76 | '((:target . "new") (:class . "external")) :test 'equalp) 77 | (ensure-same (title (first-element (link-info doc))) 78 | "beans are the new black" :test 'string=))) 79 | 80 | ;; not sure how this should work 81 | (addtest (test-reference-links 82 | :expected-failure "parsing multi-line reference links") 83 | title-and-properties-3 84 | (let ((doc 85 | (cl-markdown:markdown 86 | "I like [beans][]. Do you? 87 | 88 | [beans]: http://www.beans.com \"beans are the new black\" 89 | target new class external 90 | " :stream :none))) 91 | (ensure-same (properties (first-element (link-info doc))) 92 | '((:target . "new") (:class . "external")) :test 'equalp) 93 | (ensure-same (title (first-element (link-info doc))) 94 | "beans are the new black" :test 'string=))) 95 | 96 | 97 | (addtest (test-reference-links 98 | :expected-failure "parsing multi-line reference links") 99 | title-on-new-line 100 | (let ((doc 101 | (cl-markdown:markdown 102 | "I like [beans][]. Do you? 103 | 104 | [beans]: http://www.beans.com 105 | \"beans are the new black\" 106 | " :stream :none))) 107 | (ensure-same (title (first-element (link-info doc))) 108 | "beans are the new black" :test 'string=))) 109 | 110 | -------------------------------------------------------------------------------- /unit-tests/test-spans.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:cl-markdown-test) 2 | 3 | (deftestsuite test-spans (cl-markdown-test-all) ()) 4 | 5 | (addtest (test-spans) 6 | test-1-replacement 7 | (ensure-same 8 | (scan-lines-with-scanners 9 | (list "Can you say **strong**?") 10 | `((,(create-scanner '(:sequence strong-1)) strong) 11 | (,(create-scanner '(:sequence strong-2)) strong))) 12 | '("Can you say " (STRONG "strong") "?") 13 | :test 'equalp)) 14 | 15 | (addtest (test-spans) 16 | test-2-replacement 17 | (ensure-same 18 | (scan-lines-with-scanners 19 | (list "Can you say **strong**? I can **say** strong!") 20 | `((,(create-scanner '(:sequence strong-1)) strong) 21 | (,(create-scanner '(:sequence strong-2)) strong))) 22 | '("Can you say " (STRONG "strong") 23 | "? I can " (STRONG "say") " strong!") 24 | :test 'equalp)) 25 | 26 | ;;; --------------------------------------------------------------------------- 27 | 28 | (addtest (test-spans) 29 | test-inline-link 30 | (ensure-same 31 | (scan-lines-with-scanners 32 | (list "This is [Google](http://google.com/). OK") 33 | `((,(create-scanner '(:sequence inline-link)) inline-link))) 34 | '("This is " (INLINE-LINK "Google" "http://google.com/" NIL) ". OK") 35 | :test 'equalp)) 36 | 37 | ;;; --------------------------------------------------------------------------- 38 | 39 | (addtest (test-spans) 40 | test-inline-link-with-title 41 | (ensure-same 42 | (scan-lines-with-scanners 43 | (list "This is [Google](http://google.com/ \"A nice title\"). OK") 44 | `((,(create-scanner '(:sequence inline-link)) inline-link))) 45 | '("This is " (INLINE-LINK "Google" "http://google.com/" "A nice title") ". OK") 46 | :test 'equalp)) 47 | 48 | ;;; --------------------------------------------------------------------------- 49 | 50 | (addtest (test-spans) 51 | test-reference-link-1 52 | (ensure-same 53 | (scan-lines-with-scanners 54 | (list "This is [Google][Foo]. OK") 55 | `((,(create-scanner '(:sequence reference-link)) reference-link))) 56 | '("This is " (reference-link "Google" "Foo") ". OK") 57 | :test 'equalp)) 58 | 59 | (addtest (test-spans) 60 | test-reference-link-2 61 | (ensure-same 62 | (scan-lines-with-scanners 63 | (list "This is [Google] [Foo]. OK") 64 | `((,(create-scanner '(:sequence reference-link)) reference-link))) 65 | '("This is " (reference-link "Google" "Foo") ". OK") 66 | :test 'equalp)) 67 | 68 | (addtest (test-spans) 69 | test-reference-link-implicit 70 | (ensure-same 71 | (scan-lines-with-scanners 72 | (list "This is [Google][]. OK") 73 | `((,(create-scanner '(:sequence reference-link)) reference-link))) 74 | '("This is " (reference-link "Google" "") ". OK") 75 | :test 'equalp)) 76 | 77 | (addtest (test-spans) 78 | test-reference-link-with-spaces 79 | (ensure-same 80 | (scan-lines-with-scanners 81 | (list "This is [Daring Fireball][]. OK") 82 | `((,(create-scanner '(:sequence reference-link)) reference-link))) 83 | '("This is " (reference-link "Daring Fireball" "") ". OK") 84 | :test 'equalp)) 85 | 86 | 87 | (deftestsuite test-strong-2 (test-spans) 88 | ((scanner (create-scanner 89 | '(:sequence 90 | (:greedy-repetition 2 2 #\*) 91 | (:register 92 | (:sequence (:greedy-repetition 0 nil (:inverted-char-class #\*)))) 93 | (:greedy-repetition 2 2 #\*)))))) 94 | 95 | (addtest (test-strong-2) 96 | test-1 97 | (ensure (scan scanner "**hello**"))) 98 | 99 | (addtest (test-strong-2) 100 | test-1 101 | (ensure (not (scan scanner "**hello *")))) 102 | 103 | (addtest (test-strong-2) 104 | test-1 105 | (ensure-same (scan scanner "***hello***") (values 1 10 #(3) #(8)) :test #'equalp)) 106 | 107 | (addtest (test-strong-2) 108 | test-1 109 | (ensure-same (scan scanner "*** hello there ***") (values 1 18 #(3) #(16)) :test #'equalp)) 110 | 111 | 112 | -------------------------------------------------------------------------------- /website/website.tmproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | currentDocument 6 | source/index.md 7 | documents 8 | 9 | 10 | name 11 | images 12 | regexFolderFilter 13 | !.*/(\.[^/]*|CVS|_darcs|_MTN|\{arch\}|blib|.*~\.nib|.*\.(framework|app|pbproj|pbxproj|xcode(proj)?|bundle))$ 14 | sourceDirectory 15 | source/images 16 | 17 | 18 | filename 19 | source/index.md 20 | lastUsed 21 | 2011-01-08T22:16:50Z 22 | 23 | 24 | expanded 25 | 26 | name 27 | resources 28 | regexFolderFilter 29 | !.*/(\.[^/]*|CVS|_darcs|_MTN|\{arch\}|blib|.*~\.nib|.*\.(framework|app|pbproj|pbxproj|xcode(proj)?|bundle))$ 30 | sourceDirectory 31 | source/resources 32 | 33 | 34 | filename 35 | source/style.css 36 | lastUsed 37 | 2007-11-21T15:32:54Z 38 | 39 | 40 | filename 41 | source/user-guide.css 42 | lastUsed 43 | 2011-01-08T22:16:50Z 44 | 45 | 46 | filename 47 | source/user-guide.md 48 | lastUsed 49 | 2007-12-13T23:24:58Z 50 | 51 | 52 | filename 53 | ../../shared/shared-links.md 54 | selected 55 | 56 | 57 | 58 | fileHierarchyDrawerWidth 59 | 200 60 | metaData 61 | 62 | source/index.md 63 | 64 | caret 65 | 66 | column 67 | 27 68 | line 69 | 57 70 | 71 | firstVisibleColumn 72 | 0 73 | firstVisibleLine 74 | 40 75 | 76 | source/resources/footer.md 77 | 78 | caret 79 | 80 | column 81 | 6 82 | line 83 | 10 84 | 85 | firstVisibleColumn 86 | 0 87 | firstVisibleLine 88 | 0 89 | 90 | source/resources/ug-footer.md 91 | 92 | caret 93 | 94 | column 95 | 6 96 | line 97 | 5 98 | 99 | firstVisibleColumn 100 | 0 101 | firstVisibleLine 102 | 0 103 | 104 | source/resources/ug-header.md 105 | 106 | caret 107 | 108 | column 109 | 7 110 | line 111 | 8 112 | 113 | firstVisibleColumn 114 | 0 115 | firstVisibleLine 116 | 0 117 | 118 | source/resources/ug-navigation.md 119 | 120 | caret 121 | 122 | column 123 | 0 124 | line 125 | 0 126 | 127 | firstVisibleColumn 128 | 0 129 | firstVisibleLine 130 | 0 131 | 132 | source/user-guide.css 133 | 134 | caret 135 | 136 | column 137 | 0 138 | line 139 | 0 140 | 141 | firstVisibleColumn 142 | 0 143 | firstVisibleLine 144 | 0 145 | 146 | 147 | openDocuments 148 | 149 | source/resources/ug-footer.md 150 | source/resources/footer.md 151 | source/index.md 152 | source/user-guide.css 153 | source/resources/ug-header.md 154 | source/resources/ug-navigation.md 155 | 156 | showFileHierarchyDrawer 157 | 158 | windowFrame 159 | {{503, 0}, {684, 778}} 160 | 161 | 162 | -------------------------------------------------------------------------------- /unit-tests/test-markdown.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:cl-markdown-test) 2 | 3 | #| 4 | (run-tests :suite 'test-markdown) 5 | |# 6 | 7 | (deftestsuite cl-markdown-test () ()) 8 | 9 | (deftestsuite test-bracket-processing (cl-markdown-test) 10 | ()) 11 | 12 | (addtest (test-bracket-processing) 13 | donot-process-code 14 | (let ((text 15 | (nth-value 1 16 | (markdown "{set-property a \"set 1\"} 17 | 18 | Paragraph 1 19 | 20 | Code 1 21 | {set-property a \"set 2\"} 22 | More code 23 | 24 | All done: a = {property a}" :stream nil)))) 25 | (ensure (search "a = set 1" text :test 'char=) 26 | :report "a set correctly") 27 | (ensure (search "set 2" text :test 'char=) 28 | :report "code not mangled"))) 29 | 30 | (addtest (test-bracket-processing) 31 | double-brackets-for-code 32 | (let ((text 33 | (nth-value 1 34 | (markdown "{set-property a \"set 1\"} 35 | 36 | Paragraph 1 37 | 38 | Code 1 39 | {{set-property a \"set 2\"}} 40 | More code 41 | 42 | All done: a = {property a}" :stream nil)))) 43 | (ensure (search "a = set 2" text :test 'char=) 44 | :report "a set correctly") 45 | (ensure-null (search "a \"set 2" text :test 'char=) 46 | :report "code not mangled"))) 47 | 48 | ;;;; 49 | 50 | (deftestsuite nested-properties (cl-markdown-test) 51 | ((temporary-directory "/tmp/")) 52 | (:setup 53 | (with-new-file (out (relative-pathname temporary-directory "bandi-1.md")) 54 | (format out " 55 | {set-property slush \"1234-simple\"} 56 | ")))) 57 | 58 | (addtest (nested-properties) 59 | try-it 60 | (let ((text 61 | (nth-value 62 | 1 (markdown 63 | "a {set-property a \"alpha\"} 64 | b {set-property b \"{property a}\"} 65 | c {set-property c \"a is {property b}\"} 66 | 67 | {property b} 68 | 69 | # Title is {property c} 70 | 71 | Hi there" :stream nil)))) 72 | (ensure (search "Title is a is alpha" text :test 'char=)))) 73 | 74 | (addtest (nested-properties) 75 | works-with-included-documents-too 76 | (let ((text 77 | (nth-value 78 | 1 (markdown 79 | (concatenate 80 | 'string " 81 | {include " (namestring (relative-pathname temporary-directory "bandi-1.md")) 82 | "} 83 | a {set-property a \"this is {property slush} too\"} 84 | b {set-property b \"{property a}\"} 85 | c {set-property c \"a is {property a}\"} 86 | 87 | # Title is {property c} 88 | 89 | Hi there") :stream nil)))) 90 | (ensure (search "Title is a is this is 1234-simple" text :test 'char=)))) 91 | 92 | 93 | 94 | (defvar *last-document* nil) 95 | 96 | (defun shell-tidy (source) 97 | (bind (((:values result error status) 98 | (shell-command 99 | (format nil "tidy --show-body-only 1 --quiet 1 ~ 100 | --show-warnings 0") 101 | :input source))) 102 | (values result error status))) 103 | 104 | (defun shell-markdown (source) 105 | (bind (((:values result error status) 106 | (shell-command 107 | (format nil "markdown") 108 | :input source))) 109 | (values result error status))) 110 | 111 | (deftestsuite test-snippets (cl-markdown-test) 112 | () 113 | :equality-test #'string-equal 114 | (:function 115 | (check-html-output 116 | (source html) 117 | (ensure-same 118 | (shell-tidy 119 | (nth-value 120 | 1 (markdown source :stream nil :format :html))) 121 | (shell-tidy html) :test 'samep))) 122 | (:function 123 | (check-output 124 | (source) 125 | (ensure-same 126 | (bind (((:values doc text) 127 | (markdown source :stream nil :format :html))) 128 | (setf *last-document* doc) 129 | ;; just get the first value 130 | (values (shell-tidy text))) 131 | (shell-tidy (shell-markdown source)) 132 | :test (lambda (a b) 133 | (compare-line-by-line a b :key 'cl-markdown::strip-whitespace 134 | :test 'string-equal)))))) 135 | 136 | ;;;;; 137 | 138 | (deftestsuite no-markdown-in-inline-html (cl-markdown-test) 139 | () 140 | :equality-test #'string=) 141 | 142 | (addtest (no-markdown-in-inline-html) 143 | no-emphasis 144 | (ensure-same 145 | (remove-if 'whitespacep 146 | (nth-value 147 | 1 (markdown "Hi " 148 | :format :html :stream nil))) 149 | "

Hi

")) 150 | 151 | ;;;;; 152 | 153 | (deftestsuite inline-html (cl-markdown-test) 154 | () 155 | :equality-test #'string=) 156 | 157 | (addtest (inline-html) 158 | do-not-encode 159 | (ensure-same 160 | (remove-if 'whitespacep 161 | (nth-value 162 | 1 (markdown "Hi there" 163 | :format :html :stream nil))) 164 | "

Hithere

")) 165 | 166 | (addtest (inline-html) 167 | encode-in-code 168 | (ensure-same 169 | (remove-if 'whitespacep 170 | (nth-value 171 | 1 (markdown "Hi `there`" 172 | :format :html :stream nil))) 173 | "

Hi<em>there</em>

")) 174 | 175 | -------------------------------------------------------------------------------- /website/source/index.md: -------------------------------------------------------------------------------- 1 | {include resources/header.md} 2 | {set-property title "CL-Markdown - Markdown and More"} 3 | 4 |
5 | 22 |
23 | 24 | ### What it is 25 | 26 | (Note: CL-Markdown just split off it's Lisp documentation 27 | abilities into the [docudown][] project. Don't be alarmed. 28 | Everything is good.) 29 | 30 | [Markdown][] is [John Gruber][df]'s text markup langauge and 31 | the Perl program that converts documents written in that 32 | language into HTML. CL-Markdown is a Common Lisp rewrite of 33 | Markdown. CL-Markdown is licensed under the [MIT 34 | license][mit-license]. 35 | 36 | You can see the source of this page by clicking in the 37 | address bar of your browser and changing the extension from 38 | `html` to `text`. For example, this page's source is at 39 | [index.text](index.text). 40 | 41 | You can view a comparison of Markdown and CL-Markdown output 42 | [here][8]. 43 | 44 | {anchor mailing-lists} 45 | 46 | ### Mailing Lists 47 | 48 | * [devel-list][]: A list for questions, patches, bug 49 | reports, and so on; It's for everything other than 50 | announcements. 51 | 52 | {anchor downloads} 53 | 54 | ### Where is it 55 | 56 | metabang.com is switching from [darcs][] to [git][] 57 | for source control; the current cl-markdown repository is on 58 | [github][github-cl-markdown] and you can clone it using: 59 | 60 | git clone git://github.com/gwkkwg/cl-markdown 61 | 62 | (note that this won't let you build CL-Markdown unless you 63 | also get all of its dependencies which I should list but don't 64 | because I haven't found (er, made) the time to automate the 65 | process yet...) 66 | 67 | The easiest way to get setup with CL-Markdown is by using 68 | [QuickLisp][] or [ASDF-Install][14] (deprecated). If that 69 | doesn't float your boat, there is a handy [gzipped tar 70 | file][15] 71 | 72 | {anchor news} 73 | 74 | ### What is happening 75 | 76 |
77 |
8 January 2011
78 |
Moved to github. Very minor cleanup.
79 |
28 May 2008
80 |
Many small improvements, bug fixes, tweaks, and 81 | extensions. The biggest change, however, is that I've 82 | move the Lisp documentation work into it's own 83 | [project][docudown]. This keeps CL-Markdown simpler. 84 | The dependencies on [moptilities][] and 85 | [defsystem-compatibility][] have both been removed. 86 | A dependency on [anaphora][clnet-anaphora] has been added. 87 |
88 |
30 August 2007
89 |
Tons of improvements in the documentation extension, lots of 90 | cleanup, better HTML generation, better footnotes, what's not to like! 91 |
92 |
20 Feb 2007
93 |
Lots of stuff has happened; see the change log for details. 94 |
95 |
5 June 2006
96 |
More tweaking of block structure processing and paragraph marking. In every day and in every way, it's getting better and better. 97 |
98 |
22 May 2006
99 |
Removed LML2 dependency for CL-Markdown and fixed some bugs! 100 |
101 |
17 May 2006
102 |
Updated with SBCL and Allegro support (son far only alisp) 103 |
104 |
8 May 2006
105 |
Created site. 106 |
107 |
108 | 109 |
110 |
111 | 112 | {include resources/footer.md} 113 | 114 | [1]: http://common-lisp.net/project/cl-containers/shared/metabang-2.png (metabang.com) 115 | [2]: http://www.metabang.com/ (metabang.com) 116 | [3]: #mailing-lists 117 | [4]: #downloads 118 | [5]: documentation/ (documentation link) 119 | [6]: #news 120 | [7]: changelog.html 121 | [8]: comparison-tests 122 | [9]: http://trac.common-lisp.net/cl-markdown 123 | [10]: http://trac.common-lisp.net/cl-markdown/newticket 124 | [11]: http://common-lisp.net/cgi-bin/mailman/listinfo/cl-markdown-announce 125 | [12]: http://common-lisp.net/cgi-bin/mailman/listinfo/cl-markdown-devel 126 | [13]: downloads 127 | [14]: http://www.cliki.net/asdf-install 128 | [15]: http://common-lisp.net/project/cl-markdown/cl-markdown_latest.tar.gz 129 | [16]: http://www.darcs.net/ 130 | [17]: news 131 | [18]: http://common-lisp.net/project/cl-containers/shared/buttons/xhtml.gif (valid xhtml button) 132 | [19]: http://validator.w3.org/check/referer (xhtml1.1) 133 | [20]: http://common-lisp.net/project/cl-containers/shared/buttons/hacker.png (hacker emblem) 134 | [21]: http://www.catb.org/hacker-emblem/ (hacker) 135 | [22]: http://common-lisp.net/project/cl-containers/shared/buttons/lml2-powered.png (lml2 powered) 136 | [23]: http://lml2.b9.com/ (lml2 powered) 137 | [24]: http://common-lisp.net/project/cl-containers/shared/buttons/lambda-lisp.png (ALU emblem) 138 | [25]: http://www.lisp.org/ (Association of Lisp Users) 139 | [26]: http://common-lisp.net/project/cl-containers/shared/buttons/lisp-lizard.png (Common-Lisp.net) 140 | [27]: http://common-lisp.net/ (Common-Lisp.net) 141 | 142 | -------------------------------------------------------------------------------- /dev/class-defs.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:cl-markdown) 2 | 3 | ;; someday 4 | (defclass markdown-warning () 5 | ()) 6 | 7 | (defclass* abstract-document () 8 | ((chunks (make-container 'vector-container) r) 9 | (link-info (make-container 'simple-associative-container 10 | :test #'equalp) r) 11 | (level 0 a) 12 | (markup nil a) 13 | (properties (make-container 'alist-container 14 | :test #'string-equal) r) 15 | (metadata (make-container 'alist-container 16 | :test #'string-equal) r) 17 | (bracket-references (make-container 'flexible-vector-container) r) 18 | (parent nil ir) 19 | (warnings nil a) 20 | (source nil ir) 21 | (destination nil ia) ;more or less the last place 22 | ;it was rendered 23 | (children nil ia))) 24 | 25 | (defmethod print-object ((object abstract-document) stream) 26 | (print-unreadable-object (object stream :type t :identity t) 27 | (format stream "~a" (short-source (source object))))) 28 | 29 | (defclass* document (abstract-document) 30 | ()) 31 | 32 | (defclass* child-document (document) 33 | ()) 34 | 35 | (defclass* multi-document (abstract-document) 36 | ()) 37 | 38 | (defmethod print-object ((object multi-document) stream) 39 | (print-unreadable-object (object stream :type t :identity t) 40 | (format stream "~d children" (length (children object))))) 41 | 42 | (defclass* included-document (abstract-document) 43 | ()) 44 | 45 | (defgeneric document-property (name &optional default) 46 | (:documentation "Returns the value of the property `name` of the `*current-document*` or the default if the property is not defined or there is no `*current-document*`.")) 47 | 48 | (defmethod document-property (name &optional default) 49 | (or (when *current-document* 50 | (multiple-value-bind (value found?) 51 | (item-at-1 (properties *current-document*) 52 | (form-property-name name)) 53 | (when found? (return-from document-property (first value))))) 54 | (when (and *current-document* 55 | (parent *current-document*)) 56 | (let ((*current-document* (parent *current-document*))) 57 | (document-property name default))) 58 | default)) 59 | 60 | (defmethod (setf document-property) (value name) 61 | (when *current-document* 62 | (setf (item-at-1 (properties *current-document*) 63 | (form-property-name name)) 64 | ;; so that we don't lose 'nil' 65 | (list value))) 66 | ;;?? weird since nothing happened 67 | (values value)) 68 | 69 | (defun find-link (id) 70 | (or (item-at-1 (link-info *current-document*) id) 71 | (and (parent *current-document*) 72 | (let ((*current-document* (parent *current-document*))) 73 | (find-link id))))) 74 | 75 | (defun form-property-name (name) 76 | (form-keyword (typecase name 77 | (string (intern name 78 | (load-time-value (find-package :keyword)))) 79 | (symbol (form-property-name (symbol-name name))) 80 | (t name)))) 81 | 82 | (defclass* chunk () 83 | ((lines (make-container 'vector-container) r) 84 | (blank-line-before? nil ia) 85 | (blank-line-after? nil ia) 86 | (started-by nil ia) 87 | (ended-by nil ia) 88 | (ignore? nil a) 89 | (markup-class nil ia) 90 | (indentation 0 ia) 91 | (level 0 ia) 92 | (paragraph? nil ia) 93 | (properties (make-container 'alist-container 94 | :test #'string-equal) r) 95 | (stripper? nil ia) 96 | (process? t ia))) 97 | 98 | (defmethod initialize-instance :after ((object chunk) &key lines) 99 | (when lines 100 | (iterate-elements lines (lambda (line) (insert-item (lines object) line))))) 101 | 102 | (defmethod print-object ((chunk chunk) stream) 103 | (print-unreadable-object (chunk stream :type t) 104 | (format stream "~a~A/~A ~D lines ~A ~A" 105 | (if (paragraph? chunk) "*" "") 106 | (markup-class chunk) 107 | (level chunk) 108 | (size (lines chunk)) 109 | (started-by chunk) 110 | (ended-by chunk)))) 111 | 112 | (defclass* chunk-parsing-environment () 113 | ((name nil ir) 114 | (chunk-enders nil ia) 115 | (chunk-starters nil ia) 116 | (line-coders nil ia) 117 | (parser-map nil ia))) 118 | 119 | (defclass* parsing-environment () 120 | ((chunk-parsing-environment (make-container 'stack-container) r) 121 | (chunk-post-processors nil ia) 122 | (chunk-level 0 ia) 123 | (current-strip "" ia) 124 | (line-code->stripper (make-container 'simple-associative-container 125 | :initial-element nil #+(or) 'null-stripper) r) 126 | (strippers (make-container 'stack-container) r))) 127 | 128 | (defun current-chunk-parser () 129 | (first-item (chunk-parsing-environment *parsing-environment*))) 130 | 131 | (defclass* basic-link-info () 132 | ((id nil ia))) 133 | 134 | (defclass* link-info (basic-link-info) 135 | ((url nil ir) 136 | (title nil ia) 137 | (properties nil ia))) 138 | 139 | (defmethod initialize-instance :after ((link link-info) &key properties) 140 | (when (stringp properties) 141 | (setf (properties link) 142 | (collect-window-over-elements 143 | (string->list properties) 2 2 144 | :transform 145 | (lambda (pair) (cons (form-keyword (first pair)) 146 | (second pair))))))) 147 | 148 | (defclass* extended-link-info (basic-link-info) 149 | ((kind nil ir) 150 | (contents nil ia))) 151 | 152 | (defmethod print-object ((object link-info) stream) 153 | (print-unreadable-object (object stream :type t :identity t) 154 | (format stream "~A -> ~A" (id object) (url object)))) 155 | -------------------------------------------------------------------------------- /dev/footnotes.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:cl-markdown) 2 | 3 | #| 4 | To do: 5 | 6 | - allow footnotes to appear on a completely separate page 7 | - do footnotes as a popup window with mouse over 8 | - handle footnotes 'out of band' a la links 9 | 10 | Footnotes 11 | {note foo} 12 | {note "This is a note"} 13 | {note "Foo"} 14 | {note This is a note} 15 | 16 | (markdown 17 | "That is what he thought.{footnote foo} 18 | 19 | [foo]> \"This is a longer note with 20 | linefeeds, *mark-up*, and \\\"escaped\\\" quotes. 21 | I'll be wicked surprised if it works out of the 22 | box.\" 23 | ") 24 | 25 | Need to 26 | 27 | 1. get a number 28 | 2. add link where the footnote starts 29 | 3. add anchor where the footnote starts 30 | 4. add footnote text at bottom of document / separate page 31 | 5. add link back to anchor in footnote 32 | 33 | Our footnote HTML is so heavily influenced by DF that you might think 34 | we just copied it all. 35 | 36 | (markdown " 37 | Maybe people{footnote Well, at least one person} find CL-Markdown 38 | to be the bees knees, the cats pajamas and the gnats goulash. In 39 | fact, if computers could dance, you could tell that one had 40 | CL-Markdown installed on it just by watching.{footnote Not really.} 41 | 42 | {footnotes} 43 | 44 | This was generated {today} at {now}.") 45 | |# 46 | 47 | (defclass* footnote-info () 48 | ((id nil ia) 49 | (text nil ia) 50 | (reference-name nil ia) 51 | (name nil ia))) 52 | 53 | (eval-when (:load-toplevel :execute) 54 | (setf *extensions* (remove 'footnote *extensions* :key #'first)) 55 | (push (list 'footnote t) *extensions*) 56 | (setf *extensions* (remove 'footnotes *extensions* :key #'first)) 57 | (push (list 'footnotes t) *extensions*)) 58 | 59 | ;; provides an example of using result during render phase 60 | (defun footnote (phase args result) 61 | ;; {documentation text} 62 | (let ((footnotes 63 | (or (document-property :footnote) 64 | (setf (document-property :footnote) 65 | (make-instance 'vector-container))))) 66 | (cond ((eq phase :parse) 67 | (let* ((text (format nil "~{~a ~}" args))) 68 | (when text 69 | (bind ((id (size footnotes)) 70 | (fn-basename 71 | (format nil "~d-~a" 72 | id 73 | (format-date "%Y-%m-%d" 74 | (document-property 75 | :date-modified 76 | (get-universal-time))))) 77 | (fn-name (format nil "fn~a" fn-basename)) 78 | (ref-name (format nil "fnr~a" fn-basename))) 79 | (insert-item footnotes 80 | (make-instance 81 | 'footnote-info 82 | :id id 83 | :name fn-name 84 | :reference-name ref-name 85 | :text text)) 86 | (values id))))) 87 | ((eq phase :render) 88 | (let ((footnote (item-at footnotes (first result)))) 89 | (output-anchor (reference-name footnote)) 90 | (format *output-stream* 91 | "~d" 92 | (name footnote) 93 | (1+ (id footnote)))))))) 94 | 95 | (defun footnotes (phase args result) 96 | (declare (ignore args result)) 97 | (ecase phase 98 | (:parse) 99 | (:render 100 | (unless (empty-p (document-property :footnote)) 101 | (format *output-stream* "~&
") 102 | (format *output-stream* "~&
    ") 103 | (iterate-elements 104 | (document-property :footnote) 105 | (lambda (footnote) 106 | (format *output-stream* "~&
  1. ") 107 | (output-anchor (name footnote)) 108 | (markdown (text footnote) 109 | :stream *output-stream* 110 | :format *current-format* 111 | :properties '((:html . nil) 112 | (:omit-final-paragraph . t) 113 | (:omit-initial-paragraph . t)) 114 | :document-class 'included-document) 115 | (format *output-stream* "
  2. "))) 121 | (format *output-stream* 122 | "~&
~&
"))))) 123 | 124 | ;; not yet 125 | #| 126 | (defun handle-footnote-links (document) 127 | (iterate-elements 128 | (chunks document) 129 | (lambda (chunk) 130 | (when (line-is-footnote-text-p) 131 | (bind (((values nil link-info) 132 | (scan-to-strings '(:sequence footnote-text) 133 | (first-element (lines chunk)))) 134 | (id (aref link-info 0)) 135 | (text (aref link-info 1))) 136 | (setf (item-at (link-info document) id) 137 | (make-instance 'footnote-text 138 | :id id :title text) 139 | (ignore? chunk) t))))) 140 | ;; now remove the unneeded chunks 141 | (removed-ignored-chunks? document) 142 | document) 143 | 144 | (defun line-is-footnote-text-p (line) 145 | (scan #.(ppcre:create-scanner '(:sequence footnote-text)) line)) 146 | 147 | (define-parse-tree-synonym 148 | footnote-label 149 | (:sequence 150 | :start-anchor 151 | (:greedy-repetition 0 3 :whitespace-char-class) 152 | bracketed 153 | #\> 154 | (:greedy-repetition 0 nil :whitespace-char-class) 155 | (:register 156 | (:alternation 157 | (:sequence 158 | #\" (:greedy-repetition 0 nil (:inverted-char-class #\") #\")) 159 | (:greedy-repetition 0 nil :everything))))) 160 | 161 | #+(or) 162 | (scan-to-strings 163 | (create-scanner 'footnote-label) 164 | " [a]> why are you here 165 | ok") 166 | 167 | #+(or) 168 | (scan-to-strings 169 | (create-scanner 'footnote-label) 170 | " [a]> \"why are you here? 171 | I am here because that is why. 172 | 173 | OK? ok!\"") 174 | 175 | |# -------------------------------------------------------------------------------- /website/source/user-guide.md: -------------------------------------------------------------------------------- 1 | {include resources/ug-header.md} 2 | {set-property title "CL-Markdown User's Guide"} 3 | 4 | # CL-Markdown - Quick Start 5 | 6 | {table-of-contents :start 2 :depth 3} 7 | 8 | CL-Markdown is an enhanced version of John Gruber's [Markdown][] text 9 | markup langauge. Markdown's goal is to keep text readable as *text* and 10 | as HTML. CL-Markdown keeps this principle and adds a flexible extension 11 | mechanism so that you can build complex documents easily. 12 | 13 | [Markdown]: http://daringfireball.net/projects/markdown/ 14 | 15 | 16 | ### Getting Started 17 | 18 | The easiest way to install CL-Markdown is using the 19 | [bundle][]. You can also use [ASDF-Install][], download 20 | tarballs or grab the sources directly (usings [darcs][]). If 21 | you do use the bundle, here is what you'd do: 22 | 23 | shell> cd 24 | shell> curl http://common-lisp.net/project/cl-markdown/cl-markdown-bundle.tar.gz > cl-markdown-bundle.tar.gz 25 | shell> tar -zxvf cl-markdown-bundle.tar.gz 26 | shell> lisp 27 | ;; Super Lisp 5.3 (just kidding) 28 | lisp: (require 'asdf) 29 | lisp: (load "cl-markdown-bundle/cl-markdown.asd") 30 | lisp: (asdf:oos 'asdf:load-op 'cl-markdown) 31 | lisp: (in-package cl-markdown) 32 | 33 | The top-level CL-Markdown command is `markdown`. It creates a 34 | `document` from a source (pathname, stream or string) and 35 | then sends the document to a stream in a `format`. The 36 | default format is `:html` and the default output is `t` 37 | (which sends the output to `*standard-output*`.). You can use 38 | an already open stream for output, provide a pathname to a 39 | file (which will be overwritten!) or use the symbol `nil` to 40 | direct output to a new stream. At this time, support for 41 | formats other than HTML is not provided. For example: 42 | 43 | lisp: (markdown "# Hello *there*") 44 | "

Hello there

" 45 | 46 | CL-Markdown implements most of John Gruber's 47 | [specification][markdown-specification] (though it does not 48 | yet handle e-mails and some edges cases). It also adds a new 49 | syntax for extensions. 50 | 51 | [markdown-specification]: http://daringfireball.net/projects/markdown/syntax 52 | 53 | ### Function calls: \{ and \} 54 | 55 | Calling extension functions requires three things: 56 | 57 | 1. writing (or finding) the extension that you want 58 | 2. telling CL-Markdown that you want to use the extension 59 | 3. writing your Markdown text with calls to the extension 60 | 61 | The last part is the easiest; all you need to do is open a 62 | curly brace, type the name of extension function, type in the 63 | arguments (separated by spaces) and type a closing curly 64 | brace. For example: 65 | 66 | "\{now\}" will generate the text "{now}". 67 | 68 | The second step is necessary because CL-Markdown won't 69 | recognize functions as functions unless you tell it to up 70 | front. After all, you wouldn't want to allow people to 71 | execute arbitrary code; it **might** be a security risk 72 | (smile). Because CL-Markdown operates in two stages, there 73 | are two times when functions can be called: during parsing 74 | and during rendering. Functions active during these stages 75 | are keep in the special variables `*render-active-functions*` 76 | and `*parse-active-functions*`. 77 | 78 | An example might make this clearer. First, we'll call Markdown 79 | without specifying any functions: 80 | 81 | ? (markdown "Today is {today}. It is {now}." 82 | :format :html :stream t) 83 |

Today is 84 | ; Warning: Inactive or undefined CL-Markdown function TODAY 85 | ; While executing: # 86 | . It is 87 | ; Warning: Inactive or undefined CL-Markdown function NOW 88 | ; While executing: # 89 | .

90 | 91 | As you can see, the functions weren't ones that CL-Markdown was ready 92 | to recognize, so we got warnings and no text was generated. If we 93 | tell CL-Markdown that `today` and `now` should be treated as 94 | functions, then we see a far prettier picture: 95 | 96 | ? (let ((*render-active-functions* 97 | (append '(today now) *render-active-functions*))) 98 | (markdown "Today is {today}. It is {now}." 99 | :format :html :stream t)) 100 |

Today is 1 August 2006. It is 11:36.

101 | 102 | By now, we've seen how to include function calls in CL-Markdown 103 | documents and how to generate those documents with CL-Markdown. The 104 | final piece of the puzzle is actually writing the extensions. 105 | 106 | 107 | #### Writing Cl-Markdown extensions 108 | 109 | There are several ways to write extensions. {footnote 110 | Extensions beg for a little {abbrev DSL Domain Specific 111 | Language} but those macros are still to be written.} The 112 | easiest is one is to write functions active during rendering 113 | that return the text that you wish to be included in the 114 | document. For example: 115 | 116 | (defun today (phase arguments result) 117 | (declare (ignore phase arguments result)) 118 | (format-date "%e %B %Y" (get-universal-time))) 119 | 120 | The format-date command is part of [metatilities-base][]; it 121 | returns a string of the date using the C library inspired 122 | date format. This string is placed in the document whereever 123 | the function call (\{today\}) is found. 124 | 125 | [metatilities]: 126 | 127 | Alternately, one can use the `*output-stream*` variable to 128 | insert more complicated text. This would look like: 129 | 130 | (defun now (phase arguments result) 131 | (declare (ignore phase arguments result)) 132 | (format *output-stream* "~a" 133 | (format-date "%H:%M" (get-universal-time))) 134 | nil) 135 | 136 | (Note that `now` returns `nil` so that the date isn't inserted 137 | twice!). 138 | 139 | The other alternative is to use your function calls to alter 140 | the structure of the CL-Markdown document and then let 141 | Markdown deal with some or all of the rest. The `anchor` 142 | extension provides an example of this: 143 | 144 | (defun anchor (phase &rest args) 145 | (ecase phase 146 | (:parse 147 | (let ((name (caar args)) 148 | (title (cadar args))) 149 | (setf (item-at (link-info *current-document*) name) 150 | (make-instance 'link-info 151 | :id name :url (format nil "#~a" name) 152 | :title (or title ""))))) 153 | (:render (let ((name (caar args))) 154 | (format nil "" 155 | name name))))) 156 | 157 | `Anchor` makes it easier to insert anchors into your document 158 | and to link to those anchors from elsewhere. It is active 159 | during both parsing and rendering. During the parsing phase, 160 | it uses it's arguments to determine the name and title of the 161 | link and places this into the current document's link 162 | information table. During rendering, it outputs the HTML 163 | needed to mark the link. {footnote If you would like to see 164 | more examples, look in the files `extensions.lisp` or 165 | `footnotes.lisp`.} 166 | 167 |
168 | 169 | {footnotes} 170 | 171 | {include resources/ug-footer.md} 172 | -------------------------------------------------------------------------------- /dev/extension-mechanisms.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:cl-markdown) 2 | 3 | #| 4 | extensions should have a unique name and a priority (as should the built-ins) 5 | |# 6 | 7 | ;;?? only add once 8 | (defun add-extension (extension &key (filter (constantly t))) 9 | (iterate-key-value 10 | *spanner-parsing-environments* 11 | (lambda (key value) 12 | (when (funcall filter key) 13 | (insert-new-item value extension))))) 14 | 15 | #| 16 | (markdown "Hello {user-name :format :long}, how are you. Go {{here}}." :format :none) 17 | ==> '("Hello " 18 | (EVAL "user-name :format :long") 19 | ", how are you. Go " 20 | (MARKDOWN::WIKI-LINK "here") 21 | ". ") 22 | 23 | (let ((*render-active-functions* 24 | (append '(today now) *render-active-functions*))) 25 | (markdown "Today is {today}. It is {now}." 26 | :format :html :stream t)) 27 | 28 | |# 29 | 30 | (define-parse-tree-synonym 31 | wiki-link (:sequence 32 | #\{ #\{ 33 | (:register (:greedy-repetition 0 nil (:inverted-char-class #\}))) 34 | #\} #\})) 35 | 36 | (define-parse-tree-synonym 37 | eval (:sequence 38 | #\{ 39 | (:register (:greedy-repetition 0 nil (:inverted-char-class #\}))) 40 | #\})) 41 | 42 | (define-parse-tree-synonym 43 | eval-in-code (:sequence 44 | #\{ #\{ 45 | (:register (:greedy-repetition 0 nil (:inverted-char-class #\}))) 46 | #\} #\})) 47 | 48 | ;; should only happen once! (but need names to do this correctly) 49 | (eval-when (:load-toplevel :execute) 50 | #+(or) 51 | (add-extension (list (create-scanner '(:sequence wiki-link)) 'wiki-link) 52 | :filter (lambda (key) (not (equal key '(code))))) 53 | (add-extension 54 | (make-markdown-scanner 55 | :regex (create-scanner '(:sequence eval)) 56 | :name 'eval 57 | :priority 1.5) 58 | :filter 59 | (lambda (key) (not (equal key '(code))))) 60 | (add-extension 61 | (make-markdown-scanner 62 | :regex (create-scanner '(:sequence eval-in-code)) 63 | :name 'code-eval 64 | :priority 1.5) 65 | :filter 66 | (lambda (key) (equal key '(code))))) 67 | 68 | (defmethod render-span-to-html ((code (eql 'eval)) body encoding-method) 69 | (declare (ignore encoding-method)) 70 | (render-handle-eval body)) 71 | 72 | (defmethod render-span-to-html ((code (eql 'code-eval)) body encoding-method) 73 | (declare (ignore encoding-method)) 74 | (render-handle-eval body)) 75 | 76 | (defun render-handle-eval (body) 77 | ;;?? parse out commands and arguments (deal with quoting, etc) 78 | (bind (((command arguments result _) body) 79 | (result 80 | (cond ((and (member command *render-active-functions*) 81 | (fboundp command)) 82 | (funcall command :render arguments (ensure-list result))) 83 | ((and (member command *render-active-functions*) 84 | (not (fboundp command))) 85 | (warn "Undefined CL-Markdown function ~s" command)) 86 | (t 87 | nil)))) 88 | (when result 89 | (output-html (list result)) 90 | (setf *magic-space-p* nil) 91 | (setf *magic-line-p* -1)))) 92 | 93 | (defun canonize-command (command) 94 | (intern (symbol-name command) 95 | (load-time-value (find-package :cl-markdown-user)))) 96 | 97 | (defmethod process-span ((name (eql 'eval)) registers) 98 | ;; the one register contains the command and the buffer index. 99 | (bind (((command &rest args) 100 | (%pull-arguments-from-string (first registers))) 101 | (buffer-index (and args (fixnump (first args)) (first args)))) 102 | (process-handle-eval 103 | command 104 | (or (and buffer-index 105 | (%pull-arguments-from-string 106 | (item-at (bracket-references *current-document*) 107 | buffer-index))) 108 | args)))) 109 | 110 | (defmethod process-span ((name (eql 'code-eval)) registers) 111 | ;;; the one register contains the command and all its arguments as one 112 | ;; big string we tokenize it and make sure the command exists and, if 113 | ;; it is 'active' during parsing, we call it for effect. 114 | (bind (((command &rest arguments) 115 | (%pull-arguments-from-string (first registers)))) 116 | (process-handle-eval command arguments))) 117 | 118 | (defun process-handle-eval (command arguments) 119 | (bind ((command (canonize-command command)) 120 | ((:values result processed?) 121 | (when (member command *parse-active-functions*) 122 | (if (fboundp command) 123 | (values (funcall command :parse arguments nil) t) 124 | (warn "Undefined CL-Markdown parse active function ~s" 125 | command))))) 126 | #+(or) 127 | (format t "~&~s: ~s ~a ~a" 128 | command (symbol-package command) (fboundp command) 129 | (member command *parse-active-functions*) result) 130 | `(,command ,arguments ,result ,processed?))) 131 | 132 | (defmethod process-span-in-span-p ((span-1 t) (span-2 (eql 'eval))) 133 | (values nil)) 134 | 135 | (defmethod process-span-in-span-p ((span-1 t) (span-2 (eql 'code-eval))) 136 | (values nil)) 137 | 138 | (defun %pull-arguments-from-string (string) 139 | (let ((start 0) 140 | (done (load-time-value (list :eof))) 141 | (result nil)) 142 | (loop collect 143 | (multiple-value-bind (value new-start) 144 | (ignore-errors (read-from-string string nil done :start start)) 145 | (when (eq value done) 146 | (return)) 147 | (cond ((and new-start (numberp new-start)) 148 | (setf start new-start) 149 | (push value result)) 150 | (t 151 | (incf start))))) 152 | (nreverse result))) 153 | 154 | ;;;;; 155 | 156 | #| Another extension mechanism 157 | 158 | |# 159 | 160 | (defmethod generate-link-output-for-kind 161 | ((kind (eql :glossary)) (link-info extended-link-info) text) 162 | (let ((text (if (consp text) (first text) text))) 163 | (format *output-stream* 164 | "~a" 165 | (id link-info) 166 | text 167 | text))) 168 | 169 | (defextension (glossary) 170 | (when (eq phase :render) 171 | (format *output-stream* "~&
") 172 | (format *output-stream* "~&
") 173 | (iterate-key-value 174 | (link-info *current-document*) 175 | (lambda (key link) 176 | (when (and (typep link 'extended-link-info) 177 | (eq (kind link) :glossary)) 178 | (format *output-stream* "~&
~a
" key (id link)) 179 | (markdown (contents link) 180 | :stream *output-stream* 181 | :format *current-format* 182 | :properties '((:html . nil) 183 | (:omit-final-paragraph . t) 184 | (:omit-initial-paragraph . t)) 185 | :document-class 'included-document) 186 | (format *output-stream* "
")))) 187 | (format *output-stream* "
~%
~%"))) 188 | 189 | ;;; sort of works 190 | ;; can't use html in title 191 | (defmethod generate-link-output-for-kind 192 | ((kind (eql :abbreviation)) (link-info extended-link-info) text) 193 | (let ((output (nth-value 1 (markdown (contents link-info) 194 | :stream nil 195 | :document-class 'included-document)))) 196 | (format *output-stream* "~a" 197 | output 198 | text))) 199 | 200 | -------------------------------------------------------------------------------- /dev/dead-code/lml2.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:cl-markdown) 2 | 3 | (defparameter *markup->lml2* 4 | (make-container 5 | 'simple-associative-container 6 | :test #'equal 7 | :initial-contents 8 | '((header1) (nil :h1) 9 | (header2) (nil :h2) 10 | (header3) (nil :h3) 11 | (header4) (nil :h4) 12 | (header5) (nil :h5) 13 | (header6) (nil :h6) 14 | 15 | (bullet) ((:ul) :li) 16 | (code) ((:pre :code) nil) 17 | (number) ((:ol) :li) 18 | (quote) ((:blockquote) nil) 19 | (horizontal-rule) (nil :hr)))) 20 | 21 | ;;; --------------------------------------------------------------------------- 22 | 23 | (defmethod render ((document document) (style (eql :lml2)) stream) 24 | (let ((*current-document* document)) 25 | (setf (level document) 0 26 | (markup document) nil) 27 | (let* ((chunks (collect-elements (chunks document))) 28 | (result (lml2-list->tree chunks))) 29 | (if stream 30 | (format stream "~S" result) 31 | result)))) 32 | 33 | ;;; --------------------------------------------------------------------------- 34 | 35 | (defun lml2-marker (chunk) 36 | (bind ((markup (markup-class-for-lml2 chunk))) 37 | (first markup))) 38 | 39 | ;;; --------------------------------------------------------------------------- 40 | 41 | (defmethod render-to-lml2 ((chunk chunk)) 42 | (bind ((block (collect-elements 43 | (lines chunk) 44 | :transform (lambda (line) 45 | (render-to-lml2 line)))) 46 | (markup (second (markup-class-for-lml2 chunk))) 47 | (paragraph? (paragraph? chunk))) 48 | (cond ((and paragraph? markup) 49 | (values `(,markup (:P ,@block)) t)) 50 | (paragraph? 51 | (values `(:P ,@block) t)) 52 | (markup 53 | (values `(,markup ,@block) t)) 54 | (t 55 | (values block nil))))) 56 | 57 | ;;; --------------------------------------------------------------------------- 58 | 59 | (defmethod markup-class-for-lml2 ((chunk chunk)) 60 | (when (markup-class chunk) 61 | (let ((translation (item-at-1 *markup->lml2* (markup-class chunk)))) 62 | (unless translation 63 | (warn "No translation for '~A'" (markup-class chunk))) 64 | translation))) 65 | 66 | ;;; --------------------------------------------------------------------------- 67 | 68 | (defmethod render-to-lml2 ((chunk list)) 69 | (render-span-to-lml2 (first chunk) (rest chunk))) 70 | 71 | ;;; --------------------------------------------------------------------------- 72 | 73 | (defmethod render-to-lml2 ((chunk string)) 74 | ;;?? unlovely 75 | (format nil "~A" chunk)) 76 | 77 | ;;; --------------------------------------------------------------------------- 78 | 79 | (defmethod render-span-to-lml2 ((code (eql 'strong)) body) 80 | `(:strong ,@body)) 81 | 82 | ;;; --------------------------------------------------------------------------- 83 | 84 | (defmethod render-span-to-lml2 ((code (eql 'mail)) body) 85 | (let ((address (first body))) 86 | `((:a :href ,(format nil "mailto:~A" address)) ,address))) 87 | 88 | ;;; --------------------------------------------------------------------------- 89 | 90 | (defmethod render-span-to-lml2 ((code (eql 'emphasis)) body) 91 | `(:em ,@body)) 92 | 93 | ;;; --------------------------------------------------------------------------- 94 | 95 | (defmethod render-span-to-lml2 ((code (eql 'strong-em)) body) 96 | `(:strong (:em ,@body))) 97 | 98 | ;;; --------------------------------------------------------------------------- 99 | 100 | (defmethod render-span-to-lml2 ((code (eql 'code)) body) 101 | `(:code ,(render-to-lml2 (first body)))) 102 | 103 | ;;; --------------------------------------------------------------------------- 104 | 105 | (defmethod render-span-to-lml2 ((code (eql 'entity)) body) 106 | (first body)) 107 | 108 | ;;; --------------------------------------------------------------------------- 109 | 110 | (defmethod render-span-to-lml2 ((code (eql 'reference-link)) body) 111 | (bind (((text &optional (id text)) body) 112 | (link-info (item-at-1 (link-info *current-document*) id))) 113 | (if link-info 114 | `((:a :href ,(url link-info) ,@(awhen (title link-info) `(:title ,it))) 115 | ,text) 116 | `,text))) 117 | 118 | ;;; --------------------------------------------------------------------------- 119 | 120 | (defmethod render-span-to-lml2 ((code (eql 'inline-link)) body) 121 | (bind (((text &optional (url "") title) body)) 122 | `((:a :href ,url ,@(awhen title `(:title ,it))) 123 | ,text))) 124 | 125 | ;;; --------------------------------------------------------------------------- 126 | 127 | (defmethod render-span-to-lml2 ((code (eql 'link)) body) 128 | (bind ((url body)) 129 | `((:a :href ,@url) ,@url))) 130 | 131 | ;;; --------------------------------------------------------------------------- 132 | 133 | (defmethod render-span-to-lml2 ((code (eql 'html)) body) 134 | (html-encode:encode-for-pre (first body))) 135 | 136 | 137 | 138 | (defun lml2-list->tree (chunks &key (level nil)) 139 | (unless level 140 | (setf level (or (and (first chunks) (level (first chunks))) 0))) 141 | 142 | (labels ((do-it (chunks level) 143 | 144 | ;;?? rather inpenetrable... don't understand at the level I should... 145 | (apply-mark 146 | (lml2-marker (first chunks)) 147 | (let (output append? result) 148 | (loop for rest = chunks then (rest rest) 149 | for chunk = (first rest) then (first rest) 150 | while chunk 151 | for new-level = (level chunk) 152 | 153 | do (setf (values output append?) (render-to-lml2 chunk)) 154 | 155 | do (format t "~%C(~D/~D): ~A, ~A" level new-level append? chunk) 156 | 157 | when (and (= level new-level) append?) do 158 | (setf result `(,output ,@result)) 159 | 160 | when (and (= level new-level) (not append?)) do 161 | (setf result `(,@output ,@result)) 162 | 163 | when (< level new-level) do 164 | (multiple-value-bind (block remaining method) 165 | (next-block rest new-level) 166 | (let ((inner (do-it block (1+ level)))) 167 | ; (format t "~%--- ~A" method) 168 | (setf rest remaining) 169 | (ecase method 170 | (:level (if (listp (first result)) 171 | (push-end inner (first result)) 172 | (push inner result))) 173 | (:markup (push inner result)) 174 | (:none 175 | (setf result `(,inner ,@result)))))) 176 | 177 | when (> level new-level) do 178 | (warn "unexpected chunk level")) 179 | (reverse result))))) 180 | (apply #'do-it chunks level))) 181 | 182 | ;;; --------------------------------------------------------------------------- 183 | 184 | (defun apply-mark (mark rest) 185 | (cond ((null mark) rest) 186 | ((consp mark) 187 | (if (length-1-list-p mark) 188 | `(,(first mark) ,@(apply-mark (rest mark) rest)) 189 | `(,(first mark) (,@(apply-mark (rest mark) rest))))) 190 | (t 191 | (error "unhandled case")))) 192 | 193 | 194 | 195 | -------------------------------------------------------------------------------- /dev/multiple-documents.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:cl-markdown) 2 | 3 | (defun markdown-many (pairs &rest args 4 | &key format additional-extensions render-extensions 5 | &allow-other-keys) 6 | "Markdown-many processes several documents simultaneously as if it 7 | was processing one large document. Its chief purpose is to make it easy to 8 | create inter-document links. Markdown-many takes as input 9 | 10 | * `pairs` - a list of lists where each sublist contains the markdown 11 | file to be processed as `input` in its first element and the name of 12 | the file to be produced as the `output`. 13 | * `:format` - a keyword argument specifying the kind of output document 14 | to produce 15 | * `:additional-extensions` - a list of extensions that should be active 16 | both while parsing and rendering. 17 | * `:render-extensions` - a list of extensions that should be active 18 | during rendering. 19 | 20 | Here is an example: suppose document-1.md contains 21 | 22 | # Document-1 23 | 24 | See [document-2][] for details. 25 | 26 | and document-2.md contains 27 | 28 | # Document 2 29 | 30 | [Document-1][] provides an overview. 31 | 32 | Getting these links to work using only Markdown will require added explicit 33 | reference link information that will be tied to the file _names_. Markdown-many, 34 | on the other hand, will automatically combine the link information and 35 | processes it automatically. 36 | " 37 | (let ((main-document (make-instance 'multi-document)) 38 | (docs nil)) 39 | (setf docs 40 | (loop for datum in pairs collect 41 | (bind (((source destination &rest doc-args) datum)) 42 | (format t "~&Parsing: ~s~%" source) 43 | (list (apply #'markdown source 44 | :document-class 'child-document 45 | :parent main-document 46 | :format :none (merge-arguments args doc-args)) 47 | destination)))) 48 | ;; transfer information from docs to the parent 49 | (loop for (doc destination) in docs do 50 | (transfer-document-data main-document doc destination)) 51 | ;; render 'em 52 | (loop for (doc destination) in docs do 53 | (format t "~&Rendering: ~s" destination) 54 | (let ((*current-document* doc) 55 | (*render-active-functions* 56 | (mapcar #'canonize-command 57 | (or render-extensions 58 | (if additional-extensions 59 | `(,@additional-extensions 60 | ,@*render-active-functions*) 61 | *render-active-functions*))))) 62 | (render-to-stream doc format destination))) 63 | (setf (children main-document) (mapcar #'first docs)) 64 | (values main-document docs))) 65 | 66 | (defun merge-arguments (args-1 args-2) 67 | (let ((result args-1)) 68 | (map-window-over-elements 69 | args-2 2 2 70 | (lambda (pair) 71 | (bind (((key value) pair) 72 | (present (getf result key))) 73 | (setf (getf result key) 74 | (if present 75 | (append (ensure-list present) (ensure-list value)) 76 | value))))) 77 | result)) 78 | 79 | #+(or) 80 | (merge-arguments '(:a (1) :b (2)) '(:c (3) :a (2))) 81 | 82 | #+(or) 83 | (defun _render-one (doc) 84 | (let ((*current-document* doc) 85 | (*render-active-functions* 86 | (mapcar #'canonize-command 87 | `(cl-markdown::docs cl-markdown::docs-index 88 | cl-markdown::today cl-markdown::now 89 | cl-markdown::glossary 90 | ,@*render-active-functions*)))) 91 | (render-to-stream doc :html #p"/tmp/one.html"))) 92 | 93 | #+(or) 94 | (untrace markdown) 95 | 96 | #+(or) 97 | (compile 'markdown-many) 98 | 99 | #+(or) 100 | (cl-markdown:markdown-many 101 | `((,(system-relative-pathname 'cl-markdown "dev/md1.md") 102 | ,(system-relative-pathname 'cl-markdown "dev/md1.html")) 103 | (,(system-relative-pathname 'cl-markdown "dev/md2.md") 104 | ,(system-relative-pathname 'cl-markdown "dev/md2.html"))) 105 | :format :html) 106 | 107 | (defun transfer-document-data (parent child destination) 108 | (transfer-link-info parent child destination) 109 | (transfer-selected-properties 110 | parent child 111 | (set-difference (collect-keys (properties child)) 112 | (list :footnote :style-sheet :style-sheets :title))) 113 | (transfer-document-metadata parent child)) 114 | 115 | (defun transfer-document-metadata (parent child) 116 | (iterate-key-value 117 | (metadata child) 118 | (lambda (key value) 119 | ; (print (list :p (item-at-1 (metadata parent) key) 120 | ; :c value)) 121 | (aif (item-at-1 (metadata parent) key) 122 | (setf (item-at-1 (metadata parent) key) (merge-entries it value)) 123 | (setf (item-at-1 (metadata parent) key) value))))) 124 | 125 | 126 | (defun transfer-selected-properties (parent child properties) 127 | (let ((*current-document* parent)) 128 | (iterate-elements 129 | properties 130 | (lambda (property) 131 | (when (item-at-1 (properties child) property) 132 | (setf (document-property property) 133 | (first (item-at-1 (properties child) property)))))))) 134 | 135 | (defun transfer-link-info (parent child destination) 136 | (let ((*current-document* parent)) 137 | (iterate-key-value 138 | (link-info child) 139 | (lambda (id info) 140 | (setf (item-at (link-info parent) id) 141 | (transfer-1-link-info info parent child destination)))))) 142 | 143 | (defgeneric transfer-1-link-info (info parent child destination)) 144 | 145 | (defmethod transfer-1-link-info ((info link-info) parent child destination) 146 | (declare (ignore parent child)) 147 | (make-instance 'link-info 148 | :id (id info) 149 | :url (if (relative-url-p (url info)) 150 | (format nil "~@[~a~]~@[.~a~]~a" 151 | (pathname-name destination) 152 | (pathname-type destination) 153 | (url info)) 154 | (url info)) 155 | :title (title info) 156 | :properties (properties info))) 157 | 158 | (defun relative-url-wrt-destination (url destination) 159 | (if (relative-url-p url) 160 | (format nil "~@[~a~]~@[.~a~]~a" 161 | (pathname-name destination) 162 | (pathname-type destination) 163 | url) 164 | url)) 165 | 166 | (defun relative-url-p (url) 167 | ;; FIXME -- look at the spec... 168 | (not 169 | (or (starts-with url "http:") 170 | (starts-with url "mailto:") 171 | (starts-with url "file:")))) 172 | 173 | (defmethod transfer-1-link-info ((info extended-link-info) 174 | parent child destination) 175 | (declare (ignore parent child destination)) 176 | (make-instance 'extended-link-info 177 | :id (id info) 178 | :kind (kind info) 179 | :contents (contents info))) 180 | 181 | 182 | ;;; 183 | 184 | 185 | ;; A slightly horrid hack that is good enough for indices but 186 | ;; completely untested 187 | (defgeneric ugly-create-from-template (thing) 188 | ) 189 | 190 | (defmethod ugly-create-from-template ((thing standard-object)) 191 | (make-instance (class-of thing))) 192 | 193 | (defgeneric merge-entries (a b) 194 | (:documentation "Returns a new container C \(of the same type as `a`\) 195 | such that C contains every *entry* in a and b. C may share structure with 196 | `a` and `b`.")) 197 | 198 | (defmethod merge-entries :around ((a t) (b t)) 199 | ; (print (list :me a b)) 200 | (call-next-method)) 201 | 202 | (defmethod merge-entries ((a null) (b t)) 203 | b) 204 | 205 | (defmethod merge-entries ((a null) (b iteratable-container-mixin)) 206 | (error "not implemented")) 207 | 208 | (defmethod merge-entries ((a null) (b key-value-iteratable-container-mixin)) 209 | (merge-using-key-value (ugly-create-from-template b) b)) 210 | 211 | (defmethod merge-entries ((a t) (b t)) 212 | (cond ((and (key-value-iteratable-p a) 213 | (key-value-iteratable-p b)) 214 | #+(or) 215 | (merge-key-value-via-iteration a b) 216 | (error "not implemented")) 217 | ((and (iteratable-p a) 218 | (iteratable-p b)) 219 | (merge-elements-via-iteration a b)) 220 | (t 221 | ;; FIXME - drop b? 222 | a))) 223 | 224 | (defmethod merge-entries ((a list) (b t)) 225 | (append a (list b))) 226 | 227 | (defmethod merge-entries ((a list) (b list)) 228 | (merge-elements-via-iteration a b)) 229 | 230 | (defmethod merge-entries ((a iteratable-container-mixin) 231 | (b iteratable-container-mixin)) 232 | (merge-elements-via-iteration a b)) 233 | 234 | (defmethod merge-entries 235 | ((a key-value-iteratable-container-mixin) 236 | (b key-value-iteratable-container-mixin)) 237 | (let ((new (ugly-create-from-template a))) 238 | (merge-using-key-value new a) 239 | (merge-using-key-value new b) 240 | new)) 241 | 242 | (defun merge-elements-via-iteration (a b) 243 | (let ((new (ugly-create-from-template a))) 244 | (iterate-elements a (lambda (elt) (insert-item new elt))) 245 | (iterate-elements b (lambda (elt) (insert-item new elt))) 246 | new)) 247 | 248 | (defun merge-using-key-value (a b) 249 | (iterate-key-value b (lambda (key value) 250 | (let ((existing (item-at a key))) 251 | (setf (item-at a key) 252 | (if existing 253 | (merge-entries existing value) 254 | value))))) 255 | a) 256 | -------------------------------------------------------------------------------- /unit-tests/markdown-tests/Markdown Documentation - Basics.text: -------------------------------------------------------------------------------- 1 | Markdown: Basics 2 | ================ 3 | 4 | 11 | 12 | 13 | Getting the Gist of Markdown's Formatting Syntax 14 | ------------------------------------------------ 15 | 16 | This page offers a brief overview of what it's like to use Markdown. 17 | The [syntax page] [s] provides complete, detailed documentation for 18 | every feature, but Markdown should be very easy to pick up simply by 19 | looking at a few examples of it in action. The examples on this page 20 | are written in a before/after style, showing example syntax and the 21 | HTML output produced by Markdown. 22 | 23 | It's also helpful to simply try Markdown out; the [Dingus] [d] is a 24 | web application that allows you type your own Markdown-formatted text 25 | and translate it to XHTML. 26 | 27 | **Note:** This document is itself written using Markdown; you 28 | can [see the source for it by adding '.text' to the URL] [src]. 29 | 30 | [s]: /projects/markdown/syntax "Markdown Syntax" 31 | [d]: /projects/markdown/dingus "Markdown Dingus" 32 | [src]: /projects/markdown/basics.text 33 | 34 | 35 | ## Paragraphs, Headers, Blockquotes ## 36 | 37 | A paragraph is simply one or more consecutive lines of text, separated 38 | by one or more blank lines. (A blank line is any line that looks like a 39 | blank line -- a line containing nothing spaces or tabs is considered 40 | blank.) Normal paragraphs should not be intended with spaces or tabs. 41 | 42 | Markdown offers two styles of headers: *Setext* and *atx*. 43 | Setext-style headers for `

` and `

` are created by 44 | "underlining" with equal signs (`=`) and hyphens (`-`), respectively. 45 | To create an atx-style header, you put 1-6 hash marks (`#`) at the 46 | beginning of the line -- the number of hashes equals the resulting 47 | HTML header level. 48 | 49 | Blockquotes are indicated using email-style '`>`' angle brackets. 50 | 51 | Markdown: 52 | 53 | A First Level Header 54 | ==================== 55 | 56 | A Second Level Header 57 | --------------------- 58 | 59 | Now is the time for all good men to come to 60 | the aid of their country. This is just a 61 | regular paragraph. 62 | 63 | The quick brown fox jumped over the lazy 64 | dog's back. 65 | 66 | ### Header 3 67 | 68 | > This is a blockquote. 69 | > 70 | > This is the second paragraph in the blockquote. 71 | > 72 | > ## This is an H2 in a blockquote 73 | 74 | 75 | Output: 76 | 77 |

A First Level Header

78 | 79 |

A Second Level Header

80 | 81 |

Now is the time for all good men to come to 82 | the aid of their country. This is just a 83 | regular paragraph.

84 | 85 |

The quick brown fox jumped over the lazy 86 | dog's back.

87 | 88 |

Header 3

89 | 90 |
91 |

This is a blockquote.

92 | 93 |

This is the second paragraph in the blockquote.

94 | 95 |

This is an H2 in a blockquote

96 |
97 | 98 | 99 | 100 | ### Phrase Emphasis ### 101 | 102 | Markdown uses asterisks and underscores to indicate spans of emphasis. 103 | 104 | Markdown: 105 | 106 | Some of these words *are emphasized*. 107 | Some of these words _are emphasized also_. 108 | 109 | Use two asterisks for **strong emphasis**. 110 | Or, if you prefer, __use two underscores instead__. 111 | 112 | Output: 113 | 114 |

Some of these words are emphasized. 115 | Some of these words are emphasized also.

116 | 117 |

Use two asterisks for strong emphasis. 118 | Or, if you prefer, use two underscores instead.

119 | 120 | 121 | 122 | ## Lists ## 123 | 124 | Unordered (bulleted) lists use asterisks, pluses, and hyphens (`*`, 125 | `+`, and `-`) as list markers. These three markers are 126 | interchangable; this: 127 | 128 | * Candy. 129 | * Gum. 130 | * Booze. 131 | 132 | this: 133 | 134 | + Candy. 135 | + Gum. 136 | + Booze. 137 | 138 | and this: 139 | 140 | - Candy. 141 | - Gum. 142 | - Booze. 143 | 144 | all produce the same output: 145 | 146 |
    147 |
  • Candy.
  • 148 |
  • Gum.
  • 149 |
  • Booze.
  • 150 |
151 | 152 | Ordered (numbered) lists use regular numbers, followed by periods, as 153 | list markers: 154 | 155 | 1. Red 156 | 2. Green 157 | 3. Blue 158 | 159 | Output: 160 | 161 |
    162 |
  1. Red
  2. 163 |
  3. Green
  4. 164 |
  5. Blue
  6. 165 |
166 | 167 | If you put blank lines between items, you'll get `

` tags for the 168 | list item text. You can create multi-paragraph list items by indenting 169 | the paragraphs by 4 spaces or 1 tab: 170 | 171 | * A list item. 172 | 173 | With multiple paragraphs. 174 | 175 | * Another item in the list. 176 | 177 | Output: 178 | 179 |

    180 |
  • A list item.

    181 |

    With multiple paragraphs.

  • 182 |
  • Another item in the list.

  • 183 |
184 | 185 | 186 | 187 | ### Links ### 188 | 189 | Markdown supports two styles for creating links: *inline* and 190 | *reference*. With both styles, you use square brackets to delimit the 191 | text you want to turn into a link. 192 | 193 | Inline-style links use parentheses immediately after the link text. 194 | For example: 195 | 196 | This is an [example link](http://example.com/). 197 | 198 | Output: 199 | 200 |

This is an 201 | example link.

202 | 203 | Optionally, you may include a title attribute in the parentheses: 204 | 205 | This is an [example link](http://example.com/ "With a Title"). 206 | 207 | Output: 208 | 209 |

This is an 210 | example link.

211 | 212 | Reference-style links allow you to refer to your links by names, which 213 | you define elsewhere in your document: 214 | 215 | I get 10 times more traffic from [Google][1] than from 216 | [Yahoo][2] or [MSN][3]. 217 | 218 | [1]: http://google.com/ "Google" 219 | [2]: http://search.yahoo.com/ "Yahoo Search" 220 | [3]: http://search.msn.com/ "MSN Search" 221 | 222 | Output: 223 | 224 |

I get 10 times more traffic from Google than from Yahoo or MSN.

228 | 229 | The title attribute is optional. Link names may contain letters, 230 | numbers and spaces, but are *not* case sensitive: 231 | 232 | I start my morning with a cup of coffee and 233 | [The New York Times][NY Times]. 234 | 235 | [ny times]: http://www.nytimes.com/ 236 | 237 | Output: 238 | 239 |

I start my morning with a cup of coffee and 240 | The New York Times.

241 | 242 | 243 | ### Images ### 244 | 245 | Image syntax is very much like link syntax. 246 | 247 | Inline (titles are optional): 248 | 249 | ![alt text](/path/to/img.jpg "Title") 250 | 251 | Reference-style: 252 | 253 | ![alt text][id] 254 | 255 | [id]: /path/to/img.jpg "Title" 256 | 257 | Both of the above examples produce the same output: 258 | 259 | alt text 260 | 261 | 262 | 263 | ### Code ### 264 | 265 | In a regular paragraph, you can create code span by wrapping text in 266 | backtick quotes. Any ampersands (`&`) and angle brackets (`<` or 267 | `>`) will automatically be translated into HTML entities. This makes 268 | it easy to use Markdown to write about HTML example code: 269 | 270 | I strongly recommend against using any `` tags. 271 | 272 | I wish SmartyPants used named entities like `—` 273 | instead of decimal-encoded entites like `—`. 274 | 275 | Output: 276 | 277 |

I strongly recommend against using any 278 | <blink> tags.

279 | 280 |

I wish SmartyPants used named entities like 281 | &mdash; instead of decimal-encoded 282 | entites like &#8212;.

283 | 284 | 285 | To specify an entire block of pre-formatted code, indent every line of 286 | the block by 4 spaces or 1 tab. Just like with code spans, `&`, `<`, 287 | and `>` characters will be escaped automatically. 288 | 289 | Markdown: 290 | 291 | If you want your page to validate under XHTML 1.0 Strict, 292 | you've got to put paragraph tags in your blockquotes: 293 | 294 |
295 |

For example.

296 |
297 | 298 | Output: 299 | 300 |

If you want your page to validate under XHTML 1.0 Strict, 301 | you've got to put paragraph tags in your blockquotes:

302 | 303 |
<blockquote>
304 |         <p>For example.</p>
305 |     </blockquote>
306 |     
307 | -------------------------------------------------------------------------------- /unit-tests/test-snippets.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:cl-markdown-test) 2 | 3 | #| 4 | Could use something like the form below to test the structure of the output 5 | and thereby differentiate between parsing problems and output problems 6 | 7 | (collect-elements 8 | (chunks d) 9 | :transform 10 | (lambda (chunk) 11 | (list (started-by chunk) (ended-by chunk) 12 | (level chunk) (markup-class chunk))) 13 | :filter (lambda (chunk) (not (ignore? chunk)))) 14 | 15 | |# 16 | (deftestsuite test-escapes (test-snippets) 17 | ()) 18 | 19 | (addtest (test-escapes) 20 | catch-markdown-ones 21 | (check-output "\\\\ \\` \\* \\_ \\[ \\] \\( \\) \\# \\. \\! \\>")) 22 | 23 | (addtest (test-escapes) 24 | catch-markdown-ones-2 25 | (ensure-cases (var) 26 | '(("\\") ("`") ("*") ("_") 27 | ("[") ("]") ("(") (")") 28 | ("#") (".") ("!") 29 | (">")) 30 | (check-output (format nil "hi \\~a dude" var)))) 31 | 32 | (addtest (test-escapes :expected-failure "Problem in test suite... Markdown output is bad") 33 | catch-markdown-ones-< 34 | (check-output "\\<")) 35 | 36 | (addtest (test-escapes) 37 | code-and-escapes 38 | (check-output "`\\*hi\\*`")) 39 | 40 | (addtest (test-escapes) 41 | star-and-escapes 42 | (check-output "*\\*hi\\**")) 43 | 44 | 45 | (deftestsuite test-lists-and-paragraphs (test-snippets) 46 | ()) 47 | 48 | (addtest (test-lists-and-paragraphs) 49 | list-item-with-paragraph-1 50 | (check-output " 51 | * List item 52 | 53 | with another paragraph 54 | 55 | and some code 56 | 57 | * Another item 58 | 59 | this ends the list and starts a paragraph.")) 60 | 61 | (addtest (test-lists-and-paragraphs) 62 | mingling-text-and-code 63 | (check-output " 64 | para 65 | 66 | code 67 | 68 | para 69 | 70 | code 71 | 72 | ")) 73 | 74 | 75 | #+(or) 76 | (markdown 77 | " 78 | * List item 79 | 80 | with another paragraph 81 | 82 | and some code 83 | 84 | * Another item 85 | 86 | this ends the list and starts a paragraph.") 87 | 88 | (addtest (test-lists-and-paragraphs) 89 | list-item-with-paragraph-2 90 | (check-output 91 | " 92 | * List item 93 | 94 | and some code 95 | ")) 96 | 97 | (addtest (test-lists-and-paragraphs) 98 | list-item-with-paragraph-3 99 | (check-output " 100 | * Another item 101 | 102 | paragraph ")) 103 | 104 | (addtest (test-lists-and-paragraphs) 105 | list-item-with-paragraph-4 106 | (check-output " 107 | * Item 1 108 | 109 | paragraph 1 110 | 111 | * Item 2 112 | 113 | paragraph 2 114 | 115 | The end")) 116 | 117 | (addtest (test-lists-and-paragraphs 118 | :expected-failure "Markdown views treats the 1. as a *.") 119 | list-item-with-paragraph-5 120 | (check-output " 121 | * Item 1 122 | 123 | 1. paragraph 1")) 124 | 125 | (addtest (test-lists-and-paragraphs) 126 | nested-lists-1 127 | (check-output " 128 | * Item 1 129 | 130 | * Item A")) 131 | 132 | ;;?? Paragraph logic reversed? 133 | (addtest (test-lists-and-paragraphs) 134 | nested-lists-2 135 | (check-output " 136 | * Item 1 137 | 138 | * Item A 139 | 140 | * Item 2")) 141 | 142 | (addtest (test-lists-and-paragraphs) 143 | nested-lists-3 144 | (check-output " 145 | * a 146 | * b 147 | * c 148 | * d 149 | ")) 150 | 151 | (addtest (test-lists-and-paragraphs) 152 | nested-lists-4 153 | (check-output " 154 | * a 155 | * b 156 | * c 157 | * d 158 | ")) 159 | 160 | (addtest (test-lists-and-paragraphs) 161 | nested-lists-with-hard-returns 162 | (check-output " 163 | * Item 1 164 | is spunky 165 | 166 | * Item A 167 | ")) 168 | 169 | (addtest (test-lists-and-paragraphs) 170 | lists-and-code-1 171 | (ensure-same 172 | (nth-value 1 173 | (cl-markdown:markdown 174 | " 175 | * The select form rewrites... If we add another line. 176 | 177 | (select (?x) 178 | (q ?x !property node)) 179 | 180 | ")) 181 | (nth-value 1 182 | (cl-markdown:markdown 183 | " 184 | * The select form rewrites... 185 | If we add another line. 186 | 187 | (select (?x) 188 | (q ?x !property node)) 189 | 190 | ")) :test 'string=)) 191 | 192 | (addtest (test-lists-and-paragraphs 193 | :expected-failure "paragraphs") 194 | lists-and-blockquote 195 | (check-output "paragraph 1 196 | 197 | > ok 198 | 199 | * item 1 200 | 201 | q2. I thiought I had this one 202 | 203 | ok")) 204 | 205 | ;;;;; 206 | 207 | (deftestsuite test-break (test-snippets) 208 | () 209 | (:tests 210 | (no-spaces (check-output "hello 211 | there")) 212 | (one-space (check-output "hello 213 | there")) 214 | (two-spaces (check-output "hello 215 | there")) 216 | (three-spaces (check-output "hello 217 | there")))) 218 | 219 | ;; NOTE: markdown doesn't add the
unless there is content _after_ 220 | ;; line that ends with two spaces... 221 | (addtest (test-break) 222 | rest-of-line 223 | (check-output "this is **strong** 224 | ok?")) 225 | 226 | (addtest (test-break) 227 | rest-of-line-2 228 | (check-output "this _is_ **strong** 229 | ok?")) 230 | 231 | (addtest (test-break :expected-failure "markdown doesn't add the
unless there is content _after_ line that ends with two spaces...") 232 | rest-of-line-3 233 | (check-output "this _is_ **strong** ")) 234 | 235 | ;;;;; 236 | 237 | (deftestsuite entity-snippets (test-snippets) 238 | ()) 239 | 240 | (addtest (entity-snippets) 241 | entity-check-1 242 | (check-output "AT&T puts the amp in & >boss<")) 243 | 244 | (addtest (entity-snippets) 245 | entity-check-2 246 | (check-output "The AT&T is AT & T, not AT&T or AT &T")) 247 | 248 | (addtest (entity-snippets) 249 | entity-check-3 250 | (check-output " 251 | Never forget AT 252 | &T")) 253 | 254 | ;;;; 255 | 256 | (deftestsuite numbered-lists (test-snippets) 257 | ()) 258 | 259 | (addtest (numbered-lists) 260 | at-margin 261 | (check-output " 262 | 1. hi 263 | 2. there 264 | ")) 265 | 266 | (addtest (numbered-lists) 267 | indented 268 | (check-output " 269 | 1. hi 270 | 2. there 271 | ")) 272 | 273 | (addtest (numbered-lists) 274 | nospace 275 | (check-output " 276 | 1.hi 277 | 2.there 278 | ")) 279 | 280 | (addtest (numbered-lists 281 | :expected-failure "Looks like a markdown bug") 282 | nocontents 283 | (check-output " 284 | 1. 285 | 2. 286 | ")) 287 | ;;;; 288 | 289 | (deftestsuite test-horizontal-rules (test-snippets) 290 | ()) 291 | 292 | (addtest (test-horizontal-rules) 293 | horizontal-rules-1 294 | (check-output 295 | "Here are some rules. 296 | I hope you like 'em. 297 | 298 | --- 299 | *** 300 | - - - 301 | ** ** ** 302 | _ _ _____ _ _ 303 | 304 | Did you like them?")) 305 | 306 | (addtest (test-horizontal-rules) 307 | horizontal-rules-2 308 | (ensure (search 309 | "this is code" 310 | (nth-value 1 (markdown:markdown 311 | "Here is an example: 312 | 313 | this is code 314 | 315 | - - - - 316 | " :stream nil)) :test 'char=))) 317 | 318 | ;;;; 319 | 320 | (deftestsuite test-nested-lists (test-snippets) 321 | ()) 322 | 323 | (addtest (test-nested-lists) 324 | three-deep 325 | (check-output 326 | " 327 | * a 328 | * b 329 | * c")) 330 | 331 | 332 | (deftestsuite test-blockquotes (test-snippets) 333 | ()) 334 | 335 | (addtest (test-blockquotes) 336 | nested-1 337 | (check-output 338 | " 339 | > a 340 | > b 341 | ")) 342 | 343 | (addtest (test-blockquotes) 344 | nested-2 345 | (check-output 346 | " 347 | > a 348 | 349 | > b 350 | ")) 351 | 352 | (addtest (test-blockquotes) 353 | nested-3 354 | (check-output 355 | " 356 | > a 357 | 358 | >> b 359 | ")) 360 | 361 | 362 | ;;;;; 363 | 364 | 365 | ;; test example from hhalvors@Princeton.EDU 366 | (addtest (test-snippets) 367 | header-paragraph-embedded-link 368 | (check-output 369 | "## Common Lisp 370 | 371 | * An item with a link [link](link.html) and some following text.")) 372 | 373 | ;; test example from hhalvors@Princeton.EDU 374 | (addtest (test-snippets) 375 | header-paragraph-embedded-link-in-list 376 | (check-output 377 | "## Common Lisp 378 | 379 | * An item with a link [link](link.html) and some following text. 380 | * Another item")) 381 | 382 | ;; test example from hhalvors@Princeton.EDU 383 | (addtest (test-snippets) 384 | headers-and-lists 385 | (check-output 386 | "## Common Lisp 387 | 388 | * An item with a link [link](link.html) and some following text. 389 | 390 | ## A second level heading 391 | 392 | * Another item")) 393 | 394 | (addtest (test-snippets) 395 | header-in-list 396 | (check-output 397 | "* ok 398 | 399 | # eh")) 400 | 401 | (addtest (test-snippets) 402 | reference-link-text-with-line-breaks 403 | (check-output 404 | "Hi this [is 405 | so][is-so] cool. 406 | 407 | [is-so]: http://a.c.c/")) 408 | 409 | #+(or) 410 | (markdown 411 | "* ok 412 | 413 | # eh") 414 | 415 | (addtest (test-snippets) 416 | list-item-with-hard-return 417 | (check-output 418 | "* A first list item 419 | with a hard return 420 | * A second list item 421 | ")) 422 | 423 | (addtest (test-snippets) 424 | list-items-and-paragraphs-1 425 | (check-output 426 | "* first line 427 | 428 | second line")) 429 | 430 | (addtest (test-snippets) 431 | list-items-and-paragraphs-2 432 | (check-output 433 | "* first line 434 | 435 | * second line")) 436 | 437 | (addtest (test-snippets) 438 | list-items-and-paragraphs-3 439 | (check-output 440 | "* first line 441 | * second line")) 442 | 443 | (addtest (test-snippets) 444 | list-items-and-paragraphs-4 445 | (check-output 446 | "* first line 447 | second line")) 448 | 449 | (addtest (test-snippets) 450 | inline-html-1 451 | (check-output "`
foo
`")) 452 | 453 | (addtest (test-snippets) 454 | inline-html-2 455 | (check-output "Simple block on one line: 456 | 457 |
foo
458 | ")) 459 | 460 | -------------------------------------------------------------------------------- /dev/extensions.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:cl-markdown) 2 | 3 | ;; {f a0 .. an} 4 | ;; -> eval f a0 .. an -- where ai are strings 5 | ;; -> returns string that is inserted into document 6 | ;; -> or nil (cound do insertions itself) 7 | 8 | ;; no recursive function embeddings {date-stamp {today}} 9 | ;; keywords handled separately? 10 | 11 | ;; could use a macro 12 | ;; 13 | ;; to specify a name, arguments, etc and use that to parse. and export 14 | 15 | 16 | (defsimple-extension current-year 17 | (let ((format (document-property :date-format "%Y"))) 18 | (format-date format (get-universal-time)))) 19 | 20 | (defsimple-extension today 21 | (let ((format (document-property :date-format "%e %B %Y"))) 22 | (format-date format (get-universal-time)))) 23 | 24 | (defsimple-extension now 25 | (let ((format (document-property :time-format "%H:%M"))) 26 | (format-date format (get-universal-time)))) 27 | 28 | (defextension (comment :arguments ((text :required)) 29 | :insertp t) 30 | (ecase phase 31 | (:parse 32 | ;; no worries 33 | ) 34 | (:render 35 | (format nil "" text)))) 36 | 37 | (defextension (remark :arguments ((text :required)) 38 | :insertp t) 39 | (ecase phase 40 | (:parse 41 | ;; no worries 42 | ) 43 | (:render 44 | ;; stil no worries 45 | ))) 46 | 47 | (defextension (anchor :arguments ((name :required) title) :insertp t) 48 | (setf name (ensure-string name)) 49 | (let ((safe-name (html-safe-name name))) 50 | (ecase phase 51 | (:parse 52 | (setf (item-at (link-info *current-document*) name) 53 | (make-instance 'link-info 54 | :id name 55 | :url (format nil "#~a" safe-name) 56 | :title (or title "")))) 57 | (:render 58 | (format nil "" safe-name safe-name))))) 59 | 60 | (defextension (property :arguments ((name :required)) 61 | :insertp t) 62 | (ecase phase 63 | (:parse) 64 | (:render 65 | (process-child-markdown (document-property name) phase)))) 66 | 67 | (defextension (ifdef :arguments ((keys :required) 68 | (text :required :whole)) 69 | :insertp t) 70 | (ecase phase 71 | (:parse) 72 | (:render 73 | (prog1 74 | (if (or (and (atom keys) (document-property keys)) 75 | ) 76 | (process-child-markdown 77 | (format nil "~{~a~^ ~}" (ensure-list text)) phase) 78 | ""))))) 79 | 80 | #| 81 | (defvar *x*) 82 | 83 | (defextension (property :arguments ((name :required))) 84 | (ecase phase 85 | (:parse) 86 | (:render 87 | (bind (((:values d s) 88 | (markdown (document-property name) 89 | :parent *current-document* 90 | :format *current-format* 91 | :properties '((:omit-initial-paragraph t) 92 | (:omit-final-paragraph t) 93 | (:html . nil)) 94 | :stream nil))) 95 | (setf *x* d) 96 | (prog1 97 | (strip-whitespace s)))))) 98 | 99 | (let ((*current-document* *x*)) 100 | (document-property "html")) 101 | 102 | (form-property-name "html") 103 | 104 | (trace item-at-1) 105 | 106 | |# 107 | 108 | 109 | (defextension (set-property :arguments ((name :required) 110 | (value :whole)) 111 | :insertp t) 112 | (when (eq phase :parse) 113 | (setf (document-property name) value)) 114 | nil) 115 | 116 | #+(or) 117 | ;;?? 118 | (defun set-property (phase args result) 119 | (declare (ignorable phase args result)) 120 | (bind ((name (pop args)) 121 | (value 122 | (progn (if (length-1-list-p args) 123 | (first args) 124 | args)))) 125 | (assert name nil "name is required") 126 | (when (eq phase :parse) 127 | (setf (document-property name) value)) 128 | nil)) 129 | 130 | (defextension (table-of-contents :arguments ((depth :required :keyword) 131 | (start :required :keyword) 132 | (label :keyword)) 133 | :insertp t) 134 | (ecase phase 135 | (:parse 136 | (push (lambda (document) 137 | (add-toc-anchors document :depth depth :start start)) 138 | (item-at-1 (properties *current-document*) :cleanup-functions)) 139 | nil) 140 | (:render 141 | (bind ((headers (collect-toc-headings depth start))) 142 | (when headers 143 | (format *output-stream* 144 | "~&") 145 | (format *output-stream* "~&
~%") 146 | (when label 147 | (format *output-stream* "

~a

" label)) 148 | (iterate-elements 149 | headers 150 | (lambda (header) 151 | (bind (((_ anchor text) 152 | (item-at-1 (properties header) :anchor)) 153 | (save-header-lines (copy-list (lines header)))) 154 | (setf (slot-value header 'lines) 155 | `(,(format nil 156 | "~&" 157 | (if (char= (aref anchor 0) #\#) "" "#") 158 | anchor 159 | (encode-string-for-title text)) 160 | ,@(lines header) 161 | ,(format nil ""))) 162 | (render-to-html header nil) 163 | (setf (slot-value header 'lines) 164 | save-header-lines)))) 165 | (format *output-stream* "~&
~%")))))) 166 | 167 | (defun collect-toc-headings (depth start) 168 | (collect-elements 169 | (chunks *current-document*) 170 | :filter (lambda (x) 171 | (header-p x :depth depth :start start)))) 172 | 173 | (defsimple-extension toc-link 174 | (format nil "~&Top")) 175 | 176 | (defun make-ref (index level) 177 | (format nil "~(~a-~a~)" level index)) 178 | 179 | (defun add-toc-anchors (document &key depth start) 180 | (let* ((index -1) 181 | (header-level nil) 182 | (last-anchor nil) 183 | (header-indexes 184 | (nreverse 185 | (collect-elements 186 | (chunks document) 187 | :transform 188 | (lambda (chunk) 189 | (item-at-1 (properties chunk) :anchor)) 190 | :filter 191 | (lambda (chunk) 192 | (incf index) 193 | (let ((it nil)) 194 | (cond ((setf it (header-p chunk :depth depth 195 | :start start)) 196 | (setf header-level it) 197 | (setf (item-at-1 (properties chunk) :anchor) 198 | (list index 199 | (or (and last-anchor 200 | (url last-anchor)) 201 | (make-ref index header-level)) 202 | (with-output (*output-stream* nil) 203 | (render-plain chunk)))) 204 | (null last-anchor)) 205 | ((setf it (simple-anchor-p chunk)) 206 | (setf last-anchor it) 207 | nil) 208 | (t 209 | (setf last-anchor nil))))))))) 210 | (iterate-elements 211 | header-indexes 212 | (lambda (datum) 213 | ; (print datum) 214 | (bind (((index ref text) datum)) 215 | (anchor :parse `(,ref ,text) nil) 216 | (insert-item-at 217 | (chunks document) 218 | (make-instance 'chunk 219 | :lines `((eval anchor (,ref nil) nil t))) 220 | index)))))) 221 | 222 | (defun simple-anchor-p (chunk) 223 | (or (and (plusp (size (lines chunk))) 224 | (let ((link-name nil) (title nil)) 225 | (when (some-element-p 226 | (lines chunk) 227 | (lambda (line) 228 | (when (consp line) 229 | (case (first line) 230 | (simple-anchor 231 | (setf link-name (second line))) 232 | (anchor-with-text 233 | (setf link-name (third line) title (second line))))))) 234 | (make-instance 'link-info 235 | :id link-name 236 | :url (format nil "#~a" (html-safe-name link-name)) 237 | :title (or title ""))))) 238 | (and (< 0 (size (lines chunk)) 3) 239 | (length-at-least-p (first-element (lines chunk)) 2) 240 | (equal (subseq (first-element (lines chunk)) 0 2) 241 | '(eval anchor)) 242 | (fourth (first-element (lines chunk)))))) 243 | 244 | (defun header-p (chunk &key depth start) 245 | (let* ((header-elements '(header1 header2 header3 246 | header4 header5 header6)) 247 | (header-elements (subseq header-elements 248 | (1- (or start 1)) 249 | (min (or depth (length header-elements)) 250 | (length header-elements))))) 251 | (some-element-p (markup-class chunk) 252 | (lambda (class) 253 | (member class header-elements))))) 254 | 255 | #+(or) 256 | (markdown "{set-property html t} 257 | html = {property html} 258 | I like {docs markdown function}, don't you." :additional-extensions '(docs)) 259 | 260 | #+(or) 261 | (markdown 262 | "{set-property docs-package asdf-install} 263 | {set-property 264 | {docs install function} 265 | {docs asdf-install:*gnu-tar-program* variable} 266 | " 267 | :additional-extensions '(docs)) 268 | 269 | #| 270 | {set-property docs-package asdf-install} 271 | {set-property docs-heading-level 4} 272 | {set-property docs-heading-format "%type %name:"} 273 | {docs *gnu-tar-program* variable} 274 | |# 275 | 276 | (defextension (abbrev :arguments ((abbreviation :required) 277 | (text :required :whole))) 278 | (ecase phase 279 | (:parse 280 | ;; no worries 281 | ) 282 | (:render 283 | (format nil "~a" abbreviation)))) 284 | 285 | (defextension (include :arguments ((pathname :required)) 286 | :insertp t) 287 | (ecase phase 288 | (:parse 289 | ;; no worries 290 | (let ((pathname (find-include-file pathname))) 291 | ;; FIXME - if I use a list, someone in markdown calls chunks on it. 292 | (make-array 1 :initial-contents 293 | (list (process-child-markdown 294 | pathname phase :transfer-data t))))) 295 | (:render 296 | (when result 297 | (render-to-stream (aref (first result) 0) *current-format* nil))))) 298 | 299 | (defextension (include-if :arguments ((test :required) (pathname :required)) 300 | :insertp t) 301 | (ecase phase 302 | (:parse 303 | (when (document-property test) 304 | (let ((pathname (find-include-file pathname))) 305 | ;; FIXME - if I use a list, someone in markdown calls chunks on it. 306 | (make-array 1 :initial-contents 307 | (list (process-child-markdown 308 | pathname phase :transfer-data t)))))) 309 | (:render 310 | (when result 311 | (render-to-stream (aref (first result) 0) *current-format* nil))))) 312 | -------------------------------------------------------------------------------- /dev/regexes.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:markdown) 2 | 3 | (define-parse-tree-synonym 4 | line-ends-with-two-spaces 5 | (:sequence 6 | (:register (:sequence (:greedy-repetition 0 nil :everything))) 7 | #\Space #\Space :end-anchor)) 8 | 9 | (define-parse-tree-synonym 10 | emphasis-1 #.(cl-ppcre::parse-string "_([^_]*)_")) 11 | 12 | (define-parse-tree-synonym 13 | emphasis-2 #.(cl-ppcre::parse-string "\\*([^ ][^\\*]*)\\*")) 14 | 15 | (define-parse-tree-synonym 16 | strong-1 17 | (:sequence 18 | (:greedy-repetition 2 2 #\_) 19 | (:register 20 | (:sequence (:greedy-repetition 0 nil (:inverted-char-class #\_)))) 21 | (:greedy-repetition 2 2 #\_))) 22 | 23 | (define-parse-tree-synonym 24 | strong-2 25 | (:sequence 26 | (:greedy-repetition 2 2 #\*) 27 | (:register 28 | (:sequence (:greedy-repetition 0 nil (:inverted-char-class #\*)))) 29 | (:greedy-repetition 2 2 #\*))) 30 | 31 | (define-parse-tree-synonym 32 | strong-em-1 33 | (:sequence 34 | (:greedy-repetition 3 3 #\_) 35 | (:register 36 | (:sequence (:greedy-repetition 0 nil (:inverted-char-class #\_)))) 37 | (:greedy-repetition 3 3 #\_))) 38 | 39 | (define-parse-tree-synonym 40 | strong-em-2 41 | (:sequence 42 | (:greedy-repetition 3 3 #\*) 43 | (:register 44 | (:sequence (:greedy-repetition 0 nil (:inverted-char-class #\*)))) 45 | (:greedy-repetition 3 3 #\*))) 46 | 47 | (define-parse-tree-synonym 48 | backtick #.(cl-ppcre::parse-string "\\`([^\\`]*)\\`")) 49 | 50 | (define-parse-tree-synonym 51 | auto-link #.(cl-ppcre::parse-string "<(http://[^>]*)>")) 52 | 53 | (define-parse-tree-synonym 54 | auto-mail #.(cl-ppcre::parse-string "<([^> ]*@[^> ]*)>")) 55 | 56 | (define-parse-tree-synonym 57 | html #.(cl-ppcre::parse-string "(\\<[^\\>]*\\>)")) 58 | 59 | (define-parse-tree-synonym 60 | entity #.(cl-ppcre::parse-string "(&[\\#a-zA-Z0-9]*;)")) 61 | 62 | (define-parse-tree-synonym 63 | hostname-char #.(cl-ppcre::parse-string "[-a-zA-Z0-9_.]")) 64 | 65 | (define-parse-tree-synonym 66 | hostname (:sequence 67 | (:greedy-repetition 1 nil hostname-char) 68 | (:greedy-repetition 69 | 0 nil (:sequence #\. (:greedy-repetition 1 nil hostname-char))))) 70 | 71 | (define-parse-tree-synonym 72 | pathname-char (:char-class #\- 73 | (:range #\a #\z) 74 | (:range #\A #\Z) 75 | (:range #\0 #\9) 76 | #\_ #\. #\: #\@ #\& #\? #\= #\+ 77 | #\, #\! #\/ #\~ #\* #\' #\% #\\ #\$ 78 | )) 79 | 80 | (define-parse-tree-synonym 81 | url-pathname (:sequence (:greedy-repetition 0 nil pathname-char))) 82 | 83 | (define-parse-tree-synonym 84 | url (:sequence "http://" 85 | (:register hostname) 86 | (:greedy-repetition 87 | 0 1 (:sequence 88 | (:greedy-repetition 0 1 #\/) 89 | (:register url-pathname 90 | (:greedy-repetition 91 | 0 1 (:sequence #\# url-pathname))) 92 | )) 93 | (:negative-lookbehind (:char-class #\. #\, #\? #\!)))) 94 | 95 | (define-parse-tree-synonym 96 | url-no-registers 97 | (:sequence 98 | (:greedy-repetition 0 1 (:sequence "http://" hostname)) 99 | (:greedy-repetition 100 | 0 1 (:sequence (:greedy-repetition 0 1 #\/) url-pathname)) 101 | (:greedy-repetition 102 | 0 1 (:sequence #\# url-pathname)) 103 | (:negative-lookbehind (:char-class #\. #\, #\? #\!)))) 104 | 105 | (define-parse-tree-synonym 106 | bracketed (:sequence 107 | #\[ 108 | (:register (:greedy-repetition 0 nil (:inverted-char-class #\[))) 109 | #\])) 110 | 111 | (defparameter *escape-characters* 112 | "\\`*_{}[]()#.!<>") 113 | 114 | ;; FIXME - use *escape-characters* to create this parse-tree 115 | (define-parse-tree-synonym 116 | valid-escape 117 | (:alternation 118 | #\\ ;backslash 119 | #\` ;backtick 120 | #\* ;asterisk 121 | #\_ ;underscore 122 | #\{ ;curly braces 123 | #\} 124 | #\[ ;square brackets 125 | #\] 126 | #\( ;parentheses 127 | #\) 128 | #\# ;hash mark 129 | #\. ;dot 130 | #\! ;exclamation mark 131 | #\< ;brackets 132 | #\> 133 | )) 134 | 135 | (define-parse-tree-synonym 136 | escaped-character (:sequence #\\ (:register valid-escape))) 137 | 138 | (define-parse-tree-synonym 139 | escape-kludge 140 | (:sequence #\Null #\Null 141 | (:register (:greedy-repetition 0 nil 142 | (:char-class (:range #\0 #\9)))) 143 | #\Null #\Null)) 144 | 145 | (define-parse-tree-synonym 146 | link+title 147 | (:sequence 148 | #\( 149 | (:alternation 150 | (:sequence #\< 151 | (:register 152 | (:greedy-repetition 0 nil (:inverted-char-class #\) #\Space))) 153 | #\>) 154 | (:register (:greedy-repetition 0 nil (:inverted-char-class #\) #\Space)))) 155 | ; title 156 | (:greedy-repetition 157 | 0 1 158 | (:sequence 159 | (:greedy-repetition 1 nil :whitespace-char-class) 160 | (:alternation #\' #\" #\() 161 | (:register (:greedy-repetition 0 nil :everything)) 162 | (:alternation #\' #\" #\)))) 163 | #\))) 164 | 165 | (define-parse-tree-synonym 166 | inline-link (:sequence bracketed link+title)) 167 | 168 | (define-parse-tree-synonym 169 | reference-link (:sequence 170 | bracketed (:greedy-repetition 0 1 :whitespace-char-class) 171 | bracketed)) 172 | 173 | (define-parse-tree-synonym 174 | link-label (:sequence 175 | :start-anchor 176 | (:greedy-repetition 0 3 :whitespace-char-class) 177 | bracketed 178 | #\: (:greedy-repetition 0 nil :whitespace-char-class) 179 | (:register url-no-registers) 180 | (:greedy-repetition 181 | 0 1 182 | (:sequence 183 | (:greedy-repetition 1 nil :whitespace-char-class) 184 | (:greedy-repetition 185 | 0 1 186 | (:register 187 | (:alternation 188 | (:sequence 189 | #\( 190 | (:greedy-repetition 0 nil :everything) 191 | #\)) 192 | (:sequence 193 | #\" 194 | (:greedy-repetition 0 nil :everything) 195 | #\")))) 196 | (:register 197 | (:greedy-repetition 0 nil :everything)))))) 198 | 199 | (define-parse-tree-synonym 200 | extended-link-label 201 | (:sequence 202 | :start-anchor 203 | ;;; [reference]> 204 | (:greedy-repetition 0 3 :whitespace-char-class) 205 | bracketed 206 | #\> (:greedy-repetition 0 nil :whitespace-char-class) 207 | ;;; name 208 | (:register 209 | (:greedy-repetition 0 nil (:inverted-char-class :whitespace-char-class))) 210 | (:register 211 | (:greedy-repetition 0 nil :everything)) :end-anchor)) 212 | 213 | (define-parse-tree-synonym 214 | coded-reference-link 215 | (:sequence 216 | #\` 217 | (:register 218 | (:sequence 219 | 220 | ;;; NO! 221 | ;; (:non-greedy-repetition 0 nil (:inverted-char-class #\` #\[)) 222 | ; bracket 223 | (:sequence 224 | #\[ (:greedy-repetition 0 nil (:inverted-char-class #\[)) #\]) 225 | ; space 226 | (:greedy-repetition 0 1 :whitespace-char-class) 227 | ; bracket 228 | (:sequence 229 | #\[ (:greedy-repetition 0 nil (:inverted-char-class #\[)) #\]) 230 | 231 | ;;; NO! 232 | ;; (:non-greedy-repetition 0 nil (:inverted-char-class #\` #\])) 233 | 234 | )) 235 | #\`)) 236 | 237 | (define-parse-tree-synonym 238 | bracketed (:sequence 239 | #\[ 240 | (:register (:greedy-repetition 0 nil (:inverted-char-class #\[))) 241 | #\])) 242 | 243 | ;;; image-link 244 | 245 | (define-parse-tree-synonym 246 | inline-image (:sequence #\! bracketed link+title)) 247 | 248 | ;;; image-link reference 249 | 250 | (define-parse-tree-synonym 251 | inline-image (:sequence #\! bracketed link+title)) 252 | 253 | (define-parse-tree-synonym 254 | reference-image (:sequence 255 | #\! bracketed (:greedy-repetition 0 1 :whitespace-char-class) 256 | bracketed)) 257 | 258 | ;;; anchors 259 | 260 | (define-parse-tree-synonym 261 | parenthetical (:sequence 262 | #\( 263 | (:register (:greedy-repetition 0 nil (:inverted-char-class #\())) 264 | #\))) 265 | 266 | (define-parse-tree-synonym simple-anchor 267 | (:sequence #\@ parenthetical)) 268 | 269 | (define-parse-tree-synonym anchor-with-text 270 | (:sequence #\@ bracketed parenthetical)) 271 | 272 | ;;; block-level html 273 | 274 | (define-parse-tree-synonym 275 | block-level-html-end 276 | (:sequence 277 | #\< 278 | (:greedy-repetition 0 nil #\Space) 279 | #\/ 280 | (:greedy-repetition 0 nil #\Space) 281 | (:register (:alternation "div" "table" "pre" "p")) 282 | (:alternation #\Space #\>))) 283 | 284 | (define-parse-tree-synonym 285 | block-level-html-start 286 | (:sequence #\< 287 | (:register (:alternation "pre" "XXX")) 288 | (:register 289 | (:greedy-repetition 0 nil (:inverted-char-class #\>))) 290 | #\>)) 291 | 292 | #| 293 | 294 | (defun define-block-level-html-regexes (block-level-tags) 295 | (eval 296 | `(progn 297 | (define-parse-tree-synonym 298 | block-level-html-end 299 | (:sequence 300 | #\< 301 | (:greedy-repetition 0 nil #\Space) 302 | #\/ 303 | (:greedy-repetition 0 nil #\Space) 304 | (:register (:alternation "div" "table" "pre" "p")) 305 | (:alternation #\Space #\>))) 306 | 307 | (define-parse-tree-synonym 308 | block-level-html-start 309 | (:sequence #\< 310 | (:register (:alternation ,@block-level-tags)) 311 | (:register 312 | (:greedy-repetition 0 nil (:inverted-char-class #\>))) 313 | #\>))))) 314 | 315 | (load-time-value 316 | (define-block-level-html-regexes '("pre" "butteR"))) 317 | 318 | |# 319 | 320 | #+(or) 321 | (define-parse-tree-synonym 322 | block-level-html-start (:sequence 323 | #\< 324 | (:register (:alternation "div" "table" "pre" "p")) 325 | (:register (:greedy-repetition 326 | 0 nil 327 | (:inverted-char-class #\>))) 328 | #\>)) 329 | 330 | #+(or) 331 | (cl-ppcre::parse-string 332 | "<(div|table|pre|p)[^>]*>") 333 | 334 | #+(or) 335 | (define-parse-tree-synonym 336 | block-level-html-end (:sequence 337 | #\< 338 | (:greedy-repetition 0 nil #\Space) 339 | #\/ 340 | (:greedy-repetition 0 nil #\Space) 341 | (:register (:alternation "div" "table" "pre" "p")) 342 | (:alternation #\Space #\>))) 343 | 344 | 345 | 346 | -------------------------------------------------------------------------------- /dev/spans.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:cl-markdown) 2 | 3 | (defvar *current-span* nil) 4 | 5 | (defstruct (markdown-scanner (:conc-name scanner-)) 6 | name regex priority function) 7 | 8 | (setf (item-at-1 *spanner-parsing-environments* 'default) 9 | (make-instance 10 | 'sorted-list-container 11 | :sorter '< 12 | :key 'scanner-priority 13 | :initial-contents 14 | `(,(make-markdown-scanner 15 | :regex (create-scanner '(:sequence escaped-character)) 16 | :name 'escaped-character 17 | :priority 1 18 | :function 'convert-escape-temporarily) 19 | ,(make-markdown-scanner 20 | :regex (create-scanner '(:sequence inline-image)) 21 | :name 'inline-image 22 | :priority 2) 23 | ,(make-markdown-scanner 24 | :regex (create-scanner 25 | '(:sequence reference-image)) 26 | :name 'reference-image 27 | :priority 3) 28 | ;; must be before link 29 | ,(make-markdown-scanner 30 | :regex (create-scanner '(:sequence anchor-with-text)) 31 | :name 'anchor-with-text 32 | :priority 3.1) 33 | ,(make-markdown-scanner 34 | :regex (create-scanner '(:sequence simple-anchor)) 35 | :name 'simple-anchor 36 | :priority 3.2 37 | ;:function 'make-simple-anchor 38 | ) 39 | ,(make-markdown-scanner :regex (create-scanner 40 | '(:sequence coded-reference-link)) 41 | :name 'code 42 | :priority 4) 43 | ,(make-markdown-scanner :regex (create-scanner 44 | '(:sequence inline-link)) 45 | :name 'inline-link 46 | :priority 5) 47 | ,(make-markdown-scanner :regex (create-scanner 48 | '(:sequence reference-link)) 49 | :name 'reference-link 50 | :priority 6) 51 | ,(make-markdown-scanner :regex (create-scanner '(:sequence backtick)) 52 | :name 'code 53 | :priority 7) 54 | ,(make-markdown-scanner :regex (create-scanner 55 | '(:sequence strong-em-1)) 56 | :name 'strong-em 57 | :priority 8) 58 | ,(make-markdown-scanner :regex (create-scanner 59 | '(:sequence strong-em-2)) 60 | :name 'strong-em 61 | :priority 9) 62 | ,(make-markdown-scanner :regex (create-scanner '(:sequence strong-2)) 63 | :name 'strong 64 | :priority 10) 65 | ,(make-markdown-scanner :regex (create-scanner '(:sequence strong-1)) 66 | :name 'strong 67 | :priority 11) 68 | ,(make-markdown-scanner :regex (create-scanner '(:sequence emphasis-2)) 69 | :name 'emphasis 70 | :priority 12) 71 | ,(make-markdown-scanner :regex (create-scanner '(:sequence emphasis-1)) 72 | :name 'emphasis 73 | :priority 13) 74 | ,(make-markdown-scanner :regex (create-scanner '(:sequence auto-link)) 75 | :name 'link 76 | :priority 14) 77 | ,(make-markdown-scanner :regex (create-scanner '(:sequence auto-mail)) 78 | :name 'mail 79 | :priority 15) 80 | ,(make-markdown-scanner :regex (create-scanner '(:sequence entity)) 81 | :name 'entity 82 | :priority 16) 83 | ,(make-markdown-scanner :regex (create-scanner '(:sequence html)) 84 | :name 'html 85 | :priority 7.5) 86 | ,(make-markdown-scanner 87 | :regex (create-scanner '(:sequence line-ends-with-two-spaces)) 88 | :name 'break 89 | :priority 1.8) 90 | ))) 91 | 92 | (setf (item-at-1 *spanner-parsing-environments* '(code)) 93 | (make-instance 94 | 'sorted-list-container 95 | :sorter '< 96 | :key 'scanner-priority 97 | :initial-contents 98 | `(,(make-markdown-scanner 99 | :regex (create-scanner '(:sequence html)) 100 | :name 'html 101 | :priority 1) 102 | ,(make-markdown-scanner 103 | :regex (create-scanner '(:sequence entity)) 104 | :name 'entity 105 | :priority 2)))) 106 | 107 | (defun scanners-for-chunk (chunk) 108 | (let ((it nil)) 109 | (cond ((setf it (item-at-1 *spanner-parsing-environments* 110 | (markup-class chunk))) 111 | (values it (markup-class chunk))) 112 | (t 113 | (values (item-at-1 *spanner-parsing-environments* 'default) nil))))) 114 | 115 | (defmethod handle-spans ((document abstract-document)) 116 | (iterate-chunks 117 | document 118 | (lambda (chunk) 119 | (handle-spans chunk))) 120 | document) 121 | 122 | (defmethod handle-spans ((chunk chunk)) 123 | (setf (slot-value chunk 'lines) 124 | (bind ((lines (slot-value chunk 'lines)) 125 | ((:values scanners kind) (scanners-for-chunk chunk)) 126 | (*current-span* kind)) 127 | (scan-lines-with-scanners lines scanners))) 128 | chunk) 129 | 130 | (defun scan-lines-with-scanners (lines scanners) 131 | (when (or (consp lines) 132 | (typep lines 'cl-containers:iteratable-container-mixin)) 133 | (iterate-elements 134 | scanners 135 | (lambda (scanner) 136 | (let ((name (scanner-name scanner))) 137 | (setf lines 138 | (let ((result nil)) 139 | (iterate-elements 140 | lines 141 | (lambda (line) 142 | (setf result 143 | (append result (scan-one-span 144 | line name scanner scanners))))) 145 | result)))))) 146 | lines) 147 | 148 | (defmethod scan-one-span ((line (eql nil)) scanner-name scanner scanners) 149 | (declare (ignorable scanner-name scanner scanners)) 150 | (list "")) 151 | 152 | (defmethod scan-one-span ((line cons) scanner-name scanner scanners) 153 | ;;?? what special case does this handle? 154 | (if (process-span-in-span-p scanner-name (first line)) 155 | `((,(first line) 156 | ,@(let ((*current-span* (first line))) 157 | (scan-one-span (second line) scanner-name scanner scanners)) 158 | ,@(nthcdr 2 line))) 159 | (list line))) 160 | 161 | (defmethod process-span-in-span-p ((sub-span t) (current-span t)) 162 | (values t)) 163 | 164 | (defmethod process-span-in-span-p 165 | ((sub-span (eql nil)) (current-span (eql 'html))) 166 | (values nil)) 167 | 168 | (defmethod process-span-in-span-p ((sub-span t) (current-span (eql 'html))) 169 | (values nil)) 170 | 171 | (defmethod process-span-in-span-p ((sub-span (eql 'html)) (current-span t)) 172 | (values nil)) 173 | 174 | (defmethod process-span-in-span-p ((sub-span (eql 'html)) (current-span null)) 175 | (values t)) 176 | 177 | (defmethod process-span-in-span-p 178 | ((sub-span (eql 'link)) (current-span (eql 'code))) 179 | (values nil)) 180 | 181 | (defmethod process-span-in-span-p 182 | ((sub-span (eql 'html)) (current-span (eql 'code))) 183 | (values nil)) 184 | 185 | (defmethod process-span-in-span-p ((sub-span t) (current-span (eql 'code))) 186 | (values nil)) 187 | 188 | (defmethod process-span-in-span-p 189 | ((sub-span t) (current-span (eql 'coded-reference-link))) 190 | (values nil)) 191 | 192 | (defmethod scan-one-span ((line string) scanner-name scanner scanners) 193 | #+debug 194 | (print (list :sos scanner-name *current-span* 195 | (process-span-in-span-p scanner-name *current-span*) 196 | line)) 197 | (let ((found? nil) 198 | (result nil) 199 | (last-e 0) 200 | (regex (scanner-regex scanner)) 201 | (scanner-fn (scanner-function scanner))) 202 | (when (process-span-in-span-p scanner-name *current-span*) 203 | (flet ((sub-scan (it) 204 | (let ((*current-span* scanner-name)) 205 | (scan-lines-with-scanners it scanners)))) 206 | (do-scans (s e gs ge regex line) 207 | (let ((registers (loop for s-value across gs 208 | for e-value across ge 209 | when (and (not (null s-value)) 210 | (/= s-value e-value)) collect 211 | (sub-scan (subseq line s-value e-value))))) 212 | (setf registers (process-span scanner-name registers)) 213 | (let ((converted 214 | `(,@(when (plusp s) 215 | `(,(sub-scan (subseq line last-e s)))) 216 | ,(if scanner-fn 217 | (funcall scanner-fn scanner-name registers) 218 | `(,scanner-name ,@registers))))) 219 | (setf found? t 220 | last-e e 221 | result (append result converted))))) 222 | (when found? 223 | (setf result 224 | (let ((last (sub-scan (subseq line last-e)))) 225 | (if (plusp (size last)) 226 | (append result (list last)) 227 | result))) 228 | (return-from scan-one-span 229 | (values (combine-strings result) t)))))) 230 | (values (list line) nil)) 231 | 232 | (defun combine-strings (list) 233 | (let ((result nil) 234 | (current nil)) 235 | (flet ((maybe-add (something) 236 | (when something 237 | (setf result (nconc result (list something)))))) 238 | (iterate-elements 239 | list 240 | (lambda (elt) 241 | (cond ((stringp elt) 242 | (if current 243 | (setf current (concatenate 'string current elt)) 244 | (setf current elt))) 245 | (t 246 | (maybe-add current) 247 | (maybe-add elt) 248 | (setf current nil))))) 249 | (maybe-add current) 250 | result))) 251 | 252 | #+(or) 253 | (ensure-same (combine-strings '("a" "b" 23 "c" "d")) ("ab" 23 "cd")) 254 | 255 | #+(or) 256 | (ensure-same (combine-strings '("a" "b" 23 2)) ("ab" 23 2)) 257 | 258 | #+(or) 259 | (ensure-same (combine-strings '(1 2 3 )) (1 2 3)) 260 | 261 | #+(or) 262 | (defmethod scan-one-span ((line string) scanner-name scanner scanners) 263 | (let ((found? nil) 264 | (result nil) 265 | (last-e 0) 266 | (regex (scanner-regex scanner)) 267 | (scanner-fn (scanner-function scanner)) 268 | (last-thing nil)) 269 | (when (process-span-in-span-p scanner-name *current-span*) 270 | (flet ((sub-scan (it) 271 | (let ((*current-span* scanner-name)) 272 | (scan-lines-with-scanners it scanners)))) 273 | (do-scans (s e gs ge regex line) 274 | (let ((registers (loop for s-value across gs 275 | for e-value across ge 276 | when (and (not (null s-value)) 277 | (/= s-value e-value)) collect 278 | (sub-scan (subseq line s-value e-value))))) 279 | (setf registers (process-span scanner-name registers)) 280 | (let ((converted 281 | `(,@(when (plusp s) 282 | `(,(sub-scan (subseq line last-e s)))) 283 | ,(if scanner-fn 284 | (funcall scanner-fn scanner-name registers) 285 | `(,scanner-name ,@registers))))) 286 | (print (list :c converted last-thing)) 287 | (cond ((and (stringp converted) 288 | (stringp last-thing)) 289 | (setf (first (last result)) 290 | (concatenate 'string last-thing converted))) 291 | (t 292 | (setf result (append result converted)))) 293 | (setf found? t 294 | last-e e 295 | last-thing converted)))) 296 | (when found? 297 | (return-from scan-one-span 298 | (values (let ((last (sub-scan (subseq line last-e)))) 299 | (if (plusp (size last)) 300 | (append result (list last)) 301 | result)) 302 | t)))))) 303 | (values (list line) nil)) 304 | 305 | (defun make-simple-anchor (scanner-name registers) 306 | (declare (ignore scanner-name)) 307 | (format nil "{anchor ~{~a~^ ~}}" registers)) 308 | 309 | (defun convert-escape-temporarily (scanner-name registers) 310 | (declare (ignore scanner-name)) 311 | (assert (position (aref (first registers) 0) *escape-characters*)) 312 | (format nil "~c~c~a~c~c" 313 | #\Null #\Null 314 | (position (aref (first registers) 0) *escape-characters*) 315 | #\Null #\Null)) 316 | 317 | (defmethod unconvert-escapes ((thing t)) 318 | thing) 319 | 320 | (defmethod unconvert-escapes ((string string)) 321 | (cl-ppcre:regex-replace-all 322 | '(:sequence escape-kludge) string 323 | (lambda (_ &rest registers) 324 | (declare (ignore _) 325 | (dynamic-extent registers)) 326 | ;(print registers) 327 | (let ((ch (parse-integer (first registers)))) 328 | (string (aref *escape-characters* ch)))) 329 | :simple-calls t)) 330 | 331 | (defmethod unconvert-escapes ((thing list)) 332 | (collect-elements thing :transform #'unconvert-escapes)) 333 | 334 | (defmethod unconvert-escapes ((thing chunk)) 335 | (setf (slot-value thing 'lines) 336 | (collect-elements (lines thing) :transform #'unconvert-escapes))) 337 | 338 | (defmethod unconvert-escapes ((thing abstract-document)) 339 | (iterate-elements (chunks thing) #'unconvert-escapes)) 340 | -------------------------------------------------------------------------------- /unit-tests/comparison.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:cl-markdown-test) 2 | 3 | #| 4 | source .text 5 | cl-markdown .html 6 | tidy .xxxx 7 | markdown .down 8 | tidy .mark 9 | |# 10 | 11 | #+(or) 12 | (cl-markdown-test::compare-all) 13 | 14 | #+(or) 15 | (compare-markdown-and-cl-markdown 16 | (pathname-name 17 | (first (directory 18 | (make-pathname :name :wild 19 | :type "text" 20 | :defaults *test-source-directory*))))) 21 | 22 | #+(or) 23 | (compare-markdown-and-cl-markdown "Auto Links") 24 | 25 | (defvar *errors* nil) 26 | (defvar *all-wells* nil) 27 | (defvar *data* nil 28 | "What a hack! Shoot me") 29 | 30 | (defparameter *test-source-directory* 31 | (system-relative-pathname 32 | 'cl-markdown 33 | (make-pathname :directory '(:relative "unit-tests" "markdown-tests")))) 34 | 35 | (defparameter *test-output-directory* 36 | (system-relative-pathname 37 | 'cl-markdown 38 | (make-pathname :directory 39 | '(:relative "website" "output" "comparison-tests")))) 40 | 41 | (defun compare-markdown-and-cl-markdown (basename) 42 | (cl-markdown-and-tidy basename) 43 | (markdown-and-tidy basename) 44 | (create-comparison-file basename)) 45 | 46 | (defun compare-all () 47 | (setf *errors* nil 48 | *all-wells* nil) 49 | (iterate-elements 50 | (directory (make-pathname :name :wild :type "text" :defaults *test-source-directory*)) 51 | (lambda (file) 52 | (handler-case 53 | (compare-markdown-and-cl-markdown (pathname-name file)) 54 | (error (c) 55 | (push (pathname-name file) *errors*) 56 | (create-error-file (pathname-name file) c))))) 57 | (create-main-comparison-page) 58 | (copy-file (make-pathname :type "css" 59 | :name "style" 60 | :defaults *test-source-directory*) 61 | (make-pathname :type "css" 62 | :name "style" 63 | :defaults *test-output-directory*) 64 | :if-exists :supersede)) 65 | 66 | (defun create-main-comparison-page () 67 | (let ((output (make-pathname :type "html" 68 | :name "index" 69 | :defaults *test-output-directory*))) 70 | (ensure-directories-exist output) 71 | (with-new-file (s output) 72 | (lml2:html-stream 73 | s 74 | (lml2:html 75 | (:head (:title "Index | CL-Markdown / Markdown Comparison") 76 | ((:link :rel "stylesheet" :href "style.css"))) 77 | (:body 78 | ((:div :id "contents") 79 | (:p 80 | "Below are the results of running " 81 | ((:a :href "http://www.common-lisp.net/project/cl-markdown") "CL-Markdown") 82 | " and the Perl " ((:a :href "http://www.daringfireball.net/markdown") "Markdown") 83 | " script on the same input. You'll see that the current version of CL-Markdown performs well on most documents and poorly on a few. You'll also find that the rendered HTML can be very similar even where the diffs between outputs contains many insertions and deletions.") 84 | (:p 85 | "This will be updated regularly. The most recent update was " 86 | (lml2:lml-princ (format-date "%e %B %Y" (get-universal-time)))) 87 | 88 | (:h2 "Comparison Tests") 89 | 90 | (iterate-elements 91 | (directory 92 | (make-pathname :name :wild :type "text" :defaults *test-source-directory*)) 93 | (lambda (file) 94 | (bind ((entry-file (comparison-file-name (pathname-name file))) 95 | (entry (namestring (make-pathname :name (pathname-name entry-file) 96 | :type "html"))) 97 | (data (find (pathname-name file) *data* 98 | :test #'string-equal :key #'car)) 99 | ((nil replace insert delete) (or data (list nil nil nil nil)))) 100 | (lml2:html 101 | ((:span :class 102 | (cond ((find (pathname-name file) *errors* :test #'string-equal) 103 | "index-entry error") 104 | ((find (pathname-name file) *all-wells* :test #'string-equal) 105 | "index-entry good") 106 | (t "index-entry"))) 107 | ((:a :href entry) (lml2:lml-princ entry) 108 | (unless (and (and replace (zerop replace)) 109 | (and delete (zerop delete)) 110 | (and insert (zerop insert))) 111 | (lml2:lml-format " (~D, ~D, ~D)" replace delete insert)))))))) 112 | 113 | ((:div :id "notes") 114 | (:p "In the rare case that CL-Markdown produces invalid HTML. Most browsers will still display the output but " 115 | ((:a :href "tidy") "Tidy") " reports errors and produces no output. This will show up as a blank section on the comparison page. As far as I know, the HTML CL-Markdown is now always valid.") 116 | (:p "Files with this " ((:span :class "error") "color") " had Lisp errors during the run. " 117 | "Files with this " ((:span :class "good") "color") " had no differences from Markdown output during the run." 118 | "The numbers in parentheses represent the number of replacements, inserts, and deletes that occurred during the diff.")) 119 | 120 | ((:div :id "footer") "end 'o page")))))))) 121 | 122 | (defun cl-markdown-and-tidy (basename) 123 | (let* ((inpath (make-pathname :type "text" 124 | :name basename 125 | :defaults *test-source-directory*)) 126 | (output (make-pathname :type "html" 127 | :name basename 128 | :defaults *test-source-directory*))) 129 | (markdown inpath :format :html :stream output) 130 | (tidy basename "html" "xxxx") 131 | output)) 132 | 133 | (defun create-error-file (basename condition) 134 | (let ((output (comparison-file-name basename))) 135 | (ensure-directories-exist output) 136 | (with-new-file (s output) 137 | (lml2:html-stream 138 | s 139 | (lml2:html 140 | (:head (:title "CL-Markdown / Markdown Comparison") 141 | ((:link :rel "stylesheet" :href "style.css"))) 142 | (:body 143 | ((:div :id "contents") 144 | (:p "Error during parsing of '" (lml2:lml-princ basename) "'.") 145 | ((:a :href "index.html") "Back to index") 146 | (:p 147 | (:pre 148 | (lml2:lml-princ 149 | (html-encode:encode-for-pre 150 | (html-encode:encode-for-http 151 | (format nil "~A" condition)))))) 152 | 153 | (:div 154 | ((:div :id "original-source") 155 | (:h1 "Original source") 156 | ((:div :class "section-contents") 157 | (:pre 158 | (lml2:lml-princ 159 | (html-encode:encode-for-pre 160 | (file->string (make-pathname 161 | :type "text" 162 | :name basename 163 | :defaults *test-source-directory*)))))))) 164 | ((:div :id "footer") "end 'o page")))))))) 165 | 166 | (defun markdown-and-tidy (basename) 167 | (let* ((inpath (make-pathname :type "text" 168 | :name basename 169 | :defaults *test-source-directory*)) 170 | (outpath (make-pathname :type "mark" 171 | :name basename 172 | :defaults *test-source-directory*))) 173 | (metashell:shell-command 174 | (format nil "/usr/local/bin/markdown '~a' > '~A'" 175 | (system-namestring inpath) (system-namestring outpath))) 176 | 177 | (tidy basename "mark" "down") 178 | outpath)) 179 | 180 | (defun tidy (basename input-type output-type) 181 | (let* ((inpath (make-pathname :type input-type 182 | :name basename 183 | :defaults *test-source-directory*)) 184 | (tidy-output (make-pathname :type output-type 185 | :name basename 186 | :defaults *test-source-directory*)) 187 | (command (format nil 188 | "/usr/bin/tidy --show-body-only 1 --quiet 1 --show-warnings 0 '~A' > '~A'" 189 | (system-namestring inpath) 190 | (system-namestring tidy-output)))) 191 | (metashell:shell-command command) 192 | (when (zerop (kl:file-size tidy-output)) 193 | ;; an error in the HTML 194 | (error "HTML Error for ~A" basename)) 195 | tidy-output)) 196 | 197 | (defun comparison-file-name (basename) 198 | (make-pathname :defaults *test-output-directory* 199 | :type "html" 200 | :name (concatenate 'string basename "-compare"))) 201 | 202 | (defun create-comparison-file (basename) 203 | (bind ((cl-file (make-pathname :type "xxxx" 204 | :name basename 205 | :defaults *test-source-directory*)) 206 | (md-file (make-pathname :type "down" 207 | :name basename 208 | :defaults *test-source-directory*)) 209 | ((values diff replace insert delete) 210 | (html-diff::html-diff (file->string md-file) (file->string cl-file))) 211 | (output (comparison-file-name basename))) 212 | (push (list basename replace insert delete) *data*) 213 | (ensure-directories-exist output) 214 | (with-new-file (s output) 215 | (lml2:html-stream 216 | s 217 | (lml2:html 218 | (:head (:title "CL-Markdown / Markdown Comparison") 219 | ((:link :rel "stylesheet" :href "style.css"))) 220 | (:body 221 | ((:div :id "contents") 222 | ((:div :id "header") 223 | (:h1 "File: " (lml2:lml-princ basename) ".text")) 224 | ((:a :href "index.html") "Back to index") 225 | (:div 226 | ((:div :id "cl-markdown-output") 227 | (:h1 "CL-Markdown") 228 | ((:div :class "section-contents") 229 | (lml2:insert-file cl-file))) 230 | ((:div :id "markdown-output") 231 | (:h1 "Markdown") 232 | ((:div :class "section-contents") 233 | (lml2:insert-file md-file)))) 234 | (:div 235 | ((:div :id "diff-output") 236 | (:h1 "HTML Difference") 237 | ((:div :class "section-contents") 238 | (cond ((and (zerop insert) (zerop delete) (zerop replace)) 239 | (push basename *all-wells*) 240 | (lml2:lml-princ "No differences")) 241 | (t 242 | (lml2:html 243 | (:p 244 | "Insert: " (lml2:lml-princ insert) 245 | ", Delete: " (lml2:lml-princ delete) 246 | ", Replace " (lml2:lml-princ replace)) 247 | (lml2:lml-princ 248 | diff)))))) 249 | ((:div :id "cl-markdown-html") 250 | (:h1 "HTML from CL Markdown") 251 | ((:div :class "section-contents") 252 | (:pre 253 | (lml2:lml-princ 254 | (html-encode:encode-for-pre 255 | (html-encode:encode-for-http 256 | (file->string cl-file)))))))) 257 | (:div 258 | ((:div :id "original-source") 259 | (:h1 "Original source") 260 | ((:div :class "section-contents") 261 | (:pre 262 | (lml2:lml-princ 263 | (html-encode:encode-for-pre 264 | (html-encode:encode-for-http 265 | (file->string (make-pathname :type "text" 266 | :name basename 267 | :defaults *test-source-directory*))))))))) 268 | ((:div :id "footer") "end 'o page")))))))) 269 | 270 | (defun file->string (pathname) 271 | (apply 'concatenate 272 | 'string 273 | (with-iterator (iterator (make-pathname :defaults pathname) 274 | :treat-contents-as :lines 275 | :skip-empty-chunks? nil) 276 | (collect-elements 277 | iterator 278 | :transform (lambda (line) 279 | (format nil "~%~A" line)))))) 280 | 281 | -------------------------------------------------------------------------------- /dev/reports.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:cl-markdown) 2 | 3 | (eval-when (:compile-toplevel :load-toplevel :execute) 4 | (export '( 5 | symbols-exported-with-no-definition 6 | symbols-that-should-be-documented 7 | symbols-documented-by-document 8 | symbols-documented-by-documents 9 | symbols-not-documented-by-multi-document 10 | ))) 11 | 12 | (defmethod documents ((document document)) (list document)) 13 | 14 | (defmethod documents ((document multi-document)) (children document)) 15 | 16 | (defun build-documentation-report (source target document packages 17 | &key (format :html) excluded-symbols 18 | docs-package search-locations) 19 | (cl-markdown:markdown 20 | source 21 | :format format 22 | :stream target 23 | :additional-extensions '(unmentioned-symbols-report 24 | unused-exported-symbols-report 25 | markdown-warnings-report 26 | documented-symbols-report) 27 | :properties `((:documentation-document 28 | . ,document) 29 | (:documentation-documents 30 | . ,(documents document)) 31 | (:documentation-packages 32 | . ,(ensure-list packages)) 33 | (:documentation-excluded-symbols 34 | . ,excluded-symbols) 35 | ,@(when docs-package 36 | `((:docs-package . ,(find-package docs-package)))) 37 | (:search-locations 38 | . ,(append 39 | (list 40 | (system-relative-pathname 'cl-markdown "resources/")) 41 | (ensure-list search-locations))) 42 | (:style-sheet . "markdown-report-styles.css")))) 43 | 44 | (defun symbols-defined-by-packages (packages &key excluded-symbols) 45 | (sort 46 | (remove-duplicates 47 | (loop for package in (ensure-list packages) append 48 | (let ((package (find-package package))) 49 | (loop for s being the symbols 50 | of package when 51 | (and (eql package (symbol-package s)) 52 | (or (fboundp s) 53 | (boundp s) 54 | (find-class s nil) 55 | (get s 'prolog:functor) 56 | (eq (system:variable-information s) :special) 57 | (member s excluded-symbols) 58 | #+allegro 59 | (symbol-is-type-specifier-p s) 60 | ;; Could check deftypes if necessary. 61 | )) 62 | collect s)))) 63 | 'string< :key 'symbol-name)) 64 | 65 | (defun symbols-exported-with-no-definition (packages &key excluded-symbols) 66 | (sort 67 | (remove-duplicates 68 | (loop for package in (ensure-list packages) append 69 | (loop for s being the external-symbols 70 | of package unless 71 | (or (fboundp s) 72 | (boundp s) 73 | (find-class s nil) 74 | (get s 'prolog:functor) 75 | (eq (system:variable-information s) :special) 76 | (member s excluded-symbols) 77 | #+allegro 78 | (symbol-is-type-specifier-p s) 79 | ;; Could check deftypes if necessary. 80 | ) 81 | collect s))) 82 | 'string< :key 'symbol-name)) 83 | 84 | #+allegro 85 | (defun symbol-is-type-specifier-p (x) 86 | (or (excl:normalize-type 87 | x :loud (lambda (&rest r) 88 | (declare (ignore r)) 89 | (return-from symbol-is-type-specifier-p nil))) 90 | t)) 91 | 92 | (defun symbols-that-should-be-documented 93 | (packages &key 94 | (excluded-packages (list :common-lisp 95 | #+allegro :excl)) 96 | excluded-symbols) 97 | (let ((excluded-packages (mapcar #'find-package excluded-packages))) 98 | (sort 99 | (remove-duplicates 100 | (loop for package in (ensure-list packages) append 101 | (loop for s being the external-symbols 102 | of package unless 103 | (or (member s excluded-symbols) 104 | (member (symbol-package s) excluded-packages)) 105 | collect s))) 106 | 'string< :key 'symbol-name))) 107 | 108 | #+(or) 109 | (mapcar #'print 110 | (symbols-that-should-be-documented 111 | (list :db.agraph :db.agraph.parser 112 | :db.agraph.serializer 113 | :sparql 114 | :net.cluster))) 115 | 116 | (defun symbols-documented-by-document (document default-package) 117 | (let ((*package* (or (let ((*current-document* document)) 118 | (document-property :docs-package)) 119 | (find-package default-package))) 120 | (markdown-package (find-package :cl-markdown))) 121 | (flet ((fix-symbol (symbol) 122 | (if (eql (symbol-package symbol) markdown-package) 123 | (intern (symbol-name symbol) *package*) 124 | symbol))) 125 | (mapcar 126 | (lambda (thing) 127 | (etypecase thing 128 | (symbol (fix-symbol thing)) 129 | (cons (list (first thing) (fix-symbol (second thing)))))) 130 | (cl-containers:collect-keys 131 | (first (cl-containers:item-at-1 132 | (cl-markdown::properties document) :documentation-anchors)) 133 | :transform #'car))))) 134 | 135 | (defun symbols-documented-by-documents (documents default-package) 136 | (remove-duplicates 137 | (loop for document in documents append 138 | (symbols-documented-by-document document default-package)))) 139 | 140 | (defun symbols-not-documented-by-multi-document 141 | (multi-doc packages default-package &key 142 | (excluded-packages (list :common-lisp 143 | #+allegro :excl)) 144 | (excluded-symbols nil)) 145 | (sort 146 | (set-difference 147 | (symbols-that-should-be-documented 148 | packages :excluded-packages excluded-packages 149 | :excluded-symbols excluded-symbols) 150 | (symbols-documented-by-documents multi-doc default-package)) 151 | #'string-lessp)) 152 | 153 | #+ignore 154 | (symbols-not-documented-by-multi-document 155 | (second *last-multi-doc*) 156 | (list :db.agraph :db.agraph.parser 157 | :db.agraph.serializer 158 | :sparql 159 | :net.cluster) 160 | :excluded-symbols (symbols-explicitly-undocumented-for-agraph)) 161 | 162 | (defextension (unused-exported-symbols-report) 163 | (when (eq phase :render) 164 | (let ((packages (document-property :documentation-packages)) 165 | (excluded (document-property :documentation-excluded-symbols)) 166 | (*package* (or (document-property :docs-package) 167 | *package*)) 168 | (os *output-stream*)) 169 | (format os "~&
~%") 170 | (format os "~&

Exported Symbols with no apparent use

~%") 171 | (cond (packages 172 | (let ((symbols (symbols-exported-with-no-definition 173 | packages :excluded-symbols excluded))) 174 | (format os "~&
~%") 175 | (format os "~&

From packages:

~%") 176 | (format os "~&~{~&~a~^ ~}" 177 | (mapcar #'package-name packages)) 178 | (format os "~&
~%") 179 | (cond ((> (length symbols) 0) 180 | (format os "~&

~:d Symbols

~%" 181 | (length symbols)) 182 | (loop for s in symbols do 183 | (format os "~&~s ~%" s))) 184 | (t 185 | (format os "~&All exported symbols are accounted for~%" 186 | (length symbols)))) 187 | (when excluded 188 | (format os "~&
~%") 189 | (format os "~&

Ignoring the following ~:d symbols

~%" 190 | (length excluded)) 191 | (format os "~&~{~s ~^ ~%~}~%" 192 | excluded) 193 | (format os "~&
~%")))) 194 | (t 195 | (format os "~&

There are no packages specified by the 196 | property :documentation-packages~%"))) 197 | (format os "~&

~%")))) 198 | 199 | (defextension (unmentioned-symbols-report 200 | :arguments ((packages :keyword) 201 | (excluded-symbols :keyword) 202 | (donot-display-excluded-symbols :keyword))) 203 | (when (eq phase :render) 204 | (let ((packages (or packages 205 | (document-property :documentation-packages))) 206 | (excluded (or excluded-symbols 207 | (document-property :documentation-excluded-symbols))) 208 | (documents (document-property :documentation-documents)) 209 | (*package* (or (document-property :docs-package) 210 | *package*)) 211 | (os *output-stream*)) 212 | (format os "~&
~%") 213 | (format os "~&

Exported Symbols not mentioned in the documentation

~%") 214 | (cond (packages 215 | (let ((symbols (symbols-not-documented-by-multi-document 216 | documents 217 | packages 218 | *package* 219 | :excluded-symbols excluded))) 220 | (format os "~&
~%") 221 | (format os "~&

From packages:

~%") 222 | (format os "~&~{~&~a ~^ ~}" 223 | (mapcar #'package-name packages)) 224 | (format os "~&
~%") 225 | (cond ((> (length symbols) 0) 226 | (format os "~&

~:d Undocumented Symbols

~%" 227 | (length symbols)) 228 | (loop for s in symbols 229 | do (format os "~&~s ~%" s))) 230 | (t 231 | (format os "~&All exported symbols are documented.~%"))) 232 | (when excluded 233 | (format os "~&
~%") 234 | (format os "~&

Ignoring ~d symbols

~%" 235 | (length excluded)) 236 | (unless donot-display-excluded-symbols 237 | (format 238 | os "~&~{~s ~^ ~%~}~%" 239 | excluded)) 240 | (format os "~&
~%")))) 241 | (t 242 | (format os "~&

There are no packages specified by the 243 | propoerty :documentation-packages~%"))) 244 | (format os "~&

~%")))) 245 | 246 | ;; FIXME -- this is wrong (it's the same as unmentioned right now) 247 | (defextension (undocumented-symbols-report 248 | :arguments ((packages :keyword) 249 | (excluded-symbols :keyword) 250 | (donot-display-excluded-symbols :keyword))) 251 | (when (eq phase :render) 252 | (let ((packages (or packages 253 | (document-property :documentation-packages))) 254 | (excluded (or excluded-symbols 255 | (document-property :documentation-excluded-symbols))) 256 | (documents (document-property :documentation-documents)) 257 | (*package* (or (document-property :docs-package) 258 | *package*)) 259 | (os *output-stream*)) 260 | (format os "~&
~%") 261 | (format os "~&

Exported Symbols not mentioned in the documentation

~%") 262 | (cond (packages 263 | (let ((symbols (symbols-not-documented-by-multi-document 264 | documents 265 | packages 266 | *package* 267 | :excluded-symbols excluded))) 268 | (format os "~&
~%") 269 | (format os "~&

From packages:

~%") 270 | (format os "~&~{~&~a ~^ ~}" 271 | (mapcar #'package-name packages)) 272 | (format os "~&
~%") 273 | (cond ((> (length symbols) 0) 274 | (format os "~&

~:d Undocumented Symbols

~%" 275 | (length symbols)) 276 | (loop for s in symbols 277 | do (format os "~&~s ~%" s))) 278 | (t 279 | (format os "~&All exported symbols are documented.~%"))) 280 | (when excluded 281 | (format os "~&
~%") 282 | (format os "~&

Ignoring ~d symbols

~%" 283 | (length excluded)) 284 | (unless donot-display-excluded-symbols 285 | (format 286 | os "~&~{~s ~^ ~%~}~%" 287 | excluded)) 288 | (format os "~&
~%")))) 289 | (t 290 | (format os "~&

There are no packages specified by the 291 | propoerty :documentation-packages~%"))) 292 | (format os "~&

~%")))) 293 | 294 | (defextension (documented-symbols-report) 295 | (when (eq phase :render) 296 | (let* ((documents (document-property :documentation-documents)) 297 | (*package* (or (document-property :docs-package) 298 | *package*)) 299 | (os *output-stream*) 300 | (symbols (symbols-documented-by-document 301 | documents *package*))) 302 | (format os "~&
~%") 303 | (format os "~&

Documented symbols

~%") 304 | (cond ((> (length symbols) 0) 305 | (format os "~&

~:d Documented Symbols

~%" 306 | (length symbols)) 307 | (loop for s in symbols 308 | do (format os "~&~s ~%" s))) 309 | (t 310 | (format os "~&All exported symbols are documented.~%"))) 311 | (format os "~&
~%")))) 312 | 313 | (defextension (markdown-warnings-report 314 | :arguments ()) 315 | (when (eq phase :render) 316 | (bind ((os *output-stream*) 317 | (document (document-property :documentation-document)) 318 | (documents (merge-elements 319 | (warnings document) 320 | (lambda (old new) 321 | (push new old)) 322 | (lambda (new) 323 | (list new)) 324 | :key 'first :argument 'cdr 325 | :filter (lambda (pair) 326 | (typep (first pair) 'document #+(or) 'child-document)))) 327 | (warnings? nil)) 328 | (format os "~&
~%") 329 | (format os "~&

Markdown Warnings

~%") 330 | 331 | (setf documents (sort documents #'string-lessp 332 | :key (compose 'ensure-string 'source 'first))) 333 | (loop for (document warnings) in documents do 334 | (when warnings 335 | (setf warnings? t) 336 | (format os "~&
~%") 337 | (format os "~&

~a

~%" 338 | (short-source (source document))) 339 | (format os "~&
    ~%") 340 | (loop for warning in (merge-elements 341 | warnings 342 | (lambda (old new) 343 | (declare (ignore new)) 344 | (incf (second old)) 345 | old) 346 | (lambda (new) (list new 1)) 347 | :test 'equal 348 | :return :values) do 349 | (format os "~&
  • ~a
  • ~%" warning)) 350 | (format os "~&
~%") 351 | (format os "~&
~%"))) 352 | (unless warnings? 353 | (format os "~%No warnings found.~%")) 354 | (format os "~&
~%")))) 355 | -------------------------------------------------------------------------------- /unit-tests/test-chunkers.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:cl-markdown-test) 2 | 3 | #| 4 | (run-tests :suite 'test-chunkers :break-on-errors? t) 5 | 6 | |# 7 | 8 | (deftestsuite test-it-starts-with-block-level-html-p (cl-markdown-test) 9 | () 10 | (:tests 11 | ((ensure (not (it-starts-with-block-level-html-p "")))) 12 | ((ensure (not (it-starts-with-block-level-html-p "
")))) 13 | ((ensure (not (it-starts-with-block-level-html-p "hello")))) 14 | ((ensure (not (it-starts-with-block-level-html-p "")))) 15 | ((ensure (not (it-starts-with-block-level-html-p "
"))) 17 | ((ensure (it-starts-with-block-level-html-p "
"))) 18 | ((ensure (it-starts-with-block-level-html-p "
    "))) 19 | ((ensure (not (it-starts-with-block-level-html-p "<>")))) 20 | 21 | ((ensure (not (it-starts-with-block-level-html-p "")))) 22 | ((ensure (not (it-starts-with-block-level-html-p "
")))) 23 | ((ensure (not (it-starts-with-block-level-html-p "/hello")))) 24 | ((ensure (not (it-starts-with-block-level-html-p "")))) 25 | ((ensure (not (it-starts-with-block-level-html-p "
"))) 27 | ((ensure (it-starts-with-block-level-html-p ""))))) 28 | 29 | 30 | 31 | ;;;; 32 | 33 | (deftestsuite test-chunkers (cl-markdown-test) 34 | () 35 | (:equality-test 'samep)) 36 | 37 | (deftestsuite test-line-is-empty-p (test-chunkers) 38 | () 39 | (:test ((ensure (line-is-empty-p " ")))) 40 | (:test ((ensure (not (line-is-empty-p " 4"))))) 41 | (:test ((ensure (not (line-is-empty-p "4 "))))) 42 | (:test ((ensure (line-is-empty-p 43 | (coerce (list #\tab #\space #\newline) 'string)))))) 44 | 45 | (deftestsuite line-starts-with-number-p (test-chunkers) 46 | () 47 | (:test ((ensure-null (line-starts-with-number-p "1.")))) 48 | (:test ((ensure (not (line-starts-with-number-p "a."))))) 49 | (:test ((ensure (not (line-starts-with-number-p "1 hello"))))) 50 | (:test ((ensure-null (line-starts-with-number-p "10123.")))) 51 | (:test ((ensure (not (line-starts-with-number-p "10123th is big")))))) 52 | 53 | ;;; --------------------------------------------------------------------------- 54 | 55 | (deftestsuite line-starts-with-bullet-p (test-chunkers) 56 | () 57 | (:test ((ensure (line-starts-with-bullet-p "* hello")))) 58 | (:test ((ensure (not (line-starts-with-bullet-p " *."))))) 59 | (:test ((ensure (not (line-starts-with-bullet-p "*"))))) 60 | (:test ((ensure (line-starts-with-bullet-p " * ")))) 61 | (:test ((ensure (not (line-starts-with-bullet-p " *"))))) 62 | (:test ((ensure (not (line-starts-with-bullet-p " *")))))) 63 | 64 | ;;; --------------------------------------------------------------------------- 65 | 66 | (deftestsuite test-remove-marker (test-chunkers) () 67 | (:test ((ensure-same (remove-marker "* hello") "hello"))) 68 | (:test ((ensure-same (remove-marker "*. hello") "hello"))) 69 | (:test ((ensure-same (remove-marker "*. hello") "hello"))) 70 | (:test ((ensure-same (remove-marker "* hello") "hello"))) 71 | (:test ((ensure-same (remove-marker "+. hello") "hello"))) 72 | (:test ((ensure-same (remove-marker "-. hello") "hello"))) 73 | ; (:test ((ensure-same (remove-marker " -. hello") "hello"))) 74 | (:test ((ensure-same (remove-marker "-. ") "")))) 75 | 76 | ;;; --------------------------------------------------------------------------- 77 | 78 | (deftestsuite test-remove-number (test-chunkers) () 79 | (:test ((ensure-same (remove-number "1. hello") "hello"))) 80 | (:test ((ensure-same (remove-number "232. hello") "hello"))) 81 | (:test ((ensure-same (remove-number "3. hello") "hello"))) 82 | (:test ((ensure-same (remove-number "453. hello") "hello"))) 83 | (:test ((ensure-same (remove-number "2.") ""))) 84 | (:test ((ensure-same (remove-number "123. ") "")))) 85 | 86 | ;;; --------------------------------------------------------------------------- 87 | 88 | (deftestsuite test-line-is-horizontal-rule-p (test-chunkers) () 89 | (:test ((ensure (line-is-horizontal-rule-p "---")))) 90 | (:test ((ensure (line-is-horizontal-rule-p "- - -")))) 91 | (:test ((ensure (line-is-horizontal-rule-p " - - - ")))) 92 | (:test ((ensure (line-is-horizontal-rule-p " - --")))) 93 | (:test ((ensure-null (line-is-horizontal-rule-p " = = =")))) 94 | (:test ((ensure (line-is-horizontal-rule-p "__ _")))) 95 | (:test ((ensure (not (line-is-horizontal-rule-p "-_-"))))) 96 | ) 97 | 98 | ;;; --------------------------------------------------------------------------- 99 | 100 | (deftestsuite test-atx-header-markup-class (test-chunkers) () 101 | (:test ((ensure-same (atx-header-markup-class "# hello #") 'header1))) 102 | (:test ((ensure-same (atx-header-markup-class "###### hello #") 'header6))) 103 | (:test ((ensure-error (atx-header-markup-class "####### hello #")))) 104 | (:test ((ensure-error (atx-header-markup-class "h###ello"))))) 105 | 106 | ;;; --------------------------------------------------------------------------- 107 | 108 | (deftestsuite test-remove-atx-header (test-chunkers) () 109 | (:test ((ensure-same (remove-atx-header "# hello #") "hello"))) 110 | (:test ((ensure-same (remove-atx-header "###### hello #") "hello"))) 111 | (:test ((ensure-same (remove-atx-header "### ### hello") "### hello")))) 112 | 113 | ;;; --------------------------------------------------------------------------- 114 | 115 | (deftestsuite test-line-indentation (test-chunkers) 116 | () 117 | (:test ((ensure-same (line-indentation " hello") 2))) 118 | (:test ((ensure-same (line-indentation "") 0))) 119 | (:test ((ensure-same (line-indentation "hello ") 0))) 120 | (:test ((ensure-same (line-indentation 121 | (coerce (list #\tab #\space #\h #\i) 'string)) 122 | (1+ *spaces-per-tab*))))) 123 | 124 | ;;; --------------------------------------------------------------------------- 125 | 126 | (deftestsuite test-line-is-code-p (test-chunkers) 127 | () 128 | (:test ((ensure (line-is-code-p " hello")))) 129 | (:test ((ensure (line-is-code-p (format nil "~Chello" #\Tab))))) 130 | (:test ((ensure (not (line-is-code-p "hello")))))) 131 | 132 | ;;; --------------------------------------------------------------------------- 133 | 134 | (deftestsuite test-line-is-blockquote-p (test-chunkers) 135 | () 136 | (:test ((ensure (line-is-blockquote-p "> hello")))) 137 | (:test ((ensure (line-is-blockquote-p " > hello")))) 138 | (:test ((ensure (line-is-blockquote-p " > hello")))) 139 | (:test ((ensure (line-is-blockquote-p " > hello")))) 140 | (:test ((ensure (not (line-is-blockquote-p " > hello")))))) 141 | 142 | ;;; --------------------------------------------------------------------------- 143 | 144 | (deftestsuite test-one-tab-stripper (test-chunkers) () 145 | (:test ((ensure-same (one-tab-stripper "hello") 146 | (values "hello" nil)))) 147 | (:test ((ensure-same (one-tab-stripper " hello") 148 | (values "hello" t)))) 149 | (:test ((ensure-same (one-tab-stripper " hello") 150 | (values " hello" t))))) 151 | 152 | ;;; --------------------------------------------------------------------------- 153 | 154 | (deftestsuite test-blockquote-stripper (test-chunkers) () 155 | (:test ((ensure-same (blockquote-stripper "hello") 156 | (values "hello" nil)))) 157 | (:test ((ensure-same (blockquote-stripper "> hello") 158 | (values "hello" t)))) 159 | (:test ((ensure-same (blockquote-stripper ">") 160 | (values "" t)))) 161 | (:test ((ensure-same (blockquote-stripper " > > why") 162 | (values "> why" t)))) 163 | (:test ((ensure-same (blockquote-stripper " > > why") 164 | (values " > why" t)))) 165 | (:test ((ensure-same (blockquote-stripper " >> why") 166 | (values " >> why" nil))))) 167 | 168 | (deftestsuite test-maybe-strip-line (test-chunkers) 169 | () 170 | (:setup (reset *parsing-environment*))) 171 | 172 | (addtest (test-maybe-strip-line) 173 | no-strippers 174 | (ensure-same (maybe-strip-line "hello") (values "hello" 0)) 175 | (ensure-same (maybe-strip-line " hello") (values " hello" 0))) 176 | 177 | ;;; --------------------------------------------------------------------------- 178 | 179 | (deftestsuite test-maybe-strip-line-one-tab-stripper (test-maybe-strip-line) 180 | () 181 | (:setup (insert-item (strippers *parsing-environment*) 'one-tab-stripper)) 182 | (:test ((ensure-same (maybe-strip-line "hello") (values "hello" 0)))) 183 | (:test ((ensure-same (maybe-strip-line " hello") (values "hello" 1)))) 184 | (:test ((ensure-same (maybe-strip-line " * hello") (values "* hello" 1)))) 185 | (:test ((ensure-same (maybe-strip-line " hello") (values " hello" 1))))) 186 | 187 | ;;; --------------------------------------------------------------------------- 188 | 189 | (deftestsuite test-maybe-strip-line-two-tab-strippers (test-maybe-strip-line) 190 | () 191 | (:setup 192 | (insert-item (strippers *parsing-environment*) 'one-tab-stripper) 193 | (insert-item (strippers *parsing-environment*) 'one-tab-stripper)) 194 | (:test ((ensure-same (maybe-strip-line "hello") (values "hello" 0)))) 195 | (:test ((ensure-same (maybe-strip-line " hello") (values "hello" 1)))) 196 | (:test ((ensure-same (maybe-strip-line " * hello") (values "* hello" 1)))) 197 | (:test ((ensure-same (maybe-strip-line " hello") (values "hello" 2)))) 198 | (:test ((ensure-same (maybe-strip-line " hello") 199 | (values " hello" 2))))) 200 | 201 | (deftestsuite test-maybe-strip-line-one-bq-strippers (test-maybe-strip-line) 202 | () 203 | (:setup 204 | (insert-item (strippers *parsing-environment*) 'blockquote-stripper)) 205 | (:test ((ensure-same (maybe-strip-line "hello") (values "hello" 0)))) 206 | (:test ((ensure-same (maybe-strip-line "> hello") (values "hello" 1)))) 207 | (:test ((ensure-same (maybe-strip-line ">> hello") (values "> hello" 1))))) 208 | 209 | (deftestsuite test-maybe-strip-line-two-bq-strippers 210 | (test-maybe-strip-line-one-bq-strippers) 211 | () 212 | (:setup 213 | (insert-item (strippers *parsing-environment*) 'blockquote-stripper)) 214 | (:test ((ensure-same (maybe-strip-line "hello") (values "hello" 0)))) 215 | (:test ((ensure-same (maybe-strip-line "> hello") (values "hello" 1)))) 216 | (:test ((ensure-same (maybe-strip-line ">> hello") (values "hello" 2))))) 217 | 218 | ;;?? FiXME -- why?! 219 | #+(or) 220 | (deftestsuite test-chunk-source (test-chunkers) 221 | ((document (progn (princ "******") (make-container 'cl-markdown::document))))) 222 | 223 | (deftestsuite test-chunk-source (test-chunkers) 224 | (document) 225 | (:setup 226 | (setf document (make-container 'cl-markdown::document)))) 227 | 228 | (addtest (test-chunk-source) 229 | simple-1 230 | (chunk-source 231 | document 232 | "this is 233 | paragraph number one. 234 | 235 | this is paragraph number two. 236 | 237 | 238 | 239 | 240 | and this 241 | is 242 | paragraph number 243 | three.") 244 | (ensure-same (size (chunks document)) 3)) 245 | 246 | (addtest (test-chunk-source) 247 | simple-mixed-indenting-no-breaks 248 | (chunk-source 249 | document "this is 250 | paragraph number one. 251 | this is paragraph number one 252 | and this 253 | is 254 | paragraph number 255 | one") 256 | (ensure-same (size (chunks document)) 1)) 257 | 258 | (addtest (test-chunk-source) 259 | simple-bullets-with-breaks 260 | (chunk-source 261 | document "this is a list 262 | 263 | * item 1 264 | * item 2 265 | 266 | that's all.") 267 | (ensure-same (size (chunks document)) 4)) 268 | 269 | ;;; --------------------------------------------------------------------------- 270 | 271 | (addtest (test-chunk-source) 272 | simple-multiline-bullets 273 | (chunk-source 274 | document "this is a list 275 | 276 | * item 1 277 | is a bullet that take 278 | many lines 279 | * item 2 280 | 281 | that's all.") 282 | (ensure-same (size (chunks document)) 4)) 283 | 284 | ;;; --------------------------------------------------------------------------- 285 | 286 | (addtest (test-chunk-source) 287 | simple-multiline-bullets-with-breaks 288 | (chunk-source 289 | document "this is a list 290 | 291 | * item 1 292 | 293 | is a bullet that take 294 | many lines 295 | 296 | over three paragraphs 297 | 298 | * item 2 299 | 300 | that's all.") 301 | (ensure-same (size (chunks document)) 6) 302 | (ensure-same (size (lines (nth-element (chunks document) 0))) 1) 303 | (ensure-same (size (lines (nth-element (chunks document) 2))) 2) 304 | (ensure-same (indentation (nth-element (chunks document) 3)) 2) 305 | (ensure-same (indentation (nth-element (chunks document) 4)) 0)) 306 | 307 | ;;; --------------------------------------------------------------------------- 308 | 309 | (addtest (test-chunk-source) 310 | simple-bullets-and-numbers 311 | (chunk-source 312 | document "this is a list 313 | 314 | * of 315 | * bullets 316 | 1. and numbers 317 | 2. and more numbers 318 | + and then bullets 319 | - and more bullets 320 | 321 | that's all.") 322 | (ensure-same (size (chunks document)) 8)) 323 | 324 | ;;; --------------------------------------------------------------------------- 325 | 326 | (addtest (test-chunk-source) 327 | simple-headers-1 328 | (handle-setext-headers 329 | (chunk-source 330 | document "Random line 331 | Title One 332 | ======== 333 | 334 | What not 335 | 336 | ======== 337 | Just some equal signs 338 | ")) 339 | (ensure-same (size (chunks document)) 4)) 340 | 341 | (addtest (test-chunk-source) 342 | simple-headers-2 343 | (handle-setext-headers 344 | (chunk-source 345 | document " 346 | Title 347 | ======== 348 | Subtitle 349 | -------- 350 | 351 | What not is 352 | a good start to a paragraph. 353 | 354 | ")) 355 | (ensure-same (size (chunks document)) 3)) 356 | 357 | ;;; --------------------------------------------------------------------------- 358 | ;;; line-could-be-link-reference-title-p 359 | ;;; --------------------------------------------------------------------------- 360 | 361 | (deftestsuite line-could-be-link-reference-title-p (test-chunkers) 362 | () 363 | (:test ((ensure (line-could-be-link-reference-title-p " \"Hi\"")))) 364 | (:test ((ensure (line-could-be-link-reference-title-p "\"Hi\"")))) 365 | (:test ((ensure (not (line-could-be-link-reference-title-p " He said \"hi\""))))) 366 | (:test ((ensure (not (line-could-be-link-reference-title-p " \"no closing quote")))))) --------------------------------------------------------------------------------