Compare commits

..

33 commits

Author SHA1 Message Date
6263a02f89 Heading support 2025-02-27 21:05:04 -05:00
72c73ec99d Hard line break support 2025-02-27 05:28:06 -05:00
e4fe5f98e6 Soft line break support 2025-02-27 05:20:59 -05:00
2e66b03baa Paragraph testing 2025-02-27 04:41:09 -05:00
135b878233 HTML rendering 2025-02-27 04:23:25 -05:00
a09f3b60df Paragraph parsing 2025-02-27 03:37:10 -05:00
626245e629 Add slurp utility function 2025-02-27 02:24:14 -05:00
86fa9da474 more todos 2025-02-27 02:18:56 -05:00
77890ba549 Parse inline text 2025-02-27 01:46:47 -05:00
bd94410c01 Lines effect 2025-02-25 10:09:30 -05:00
db0f3a0427 prep to restart 2025-02-23 17:37:13 -05:00
81a7f09623 Undo everything to switch over to an event based parser 2025-02-23 17:12:19 -05:00
7ef90867f2 checkpoint 2025-02-23 17:05:28 -05:00
75a21b8da6 Move djot binary 2025-02-21 23:27:18 -05:00
fe239e287b Handle Multiline headings properly 2025-02-21 22:54:52 -05:00
f54df1d2a4 Pretty print internal representation in Djot utility 2025-02-21 16:34:41 -05:00
68928aeb20 Test Headings 2025-02-21 16:23:02 -05:00
85ead54619 Test linebreaks and nbsps 2025-02-21 16:17:31 -05:00
72b102d071 Setup golden value testing 2025-02-21 15:29:25 -05:00
4e83ca8d48 Todos 2025-02-21 05:51:26 -05:00
72418a0b88 Fix softbreak rendering 2025-02-21 05:47:46 -05:00
67a07a0523 Don't generate trailing slash for void tags 2025-02-21 05:43:34 -05:00
c11bf35cc8 Fix misssing indend in html void tag generation 2025-02-21 05:42:04 -05:00
d1e1dd5f59 Post process inline parsing 2025-02-21 05:40:02 -05:00
2ce4579e08 Basic Html Rendering 2025-02-21 05:13:19 -05:00
6c36d6e62a Refactor Djot into modules 2025-02-21 04:16:28 -05:00
79c74426c1 Basic Heading Parsing 2025-02-21 03:56:30 -05:00
f0718bbf2c Move Djot module 2025-02-21 03:26:12 -05:00
f60e209073 Paragraph Support 2025-02-21 03:24:23 -05:00
98ed90a9bc Inline text parsing 2025-02-21 02:49:47 -05:00
2033a48c3e Add Utility file 2025-02-20 18:07:12 -05:00
045fab1b4b Core parser effect 2025-02-19 10:59:19 -05:00
7f4bcf5f51 Disable smart constructor for now 2025-02-19 05:00:28 -05:00
33 changed files with 1044 additions and 29 deletions

2
.gitignore vendored
View file

@ -1,2 +1,4 @@
build/ build/
*.*~ *.*~
test/failures
test/*/*/output

View file

@ -13,14 +13,23 @@ authors = "Nathan McCarty"
-- langversion -- langversion
-- packages to add to search path -- packages to add to search path
depends = structures depends = contrib
, structures
, tailrec
, eff , eff
, elab-util
, elab-pretty
, sop
, prettier
-- modules to install -- modules to install
modules = SSG.Parser.Core modules = SSG.HTML
, SSG.Parser.Markdown
, SSG.HTML
, SSG.HTML.ElementTypes , SSG.HTML.ElementTypes
, SSG.Djot
, SSG.Djot.Lines
, SSG.Djot.Block
, SSG.Djot.Inline
, SSG.Djot.Render
-- main file (i.e. file to load at REPL) -- main file (i.e. file to load at REPL)
main = Main main = Main

View file

@ -5,15 +5,15 @@ import SSG.HTML
import Structures.Dependent.DList import Structures.Dependent.DList
helloWorld : Html "html" helloWorld : Html "html"
helloWorld = -- helloWorld =
tag "html" ["lang" =$ "en"] [ -- tag "html" ["lang" =$ "en"] [] [
tag "head" [] [ -- tag "head" [] [
tag "title" [] "Example" -- tag "title" [] "Example"
], -- ],
tag "body" [] [ -- tag "body" [] [
tag "p" [] [Text "Hello World!"] -- tag "p" [] [Text "Hello World!"]
] -- ]
] -- ]
main : IO () main : IO ()
main = putStr $ render helloWorld main = putStr $ render helloWorld

