Skip to content

Commit d26d9a9

Browse files
authored
chore(cms): finalising ghost exporter (#268)
1 parent 00575bf commit d26d9a9

File tree

6 files changed

+968
-68
lines changed

6 files changed

+968
-68
lines changed

utilities/ghost-exporter/Converters.fs

+29-31
Original file line numberDiff line numberDiff line change
@@ -16,28 +16,30 @@ let convertMarkups (markups: Markups.markup list) : textStyle =
1616
underline = false
1717
}
1818

19+
1920
let convertToText (text: string) (styles: bool list) : text =
2021
{
2122
data = Text text
2223
style = {
23-
bold = false
24-
italic = false
25-
underline = false
26-
} // When we inevitably switch to lists of styles we can add an additional parameter to this function to pass in the styles
24+
bold = styles.Item 0
25+
italic = styles.Item 1
26+
underline = styles.Item 2
27+
}
2728
}
2829

30+
2931
// This needs to be implemented properly once frontend is ready
3032
let convertCallout (callout: Cards.callout) : text list =
31-
[convertToText callout.text [false; false; false]]
33+
[convertToText callout.calloutText [false; false; false]]
34+
3235

3336
// This needs to be implemented properly once frontend is ready
3437
let convertToggle (toggle: Cards.toggle) : text list =
35-
[convertToText toggle.heading [true; false; false]; convertToText toggle.body [false; false; false]]
38+
[convertToText toggle.heading [true; false; false]; convertToText toggle.content [false; false; false]]
3639

3740

3841
let convertCode (code: Cards.code) : text list =
39-
[convertToText code.code [false; false; false]]
40-
42+
[convertToText ("=== THIS IS A CODE BLOCK === \n" + code.code) [false; false; false]]
4143

4244

4345
let convertCard (cards: Cards.card list) (cardIndex: int) : text list =
@@ -48,44 +50,40 @@ let convertCard (cards: Cards.card list) (cardIndex: int) : text list =
4850
| Toggle toggle -> convertToggle toggle
4951
| Code code -> convertCode code
5052

51-
let modifyOpenMarkups (openMarkups: int list) (newMarkups: int list) (numClosedMarkups: int) : int list =
52-
let rec removeMarkups (markups: int list, numClosedMarkups: int) : int list =
53-
match numClosedMarkups with
54-
| 0 -> markups
55-
| _ -> removeMarkups (List.tail markups, numClosedMarkups - 1)
56-
// My current assumption is that we append newMarkups to the list and remove starting from the last markup in the list
57-
removeMarkups (List.append openMarkups newMarkups, numClosedMarkups)
53+
54+
let rec removeMarkups (openMarkups: int list) (numClosedMarkups: int) : int list =
55+
match numClosedMarkups with
56+
| 0 -> openMarkups
57+
| _ -> removeMarkups (List.tail openMarkups) (numClosedMarkups - 1)
58+
5859

5960
let retrieveMarkups (markups: Markups.markup list) (indices: int list) : Markups.markup list =
6061
List.map (fun index -> List.item index markups) indices
6162

62-
let convertValue (sectionBlock : sectionBlock) (openMarkups : Markups.markup list) : textType =
63-
let isLink markup =
64-
match markup with
65-
| Markups.Link _ -> true
66-
| _ -> false
67-
68-
let listContainsLink list =
69-
List.exists (isType) list
7063

71-
if listContainsLink openMarkups then
72-
Text sectionBlock.value // Should be URL type in future
73-
else
74-
Text sectionBlock.value
64+
let extractValue (markups: Markups.markup list) = function
65+
| StringValue x ->
66+
let combinedLinks = markups
67+
|> List.map (function | Markups.Link link -> link.url | _ -> "")
68+
|> List.fold (+) ""
69+
if combinedLinks <> "" then x + " (" + combinedLinks + ")" else x
70+
| AtomIndex _ -> System.Environment.NewLine
71+
72+
let convertValue (sectionBlock : sectionBlock) (openMarkups : Markups.markup list) : textType =
73+
Text <| extractValue openMarkups sectionBlock.value
74+
7575

7676
let convertSectionBlocks (markups: Markups.markup list) (sectionBlock: list<sectionBlock>) : text list =
7777
let rec convertSectionBlock (sections: list<sectionBlock>) (openMarkups : int list) : text list =
7878
match sections with
7979
| [] -> []
8080
| x :: xs ->
81-
let openMarkups = modifyOpenMarkups openMarkups x.openMarkups x.numClosedMarkups
81+
let openMarkups = List.append openMarkups x.openMarkups
8282
let retrievedMarkups = retrieveMarkups markups openMarkups
83-
8483
{
8584
data = convertValue x retrievedMarkups
8685
style = convertMarkups retrievedMarkups
87-
88-
} :: (convertSectionBlock xs openMarkups)
86+
} :: (convertSectionBlock xs (removeMarkups openMarkups x.numClosedMarkups))
8987

9088
convertSectionBlock sectionBlock []
9189

utilities/ghost-exporter/Main.fs

