Skip to content

Commit

Permalink
Merge branch 'fix-json-escaping-2'
Browse files Browse the repository at this point in the history
  • Loading branch information
istathar committed Apr 24, 2022
2 parents 344ee9e + 1dceb01 commit 330744c
Show file tree
Hide file tree
Showing 9 changed files with 86 additions and 35 deletions.
4 changes: 2 additions & 2 deletions core-data/core-data.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ cabal-version: 1.12
-- see: https://github.com/sol/hpack

name: core-data
version: 0.3.2.1
version: 0.3.2.2
synopsis: Convenience wrappers around common data structures and encodings
description: Wrappers around common data structures and encodings.
.
Expand Down Expand Up @@ -46,7 +46,7 @@ library
, base >=4.11 && <5
, bytestring
, containers
, core-text >=0.3.4
, core-text >=0.3.7
, hashable >=1.2
, prettyprinter >=1.6.2
, scientific
Expand Down
59 changes: 34 additions & 25 deletions core-data/lib/Core/Encoding/Json.hs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,13 @@ module Core.Encoding.Json (
prettyValue,
) where

#if MIN_VERSION_aeson(2,0,1)
import qualified Data.Aeson.Key as Aeson
import qualified Data.Aeson.KeyMap as Aeson
#else
import qualified Data.HashMap.Strict as HashMap
#endif