View file

@ -4,6 +4,11 @@ path = "."
ipkg = "SSG.ipkg" ipkg = "SSG.ipkg"
test = "test/test.ipkg" test = "test/test.ipkg"
[custom.all.Djot]
type = "local"
path = "."
ipkg = "Djot.ipkg"
[custom.all.SSG-test] [custom.all.SSG-test]
type = "local" type = "local"
path = "test" path = "test"

5
src/SSG/Djot.idr Normal file
View file

@ -0,0 +1,5 @@
module SSG.Djot
import public SSG.Djot.Inline
import public SSG.Djot.Block
import public SSG.Djot.Render

231
src/SSG/Djot/Block.idr Normal file
View file

@ -0,0 +1,231 @@
||| DJot Block formatting parser
module SSG.Djot.Block
import SSG.Djot.Inline
import SSG.Djot.Lines
import Control.Eff
import Data.Nat
import Data.Maybe
import Data.String
import Derive.Prelude
import Derive.Pretty
import Generics.Derive
import Structures.Dependent.DList
-- Just for iutils unit tests
import System
%language ElabReflection
%hide Generics.SOP.values
%hide Generics.Derive.Show
%hide Generics.Derive.Eq
--******************************
--* Data Structures *
--******************************
||| Types of block level element
public export
data BlockType : Type where
TParagraph : BlockType
THeading : BlockType
%runElab derive "BlockType" [Generic, Show, Eq, DecEq]
||| A block element
public export
data Block : BlockType -> Type where
Paragraph : (content : List Inline) -> Block TParagraph
Heading : (level : Nat) -> (content : List Inline)
-> {auto non_zero : IsSucc level} -> {auto in_range : level `LTE` 6}
-> Block THeading
%runElab derive "Block" [Show, Eq]
--******************************
--* Parsing Utilities *
--******************************
||| Type alias for block parsing
BParser : Type -> Type
BParser t = Eff [Lines, Fail] t
-- FIXME: Proper whitespace handling
||| Detect if a line is blank
blankLine : String -> Bool
blankLine = null . trim
||| Select and apply the first matching parser from a list, with a fallback parser
selectParser :
{fallback_type : BlockType} -> {values : _}
-> (fallback : BParser (Block fallback_type))
-> DList BlockType (\t => (String -> Bool, BParser (Block t))) values
-> BParser (t : BlockType ** Block t)
selectParser fallback [] = do
x <- fallback
pure (_ ** x)
selectParser fallback ((pred, parser) :: rest) = do
Just line <- peek
| _ => fail
if pred line
then do
x <- parser
pure (_ ** x)
else selectParser fallback rest
||| Repeat a parser until failure or if we are out of lines
repeat : BParser t -> BParser (List t)
repeat x = do
Just _ <- peek
| _ => pure []
Just res <- lift . runFail $ x
| _ => pure []
map (res ::) (repeat x)
||| Run a parser
runBParser : (input : String) -> BParser t -> Maybe t
runBParser input x =
map fst . extract . runFail . runLines input $ x
||| Returns true if a character is horizontal whitespace
isHoriz : Char -> Bool
isHoriz ' ' = True
isHoriz '\t' = True
isHoriz _ = False
--******************************
--* Syntax *
--******************************
-- Forward declare for mutual recursion
||| Top level block parser
pBlock : BParser (t : BlockType ** Block t)
-- Paragraph
paragraph : BParser (Block TParagraph)
paragraph = do
contents <- map (inline . joinBy "\n") $ slurp blankLine
pure $ Paragraph contents
-- Heading
record HeadingLevel where
constructor MkHL
level : Nat
{auto non_zero : IsSucc level}
{auto in_range : level `LTE` 6}
acceptHeadingPrefix : String -> Maybe (HeadingLevel, String)
acceptHeadingPrefix str =
let (level, str) = acceptHeadingPrefix' 0 str
in case isItSucc level of
Yes non_zero =>
case level `isLTE` 6 of
Yes in_range => Just $ (MkHL level, str)
No _ => Nothing
No _ => Nothing
where
acceptHeadingPrefix' : (acc : Nat) -> String -> (Nat, String)
acceptHeadingPrefix' acc str with (asList str)
acceptHeadingPrefix' acc "" | [] = (acc, "")
acceptHeadingPrefix' acc (strCons '#' str) | ('#' :: x) =
acceptHeadingPrefix' (S acc) str | x
acceptHeadingPrefix' acc (strCons c str) | (c :: x) =
if isHoriz c
then acceptHeadingPrefix' acc str | x
else (acc, strCons c str)
headingFirstLine : BParser (HeadingLevel, String)
headingFirstLine = do
first <- take >>= fromJust
fromJust $ acceptHeadingPrefix first
heading : BParser (Block THeading)
heading = do
(MkHL level {non_zero} {in_range}, first) <- headingFirstLine
rest <- slurp blankLine
let contents = joinBy "\n" $ first :: map (stripPrefix level) rest
pure $ Heading level (inline contents)
where
stripPrefix : (l : Nat) -> String -> String
stripPrefix l str with (asList str)
stripPrefix 0 str | _ = str
stripPrefix (S k) "" | [] = ""
stripPrefix (S k) (strCons '#' str) | ('#' :: x) = stripPrefix k str | x
stripPrefix (S k) (strCons c str) | (c :: x) = strCons c str
-- Definition for top level block parser
pBlock = selectParser paragraph
[ (isJust . acceptHeadingPrefix, heading)
]
--******************************
--* Top Level Parsing Function *
--******************************
||| Parse a string as a sequences of blocks
export
block : (input : String) -> (values ** DList BlockType Block values)
block input =
case runBParser input (repeat pBlock) of
Nothing => (_ ** [])
Just x => fromList x
--******************************
--* Unit Tests *
--******************************
[myshow] Show (t : BlockType ** Block t) where
show (_ ** block) = show block
{values : _} -> Show (DList BlockType Block values) where
show xs =
let xs = map (show @{myshow}) $ toList xs
in "[\{joinBy ", " xs}]"
golden : {values : _}
-> (input : String) -> (ref : DList BlockType Block values) -> IO Bool
golden input ref = do
putStrLn "Input: \{show input}"
putStrLn "Reference:\n \{show ref}"
let (types ** output) = block input
putStrLn "Output:\n \{show output}"
pure $ ref $== output
-- @@test Paragraph Smoke
smokeParagraph : IO Bool
smokeParagraph =
golden
"Hello World!"
[Paragraph [Text "Hello World!"]]
-- @@test Two Paragraph Smoke
smokeTwoParagraph : IO Bool
smokeTwoParagraph =
golden
"Hello World!\n\nHello Alice!"
[Paragraph [Text "Hello World!"], Paragraph [Text "Hello Alice!"]]
-- @@test Heading Smoke
smokeHeading : IO Bool
smokeHeading =
golden
"# Level 1 Heading"
[Heading 1 [Text "Level 1 Heading"]]
-- @@test Multiline Prefixed Heading Smoke
smokeHeadingMultilinePrefixed : IO Bool
smokeHeadingMultilinePrefixed =
golden
"# Level 1\n# Heading"
[Heading 1 [Text "Level 1", SoftLineBreak, Text "Heading"]]
-- @@test Multiline Nonprefixed Heading Smoke
smokeHeadingMultilineNonprefixed : IO Bool
smokeHeadingMultilineNonprefixed =
golden
"# Level 1\nHeading"
[Heading 1 [Text "Level 1", SoftLineBreak, Text "Heading"]]