+8-7
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,20 @@ open Newtonsoft.Json
66

77
open FSharpPlus
88
open Fleece.Newtonsoft
9-
open Fleece.Newtonsoft.Operators
109
open System.IO
1110
open Converters
1211

1312
[<EntryPoint>]
1413
let main args =
15-
let ghostDocument: GhostDocument ParseResult = ofJsonText (File.ReadAllText args[0])
14+
let ghostDocument = ofJsonText (File.ReadAllText args[0])
1615

17-
let result =
18-
match Option.ofResult ghostDocument with
19-
| Some document -> toJsonText (GhostToCms document)
20-
| _ -> "failed to parse ghost document"
16+
match Option.ofResult ghostDocument with
17+
| Some document ->
18+
// Write the result to disk
19+
let compiledResult = toJsonText (GhostToCms document)
20+
let fileName = Path.GetFileNameWithoutExtension (args[0])
21+
File.WriteAllText ($"{Path.GetFileNameWithoutExtension (args[0])}__exported.json", compiledResult)
2122

22-
printfn "%s" result
23+
| _ -> printfn "%s" "failed to parse ghost document"
2324

2425
0

utilities/ghost-exporter/Models/CMSSyntax.fs

-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
module CMSSyntax
22

33
open Fleece
4-
open Fleece.Newtonsoft.Operators
54

65
// possible allignment of text blocks
76
type alignment =

utilities/ghost-exporter/Models/GhostSyntax.fs

+63-29
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ open System
1111

1212

1313
module Markups =
14+
let Success = Decode.Success
15+
let Invalid = Decode.Fail.invalidValue
16+
1417
type link = { linkType: string; url: string }
1518
type markup =
1619
| Link of link
@@ -20,14 +23,18 @@ module Markups =
2023
with
2124
static member OfJson json =
2225
match json with
23-
| JArray o ->
24-
match List.ofSeq o with
25-
| (JString "a") :: (JString lType) :: (JString url) :: [] -> Decode.Success (Link { linkType = lType; url = url })
26-
| (JString "code") :: [] -> Decode.Success Code
27-
| (JString "em") :: [] -> Decode.Success Emphasis
28-
| (JString "strong") :: [] -> Decode.Success Strong
29-
| _ -> Decode.Fail.invalidValue json "failed to parse into markup"
30-
| _ -> Decode.Fail.arrExpected json
26+
| JArray arr ->
27+
match List.ofSeq arr with
28+
| [(JString "code")] -> Success Code
29+
| [(JString "em")] -> Success Emphasis
30+
| [(JString "strong")] -> Success Strong
31+
| [(JString "a"); (JArray o)] ->
32+
match List.ofSeq o with
33+
| [(JString lType); (JString url)] -> Success <| Link { linkType = lType; url = url }
34+
| _ -> Invalid json "failed to parse into reference markup"
35+
| _ -> Invalid json "failed to parse into markup"
36+
| o -> Decode.Fail.arrExpected o
37+
3138

3239
module Cards =
3340
// Callout cards can be directly translated into CMS paragraph blocks
@@ -72,50 +79,58 @@ module Cards =
7279
match json with
7380
| JArray o ->
7481
match List.ofSeq o with
75-
| (JString "callout") :: (cardObject: Encoding) :: [] -> Callout <!> (callout.OfJson cardObject)
76-
| (JString "toggle") :: (cardObject: Encoding) :: [] -> Toggle <!> (toggle.OfJson cardObject)
77-
| (JString "code") :: (cardObject: Encoding) :: [] -> Code <!> (code.OfJson cardObject)
82+
| [JString "callout"; cardObject: Encoding] -> Callout <!> (callout.OfJson cardObject)
83+
| [JString "toggle"; cardObject: Encoding] -> Toggle <!> (toggle.OfJson cardObject)
84+
| [JString "code"; cardObject: Encoding] -> Code <!> (code.OfJson cardObject)
7885
| _ -> Decode.Fail.invalidValue json "failed to parse into card"
7986
| _ -> Decode.Fail.arrExpected json
8087

8188

8289
module Sections =
8390
type sectionTag =
84-
| Paragraph
85-
| Heading
86-
| Card
91+
| Paragraph
92+
| Heading
93+
| Card
8794

