Initial commit for hasciidoc
commit
28320d30ed
|
@ -0,0 +1 @@
|
||||||
|
dist/*
|
|
@ -0,0 +1,30 @@
|
||||||
|
Copyright (c) 2014, Levi Pearson
|
||||||
|
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following
|
||||||
|
disclaimer in the documentation and/or other materials provided
|
||||||
|
with the distribution.
|
||||||
|
|
||||||
|
* Neither the name of Levi Pearson nor the names of other
|
||||||
|
contributors may be used to endorse or promote products derived
|
||||||
|
from this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,120 @@
|
||||||
|
---
|
||||||
|
title: AsciiDoc Parsing
|
||||||
|
...
|
||||||
|
|
||||||
|
# Document Structure
|
||||||
|
|
||||||
|
A *document* is a series of *block elements*.
|
||||||
|
|
||||||
|
The first element is optionally a *document header*.
|
||||||
|
|
||||||
|
The next element is optionally a *preamble*.
|
||||||
|
|
||||||
|
* Document
|
||||||
|
* Header (optional)
|
||||||
|
Must be separated from the remainder of the document by one or more
|
||||||
|
blank lines and cannot contain blank lines.
|
||||||
|
Can include comments.
|
||||||
|
Can include attribute entries, typically *doctype*, *lang*, *encoding*,
|
||||||
|
*icons*, *data-uri*, *toc*, *numbered*
|
||||||
|
Command-line attributes override header attributes.
|
||||||
|
* Title
|
||||||
|
* Info (optional)
|
||||||
|
* AuthorInfo
|
||||||
|
Format: firstname[ [middlename ]lastname][ <email>]
|
||||||
|
Multi-word names should be separated by underscores,
|
||||||
|
e.g. van_Gogh.
|
||||||
|
If the format isn't matched, the entire line becomes a
|
||||||
|
FirstName.
|
||||||
|
* FirstName
|
||||||
|
* OtherNames (optional)
|
||||||
|
* MiddleName (optional)
|
||||||
|
* LastName
|
||||||
|
* EmailAddress (optional)
|
||||||
|
* RevisionInfo (optional)
|
||||||
|
* Either
|
||||||
|
* RevisionNumber (optional)
|
||||||
|
* RevisionDate
|
||||||
|
* RevisionRemark (optional)
|
||||||
|
* Or
|
||||||
|
* RCS/CVS/SVN $Id$ marker.
|
||||||
|
The *revnumber*, *revdata*, and *author* attributes are
|
||||||
|
extracted and set and displayed in the header.
|
||||||
|
The header author line may be omitted.
|
||||||
|
|
||||||
|
* Preamble (optional)
|
||||||
|
* SectionBody
|
||||||
|
* Section (0 or more)
|
||||||
|
Up to 4 section levels (1-4) in addition to the document title (level 0)
|
||||||
|
Has attributes *level* and *sectnum* implcitly defined
|
||||||
|
* Title
|
||||||
|
* SectionBody (optional)
|
||||||
|
* One of (repeated 1 or more times)
|
||||||
|
* Either
|
||||||
|
* BlockTitle (optional)
|
||||||
|
Format: .<title>
|
||||||
|
* Block
|
||||||
|
* Or
|
||||||
|
* BlockMacro
|
||||||
|
* Section (0 or more)
|
||||||
|
|
||||||
|
* Block
|
||||||
|
Floating titles probably belong in here somewhere.
|
||||||
|
* One of
|
||||||
|
* Paragraph
|
||||||
|
* DelimitedBlock
|
||||||
|
* List
|
||||||
|
* One of
|
||||||
|
* BulletedList
|
||||||
|
* ListItem (1 or more)
|
||||||
|
* NumberedList
|
||||||
|
* ListItem (1 or more)
|
||||||
|
* CalloutList
|
||||||
|
* ListItem (1 or more)
|
||||||
|
* LabeledList
|
||||||
|
* ListEntry (1 or more)
|
||||||
|
* ListLabel
|
||||||
|
* ListTerm (1 or more)
|
||||||
|
* ListItem
|
||||||
|
* ItemText
|
||||||
|
* One of (repeated 0 or more times)
|
||||||
|
* List
|
||||||
|
* ListParagraph
|
||||||
|
A paragraph with *listelement* option set
|
||||||
|
* ListContinuation
|
||||||
|
* Table
|
||||||
|
* Or (these can appear almost anywhere)
|
||||||
|
* BlockId
|
||||||
|
Applies to the following *Title*, *Paragraph*, *List*,
|
||||||
|
*DelimitedBlock*, *Table*, or *BlockMacro*
|
||||||
|
Format: [[<id>]]
|
||||||
|
* AttributeEntry
|
||||||
|
* AttributeList
|
||||||
|
Applies to the following *Block*
|
||||||
|
Most blocks support *id*, *role*, and *reftext* attributes
|
||||||
|
Format: [<attr>(, <attr>)*]
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
* Block elements are separated by line boundaries
|
||||||
|
* *Header*, *Title*, *Paragraph*, and *ItemText* cannot contain blank lines.
|
||||||
|
|
||||||
|
|
||||||
|
```.haskell
|
||||||
|
data Document = Doc
|
||||||
|
{ _docAttrs :: Attributes
|
||||||
|
, _header :: Maybe DocHeader
|
||||||
|
, _preamble :: Maybe SectionBody
|
||||||
|
, _sections :: Seq Section
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
data Section = Sec
|
||||||
|
{ _secAttrs :: Attributes
|
||||||
|
, _secTitle :: Text
|
||||||
|
, _secBody :: Maybe SectionBody
|
||||||
|
, _secChildren :: Seq Section
|
||||||
|
}
|
||||||
|
|
||||||
|
type SectionBody = Seq Block
|
||||||
|
|
||||||
|
```
|
|
@ -0,0 +1,40 @@
|
||||||
|
-- Initial hasciidoc.cabal generated by cabal init. For further
|
||||||
|
-- documentation, see http://haskell.org/cabal/users-guide/
|
||||||
|
|
||||||
|
name: hasciidoc
|
||||||
|
version: 0.1.0.0
|
||||||
|
synopsis: An asciidoc implementation
|
||||||
|
-- description:
|
||||||
|
license: BSD3
|
||||||
|
license-file: LICENSE
|
||||||
|
author: Levi Pearson
|
||||||
|
maintainer: levipearson@gmail.com
|
||||||
|
-- copyright:
|
||||||
|
category: Text
|
||||||
|
build-type: Simple
|
||||||
|
-- extra-source-files:
|
||||||
|
cabal-version: >=1.10
|
||||||
|
|
||||||
|
library
|
||||||
|
exposed-modules: Hasciidoc
|
||||||
|
-- other-modules:
|
||||||
|
-- other-extensions:
|
||||||
|
build-depends: base >=4.6 && <4.7
|
||||||
|
, attoparsec
|
||||||
|
, containers
|
||||||
|
, text
|
||||||
|
-- hs-source-dirs:
|
||||||
|
default-language: Haskell2010
|
||||||
|
hs-source-dirs: src
|
||||||
|
|
||||||
|
test-suite tests
|
||||||
|
type: exitcode-stdio-1.0
|
||||||
|
main-is: Main.hs
|
||||||
|
build-depends: base >= 4.6 && < 4.7
|
||||||
|
, attoparsec
|
||||||
|
, text
|
||||||
|
, hasciidoc
|
||||||
|
build-depends: tasty
|
||||||
|
, tasty-hspec
|
||||||
|
hs-source-dirs: tests
|
||||||
|
default-language: Haskell2010
|
|
@ -0,0 +1,95 @@
|
||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
{-# OPTIONS_GHC -fno-warn-unused-do-bind #-}
|
||||||
|
|
||||||
|
module Hasciidoc where
|
||||||
|
|
||||||
|
import Control.Applicative
|
||||||
|
import Data.Attoparsec.Text
|
||||||
|
import Data.Char
|
||||||
|
import Data.Map (Map)
|
||||||
|
import qualified Data.Map as M
|
||||||
|
import Data.Sequence (Seq)
|
||||||
|
import qualified Data.Sequence as S
|
||||||
|
import Data.Text (Text)
|
||||||
|
import qualified Data.Text as T
|
||||||
|
|
||||||
|
type Attributes = Map Text Text
|
||||||
|
|
||||||
|
data Document = Doc
|
||||||
|
{ _docAttrs :: Attributes
|
||||||
|
, _header :: Maybe DocHeader
|
||||||
|
, _preamble :: Maybe SectionBody
|
||||||
|
, _sections :: Seq Section
|
||||||
|
} deriving (Show)
|
||||||
|
|
||||||
|
data DocHeader = DocHead
|
||||||
|
{ _docTitle :: Text
|
||||||
|
} deriving (Show)
|
||||||
|
|
||||||
|
data Section = Sec
|
||||||
|
{ _secAttrs :: Attributes
|
||||||
|
, _secTitle :: Text
|
||||||
|
, _secBody :: Maybe SectionBody
|
||||||
|
, _secChildren :: Seq Section
|
||||||
|
} deriving (Show)
|
||||||
|
|
||||||
|
type SectionBody = Seq Block
|
||||||
|
|
||||||
|
data Block = Block
|
||||||
|
{ _blockKind :: BlockKind
|
||||||
|
, _blockAttrs :: Attributes
|
||||||
|
, _blockTitle :: Maybe Text
|
||||||
|
} deriving (Show)
|
||||||
|
|
||||||
|
data BlockKind = Paragraph
|
||||||
|
| DelimBlock
|
||||||
|
| List
|
||||||
|
deriving (Show)
|
||||||
|
|
||||||
|
data Inlines = TextChunk Text
|
||||||
|
deriving (Show)
|
||||||
|
|
||||||
|
pCountedChar :: Char -> Parser Int
|
||||||
|
pCountedChar c = ((+) <$> (char c >> pure 1) <*> pCountedChar c)
|
||||||
|
<|> pure 0
|
||||||
|
|
||||||
|
collapseSp = T.unwords . T.words
|
||||||
|
|
||||||
|
|
||||||
|
pTitle :: Parser (Int, Text)
|
||||||
|
pTitle = pOneLineTitle <|> pUnderTitle
|
||||||
|
|
||||||
|
pOneLineTitle :: Parser (Int, Text)
|
||||||
|
pOneLineTitle = (,) <$ char '='
|
||||||
|
<*> pCountedChar '='
|
||||||
|
<* space
|
||||||
|
<* skipSpace
|
||||||
|
<*> (collapseSp <$> takeTill (\c -> c == '=' || isEndOfLine c))
|
||||||
|
<* takeTill isEndOfLine
|
||||||
|
<* satisfy isEndOfLine
|
||||||
|
<?> "pOneLineTitle"
|
||||||
|
|
||||||
|
pUnderTitle :: Parser (Int, Text)
|
||||||
|
pUnderTitle = flip (,) <$> (collapseSp <$> takeTill isEndOfLine)
|
||||||
|
<* satisfy isEndOfLine
|
||||||
|
<*> pTitleUnderline
|
||||||
|
<?> "pUnderTitle"
|
||||||
|
|
||||||
|
pTitleUnderline :: Parser Int
|
||||||
|
pTitleUnderline = do
|
||||||
|
c <- anyChar
|
||||||
|
char c
|
||||||
|
takeTill (not . (== c))
|
||||||
|
satisfy isEndOfLine
|
||||||
|
titleCharLevel c
|
||||||
|
|
||||||
|
|
||||||
|
titleCharLevel :: Monad m => Char -> m Int
|
||||||
|
titleCharLevel c = case c of
|
||||||
|
'=' -> return 0
|
||||||
|
'-' -> return 1
|
||||||
|
'~' -> return 2
|
||||||
|
'^' -> return 3
|
||||||
|
'+' -> return 4
|
||||||
|
_ -> fail "Bad title underline character"
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
|
||||||
|
module Main where
|
||||||
|
|
||||||
|
import Data.Attoparsec.Text
|
||||||
|
import qualified Data.Text as T
|
||||||
|
|
||||||
|
import Test.Tasty
|
||||||
|
import Test.Tasty.Hspec as HS
|
||||||
|
|
||||||
|
import Hasciidoc
|
||||||
|
|
||||||
|
main :: IO ()
|
||||||
|
main = defaultMain tests
|
||||||
|
|
||||||
|
tests :: TestTree
|
||||||
|
tests = testGroup "Tests" [specs]
|
||||||
|
|
||||||
|
-- Hspec Tests
|
||||||
|
|
||||||
|
specs :: TestTree
|
||||||
|
specs = testGroup "Specifications"
|
||||||
|
[ HS.testCase "Header Parsing" headerParseSpec
|
||||||
|
]
|
||||||
|
|
||||||
|
headerParseSpec :: Spec
|
||||||
|
headerParseSpec = do
|
||||||
|
describe "Document Header" $ do
|
||||||
|
|
||||||
|
describe "Document Title" $ do
|
||||||
|
|
||||||
|
describe "pTitle" $ do
|
||||||
|
it "parses a one-line level 0 title with no right-hand delimiter" $
|
||||||
|
parseOnly pTitle "= This is a level 0 title\n"
|
||||||
|
`shouldBe`
|
||||||
|
Right (0, "This is a level 0 title")
|
||||||
|
|
||||||
|
it "parses a one-line level 0 title with a right-hand delimeter" $
|
||||||
|
parseOnly pTitle "= This is another level 0 title =\n"
|
||||||
|
`shouldBe`
|
||||||
|
Right (0, "This is another level 0 title")
|
Loading…
Reference in New Issue