240
src/SSG/Djot/Inline.idr Normal file
View file

@ -0,0 +1,240 @@
||| Djot inline formatting parser
module SSG.Djot.Inline
import Control.Eff
import Data.List1
import Data.Maybe
import Data.String
import Derive.Prelude
import Derive.Pretty
-- Just for iutils unit tests
import System
%language ElabReflection
--******************************
--* Data Structures *
--******************************
||| Types of inline styling
public export
data Inline : Type where
Text : (text : String) -> Inline
SoftLineBreak : Inline
HardLineBreak : Inline
%runElab derive "Inline" [Eq, Show, Pretty]
--******************************
--* Parsing Utilities *
--******************************
||| Type alias for inline parsing
IParser : Type -> Type
IParser t = Eff [State (List Char), Fail] t
||| Get the next char, modifiying the state
popChar : IParser Char
popChar = do
x :: xs <- get
| [] => fail
put xs
pure x
||| Get the next char, without modifying the state
peekChar : IParser Char
peekChar = do
x :: xs <- get
| [] => fail
pure x
||| Attempt to parse something without propagating the failure
try : IParser t -> IParser (Maybe t)
try x = do
state <- get
x <- lift . runFail $ x
case x of
Nothing => do
put state
pure Nothing
Just y => pure $ Just y
||| Choose a parser
oneOf : List (IParser t) -> IParser t
oneOf [] = fail
oneOf (x :: xs) = do
state <- get
x <- lift . runFail $ x
case x of
Nothing => do
put state
oneOf xs
Just y => pure y
||| Parse 0+ of something
many : IParser t -> IParser (List t)
many x = do
Just y <- try x
| _ => pure []
map (y ::) $ many x
||| Parse 1+ of something
atLeastOne : IParser t -> IParser (List1 t)
atLeastOne x = do
Just y <- try x
| _ => fail
map (y :::) (many x)
||| Match exactly the given string
exactly : String -> IParser (List Char)
exactly str = exactly' (unpack str)
where
exactly' : List Char -> IParser (List Char)
exactly' [] = pure []
exactly' (x :: xs) = do
y <- popChar
if x == y
then map (x ::) (exactly' xs)
else fail
||| Match vertical whitespace
vert : IParser (List Char)
vert = oneOf
[ exactly "\n"
, exactly "\r\n"
, exactly "\r"
]
||| Match horizontal whitespace
horiz : IParser (List Char)
horiz = oneOf
[ exactly " "
, exactly "\t"
]
||| Run a parser
runIParser : (str : String) -> IParser t -> Maybe t
runIParser str x =
fst . extract . runState (unpack str) . runFail $ x
--******************************
--* Syntax *
--******************************
-- Forward declare so we can get mutually recursive
||| Top level parser function for Inline Content
pInline : IParser Inline
||| Parse a character as plain text
text : IParser Inline
text = do
c <- popChar
pure $ Text (singleton c)
||| Parse a soft linebreak
softLineBreak : IParser Inline
softLineBreak = do
_ <- atLeastOne singleBreak
pure SoftLineBreak
where
singleBreak : IParser ()
singleBreak = do
_ <- many horiz
_ <- vert
_ <- many horiz
pure ()
hardLineBreak : IParser Inline
hardLineBreak = do
_ <- exactly "\\"
_ <- many horiz
_ <- vert
_ <- many horiz
pure HardLineBreak
-- Definition of pInline
pInline = oneOf
[ hardLineBreak
, softLineBreak
, text
]
--******************************
--* Post Processing *
--******************************
||| Combine adjacent Text entries
combineTexts : List Inline -> List Inline
combineTexts [] = []
combineTexts (Text a :: (Text b :: xs)) =
combineTexts (Text (a ++ b) :: xs)
combineTexts (x :: xs) = x :: combineTexts xs
||| Remove trailing soft line breaks
removeTrailingSoftbreak : List Inline -> List Inline
removeTrailingSoftbreak xs = reverse (removeTrailingSoftbreak' (reverse xs))
where
removeTrailingSoftbreak' : List Inline -> List Inline
removeTrailingSoftbreak' (SoftLineBreak :: xs) = removeTrailingSoftbreak' xs
removeTrailingSoftbreak' xs = xs
||| Top level post processor
postProcess : List Inline -> List Inline
postProcess xs = removeTrailingSoftbreak . combineTexts $ xs
--******************************
--* Top Level Parsing Function *
--******************************
||| Parse a string as inline content
export
inline : (input : String) -> List Inline
inline input =
postProcess . fromMaybe [] . runIParser input $ many pInline
--******************************
--* Unit Tests *
--******************************
golden : (input : String) -> (reference : List Inline) -> IO Bool
golden input reference = do
putStrLn "Input: \{show input}"
let opts = Opts 78
let ref_pretty = Doc.render opts $ pretty reference
putStrLn "Reference:\n\{unlines . map (" " ++) . lines $ ref_pretty}"
let output = inline input
let out_pretty = Doc.render opts $ pretty output
putStrLn "Output:\n\{unlines . map (" " ++) . lines $ out_pretty}"
pure $ reference == output
-- @@test Just text parses as text
testTextAsText : IO Bool
testTextAsText =
golden "Hello World!" [Text "Hello World!"]
-- @@test Soft line break smoke
smokeSoftLineBreak : IO Bool
smokeSoftLineBreak =
golden "Hello\nWorld!" [Text "Hello", SoftLineBreak, Text "World!"]
-- @@test Soft line break double line break
testDoubleSoftLineBreak : IO Bool
testDoubleSoftLineBreak =
golden "Hello\n\nWorld!" [Text "Hello", SoftLineBreak, Text "World!"]
-- @@test Soft line break trailing line break
testTrailingSoftLineBreak : IO Bool
testTrailingSoftLineBreak =
golden "Hello\n\nWorld!\n" [Text "Hello", SoftLineBreak, Text "World!"]
-- @@test Soft line break multiple trailing line breaks
testTrailingSoftLineBreaks : IO Bool
testTrailingSoftLineBreaks =
golden "Hello\n\nWorld!\n\n\n" [Text "Hello", SoftLineBreak, Text "World!"]
-- @@test Hard line break smoke
smokeHardLineBreak : IO Bool
smokeHardLineBreak =
golden "Hello\\\nWorld!" [Text "Hello", HardLineBreak, Text "World!"]

135
src/SSG/Djot/Lines.idr Normal file
View file

@ -0,0 +1,135 @@
||| An effect for reading an input as a list of lines
module SSG.Djot.Lines
import Data.String
import Control.Eff
-- Only for iutils unit tests
import System
--************************
--* Effect Definition *
--************************
export
data Lines : Type -> Type where
||| Peek the next line
Peek : Lines (Maybe String)
||| Take the next line
Take : Lines (Maybe String)
--************************
--* Effect Actions *
--************************
export
peek : Has Lines fs => Eff fs (Maybe String)
peek = send Peek
export
take : Has Lines fs => Eff fs (Maybe String)
take = send Take
--************************
--* Extra Effect Actions *
--************************
||| Take lines until a line matching the given predicate is encountered, dropping the
||| all of the matching lines until the first non matching one
|||
||| Intended to be used to slurp up to the next blank line, discarding the blanks
export
slurp : Has Lines fs => (predicate : String -> Bool) -> Eff fs (List String)
slurp predicate = do
Just line <- peek
| _ => pure []
if predicate line
then do
_ <- take
Just next <- peek
| _ => pure []
if predicate next
then slurp predicate
else pure []
else do
Just x <- take
| _ => pure []
map (x ::) (slurp predicate)
||| Pop the next line and ignore its value
export
drop : Has Lines fs => Eff fs ()
drop = do
_ <- take
pure ()
--************************
--* Effect Handlers *
--************************
||| Split the next line from a string
nextLine : String -> Maybe (String, String)
nextLine str =
if null str
then Nothing
else
let
(before, after) = break (\c => any (== c) ['\r', '\n']) str
in Just (before, removeNewline after)
where
-- If `after` is empty, we have hit the end of the string, and there is no newline
-- character to remove. If it has contents, then we need to remove the newline
removeNewline : String -> String
removeNewline str with (strM str)
removeNewline "" | StrNil = ""
removeNewline (strCons '\n' xs) | (StrCons '\n' xs) = xs
-- Handle either a \r or a \r\n
removeNewline (strCons '\r' xs) | (StrCons '\r' xs) with (strM xs)
removeNewline (strCons '\r' "") | (StrCons '\r' "") | StrNil = ""
removeNewline (strCons '\r' _) | (StrCons '\r' _) | (StrCons '\n' xs1) = xs1
removeNewline (strCons '\r' _) | (StrCons '\r' _) | (StrCons x xs1) = strCons x xs1
-- We shouldn't ever hit this case, as we would have to have contents after the
-- break, but no newline character, but we fill it in for the sake of totality
removeNewline (strCons x xs) | (StrCons x xs) = str
unLines : String -> Lines s -> (s, String)
unLines str Peek =
case nextLine str of
Nothing => (Nothing, str)
Just (before, after) => (Just before, str)
unLines str Take =
case nextLine str of
Nothing => (Nothing, str)
Just (before, after) => (Just before, after)
||| Feed a `Lines` from a provided input string
export
runLines : Has Lines fs =>
(input : String) -> Eff fs t -> Eff (fs - Lines) (t, String)
runLines input =
handleRelayS input (\x, y => pure (y, x)) $ \input2, lns, f =>
let (vv, input3) = unLines input2 lns
in f input3 vv
--************************
--* Unit Tests *
--************************
-- @@test runLines Smoke Test
runLinesSmoke : IO Bool
runLinesSmoke = do
let input = "Hello\nWorld\n\n"
putStrLn "Input: \{show input}"
let reference = lines input
putStrLn "Reference: \{show reference}"
let (output, rest) = extract $ runLines input lines'
putStrLn "Output: \{show output}"
putStrLn "Rest: \{show rest}"
pure $ output == reference && null rest
where
lines' : Eff [Lines] (List String)
lines' = do
Just next <- take
| _ => pure []
map (next ::) lines'

57
src/SSG/Djot/Render.idr Normal file
View file

@ -0,0 +1,57 @@
||| Render Djot to HTML
module SSG.Djot.Render
import SSG.HTML
import SSG.Djot.Inline
import SSG.Djot.Block
import Data.List
import Data.Nat
||| Render a single inline element as HTML
export
renderInline : Inline -> Maybe (t ** Html t)
-- FIXME: escape text
renderInline (Text text) = Just (_ ** Text text)
renderInline SoftLineBreak = Nothing
renderInline HardLineBreak = Just (_ ** Void "br" [])
||| Render a list of inline elments to html
export
renderInlines : List Inline -> (types ** DList _ Html types)
renderInlines xs = fromList . catMaybes $ map renderInline xs
||| Render a single block element as HTML
export
renderBlock : Block t -> (t ** Html t)
||| Render a list of block level elements to html
export
renderBlocks : {types: _} -> DList _ Block types -> (types' ** DList _ Html types')
renderBlock (Paragraph content) =
(_ ** Normal "p" [] (snd (renderInlines content)))
renderBlock (Heading 1 content {non_zero = ItIsSucc} {in_range}) =
(_ ** Normal "h1" [] (snd (renderInlines content)))
renderBlock (Heading 2 content {non_zero = ItIsSucc} {in_range}) =
(_ ** Normal "h2" [] (snd (renderInlines content)))
renderBlock (Heading 3 content {non_zero = ItIsSucc} {in_range}) =
(_ ** Normal "h3" [] (snd (renderInlines content)))
renderBlock (Heading 4 content {non_zero = ItIsSucc} {in_range}) =
(_ ** Normal "h4" [] (snd (renderInlines content)))
renderBlock (Heading 5 content {non_zero = ItIsSucc} {in_range}) =
(_ ** Normal "h5" [] (snd (renderInlines content)))
renderBlock (Heading 6 content {non_zero = ItIsSucc} {in_range}) =
(_ ** Normal "h6" [] (snd (renderInlines content)))
renderBlock (Heading (6 + (S n)) content {non_zero = ItIsSucc} {in_range}) =
absurd in_range
renderBlocks xs = dMap' (\_, x => renderBlock x) xs
||| Render a complete html document from a list of block level elements
export
renderDocument : {types : _} -> DList _ Block types -> Html "html"
renderDocument xs =
Normal "html" ["lang" =$ "en"] [
Normal "body" [] (snd (renderBlocks xs))
]

View file

@ -63,8 +63,8 @@ namespace Html
if length attributes > 0 if length attributes > 0
then then
let attrs = joinBy " " $ map toString attributes let attrs = joinBy " " $ map toString attributes
in "<\{type} \{attrs} />" in "\{indent}<\{type} \{attrs}>"
else "<\{type} />" else "\{indent}<\{type}>"
viewIndented indent_level (Normal type attributes {content_types} contents) = viewIndented indent_level (Normal type attributes {content_types} contents) =
let indent = replicate (indent_level * 2) ' ' in let indent = replicate (indent_level * 2) ' ' in
-- Special handling if the tag contains exactly one `Text` element, we won't -- Special handling if the tag contains exactly one `Text` element, we won't
@ -123,10 +123,10 @@ namespace Html
TagType' : (type : String) -> {auto prf : IsValidTag type} -> Type TagType' : (type : String) -> {auto prf : IsValidTag type} -> Type
TagType' type {prf} = TagType prf TagType' type {prf} = TagType prf
||| Smart constructor for tags -- ||| Smart constructor for tags
public export -- public export
tag : (type : String) -> {auto prf : IsValidTag type} -> TagType prf -- tag : (type : String) -> {auto prf : IsValidTag type} -> TagType prf
tag type {prf = (PVoid type)} = Void type -- tag type {prf = (PVoid type)} = Void type
tag type {prf = (PRawText type)} = RawText type -- tag type {prf = (PRawText type)} = RawText type
tag type {prf = (PEscapableRawText type)} = RawText type -- tag type {prf = (PEscapableRawText type)} = RawText type
tag type {prf = (PNormal type)} = Normal type -- tag type {prf = (PNormal type)} = Normal type

View file

@ -1 +0,0 @@
module SSG.Parser.Core

View file

@ -1 +0,0 @@
module SSG.Parser.Markdown

9
test/Main.idr Normal file
View file

@ -0,0 +1,9 @@
module Main
import Test.Golden.RunnerHelper
main : IO ()
main = goldenRunner
[ "Hedgehog Tests" `atDir` "hedgehog"
, "Djot -> HTML Tests" `atDir` "djotToHtml"
]

View file

@ -0,0 +1,18 @@
module Main
import SSG.Djot
import SSG.HTML
import System
import System.File
main : IO ()
main = do
Right contents <- readFile "./test.dj"
| Left err => do
printLn err
exitFailure
let (_ ** blocks) = block contents
let html = renderDocument blocks
let output = render html
putStrLn output

View file

@ -0,0 +1,33 @@
<!DOCTYPE HTML>
<html lang=en>
<body>
<p>Hello World!</p>
<p>Hello Alice!</p>
<p>
A Multi-line
Paragraph
</p>
<p>Two line breaks</p>
<p>
A multiline paragraph
with some indentation
</p>
<p>
This is a hard
<br>
line break
</p>
<p>
And again
<br>
with some spaces afterwards
</p>
<p>
And now we
mix soft
<br>
and hard
linebreaks
</p>
</body>
</html>

View file

@ -0,0 +1,5 @@
[custom.all.SSG]
type = "local"
path = "../../.."
ipkg = "SSG.ipkg"
test = "test/test.ipkg"

View file

@ -0,0 +1,6 @@
rm -rf build/
flock "$1" pack -q install-deps test.ipkg
pack -q run test.ipkg
rm -rf build/

View file

@ -0,0 +1,23 @@
Hello World!
Hello Alice!
A Multi-line
Paragraph
Two line breaks
A multiline paragraph
with some indentation
This is a hard\
line break
And again\
with some spaces afterwards
And now we
mix soft\
and hard
linebreaks

View file

@ -0,0 +1,9 @@
package a-test
depends = SSG
, hedgehog
, eff
main = Main
executable = test

View file

@ -0,0 +1,18 @@
module Main
import SSG.Djot
import SSG.HTML
import System
import System.File
main : IO ()
main = do
Right contents <- readFile "./test.dj"
| Left err => do
printLn err
exitFailure
let (_ ** blocks) = block contents
let html = renderDocument blocks
let output = render html
putStrLn output

View file

@ -0,0 +1,44 @@
<!DOCTYPE HTML>
<html lang=en>
<body>
<h1>Level 1 Heading</h1>
<h2>Level 2 Heading</h2>
<h3>Level 3 Heading</h3>
<h4>Level 4 Heading</h4>
<h5>Level 5 Heading</h5>
<h6>Level 6 Heading</h6>
<p>####### Level 7 Not a heading</p>
<h1>
Multiline heading
with prefix
</h1>
<h1>
Multiline heading
with prefix and some extra whitespace
</h1>
<h2>
Level 2 multiline heading
with prefix
</h2>
<h1>
Multiline heading
with no prefix
</h1>
<h2>
Level 2 multiline heading
with no prefix
</h2>
<h1>
Unprefixed multiline heading
with some indentation
</h1>
<h1>
Heading level 1
# With a level 2 right after it (this line shouldn't be a heading)
</h1>
<h2>
Heading level 2
With a level 1 right after it (this line shouldn't be a heading)
</h2>
</body>
</html>

View file

@ -0,0 +1,5 @@
[custom.all.SSG]
type = "local"
path = "../../.."
ipkg = "SSG.ipkg"
test = "test/test.ipkg"

View file

@ -0,0 +1,6 @@
rm -rf build/
flock "$1" pack -q install-deps test.ipkg
pack -q run test.ipkg
rm -rf build/

View file

@ -0,0 +1,38 @@
# Level 1 Heading
## Level 2 Heading
### Level 3 Heading
#### Level 4 Heading
##### Level 5 Heading
###### Level 6 Heading
####### Level 7 Not a heading
# Multiline heading
# with prefix
# Multiline heading
# with prefix and some extra whitespace
## Level 2 multiline heading
## with prefix
# Multiline heading
with no prefix
## Level 2 multiline heading
with no prefix
# Unprefixed multiline heading
with some indentation
# Heading level 1
## With a level 2 right after it (this line shouldn't be a heading)
## Heading level 2
# With a level 1 right after it (this line shouldn't be a heading)

View file

@ -0,0 +1,9 @@
package a-test
depends = SSG
, hedgehog
, eff
main = Main
executable = test

View file

@ -0,0 +1,28 @@
module Main
import SSG.Djot.Lines
import Control.Eff
import Data.String
import Hedgehog
lines' : Eff [Lines] (List String)
lines' = do
Just next <- take
| _ => pure []
map (next ::) lines'
propLinesEquiv : Property
propLinesEquiv = property $ do
str <- forAll $ string (linear 0 256) ascii
let std_lines = lines str
let (our_lines, rest) = extract $ runLines str lines'
our_lines === std_lines
main : IO ()
main = test $
[ MkGroup
"Lines effect"
[ ("Lines effect equivalent to lines", propLinesEquiv)
]
]

View file

@ -0,0 +1,3 @@
━━━ Lines effect ━━━
✓ Lines effect equivalent to lines passed 1000 tests.
✓ 1 succeeded.

View file

@ -0,0 +1,5 @@
[custom.all.SSG]
type = "local"
path = "../../.."
ipkg = "SSG.ipkg"
test = "test/test.ipkg"

View file

@ -0,0 +1,6 @@
rm -rf build/
flock "$1" pack -q install-deps test.ipkg
HEDGEHOG_COLOR=0 pack -q run test.ipkg -n 1000
rm -rf build/

View file

@ -0,0 +1,9 @@
package a-test
depends = SSG
, hedgehog
, eff
main = Main
executable = test

View file

@ -1,4 +0,0 @@
module Main
main : IO ()
main = putStrLn "Test successful!"

View file

@ -14,6 +14,7 @@ authors = "Nathan McCarty"
-- packages to add to search path -- packages to add to search path
depends = SSG depends = SSG
, golden-runner-helper
-- modules to install -- modules to install
-- modules = -- modules =
@ -24,7 +25,7 @@ main = Main
-- name of executable -- name of executable
executable = "SSG-test" executable = "SSG-test"
-- opts = -- opts =
sourcedir = "src" -- sourcedir = "."
-- builddir = -- builddir =
-- outputdir = -- outputdir =

View file

@ -2,5 +2,67 @@
** TODO Special handling for =pre= in =view= ** TODO Special handling for =pre= in =view=
** TODO Smart spec compliance for =attribute= ** TODO Smart spec compliance for =attribute=
Probably want to refine the attribute and value strings, and be smarter about how we choose to quote the value string. Probably want to refine the attribute and value strings, and be smarter about how we choose to quote the value string.
** TODO Don't generate any further indent once we are already inside a =p=
** TODO Text escaping function
** NO Move =Raw= out of =Tag= ** NO Move =Raw= out of =Tag=
Decided to rename =Tag= to =Html=, and =Raw= to =Text=, which makes this make sense Decided to rename =Tag= to =Html=, and =Raw= to =Text=, which makes this make sense
* Parser Core
** TODO Refine =location= in =ParserLocation=
** TODO Error messages
** TODO Combinators for predictive parsing
* Djot [4/40]
:PROPERTIES:
:COOKIE_DATA: recursive
:END:
** Parsing [4/34]
*** Block Level
**** TODO Block Quote
**** TODO List Item
**** TODO List
**** TODO Code Block
**** TODO Thematic Break
**** TODO Raw Block
**** TODO Div
**** TODO Pipe Table
**** TODO Reference Link Definition
**** TODO Footnote
**** TODO Block Attributes
**** TODO Heading Links
**** DONE Paragraph
**** DONE Heading
*** Inline
**** TODO Escaped Text
**** TODO Link
**** TODO Image
**** TODO Autolink
**** TODO Verbatim
**** TODO Emphasis/Strong
**** TODO Highlighted
**** TODO Super/subscript
**** TODO Insert/delete
**** TODO Smart Punctuation
**** TODO Math
**** TODO Footnote Reference
**** TODO Comment
**** TODO Symbols
**** TODO Raw Inline
**** TODO Inline Attributes
**** DONE Ordinary Text
**** DONE Linebreak
*** Lines effect
**** TODO =IO= Backed implementation
*** Known Inaccuracies
**** TODO Stripping of prefixes from multiline headings isn't entirely accurate
Currently, it strips at least =level= ~#~'s from the start of the line, but doesn't check to see if its the correct number of them.
I need to see how other implementations handle this
** Extensions
*** TODO GFM-style alerts
*** TODO Emoji extension
**** TODO Unicode Emoji
**** TODO Icon font emoji
**** TODO Autolink for source forges
** Misc
*** TODO Add =fromString= for =Inline=
* Features
** TODO Preview rendered post in terminal
Centered headings, text styling, and syntax highlighted code blocks