Skip to content

Commit 5889e4f

Browse files
authored
GitHub flavored table support, pretty aeson (#8)
1 parent a10b1e2 commit 5889e4f

24 files changed

+436
-47
lines changed

answers-script.cabal

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ library
7373
filepath,
7474
lens,
7575
text,
76-
cmark,
76+
cmark-gfm,
7777
containers,
7878
directory,
7979
cryptohash-sha1,
@@ -86,8 +86,9 @@ library
8686
transformers,
8787
monad-loops,
8888
aeson,
89+
aeson-pretty,
8990
attoparsec,
90-
cmark-lens
91+
cmark-gfm-lens
9192

9293
-- Directories containing source files.
9394
hs-source-dirs: src
@@ -155,6 +156,6 @@ test-suite answers-script-test
155156
bytestring,
156157
lens,
157158
attoparsec,
158-
cmark,
159-
cmark-lens,
159+
cmark-gfm,
160+
cmark-gfm-lens,
160161
directory

cabal.project

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,10 @@ source-repository-package
2525
type: git
2626
location: https://github.com/ingun37/cmark-lens.git
2727
tag: 4456f10deccb61419ce28811db448c266931190f
28-
--sha256: 0s1zzpd1bgap403awkjar365qxsysw4lwq6i23whjydqj3whqdhb
28+
--sha256: 0s1zzpd1bgap403awkjar365qxsysw4lwq6i23whjydqj3whqdhb
29+
30+
source-repository-package
31+
type: git
32+
location: https://github.com/ingun37/cmark-gfm-lens.git
33+
tag: 7c57b38c7ddb8888df8b3b92800a42a1ac0ac359
34+
--sha256: 0ssznk105b7ai59pr2d6i1if7h4s4f00vs810afpyby6mvfvbvfy

src/MatlabMark.hs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33

44
module MatlabMark (generateMatlabAnswersDB, readMatlabMD) where
55

6-
import CMark
7-
import CMark.Lens
6+
import CMarkGFM
7+
import CMarkGFM.Lens
88
import Control.Lens
99
import Data.Attoparsec.Text qualified as A
1010
import Data.Text qualified as T
@@ -38,7 +38,7 @@ parseVersion = do
3838
generateMatlabAnswersDB :: FilePath -> Node -> IO ()
3939
generateMatlabAnswersDB outputDirPath node =
4040
let (intro, groups) = groupByProblems node
41-
toDocText = CMark.nodeToCommonmark [] Nothing . Node Nothing DOCUMENT
41+
toDocText = nodeToCommonmark [] Nothing . Node Nothing DOCUMENT
4242
writeMD name nodes = do
4343
D.createDirectory $ outputDirPath F.</> name
4444
TIO.writeFile (outputDirPath F.</> name F.</> "a.md") (toDocText nodes)
@@ -47,4 +47,4 @@ generateMatlabAnswersDB outputDirPath node =
4747
mapM_ (uncurry writeMD) groups
4848

4949
readMatlabMD :: FilePath -> IO Node
50-
readMatlabMD mdFilePath = CMark.commonmarkToNode [] . changeMatlabMarkdownDelimeters <$> TIO.readFile mdFilePath
50+
readMatlabMD mdFilePath = commonmarkToNode [] [] . changeMatlabMarkdownDelimeters <$> TIO.readFile mdFilePath

src/MyLib.hs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@
33

44
module MyLib (someFunc) where
55

6-
import CMark qualified
6+
import CMarkGFM qualified as CMark
77
import Control.Lens
88
import Control.Monad qualified as Monad
99
import Crypto.Hash.SHA1 qualified as SHA (hash)
1010
import Data.Aeson as Json
11+
import Data.Aeson.Encode.Pretty qualified as PrettyJson
1112
import Data.ByteString.Base16 qualified as B16 (encode)
1213
import Data.ByteString.Char8 qualified as C8
14+
import Data.ByteString.Lazy qualified as B
1315
import Data.Foldable qualified as Foldable
1416
import Data.List qualified as List
1517
import Data.List.NonEmpty qualified as NE
@@ -153,7 +155,7 @@ theWriter source destination prefix (parentPathComponents, item) = do
153155
putStrLn $ " Compiling " ++ src ++ " -> " ++ dst
154156
let safePrefix = List.dropWhileEnd (== '/') $ dropWhile (== '/') prefix
155157
let finalPrefix = List.intercalate "/" (filter (not . null) [safePrefix, "resources", _hash])
156-
TIO.writeFile dst (CMark.nodeToHtml [] $ MyMark.prefixImageUrl finalPrefix $ CMark.commonmarkToNode [] _content)
158+
TIO.writeFile dst (CMark.nodeToHtml [CMark.optUnsafe] [] $ MyMark.prefixImageUrl finalPrefix $ CMark.commonmarkToNode [] [CMark.extTable] _content)
157159

158160
let writeFileType key =
159161
\case
@@ -193,7 +195,7 @@ someFunc prefixPath source destination = do
193195
let pageDatas = Tree.foldTree folder tree
194196
let pagesDir = destination File.</> "pages"
195197
Dir.createDirectoryIfMissing True pagesDir
196-
let writePageData pg = Json.encodeFile (pagesDir File.</> (pg ^. pageContent . hash) ++ ".json") pg
198+
let writePageData pg = B.writeFile (pagesDir File.</> (pg ^. pageContent . hash) ++ ".json") (PrettyJson.encodePretty pg)
197199
Monad.forM_ pageDatas writePageData
198200
return pageDatas
199201

src/MyMark.hs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,29 @@
1+
{-# LANGUAGE OverloadedStrings #-}
2+
13
module MyMark (prefixImageUrl) where
24

3-
import CMark
5+
import CMarkGFM
46
import Data.Text qualified as T
57

8+
tableCellToHTML :: Node -> T.Text
9+
tableCellToHTML (Node _ _ nodes) = "<td>\n" <> T.intercalate "" (map (CMarkGFM.nodeToHtml [] []) (workOnInlineMath nodes)) <> "\n</td>"
10+
11+
tableRowToHTML :: Node -> T.Text
12+
tableRowToHTML (Node _ _ nodes) = "<tr>\n" <> T.intercalate "\n" (map tableCellToHTML nodes) <> "\n</tr>"
13+
14+
tableToInlineHTML :: [TableCellAlignment] -> [Node] -> Node
15+
tableToInlineHTML _ nodes = Node Nothing (HTML_BLOCK $ "<table>\n" <> T.intercalate "\n" (map tableRowToHTML nodes) <> "\n</table>") []
16+
617
prefixImageUrl :: String -> Node -> Node
718
prefixImageUrl prefix node =
819
let safePrefix = T.pack ("/" ++ prefix ++ "/")
920
replaceUrl url = if T.isPrefixOf (T.pack "http") url then url else safePrefix <> T.dropWhile (== '/') url
10-
recurse (Node posInfo nodeType nodes) =
21+
recurse (Node _ nodeType nodes) =
1122
case nodeType of
1223
IMAGE url title -> Node Nothing (IMAGE (replaceUrl url) title) nodes
1324
PARAGRAPH -> Node Nothing PARAGRAPH $ workOnInlineMath (map (prefixImageUrl prefix) nodes)
1425
CODE_BLOCK info text -> if info == T.pack "math" then mathBlock text else Node Nothing nodeType nodes
26+
TABLE aligns -> tableToInlineHTML aligns nodes
1527
_ -> Node Nothing nodeType (recurse <$> nodes)
1628
in recurse node
1729

test/Main.hs

Lines changed: 19 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,20 @@
11
{-# LANGUAGE OverloadedStrings #-}
2+
23
module Main (main) where
34

5+
import Control.Lens
46
import Control.Monad
57
import Data.ByteString qualified as B
68
import Data.Either qualified as E
79
import Data.Text qualified as T
8-
import Data.Text.IO qualified as TIO
910
import Data.Text.Encoding qualified as Encoding
10-
import MyLib qualified
11+
import Data.Text.IO qualified as TIO
1112
import MatlabMark qualified
12-
import System.Directory.Tree qualified as DT
13+
import MyLib qualified
1314
import System.Directory qualified as D
15+
import System.Directory.Tree qualified as DT
1416
import System.FilePath qualified as F
1517
import Test.Hspec
16-
import Control.Lens
17-
import CMark qualified
18-
import CMark.Lens
19-
import Data.Attoparsec.Text qualified as A
2018

2119
main :: IO ()
2220
main = hspec $ do
@@ -27,6 +25,13 @@ main = hspec $ do
2725
it "asset build test" $ do
2826
testCase
2927

28+
isEmptyDir :: DT.DirTree a -> Bool
29+
isEmptyDir (DT.Dir _ xs) = null xs
30+
isEmptyDir _ = False
31+
32+
makeComparable :: DT.AnchoredDirTree a -> [DT.DirTree a]
33+
makeComparable = filter (not . isEmptyDir) . DT.flattenDir . set DT._name "" . view DT._dirTree
34+
3035
testCase :: IO ()
3136
testCase =
3237
do
@@ -37,25 +42,18 @@ testCase =
3742
let reader x = do
3843
b <- B.readFile x
3944
return $ E.fromRight (T.pack $ F.takeBaseName x ++ ": " ++ show (B.length b)) $ Encoding.decodeUtf8' b
40-
a <- DT.readDirectoryWith reader dst
41-
let a' = a ^.DT._dirTree
42-
let a'' = DT.flattenDir (set DT._name "" a')
43-
b <- DT.readDirectoryWith reader expect
44-
let b' = b ^.DT._dirTree
45-
let b'' = DT.flattenDir (set DT._name "" b')
46-
zipWithM_ shouldBe a'' b''
45+
a <- makeComparable <$> DT.readDirectoryWith reader dst
46+
b <- makeComparable <$> DT.readDirectoryWith reader expect
47+
zipWithM_ shouldBe a b
4748

4849
matlab :: IO ()
4950
matlab =
5051
do
5152
node <- MatlabMark.readMatlabMD $ "test" F.</> "matlab-short.md"
5253
let dst = "test" F.</> "matlab-dst"
54+
D.removeDirectoryRecursive dst
5355
D.createDirectoryIfMissing True dst
5456
MatlabMark.generateMatlabAnswersDB dst node
55-
a <- DT.readDirectoryWith TIO.readFile dst
56-
let a' = a ^.DT._dirTree
57-
let a'' = DT.flattenDir (set DT._name "" a')
58-
b <- DT.readDirectoryWith TIO.readFile ("test" F.</> "matlab-expect")
59-
let b' = b ^.DT._dirTree
60-
let b'' = DT.flattenDir (set DT._name "" b')
61-
zipWithM_ shouldBe a'' b''
57+
a <- makeComparable <$> DT.readDirectoryWith TIO.readFile dst
58+
b <- makeComparable <$> DT.readDirectoryWith TIO.readFile ("test" F.</> "matlab-expect")
59+
zipWithM_ shouldBe a b
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"_childPageContents": [
3+
{
4+
"_answers": 0,
5+
"_attributes": {},
6+
"_hash": "ba45627f4d5702a2d14eaced7927fd3798c60167",
7+
"_pageTitle": "11. Equilibrium and Elasticity"
8+
}
9+
],
10+
"_pageContent": {
11+
"_answers": 0,
12+
"_attributes": {},
13+
"_hash": "0af5b76897bd59b64b3d577b459c75a1b5b6b3ca",
14+
"_pageTitle": "University Physics with Modern Physics"
15+
},
16+
"_parentHash": "2aed5404c83f7a46aa249e0a6328af756b19d513"
17+
}
Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,30 @@
1-
{"_pageContent":{"_pageTitle":"5.1 Continuous Mappings","_hash":"2125c437aaac928d5852224ff00a83f9d9776dda","_attributes":{},"_answers":1},"_parentHash":"6c11e3a31b4b8bd07bdef3f87887ab202a568679","_childPageContents":[{"_pageTitle":"8","_hash":"3e8d61043ed9bdb6b3cd4f197f0203bbdcb13db1","_attributes":{"a.md":{"_time":"2020-10-18T14:48:51+09:00","_attributeFile":{"_content":"I'll refer $`\\mathcal{T}`$ as $`\\mathcal{T}_X`$, $`\\mathcal{T}_1`$ as $`\\mathcal{T}_Y`$, $`\\mathcal{T}_2`$ as $`\\mathcal{T}_A`$, $`\\mathcal{T}_3`$ as $`\\mathcal{T}_B`$.\n\nLet's define $`x \\in X`$ and $`U \\in \\mathcal{T}_Y`$ such that $`f(x) \\in U`$. By definition of continuous mapping, there must exists a $`V`$ such that $`x \\in V \\in \\mathcal{T}_X`$ and $`f(x) \\in fV \\subseteq U`$.\n\nIf for any $`x \\in A`$ and any B-induced open set $`U_B \\in \\mathcal{T}_B`$, which must imply existance of $`U \\in \\mathcal{T}_Y`$, such that $`g(x) \\in U_B`$,\n\n![](IMG_44AF9D4BED6C-1.jpeg)\n\n...there exists an A-induced open set $`V_A`$, which must imply existance of $`V \\in \\mathcal{T}_A`$, such that the image $`gV_A`$ satisfies $`g(x) \\in gV_A \\subseteq U_B`$, then $`g`$ must be continuous. What we have to know is that if the image $`gV_A`$is subset of $`U_B`$, in other words, every $`x \\in V_A`$ will satisfiy $`g(x) \\in U_B`$. Let's proove it.\n\n![](IMG_E7FB922B8E70-1.jpeg)\n\n```math\n\\begin{aligned}\n & x \\in V_A \\\\\n & \\rightarrow x \\in V \\\\\n & \\rightarrow g(x) \\in gV \\\\\n & \\rightarrow g(x) \\in \\text{ some } U & \\text{ since } f \\text{ is continuous} \\\\\n & \\rightarrow g(x) \\in U \\cap B & \\text{ since codomain of } g \\text{ is } B \\\\\n & \\rightarrow g(x) \\in U_B\n\\end{aligned}\n```"}},"q.md":{"_time":"2020-04-12T01:08:01+09:00","_attributeFile":{"_content":"Let $`(X,\\mathcal{T})`$ and $`(Y,\\mathcal{T}_1)`$ be topological spaces and $`f:(X,\\mathcal{T}) \\rightarrow (Y,\\mathcal{T}_1)`$ a continuous mapping. Let $`A`$ be a subset of $`X`$, $`\\mathcal{T}_2`$ the induced topology on $`A`$, $`B = f(A)`$, $`\\mathcal{T}_3`$ the induced topology on $`B`$ and $`g:(A,\\mathcal{T}_2) \\rightarrow (B,\\mathcal{T}_3)`$ the restriction of $`f`$ to $`A`$. Prove that $`g`$ is continuous."}}},"_answers":1}]}
1+
{
2+
"_childPageContents": [
3+
{
4+
"_answers": 1,
5+
"_attributes": {
6+
"a.md": {
7+
"_attributeFile": {
8+
"_content": "I'll refer $`\\mathcal{T}`$ as $`\\mathcal{T}_X`$, $`\\mathcal{T}_1`$ as $`\\mathcal{T}_Y`$, $`\\mathcal{T}_2`$ as $`\\mathcal{T}_A`$, $`\\mathcal{T}_3`$ as $`\\mathcal{T}_B`$.\n\nLet's define $`x \\in X`$ and $`U \\in \\mathcal{T}_Y`$ such that $`f(x) \\in U`$. By definition of continuous mapping, there must exists a $`V`$ such that $`x \\in V \\in \\mathcal{T}_X`$ and $`f(x) \\in fV \\subseteq U`$.\n\nIf for any $`x \\in A`$ and any B-induced open set $`U_B \\in \\mathcal{T}_B`$, which must imply existance of $`U \\in \\mathcal{T}_Y`$, such that $`g(x) \\in U_B`$,\n\n![](IMG_44AF9D4BED6C-1.jpeg)\n\n...there exists an A-induced open set $`V_A`$, which must imply existance of $`V \\in \\mathcal{T}_A`$, such that the image $`gV_A`$ satisfies $`g(x) \\in gV_A \\subseteq U_B`$, then $`g`$ must be continuous. What we have to know is that if the image $`gV_A`$is subset of $`U_B`$, in other words, every $`x \\in V_A`$ will satisfiy $`g(x) \\in U_B`$. Let's proove it.\n\n![](IMG_E7FB922B8E70-1.jpeg)\n\n```math\n\\begin{aligned}\n & x \\in V_A \\\\\n & \\rightarrow x \\in V \\\\\n & \\rightarrow g(x) \\in gV \\\\\n & \\rightarrow g(x) \\in \\text{ some } U & \\text{ since } f \\text{ is continuous} \\\\\n & \\rightarrow g(x) \\in U \\cap B & \\text{ since codomain of } g \\text{ is } B \\\\\n & \\rightarrow g(x) \\in U_B\n\\end{aligned}\n```"
9+
},
10+
"_time": "2020-10-18T14:48:51+09:00"
11+
},
12+
"q.md": {
13+
"_attributeFile": {
14+
"_content": "Let $`(X,\\mathcal{T})`$ and $`(Y,\\mathcal{T}_1)`$ be topological spaces and $`f:(X,\\mathcal{T}) \\rightarrow (Y,\\mathcal{T}_1)`$ a continuous mapping. Let $`A`$ be a subset of $`X`$, $`\\mathcal{T}_2`$ the induced topology on $`A`$, $`B = f(A)`$, $`\\mathcal{T}_3`$ the induced topology on $`B`$ and $`g:(A,\\mathcal{T}_2) \\rightarrow (B,\\mathcal{T}_3)`$ the restriction of $`f`$ to $`A`$. Prove that $`g`$ is continuous."
15+
},
16+
"_time": "2020-04-12T01:08:01+09:00"
17+
}
18+
},
19+
"_hash": "3e8d61043ed9bdb6b3cd4f197f0203bbdcb13db1",
20+
"_pageTitle": "8"
21+
}
22+
],
23+
"_pageContent": {
24+
"_answers": 1,
25+
"_attributes": {},
26+
"_hash": "2125c437aaac928d5852224ff00a83f9d9776dda",
27+
"_pageTitle": "5.1 Continuous Mappings"
28+
},
29+
"_parentHash": "6c11e3a31b4b8bd07bdef3f87887ab202a568679"
30+
}
Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,50 @@
1-
{"_pageContent":{"_pageTitle":"books","_hash":"2aed5404c83f7a46aa249e0a6328af756b19d513","_attributes":{"q.md":{"_time":"2022-09-25T22:34:28+09:00","_attributeFile":{"_content":"# DON'T PANIC\n\nAlthough it has many omissions and contains much that is apocryphal, or at least wildly inaccurate, but it scores over the other answers over the internet in few important respects. First, the way it is written is very subjective, and second, it has the words DON'T PANIC inscribed in large friendly letters on its home."}}},"_answers":3},"_parentHash":"da39a3ee5e6b4b0d3255bfef95601890afd80709","_childPageContents":[{"_pageTitle":"Topology Without Tears","_hash":"4c1513c92422dc16b3c5f13bd03d34ba0feeb6df","_attributes":{"author.txt":{"_time":"2022-09-25T22:34:28+09:00","_attributeFile":{"_content":"Sidney A. Morris"}}},"_answers":1},{"_pageTitle":"Category Theory For Programmers","_hash":"b614f31d04b3bc2b3d23ee4337475251429e5a9f","_attributes":{"author.txt":{"_time":"2022-09-25T22:34:28+09:00","_attributeFile":{"_content":"Bartosz Milewski"}}},"_answers":2}]}
1+
{
2+
"_childPageContents": [
3+
{
4+
"_answers": 0,
5+
"_attributes": {},
6+
"_hash": "0af5b76897bd59b64b3d577b459c75a1b5b6b3ca",
7+
"_pageTitle": "University Physics with Modern Physics"
8+
},
9+
{
10+
"_answers": 1,
11+
"_attributes": {
12+
"author.txt": {
13+
"_attributeFile": {
14+
"_content": "Sidney A. Morris"
15+
},
16+
"_time": "2022-09-25T22:34:28+09:00"
17+
}
18+
},
19+
"_hash": "4c1513c92422dc16b3c5f13bd03d34ba0feeb6df",
20+
"_pageTitle": "Topology Without Tears"
21+
},
22+
{
23+
"_answers": 2,
24+
"_attributes": {
25+
"author.txt": {
26+
"_attributeFile": {
27+
"_content": "Bartosz Milewski"
28+
},
29+
"_time": "2022-09-25T22:34:28+09:00"
30+
}
31+
},
32+
"_hash": "b614f31d04b3bc2b3d23ee4337475251429e5a9f",
33+
"_pageTitle": "Category Theory For Programmers"
34+
}
35+
],
36+
"_pageContent": {
37+
"_answers": 3,
38+
"_attributes": {
39+
"q.md": {
40+
"_attributeFile": {
41+
"_content": "# DON'T PANIC\n\nAlthough it has many omissions and contains much that is apocryphal, or at least wildly inaccurate, but it scores over the other answers over the internet in few important respects. First, the way it is written is very subjective, and second, it has the words DON'T PANIC inscribed in large friendly letters on its home."
42+
},
43+
"_time": "2022-09-25T22:34:28+09:00"
44+
}
45+
},
46+
"_hash": "2aed5404c83f7a46aa249e0a6328af756b19d513",
47+
"_pageTitle": "books"
48+
},
49+
"_parentHash": "da39a3ee5e6b4b0d3255bfef95601890afd80709"
50+
}

0 commit comments

Comments
 (0)