8895
// Specialised parsers since this library doesn't seem to give us any :(((((((
8996
// Might stack overflow on large lists but I dont see us ever having to deal with that, if stack overflows are an issue just optimise to be tail call recursive
9097
let rec parseListInner (parser: Encoding -> Result<'a, DecodeError>) arr : Result<'a list, DecodeError> =
9198
match arr with
99+
| [] -> Ok []
92100
| x :: xs -> match parseListInner parser xs with
93101
| Ok arr -> (fun parsedValue -> parsedValue :: arr) <!> (parser x)
94102
| Error x -> Error x
95-
| [] -> Ok []
96103

97104
let parseList parser arr = parseListInner parser (List.ofSeq arr)
98105
let parseNumberList = parseList (function | JNumber x -> Ok (Decimal.ToInt32 x) | x -> Decode.Fail.numExpected x)
99106

100107
// Individual styling rules for a section
108+
type sectionType =
109+
| AtomIndex of int
110+
| StringValue of string
111+
101112
type sectionBlock = {
102113
openMarkups: int list;
103114
numClosedMarkups: int;
104-
value: string;
115+
value: sectionType;
105116
} with
117+
static member createSectionBlock = fun openMarkups numClosedMarkups value -> { openMarkups = openMarkups; numClosedMarkups = numClosedMarkups; value = value }
106118
static member OfJson json =
107119
match json with
108120
| JArray o -> match List.ofSeq o with
109-
| (JNumber 0m) :: (JArray openMarkups) :: (JNumber numClosedMarkups) :: (JString text) :: [] ->
110-
(fun openMarkups numClosedMarkups text -> { openMarkups = openMarkups; numClosedMarkups = numClosedMarkups; value = text })
111-
<!> (parseNumberList openMarkups)
112-
<*> (Ok (Decimal.ToInt32 numClosedMarkups))
113-
<*> (Ok text)
121+
| [JNumber sectionType; JArray openMarkups; JNumber numClosedMarkups; value] ->
122+
let block = sectionBlock.createSectionBlock <!> (parseNumberList openMarkups) <*> (Ok (Decimal.ToInt32 numClosedMarkups))
123+
match (sectionType, value) with
124+
| (0m, JString text) -> block <*> (Ok <| StringValue text)
125+
| (1m, JNumber atomIndex) -> block <*> (Ok <| AtomIndex (Decimal.ToInt32 atomIndex))
126+
| _ -> Decode.Fail.invalidValue json "failed to parse section type"
127+
114128
| _ -> Decode.Fail.invalidValue json "failed to parse into section block"
115129

116130
| json -> Decode.Fail.arrExpected json
117131

118-
let parseSectionBlockList = parseList sectionBlock.OfJson
132+
let parseSectionBlockList = parseList sectionBlock.OfJson
133+
let parseSectionBlockListInner = parseListInner sectionBlock.OfJson
119134

120135
type blockValue =
121136
| Section of list<sectionBlock>
@@ -126,15 +141,35 @@ module Sections =
126141
tag: sectionTag;
127142
blocks: blockValue;
128143
} with
144+
static member createSection = fun sections tag -> { tag = tag; blocks = Section sections }
129145
static member OfJson json =
130146
match json with
131147
| JArray o -> match List.ofSeq o with
132-
| (JNumber 1m) :: (JString "p") :: (JArray subsections) :: [] -> (fun sections -> { tag = Paragraph; blocks = Section sections }) <!> (parseSectionBlockList subsections)
133-
| (JNumber 1m) :: (JString "h2") :: (JArray subsections) :: [] -> (fun sections -> { tag = Heading; blocks = Section sections }) <!> (parseSectionBlockList subsections)
134-
| (JNumber 10m) :: (JNumber x) :: [] -> Decode.Success { tag = Card; blocks = CardReference <| Decimal.ToInt32 x }
135-
| _ -> Decode.Fail.invalidValue json "failed to parse into sections"
136-
| json -> Decode.Fail.arrExpected json
137-
// Sections are the core of the ghost mobiledoc format, they're what will directly dictate the structure of the exported CMS json
148+
| [JNumber 10m; JNumber x] -> Decode.Success { tag = Card; blocks = CardReference <| Decimal.ToInt32 x }
149+
| [JNumber 3m; JString sectionType; JArray subsectionArray] ->
150+
// this is so hacky im sorry :(, please fix this later
151+
let rec sequenceSubsections = function
152+
| [] -> Ok []
153+
| x :: xs ->
154+
match x with
155+
| JArray inner -> match parseSectionBlockList inner with
156+
| Ok arr -> (fun parsedValue -> List.append arr parsedValue) <!> (sequenceSubsections xs)
157+
| Error x -> Error x
158+
| o -> Decode.Fail.arrExpected o
159+
160+
section.createSection <!> (sequenceSubsections <| List.ofSeq subsectionArray) <*> (Ok Paragraph)
161+
162+
| [JNumber 1m; JString sectionType; JArray subsections] ->
163+
let partialSection = section.createSection <!> (parseSectionBlockList subsections)
164+
match sectionType with
165+
| "p" -> partialSection <*> (Ok Paragraph)
166+
| "h2" | "h3" | "h4" -> partialSection <*> (Ok Heading)
167+
| _ -> Decode.Fail.invalidValue json "failed to parse into sections"
168+
169+
| _ -> Decode.Fail.invalidValue json "failed to parse into sections"
170+
| json ->
171+
printf "%A" json
172+
Decode.Fail.arrExpected json
138173

139174

140175
type GhostDocument = {
@@ -149,7 +184,6 @@ type GhostDocument = {
149184
let! markups = o .@ "markups"
150185
let! cards = o .@ "cards"
151186
let! sections = o .@ "sections"
152-
153187
return {
154188
Cards = cards
155189
Markups = markups

0 commit comments

Comments
 (0)