import Core.Data.Structures (Key, Map, fromMap, intoMap)
import Core.Text.Bytes (Bytes, fromBytes, intoBytes)
import Core.Text.Colour (
Expand All @@ -86,21 +93,15 @@ import Core.Text.Rope (
fromRope,
intoRope,
singletonRope,
unconsRope,
)
import Core.Text.Utilities (
Render (Token, colourize, highlight),
breakPieces,
breakRope,
)
import qualified Data.Aeson as Aeson

#if MIN_VERSION_aeson(2,0,1)
import qualified Data.Aeson.Key as Aeson
import qualified Data.Aeson.KeyMap as Aeson
#else
import qualified Data.HashMap.Strict as HashMap
#endif

import Data.Aeson (FromJSON, Value (String))
import qualified Data.Aeson as Aeson
import Data.Char (intToDigit)
import Data.Coerce
import Data.Hashable (Hashable)
import qualified Data.List as List
Expand Down Expand Up @@ -178,26 +179,34 @@ encodeToRope value = case value of
closebracket = singletonRope ']'

{- |
Escape any quotes or backslashes in a JsonString.
Escape any quotes, backslashes, or other possible rubbish in a 'JsonString'.
-}
escapeString :: Rope -> Rope
escapeString text =
let text1 = escapeBackslashes text
text2 = escapeQuotes text1
in text2
let (before, after) = breakRope needsEscaping text
in case unconsRope after of
Nothing ->
text
Just (c, after') ->
before <> escapeCharacter c <> escapeString after'
where
needsEscaping c =
c == '\"' || c == '\\' || c < '\x20'
{-# INLINEABLE escapeString #-}

escapeBackslashes :: Rope -> Rope
escapeBackslashes text =
let pieces = breakPieces (== '\\') text
in mconcat (List.intersperse "\\\\" pieces)
{-# INLINEABLE escapeBackslashes #-}

escapeQuotes :: Rope -> Rope
escapeQuotes text =
let pieces = breakPieces (== '"') text
in mconcat (List.intersperse "\\\"" pieces)
{-# INLINEABLE escapeQuotes #-}
escapeCharacter :: Char -> Rope
escapeCharacter c =
case c of
'\"' -> "\\\""
'\\' -> "\\\\"
'\n' -> "\\n"
'\r' -> "\\r"
'\t' -> "\\t"
_ ->
if c < '\x10'
then "\\u000" <> singletonRope (intToDigit (fromEnum c))
else "\\u001" <> singletonRope (intToDigit ((fromEnum c) - 16))
{-# INLINEABLE escapeCharacter #-}

{- |
Given an array of bytes, attempt to decode it as a JSON value.
Expand Down
4 changes: 2 additions & 2 deletions core-data/package.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: core-data
version: 0.3.2.1
version: 0.3.2.2
synopsis: Convenience wrappers around common data structures and encodings
description: |
Wrappers around common data structures and encodings.
Expand Down Expand Up @@ -33,7 +33,7 @@ dependencies:
- text
- unordered-containers
- vector
- core-text >= 0.3.4
- core-text >= 0.3.7

library:
source-dirs: lib
Expand Down
6 changes: 3 additions & 3 deletions core-program/lib/Core/Program/Metadata.hs
Original file line number Diff line number Diff line change
Expand Up @@ -150,12 +150,12 @@ readCabalFile = runIO $ do

parseCabalFile :: Bytes -> Map Rope Rope
parseCabalFile contents =
let breakup = intoMap . fmap (breakRope (== ':')) . breakLines . fromBytes
let breakup = intoMap . fmap (breakRope' (== ':')) . breakLines . fromBytes
in breakup contents

-- this should probably be a function in Core.Text.Rope
breakRope :: (Char -> Bool) -> Rope -> (Rope, Rope)
breakRope predicate text =
breakRope' :: (Char -> Bool) -> Rope -> (Rope, Rope)
breakRope' predicate text =
let pieces = take 2 (breakPieces predicate text)
in case pieces of
[] -> ("", "")
Expand Down
2 changes: 1 addition & 1 deletion core-text/core-text.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ cabal-version: 1.18
-- see: https://github.com/sol/hpack

name: core-text
version: 0.3.6.0
version: 0.3.7.0
synopsis: A rope type based on a finger tree over UTF-8 fragments
description: A rope data type for text, built as a finger tree over UTF-8 text
fragments. The package also includes utiltiy functions for breaking and
Expand Down
22 changes: 22 additions & 0 deletions core-text/lib/Core/Text/Breaking.hs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

-- This is an Internal module, hidden from Haddock
module Core.Text.Breaking (
breakRope,
breakWords,
breakLines,
breakPieces,
Expand Down Expand Up @@ -142,3 +143,24 @@ intoChunks predicate piece =
in if trailing
then intoRope chunk : emptyRope : []
else intoRope chunk : intoChunks predicate remainder'

{-
The utilities breakPieces and its helpers above were written long before this
code. The special purpose functions above might have been written more easily
if this below had been written first, but they _are_ special cases and they're
done, so {shrug} if someone wants to unify these go right head, otherwise this
can stand as almost but not-quite repetition.
-}

{- |
Given a piece of 'Rope' and a predicate, break the text into two pieces at the first
site where that predicate returns 'True'.
@since 0.3.7
-}
breakRope :: (Char -> Bool) -> Rope -> (Rope, Rope)
breakRope predicate text =
let possibleIndex = findIndexRope predicate text
in case possibleIndex of
Nothing -> (text, emptyRope)
Just i -> splitRope i text
21 changes: 20 additions & 1 deletion core-text/lib/Core/Text/Rope.hs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ module Core.Text.Rope (
replicateRope,
replicateChar,
widthRope,
unconsRope,
splitRope,
takeRope,
insertRope,
Expand Down Expand Up @@ -154,7 +155,7 @@ import qualified Data.Text.Short as S (
splitAt,
toBuilder,
toText,
unpack,
unpack, uncons
)
import qualified Data.Text.Short.Unsafe as S (fromByteStringUnsafe)
import GHC.Generics (Generic)
Expand Down Expand Up @@ -332,6 +333,24 @@ nullRope (Rope x) = case F.viewl x of
F.EmptyL -> True
(F.:<) piece _ -> S.null piece

{- |
Read the first character from a 'Rope', assuming it's length 1 or greater,
returning 'Just' that character and the remainder of the text. Returns
'Nothing' if the input is 0 length.
@since 0.3.7
-}
unconsRope :: Rope -> Maybe (Char, Rope)
unconsRope text =
let x = unRope text
in case F.viewl x of
F.EmptyL -> Nothing
(F.:<) piece x' ->
case S.uncons piece of
Nothing -> Nothing
Just (c, piece') -> Just (c, Rope ((F.<|) piece' x'))


{- |
Break the text into two pieces at the specified offset.
Expand Down
1 change: 1 addition & 0 deletions core-text/lib/Core/Text/Utilities.hs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ module Core.Text.Utilities (
-- * Helpers
indefinite,
oxford,
breakRope,
breakWords,
breakLines,
breakPieces,
Expand Down
2 changes: 1 addition & 1 deletion core-text/package.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: core-text
version: 0.3.6.0
version: 0.3.7.0
synopsis: A rope type based on a finger tree over UTF-8 fragments
description: |
A rope data type for text, built as a finger tree over UTF-8 text
Expand Down

0 comments on commit 330744c

Please sign in to comment.