Compare commits
21 commits
Author | SHA1 | Date | |
---|---|---|---|
|
b4506e7ed5 | ||
|
9ecde51921 | ||
|
91ccf173ba | ||
|
dc17b1e816 | ||
|
4a5d5e541a | ||
|
925c70f52b | ||
|
07218a32a6 | ||
|
2e9860d147 | ||
|
59c6ab4209 | ||
|
e18fee7272 | ||
|
1ee03ea495 | ||
|
4d45ab4bd5 | ||
|
52d88c95a7 | ||
|
2027c8569f | ||
|
5bf0487023 | ||
|
82c0fe7631 | ||
|
668d2fcad0 | ||
|
8fb8b3c176 | ||
|
9d776d9c39 | ||
|
609cf6c012 | ||
|
301a676d9b |
16 changed files with 578 additions and 157 deletions
|
@ -1,2 +1,3 @@
|
|||
dist-newstyle
|
||||
.shake
|
||||
Dockerfile
|
||||
|
|
56
.github/workflows/build-container.yml
vendored
Normal file
56
.github/workflows/build-container.yml
vendored
Normal file
|
@ -0,0 +1,56 @@
|
|||
name: Build container
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
# copied everything below this line from https://docs.github.com/en/actions/publishing-packages/publishing-docker-images
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
# There is a single job in this workflow. It's configured to run on the latest available version of Ubuntu.
|
||||
jobs:
|
||||
build-and-push-image:
|
||||
runs-on: ubuntu-latest
|
||||
# Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job.
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
#
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
# Uses the `docker/login-action` action to log in to the Container registry registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here.
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
# This step uses [docker/metadata-action](https://github.com/docker/metadata-action#about) to extract tags and labels that will be applied to the specified image. The `id` "meta" allows the output of this step to be referenced in a subsequent step. The `images` value provides the base name for the tags and labels.
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
# This step uses the `docker/build-push-action` action to build the image, based on your repository's `Dockerfile`. If the build succeeds, it pushes the image to GitHub Packages.
|
||||
# It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see "[Usage](https://github.com/docker/build-push-action#usage)" in the README of the `docker/build-push-action` repository.
|
||||
# It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step.
|
||||
- name: Build and push Docker image (versioned)
|
||||
uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: ghcr.io/pagwin2/psb:v1
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
||||
- name: Build and push Docker image (latest)
|
||||
uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: ghcr.io/pagwin2/psb:latest
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
|
@ -14,4 +14,6 @@ RUN cabal build
|
|||
|
||||
WORKDIR /github/workspace
|
||||
|
||||
ENTRYPOINT ["/mnt/dist-newstyle/build/x86_64-linux/ghc-9.4.8/psb-0.1.0.0/x/psb/build/psb/psb", "build", "-p2"]
|
||||
RUN export folder=$(ls /mnt/dist-newstyle/build/x86_64-linux) && mv /mnt/dist-newstyle/build/x86_64-linux/"$folder"/psb-0.1.0.0/x/psb/build/psb/psb /mnt/psb
|
||||
|
||||
ENTRYPOINT ["/mnt/psb", "build"]
|
||||
|
|
23
TODO
23
TODO
|
@ -1,23 +0,0 @@
|
|||
github action for deploy to github pages
|
||||
|
||||
minify js and css when copying over instead of just copying
|
||||
|
||||
make typst dependant on yaml?
|
||||
|
||||
add rst support so I'm not dependant on typst not breaking
|
||||
|
||||
add a cli flag to send webmentions and track the ones sent/acked in some file, probably a simple csv with path and what got linked to (https://indieweb.org/Webmention) (https://indieweb.org/Webmention-developer)
|
||||
|
||||
make it so typst citation section has a header and/or is a div elem instead of a section element
|
||||
|
||||
process source code blocks with tree sitter https://hackage.haskell.org/package/tree-sitter
|
||||
|
||||
Check to see if pandoc html output can be changed somehow (for header anchor links)
|
||||
https://hackage.haskell.org/package/pandoc-3.5/docs/Text-Pandoc-Writers.html#t:Writer
|
||||
HXT on the generated html is an option
|
||||
https://pandoc.org/MANUAL.html#custom-readers-and-writers might be an option https://pandoc.org/lua-filters.html
|
||||
most likely will be a generic lua filter though https://pandoc.org/lua-filters.html
|
||||
|
||||
|
||||
|
||||
see if performance can be improved (it isn't slow atm but it definitely feels like there's a bottleneck)
|
9
TODO.md
Normal file
9
TODO.md
Normal file
|
@ -0,0 +1,9 @@
|
|||
- add rst support and convert markdown handling to custom parser instead of pandoc
|
||||
|
||||
- process source code blocks with tree sitter https://hackage.haskell.org/package/tree-sitter
|
||||
|
||||
- minify js and css when copying over instead of just copying
|
||||
|
||||
- look into adding postcss support perhaps
|
||||
|
||||
- see if performance can be improved (it isn't slow atm but it definitely feels like there's a bottleneck)
|
|
@ -3,4 +3,4 @@ name: 'psb'
|
|||
description: 'Use PSB to build static a static site'
|
||||
runs:
|
||||
using: 'docker'
|
||||
image: 'Dockerfile'
|
||||
image: 'docker://ghcr.io/pagwin2/psb:v1'
|
||||
|
|
|
@ -4,11 +4,11 @@ outputDir :: String
|
|||
outputDir = "publish"
|
||||
|
||||
assetGlobs :: [String]
|
||||
assetGlobs = ["static//*", "robots.txt"]
|
||||
assetGlobs = ["static//*", "robots.txt", "sw.js", "favicon.ico"]
|
||||
|
||||
-- CAN ONLY BE TYPST DOCS UNLESS YOU CHANGE THINGS AT THE `pages` RULE in `Main.hs
|
||||
pagePaths :: [String]
|
||||
pagePaths = []
|
||||
|
||||
postGlobs :: [String]
|
||||
postGlobs = ["posts/*.typ", "posts/*.md"]
|
||||
postGlobs = ["posts/*.md"]
|
||||
|
|
18
app/IR.hs
Normal file
18
app/IR.hs
Normal file
|
@ -0,0 +1,18 @@
|
|||
module IR where
|
||||
|
||||
import Data.Text
|
||||
|
||||
-- Html and Math tags come with their data because they are leaves for us
|
||||
-- We aren't parsing that if we can avoid it
|
||||
data Tag = Heading {level :: Int} | Paragraph | Blockquote | Code | Html {html :: Text} | Anchor | Italic | Bold | Math {mathML :: Text}
|
||||
|
||||
data Data = Ast {ast :: AST} | Text {text :: Text}
|
||||
|
||||
data AST = AST {tag :: Tag, child :: [Data]}
|
||||
|
||||
-- for processing math
|
||||
-- https://hackage.haskell.org/package/typst-0.6.1/docs/Typst-Parse.html#v:parseTypst
|
||||
-- and
|
||||
-- https://hackage.haskell.org/package/typst-symbols-0.1.7/docs/Typst-Symbols.html
|
||||
-- are going to be used for handling typst and
|
||||
-- texmath for latex handling
|
65
app/Main.hs
65
app/Main.hs
|
@ -11,7 +11,6 @@ import Config
|
|||
import Control.Monad (forM, when)
|
||||
import qualified Data.HashMap.Strict as HM
|
||||
import Data.List (sortOn)
|
||||
import Data.Maybe (fromJust)
|
||||
import qualified Data.Ord as Ord
|
||||
import qualified Data.Text as T
|
||||
import Deriving.Aeson
|
||||
|
@ -20,7 +19,6 @@ import Development.Shake (Action, Rules, (%>), (|%>), (~>))
|
|||
import qualified Development.Shake as Shake
|
||||
import Development.Shake.FilePath ((</>))
|
||||
import qualified Development.Shake.FilePath as FP
|
||||
import qualified Development.Shake.FilePath as Shake
|
||||
import Templates
|
||||
import Types
|
||||
import Utilities
|
||||
|
@ -59,7 +57,6 @@ buildRules :: Rules ()
|
|||
buildRules = do
|
||||
home
|
||||
assets
|
||||
pages
|
||||
postsRule
|
||||
rss
|
||||
|
||||
|
@ -70,28 +67,6 @@ assets =
|
|||
let src = FP.dropDirectory1 target
|
||||
Shake.copyFileChanged src target
|
||||
|
||||
-- Shake.putInfo $ "Copied " <> target <> " from " <> src
|
||||
|
||||
-- handling typst only because pages should only be typst no reason for backwards compat on that
|
||||
pages :: Rules ()
|
||||
pages =
|
||||
map indexHtmlOutputPath pagePaths |%> \target -> do
|
||||
let src = indexHtmlTypstSourcePath target
|
||||
let metaSrc = indexHtmlTypstMetaPath target
|
||||
html <- typstToHtml src
|
||||
meta <- yamlToPost metaSrc
|
||||
time <- Utilities.now
|
||||
let page =
|
||||
Page
|
||||
{ pageTitle = postTitle meta,
|
||||
pageContent = html,
|
||||
pageNow = time,
|
||||
pageSection = T.pack $ fromJust $ Shake.stripExtension "html" target
|
||||
}
|
||||
applyTemplateAndWrite "default.html" page target
|
||||
|
||||
-- Shake.putInfo $ "Built " <> target <> " from " <> src
|
||||
|
||||
-- there's probably a better way of doing this that allows for the target's origin file extension to get passed in but for now we're doing brute force
|
||||
postsRule :: Rules ()
|
||||
postsRule =
|
||||
|
@ -105,33 +80,12 @@ postsRule =
|
|||
when
|
||||
should
|
||||
( case FP.takeExtension path of
|
||||
".typ" -> typstPost path
|
||||
".md" -> markdownPost path
|
||||
_ -> error $ "invalid file extension for post " <> target
|
||||
)
|
||||
)
|
||||
return ()
|
||||
|
||||
typstPost :: FP.FilePath -> Action ()
|
||||
typstPost src = do
|
||||
Shake.need [src]
|
||||
let target = indexHtmlOutputPath src
|
||||
|
||||
post <- readTypstPost src
|
||||
let rPost = fromPost post
|
||||
postHtml <- applyTemplate "post.html" rPost
|
||||
time <- Utilities.now
|
||||
let page =
|
||||
Page
|
||||
{ pageTitle = rPostTitle rPost,
|
||||
pageContent = postHtml,
|
||||
pageNow = time,
|
||||
pageSection = T.pack $ fromJust $ Shake.stripExtension "html" target
|
||||
}
|
||||
applyTemplateAndWrite "default.html" page target
|
||||
|
||||
-- Shake.putInfo $ "Built " <> target <> " from " <> src
|
||||
|
||||
markdownPost :: FP.FilePath -> Action ()
|
||||
markdownPost src = do
|
||||
Shake.need [src]
|
||||
|
@ -139,16 +93,16 @@ markdownPost src = do
|
|||
|
||||
post <- readMarkdownPost src
|
||||
let rPost = fromPost post
|
||||
-- Shake.putInfo $ show . toJSON $ rPost
|
||||
postHtml <- applyTemplate "post.html" rPost
|
||||
|
||||
time <- Utilities.now
|
||||
-- Shake.putInfo $ T.unpack $ urlConvert target
|
||||
let page =
|
||||
Page
|
||||
{ pageTitle = rPostTitle rPost,
|
||||
pageContent = postHtml,
|
||||
pageNow = time,
|
||||
pageSection = T.pack $ fromJust $ Shake.stripExtension "html" target
|
||||
pageUrl = urlConvert target
|
||||
}
|
||||
applyTemplateAndWrite "default.html" page target
|
||||
|
||||
|
@ -164,12 +118,13 @@ home =
|
|||
let posts' = map fromPost posts
|
||||
html <- applyTemplate "home.html" $ HM.singleton "posts" posts'
|
||||
time <- Utilities.now
|
||||
-- Shake.putInfo $ T.unpack $ urlConvert target
|
||||
let page =
|
||||
Page
|
||||
{ pageTitle = T.pack "Home",
|
||||
pageContent = html,
|
||||
pageNow = time,
|
||||
pageSection = T.pack $ fromJust $ Shake.stripExtension "html" target
|
||||
pageUrl = urlConvert target
|
||||
}
|
||||
applyTemplateAndWrite "default.html" page target
|
||||
|
||||
|
@ -195,21 +150,9 @@ rss =
|
|||
readPost :: FilePath -> Action Post
|
||||
readPost postPath = do
|
||||
case FP.takeExtension postPath of
|
||||
".typ" -> readTypstPost postPath
|
||||
".md" -> readMarkdownPost postPath
|
||||
_ -> error $ "unknown file extension for file" <> postPath
|
||||
|
||||
readTypstPost :: FilePath -> Action Post
|
||||
readTypstPost postPath = do
|
||||
html <- typstToHtml postPath
|
||||
post <- yamlToPost $ typstMetaPath postPath
|
||||
-- Shake.putInfo $ "Read " <> postPath
|
||||
return $
|
||||
post
|
||||
{ postContent = Just html,
|
||||
postLink = Just . T.pack $ "/" <> FP.dropExtension postPath <> "/"
|
||||
}
|
||||
|
||||
readMarkdownPost :: FilePath -> Action Post
|
||||
readMarkdownPost postPath = do
|
||||
(post, html) <- markdownToHtml postPath
|
||||
|
|
5
app/Markdown.hs
Normal file
5
app/Markdown.hs
Normal file
|
@ -0,0 +1,5 @@
|
|||
{-# LANGUAGE OverloadedStrings #-}
|
||||
|
||||
module Markdown () where
|
||||
|
||||
import CMark
|
35
app/Restruct.hs
Normal file
35
app/Restruct.hs
Normal file
|
@ -0,0 +1,35 @@
|
|||
module Restruct where
|
||||
|
||||
-- https://docutils.sourceforge.io/rst.html
|
||||
-- https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html
|
||||
|
||||
-- https://hackage.haskell.org/package/parsec-3.1.18.0/docs/doc-index-All.html
|
||||
|
||||
import Data.Text (Text)
|
||||
import Data.Void (Void)
|
||||
import Text.Parsec as P
|
||||
|
||||
data RestElement
|
||||
= RBody RestBody
|
||||
| RTransition
|
||||
| -- list of integers is the location in the section heirachy it is, Text is the title
|
||||
-- NOTE: future me don't bother with proper restext convention do header depth via #n prefix to the title
|
||||
RSection [Int] Text RestBody
|
||||
|
||||
data RestBody
|
||||
= RParagraph [RInlineText]
|
||||
| RBulletList Void
|
||||
| REnumList Void
|
||||
| RDefinitionList Void
|
||||
| RFieldList Void
|
||||
| ROptionList Void
|
||||
| RLiteralBlock Void
|
||||
| RLineBlock Void
|
||||
| RBlockQuote Void
|
||||
| -- skipping doctest blocks because no I'll just use a literal block thanks
|
||||
RTable Void
|
||||
| RExplicit Void
|
||||
|
||||
data MarkupModifier = Underline | Bold | Italic
|
||||
|
||||
data RInlineText = RInLineText {text :: Text, modifiers :: [MarkupModifier]}
|
|
@ -10,8 +10,8 @@ data Page = Page
|
|||
pageContent :: Text,
|
||||
-- build time
|
||||
pageNow :: Text,
|
||||
-- css class for page section
|
||||
pageSection :: Text
|
||||
--
|
||||
pageUrl :: Text
|
||||
}
|
||||
deriving (Show, Generic)
|
||||
deriving (ToJSON) via PrefixedSnake "page" Page
|
||||
|
|
|
@ -25,14 +25,7 @@ indexHtmlOutputPath srcPath =
|
|||
-- were applicative shenanigans necessary? no
|
||||
-- but using them felt cool
|
||||
indexHtmlSourcePaths :: FilePath -> [FilePath]
|
||||
indexHtmlSourcePaths path = [indexHtmlTypstSourcePath, indexHtmlMarkdownSourcePath] <*> [path]
|
||||
|
||||
indexHtmlTypstSourcePath :: FilePath -> FilePath
|
||||
indexHtmlTypstSourcePath =
|
||||
FP.dropDirectory1
|
||||
. (<.> "typ")
|
||||
. FP.dropTrailingPathSeparator
|
||||
. FP.dropFileName
|
||||
indexHtmlSourcePaths path = [indexHtmlMarkdownSourcePath] <*> [path]
|
||||
|
||||
indexHtmlMarkdownSourcePath :: FilePath -> FilePath
|
||||
indexHtmlMarkdownSourcePath =
|
||||
|
@ -41,25 +34,6 @@ indexHtmlMarkdownSourcePath =
|
|||
. FP.dropTrailingPathSeparator
|
||||
. FP.dropFileName
|
||||
|
||||
indexHtmlTypstMetaPath :: FilePath -> FilePath
|
||||
indexHtmlTypstMetaPath = typstMetaPath . indexHtmlTypstSourcePath
|
||||
|
||||
typstMetaPath :: FilePath -> FilePath
|
||||
typstMetaPath typstPath = FP.dropExtension typstPath <.> "yaml"
|
||||
|
||||
typstToHtml :: FilePath -> Action Text
|
||||
typstToHtml filePath = do
|
||||
content <- Shake.readFile' filePath
|
||||
Shake.quietly . Shake.traced "Typst to HTML" $ do
|
||||
doc <- runPandoc . Pandoc.readTypst readerOptions . T.pack $ content
|
||||
html <- runPandoc . Pandoc.writeHtml5String writerOptions $ doc
|
||||
return html
|
||||
where
|
||||
readerOptions =
|
||||
Pandoc.def {Pandoc.readerExtensions = Pandoc.pandocExtensions}
|
||||
writerOptions =
|
||||
Pandoc.def {Pandoc.writerExtensions = Pandoc.pandocExtensions}
|
||||
|
||||
markdownToHtml :: (FromJSON a) => FilePath -> Action (a, Text)
|
||||
markdownToHtml filePath = do
|
||||
content <- Shake.readFile' filePath
|
||||
|
@ -137,14 +111,11 @@ yamlToPost path = do
|
|||
-- let post' = dateTransform post
|
||||
return post
|
||||
|
||||
isTypstPost :: FilePath -> Bool
|
||||
isTypstPost path = FP.takeExtension path == ".typ"
|
||||
|
||||
isMarkdownPost :: FilePath -> Bool
|
||||
isMarkdownPost path = FP.takeExtension path == ".md"
|
||||
|
||||
postHandles :: [(FilePath -> Bool, FilePath -> Action Post)]
|
||||
postHandles = [(isTypstPost, yamlToPost . typstMetaPath), (isMarkdownPost, markdownToPost)]
|
||||
postHandles = [(isMarkdownPost, markdownToPost)]
|
||||
|
||||
isDraft :: FilePath -> Action Bool
|
||||
isDraft path = do
|
||||
|
@ -167,3 +138,6 @@ parseDate str = do
|
|||
date <- parseTimeM False defaultTimeLocale "%Y-%-m-%-d" $ T.unpack str
|
||||
-- need to append the time to avoid potential issues
|
||||
return $ T.pack $ formatTime @UTCTime defaultTimeLocale "%Y-%m-%dT00:00:00Z" date
|
||||
|
||||
urlConvert :: FilePath -> Text
|
||||
urlConvert = T.pack . FP.dropFileName . flip FP.replaceDirectory1 "https://pagwin.xyz"
|
||||
|
|
148
markdown.ebnf
Normal file
148
markdown.ebnf
Normal file
|
@ -0,0 +1,148 @@
|
|||
(* Markdown EBNF Grammar *)
|
||||
|
||||
document = { block } ;
|
||||
|
||||
block = heading
|
||||
| horizontal_rule
|
||||
| code_block
|
||||
| quote_block
|
||||
| list
|
||||
| table
|
||||
| paragraph
|
||||
| blank_line ;
|
||||
|
||||
(* Headings *)
|
||||
heading = atx_heading | setext_heading ;
|
||||
|
||||
atx_heading = "#" { "#" } [ " " ] inline_text newline ;
|
||||
|
||||
setext_heading = inline_text newline
|
||||
( ( "=" { "=" } ) | ( "-" { "-" } ) ) newline ;
|
||||
|
||||
(* Horizontal Rule *)
|
||||
horizontal_rule = ( ( "*" [ " " ] "*" [ " " ] "*" { [ " " ] "*" } )
|
||||
| ( "-" [ " " ] "-" [ " " ] "-" { [ " " ] "-" } )
|
||||
| ( "_" [ " " ] "_" [ " " ] "_" { [ " " ] "_" } ) ) newline ;
|
||||
|
||||
(* Code Blocks *)
|
||||
code_block = fenced_code_block | indented_code_block ;
|
||||
|
||||
fenced_code_block = "```" [ language_identifier ] newline
|
||||
{ code_line }
|
||||
"```" newline ;
|
||||
|
||||
indented_code_block = { " " code_line } ;
|
||||
|
||||
code_line = { character - newline } newline ;
|
||||
|
||||
language_identifier = { letter | digit | "-" | "+" } ;
|
||||
|
||||
(* Quote Blocks *)
|
||||
quote_block = { ">" [ " " ] ( inline_text | "" ) newline } ;
|
||||
|
||||
(* Lists *)
|
||||
list = unordered_list | ordered_list ;
|
||||
|
||||
unordered_list = { unordered_list_item } ;
|
||||
|
||||
ordered_list = { ordered_list_item } ;
|
||||
|
||||
unordered_list_item = [ " " { " " } ] ( "*" | "+" | "-" ) " " inline_text newline
|
||||
{ continuation_line } ;
|
||||
|
||||
ordered_list_item = [ " " { " " } ] digit { digit } "." " " inline_text newline
|
||||
{ continuation_line } ;
|
||||
|
||||
continuation_line = " " inline_text newline ;
|
||||
|
||||
(* Tables *)
|
||||
table = table_header table_separator { table_row } ;
|
||||
|
||||
table_header = "|" { table_cell "|" } newline ;
|
||||
|
||||
table_separator = "|" { table_align_spec "|" } newline ;
|
||||
|
||||
table_row = "|" { table_cell "|" } newline ;
|
||||
|
||||
table_cell = { character - ( "|" | newline ) } ;
|
||||
|
||||
table_align_spec = [ ":" ] "-" { "-" } [ ":" ] ;
|
||||
|
||||
(* Paragraphs *)
|
||||
paragraph = inline_text { newline inline_text } newline ;
|
||||
|
||||
(* Inline Elements *)
|
||||
inline_text = { inline_element } ;
|
||||
|
||||
inline_element = emphasis
|
||||
| strong
|
||||
| code_span
|
||||
| link
|
||||
| image
|
||||
| autolink
|
||||
| line_break
|
||||
| plain_text ;
|
||||
|
||||
emphasis = ( "*" non_asterisk_text "*" )
|
||||
| ( "_" non_underscore_text "_" ) ;
|
||||
|
||||
strong = ( "**" non_asterisk_text "**" )
|
||||
| ( "__" non_underscore_text "__" ) ;
|
||||
|
||||
code_span = "`" { "`" } non_backtick_text { "`" } "`" ;
|
||||
|
||||
link = "[" link_text "]" "(" link_url [ " " link_title ] ")" ;
|
||||
|
||||
image = "!" "[" alt_text "]" "(" image_url [ " " image_title ] ")" ;
|
||||
|
||||
autolink = "<" ( url | email ) ">" ;
|
||||
|
||||
line_break = " " newline | "\\" newline ;
|
||||
|
||||
(* Text Content *)
|
||||
plain_text = { character - special_char } ;
|
||||
|
||||
non_asterisk_text = { character - "*" } ;
|
||||
|
||||
non_underscore_text = { character - "_" } ;
|
||||
|
||||
non_backtick_text = { character - "`" } ;
|
||||
|
||||
link_text = { character - ( "[" | "]" ) } ;
|
||||
|
||||
alt_text = { character - ( "[" | "]" ) } ;
|
||||
|
||||
link_url = { character - ( "(" | ")" | " " ) } ;
|
||||
|
||||
image_url = { character - ( "(" | ")" | " " ) } ;
|
||||
|
||||
link_title = quote_string ;
|
||||
|
||||
image_title = quote_string ;
|
||||
|
||||
quote_string = ( '"' { character - '"' } '"' )
|
||||
| ( "'" { character - "'" } "'" ) ;
|
||||
|
||||
url = "http" [ "s" ] "://" { character - ">" } ;
|
||||
|
||||
email = { character - ( "@" | ">" ) } "@" { character - ">" } ;
|
||||
|
||||
(* Utilities *)
|
||||
blank_line = newline ;
|
||||
|
||||
special_char = "*" | "_" | "`" | "[" | "]" | "(" | ")" | "#" | ">" | "|" | "!" | "\\" ;
|
||||
|
||||
newline = "\n" | "\r\n" ;
|
||||
|
||||
character = letter | digit | symbol | " " ;
|
||||
|
||||
letter = "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m"
|
||||
| "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z"
|
||||
| "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" | "K" | "L" | "M"
|
||||
| "N" | "O" | "P" | "Q" | "R" | "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z" ;
|
||||
|
||||
digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ;
|
||||
|
||||
symbol = "!" | "@" | "#" | "$" | "%" | "^" | "&" | "*" | "(" | ")" | "-" | "_" | "="
|
||||
| "+" | "[" | "]" | "{" | "}" | "\\" | "|" | ";" | ":" | "'" | '"' | "," | "."
|
||||
| "<" | ">" | "/" | "?" | "~" | "`" ;
|
40
psb.cabal
40
psb.cabal
|
@ -1,16 +1,4 @@
|
|||
cabal-version: 3.4
|
||||
-- The cabal-version field refers to the version of the .cabal specification,
|
||||
-- and can be different from the cabal-install (the tool) version and the
|
||||
-- Cabal (the library) version you are using. As such, the Cabal (the library)
|
||||
-- version used must be equal or greater than the version stated in this field.
|
||||
-- Starting from the specification version 2.2, the cabal-version field must be
|
||||
-- the first thing in the cabal file.
|
||||
|
||||
-- Initial package description 'psb' generated by
|
||||
-- 'cabal init'. For further documentation, see:
|
||||
-- http://haskell.org/cabal/users-guide/
|
||||
--
|
||||
-- The name of the package.
|
||||
name: psb
|
||||
|
||||
-- The package version.
|
||||
|
@ -22,35 +10,15 @@ name: psb
|
|||
-- | | | +--- code changes with no API change
|
||||
version: 0.1.0.0
|
||||
|
||||
-- A short (one-line) description of the package.
|
||||
-- synopsis:
|
||||
|
||||
-- A longer description of the package.
|
||||
-- description:
|
||||
|
||||
-- The license under which the package is released.
|
||||
license: MIT
|
||||
|
||||
-- The file containing the license text.
|
||||
license-file: LICENSE
|
||||
|
||||
-- The package author(s).
|
||||
author: Pagwin
|
||||
|
||||
-- An email address to which users can send suggestions, bug reports, and patches.
|
||||
maintainer: dev@pagwin.xyz
|
||||
|
||||
-- A copyright notice.
|
||||
-- copyright:
|
||||
category: Web
|
||||
build-type: Simple
|
||||
|
||||
-- Extra doc files to be distributed with the package, such as a CHANGELOG or a README.
|
||||
extra-doc-files: CHANGELOG.md
|
||||
|
||||
-- Extra source files to be distributed with the package, such as examples, or a tutorial module.
|
||||
-- extra-source-files:
|
||||
|
||||
common warnings
|
||||
ghc-options: -Wall
|
||||
|
||||
|
@ -61,14 +29,14 @@ executable psb
|
|||
-- .hs or .lhs file containing the Main module.
|
||||
main-is: Main.hs
|
||||
|
||||
-- Modules included in this executable, other than Main.
|
||||
other-modules: Config Utilities Templates Types
|
||||
other-modules: Config Utilities Templates Types IR Markdown Restruct
|
||||
|
||||
-- LANGUAGE extensions used by modules in this package.
|
||||
default-extensions: ApplicativeDo DataKinds NamedFieldPuns DerivingVia LambdaCase TypeApplications DeriveGeneric
|
||||
|
||||
-- Other library packages from which modules are imported.
|
||||
build-depends: base >=4.17.2.1, mustache >=2.4.2, pandoc >=3.2.1, shake >= 0.19.8, deriving-aeson >= 0.2.9, aeson, text, time, unordered-containers, yaml
|
||||
-- https://hackage.haskell.org/package/texmath
|
||||
-- cmark is pinned because I don't want to touch it unless I rewrite to my own code
|
||||
build-depends: base >=4.17.2.1, mustache >=2.4.2, pandoc >=3.2.1, shake >= 0.19.8, deriving-aeson >= 0.2.9, aeson, text, time, unordered-containers, yaml, parsec >= 3.1.18.0, typst >= 0.6.1, typst-symbols >= 0.1.7, cmark == 0.6.1
|
||||
|
||||
-- Directories containing source files.
|
||||
hs-source-dirs: app
|
||||
|
|
285
restructured.ebnf
Normal file
285
restructured.ebnf
Normal file
|
@ -0,0 +1,285 @@
|
|||
(* reStructuredText EBNF Grammar *)
|
||||
|
||||
document = { block } ;
|
||||
|
||||
block = section
|
||||
| transition
|
||||
| paragraph
|
||||
| literal_block
|
||||
| line_block
|
||||
| block_quote
|
||||
| doctest_block
|
||||
| table
|
||||
| bullet_list
|
||||
| enumerated_list
|
||||
| definition_list
|
||||
| field_list
|
||||
| option_list
|
||||
| directive
|
||||
| comment
|
||||
| substitution_definition
|
||||
| target
|
||||
| blank_line ;
|
||||
|
||||
(* Sections *)
|
||||
section = section_title section_underline [ section_overline ] ;
|
||||
|
||||
section_title = inline_text newline ;
|
||||
|
||||
section_underline = section_adornment newline ;
|
||||
|
||||
section_overline = section_adornment newline ;
|
||||
|
||||
section_adornment = adornment_char { adornment_char } ;
|
||||
|
||||
adornment_char = "!" | '"' | "#" | "$" | "%" | "&" | "'" | "(" | ")" | "*" | "+" | "," | "-" | "." | "/" | ":" | ";" | "<" | "=" | ">" | "?" | "@" | "[" | "\\" | "]" | "^" | "_" | "`" | "{" | "|" | "}" | "~" ;
|
||||
|
||||
(* Transitions *)
|
||||
transition = transition_marker newline ;
|
||||
|
||||
transition_marker = transition_char { transition_char } ;
|
||||
|
||||
transition_char = adornment_char ;
|
||||
|
||||
(* Paragraphs *)
|
||||
paragraph = inline_text { newline inline_text } newline ;
|
||||
|
||||
(* Literal Blocks *)
|
||||
literal_block = literal_block_marker newline { indented_line } ;
|
||||
|
||||
literal_block_marker = "::" | paragraph "::" ;
|
||||
|
||||
indented_line = indent line_content newline ;
|
||||
|
||||
indent = " " | "\t" ;
|
||||
|
||||
line_content = { character - newline } ;
|
||||
|
||||
(* Line Blocks *)
|
||||
line_block = { line_block_line } ;
|
||||
|
||||
line_block_line = "|" [ " " ] inline_text newline ;
|
||||
|
||||
(* Block Quotes *)
|
||||
block_quote = { indented_paragraph } [ attribution ] ;
|
||||
|
||||
indented_paragraph = indent inline_text { newline indent inline_text } newline ;
|
||||
|
||||
attribution = indent "-- " inline_text newline ;
|
||||
|
||||
(* Doctest Blocks *)
|
||||
doctest_block = { doctest_line } ;
|
||||
|
||||
doctest_line = ">>>" " " line_content newline
|
||||
| "..." " " line_content newline ;
|
||||
|
||||
(* Tables *)
|
||||
table = simple_table | grid_table ;
|
||||
|
||||
simple_table = simple_table_row { simple_table_row } simple_table_separator { simple_table_row } ;
|
||||
|
||||
simple_table_row = { table_cell } newline ;
|
||||
|
||||
simple_table_separator = "=" { ( "=" | " " ) } newline ;
|
||||
|
||||
grid_table = grid_table_border { grid_table_row grid_table_border } ;
|
||||
|
||||
grid_table_border = "+" { ( "-" | "+" ) } newline ;
|
||||
|
||||
grid_table_row = "|" { table_cell "|" } newline ;
|
||||
|
||||
table_cell = { character - ( "|" | newline ) } ;
|
||||
|
||||
(* Lists *)
|
||||
bullet_list = { bullet_list_item } ;
|
||||
|
||||
bullet_list_item = bullet_marker " " list_item_content ;
|
||||
|
||||
bullet_marker = "*" | "+" | "-" | "•" | "‣" | "⁃" ;
|
||||
|
||||
enumerated_list = { enumerated_list_item } ;
|
||||
|
||||
enumerated_list_item = enumeration_marker " " list_item_content ;
|
||||
|
||||
enumeration_marker = ( digit { digit } "." )
|
||||
| ( digit { digit } ")" )
|
||||
| ( "(" digit { digit } ")" )
|
||||
| ( letter "." )
|
||||
| ( letter ")" )
|
||||
| ( "(" letter ")" )
|
||||
| ( roman "." )
|
||||
| ( roman ")" )
|
||||
| ( "(" roman ")" )
|
||||
| "#." | "#)" | "(#)" ;
|
||||
|
||||
list_item_content = inline_text { newline [ indent ] inline_text } newline ;
|
||||
|
||||
definition_list = { definition_list_item } ;
|
||||
|
||||
definition_list_item = term newline indent definition newline ;
|
||||
|
||||
term = inline_text ;
|
||||
|
||||
definition = inline_text { newline indent inline_text } ;
|
||||
|
||||
(* Field Lists *)
|
||||
field_list = { field_list_item } ;
|
||||
|
||||
field_list_item = ":" field_name ":" " " field_body newline ;
|
||||
|
||||
field_name = { letter | digit | " " | "-" | "_" } ;
|
||||
|
||||
field_body = inline_text { newline indent inline_text } ;
|
||||
|
||||
(* Option Lists *)
|
||||
option_list = { option_list_item } ;
|
||||
|
||||
option_list_item = option_group " " option_description newline ;
|
||||
|
||||
option_group = option { ", " option } ;
|
||||
|
||||
option = short_option | long_option ;
|
||||
|
||||
short_option = "-" letter [ " " option_argument ] ;
|
||||
|
||||
long_option = "--" { letter | digit | "-" } [ "=" option_argument ] ;
|
||||
|
||||
option_argument = { letter | digit | "-" | "_" } ;
|
||||
|
||||
option_description = inline_text { newline indent inline_text } ;
|
||||
|
||||
(* Directives *)
|
||||
directive = ".." " " directive_name "::" [ " " directive_arguments ] newline
|
||||
[ directive_options ]
|
||||
[ blank_line ]
|
||||
[ directive_content ] ;
|
||||
|
||||
directive_name = { letter | digit | "-" | "_" } ;
|
||||
|
||||
directive_arguments = { character - newline } ;
|
||||
|
||||
directive_options = { directive_option } ;
|
||||
|
||||
directive_option = indent ":" option_name ":" [ " " option_value ] newline ;
|
||||
|
||||
option_name = { letter | digit | "-" | "_" } ;
|
||||
|
||||
option_value = { character - newline } ;
|
||||
|
||||
directive_content = { indented_line } ;
|
||||
|
||||
(* Comments *)
|
||||
comment = ".." [ " " comment_text ] newline { indented_line } ;
|
||||
|
||||
comment_text = { character - newline } ;
|
||||
|
||||
(* Substitution Definitions *)
|
||||
substitution_definition = ".." " " "|" substitution_name "|" " " directive_name "::" [ " " directive_arguments ] newline
|
||||
[ directive_options ]
|
||||
[ directive_content ] ;
|
||||
|
||||
substitution_name = { character - ( "|" | newline ) } ;
|
||||
|
||||
(* Targets *)
|
||||
target = internal_target | external_target ;
|
||||
|
||||
internal_target = ".." " " "_" target_name ":" newline ;
|
||||
|
||||
external_target = ".." " " "_" target_name ":" " " target_url newline ;
|
||||
|
||||
target_name = { character - ( ":" | newline ) } ;
|
||||
|
||||
target_url = { character - newline } ;
|
||||
|
||||
(* Inline Elements *)
|
||||
inline_text = { inline_element } ;
|
||||
|
||||
inline_element = emphasis
|
||||
| strong
|
||||
| literal
|
||||
| interpreted_text
|
||||
| phrase_reference
|
||||
| substitution_reference
|
||||
| inline_internal_target
|
||||
| hyperlink_reference
|
||||
| footnote_reference
|
||||
| citation_reference
|
||||
| inline_literal
|
||||
| plain_text ;
|
||||
|
||||
emphasis = "*" emphasis_text "*" ;
|
||||
|
||||
strong = "**" strong_text "**" ;
|
||||
|
||||
literal = "``" literal_text "``" ;
|
||||
|
||||
interpreted_text = "`" interpreted_text_content "`" [ role_suffix ]
|
||||
| role_prefix "`" interpreted_text_content "`" ;
|
||||
|
||||
role_prefix = ":" role_name ":" ;
|
||||
|
||||
role_suffix = ":" role_name ":" ;
|
||||
|
||||
role_name = { letter | digit | "-" | "_" | "." } ;
|
||||
|
||||
interpreted_text_content = { character - "`" } ;
|
||||
|
||||
phrase_reference = "`" phrase_reference_text "`_" [ "_" ] ;
|
||||
|
||||
phrase_reference_text = { character - ( "`" | "<" ) } [ " " "<" target_url ">" ] ;
|
||||
|
||||
substitution_reference = "|" substitution_name "|" [ "_" [ "_" ] ] ;
|
||||
|
||||
inline_internal_target = "_`" target_text "`" ;
|
||||
|
||||
target_text = { character - "`" } ;
|
||||
|
||||
hyperlink_reference = reference_name "_" [ "_" ] ;
|
||||
|
||||
reference_name = { letter | digit | "-" | "_" | "." } ;
|
||||
|
||||
footnote_reference = "[" footnote_label "]_" ;
|
||||
|
||||
footnote_label = digit { digit } | "#" [ footnote_name ] | "*" ;
|
||||
|
||||
footnote_name = { letter | digit | "-" | "_" } ;
|
||||
|
||||
citation_reference = "[" citation_label "]_" ;
|
||||
|
||||
citation_label = { letter | digit | "-" | "_" | "." } ;
|
||||
|
||||
inline_literal = "`" "`" literal_content "`" "`" ;
|
||||
|
||||
literal_content = { character - "`" } ;
|
||||
|
||||
(* Text Content *)
|
||||
plain_text = { character - markup_char } ;
|
||||
|
||||
emphasis_text = { character - ( "*" | newline ) } ;
|
||||
|
||||
strong_text = { character - ( "*" | newline ) } ;
|
||||
|
||||
literal_text = { character - ( "`" | newline ) } ;
|
||||
|
||||
markup_char = "*" | "`" | "_" | "|" | "[" | "]" | ":" | "." | ">" | "<" ;
|
||||
|
||||
(* Utilities *)
|
||||
blank_line = newline ;
|
||||
|
||||
roman = "i" | "ii" | "iii" | "iv" | "v" | "vi" | "vii" | "viii" | "ix" | "x"
|
||||
| "I" | "II" | "III" | "IV" | "V" | "VI" | "VII" | "VIII" | "IX" | "X" ;
|
||||
|
||||
newline = "\n" | "\r\n" ;
|
||||
|
||||
character = letter | digit | symbol | " " ;
|
||||
|
||||
letter = "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m"
|
||||
| "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z"
|
||||
| "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" | "K" | "L" | "M"
|
||||
| "N" | "O" | "P" | "Q" | "R" | "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z" ;
|
||||
|
||||
digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ;
|
||||
|
||||
symbol = "!" | "@" | "#" | "$" | "%" | "^" | "&" | "*" | "(" | ")" | "-" | "_" | "="
|
||||
| "+" | "[" | "]" | "{" | "}" | "\\" | "|" | ";" | ":" | "'" | '"' | "," | "."
|
||||
| "<" | ">" | "/" | "?" | "~" | "`" ;
|
Loading…
Reference in a new issue