Skip to content

Commit 69ce117

Browse files
committed
fix StringLiteral test, rename explicit-nl to wrapped
1 parent fe827e6 commit 69ce117

File tree

3 files changed

+111
-69
lines changed

3 files changed

+111
-69
lines changed

TODO

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@ string-literal
99
- documentation
1010
- I can infer the style when going to raw, but then how to go back?
1111
. I could set a vim variable.
12+
- I might want to be able to ignore but preserve leading gunk on the first
13+
line, e.g. for
14+
( "blah\
15+
\ blah"
16+
, ...
17+
)
1218
lines:
1319
- doesn't dedent properly
1420
- support --explicit-nl

src/StringLiteral.hs

Lines changed: 72 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import qualified System.Exit as Exit
1616

1717
usage :: String
1818
usage =
19-
"StrLit [ --explicit-nl --{add,remove,toggle}-{backslash,lines} ]\n\
19+
"StrLit [ --wrapped --{add,remove,toggle}-{backslash,lines} ]\n\
2020
\\n\
2121
\Convert between plain text and either backslash-continued string\n\
2222
\literals, or list of lines style strings. This is to work around\n\
@@ -34,7 +34,7 @@ data Operation = Add | Remove deriving (Eq, Show)
3434
data Kind = Backslash | Lines deriving (Eq, Show)
3535

3636
parseArgs :: [String] -> Maybe (Bool, Maybe Operation, Kind)
37-
parseArgs args = add <$> case filter (/="--explicit-nl") args of
37+
parseArgs args = add <$> case filter (/="--wrapped") args of
3838
["--toggle-backslash"] -> Just (Nothing, Backslash)
3939
["--add-backslash"] -> Just (Just Add, Backslash)
4040
["--remove-backslash"] -> Just (Just Remove, Backslash)
@@ -43,22 +43,22 @@ parseArgs args = add <$> case filter (/="--explicit-nl") args of
4343
["--remove-lines"] -> Just (Just Remove, Lines)
4444
_ -> Nothing
4545
where
46-
add (b, c) = (explicitNl, b, c)
47-
explicitNl = "--explicit-nl" `elem` args
46+
add (b, c) = (wrapped, b, c)
47+
wrapped = "--wrapped" `elem` args
4848

4949
process :: (Bool, Maybe Operation, Kind) -> [Text] -> [Text]
50-
process (explicitNl, op, kind) = case (explicitNl, op, kind) of
50+
process (wrapped, op, kind) = case (wrapped, op, kind) of
5151
(_, Nothing, Backslash) -> \lines -> if inferBackslashed lines
52-
then process (explicitNl, Just Remove, kind) lines
53-
else process (explicitNl, Just Add, kind) lines
52+
then process (wrapped, Just Remove, kind) lines
53+
else process (wrapped, Just Add, kind) lines
5454
(_, Nothing, Lines) -> \lines -> if inferList lines
55-
then process (explicitNl, Just Remove, kind) lines
56-
else process (explicitNl, Just Add, kind) lines
55+
then process (wrapped, Just Remove, kind) lines
56+
else process (wrapped, Just Add, kind) lines
5757

5858
(False, Just Add, Backslash) -> addBackslash
5959
(False, Just Remove, Backslash) -> removeBackslash
60-
(True, Just Add, Backslash) -> addBackslashExplicit
61-
(True, Just Remove, Backslash) -> removeBackslashExplicit
60+
(True, Just Add, Backslash) -> addBackslashWrapped
61+
(True, Just Remove, Backslash) -> removeBackslashWrapped
6262
(_, Just Add, Lines) -> addLines
6363
(_, Just Remove, Lines) -> removeLines
6464

@@ -71,9 +71,12 @@ inferBackslashed :: [Text] -> Bool
7171
inferBackslashed [] = False
7272
inferBackslashed (line:_) = "\"" `Text.isPrefixOf` Text.stripStart line
7373

74-
{- |
74+
{- | Add backslashes assuming the text will be wrapped by someone else later.
75+
7576
An extra newline becomes a leading \n. A leading space is added if
76-
there is no leading \n, except for the first line.
77+
there is no leading \n, except for the first line. Other than that,
78+
leading and trailing spaces are stripped, since the assumption is that
79+
this is just a list of words.
7780
7881
> this is
7982
> raw
@@ -86,12 +89,14 @@ inferBackslashed (line:_) = "\"" `Text.isPrefixOf` Text.stripStart line
8689
> \ raw\
8790
> \\ntext"
8891
-}
89-
addBackslashExplicit :: [Text] -> [Text]
90-
addBackslashExplicit = indent . map3 add1 addn end . collectNewlines . dedent
92+
addBackslashWrapped :: [Text] -> [Text]
93+
addBackslashWrapped =
94+
indent . mapAround start middle end only . collectNewlines . dedentAll
9195
where
92-
add1 s = "\"" <> s <> "\\"
93-
addn s = "\\" <> leadingSpace s <> "\\"
94-
end s = "\\" <> leadingSpace s <> "\""
96+
start = surround "\"" "\\"
97+
middle = surround "\\" "\\" . leadingSpace
98+
end = surround "\\" "\"" . leadingSpace
99+
only = surround "\"" "\""
95100
leadingSpace s
96101
| "\\n" `Text.isPrefixOf` s = s
97102
| otherwise = " " <> s
@@ -104,20 +109,21 @@ collectNewlines = filter (not . Text.null) . snd . List.mapAccumL collect 0
104109
| otherwise = (0, Text.replicate newlines "\\n" <> line)
105110

106111

107-
{- Invert 'addBackslashExplicit'. Drop a leading space unless there was
112+
{- | Invert 'addBackslashWrapped'. Drop a leading space unless there was
108113
a leading \n.
109114
-}
110-
removeBackslashExplicit :: [Text] -> [Text]
111-
removeBackslashExplicit =
115+
removeBackslashWrapped :: [Text] -> [Text]
116+
removeBackslashWrapped =
112117
indent . map stripLeadingSpace . zipPrev . concatMap addNewlines
113-
. map3 remove1 removen end . dedent
118+
. mapAround start middle end only . dedent
114119
where
115120
addNewlines s =
116121
replicate (length pre) "" ++ [Text.intercalate "\\n" post]
117122
where (pre, post) = span Text.null $ Text.splitOn "\\n" s
118-
remove1 = stripPrefix "\"" . stripSuffix "\\"
119-
removen = stripPrefix "\\" . stripSuffix "\\"
120-
end = stripPrefix "\\" . stripSuffix "\""
123+
start = strip "\"" "\\"
124+
middle = strip "\\" "\\"
125+
end = strip "\\" "\""
126+
only = strip "\"" "\""
121127
stripLeadingSpace (Just "", s) = s
122128
stripLeadingSpace (_, s)
123129
| ' ' : c : _ <- Text.unpack s, c /= ' ' = Text.drop 1 s
@@ -132,10 +138,10 @@ addBackslash = indent . map3 add1 addn end . map quote . dedent
132138
end s = "\\" <> s <> "\\n\""
133139

134140
removeBackslash :: [Text] -> [Text]
135-
removeBackslash = indent . map unquote . map3 remove1 removen end . dedent
141+
removeBackslash = indent . map unquote . map3 start middle end . dedent
136142
where
137-
remove1 = stripPrefix "\"" . spaces . nl
138-
removen = stripPrefix "\\" . spaces . nl
143+
start = stripPrefix "\"" . spaces . nl
144+
middle = stripPrefix "\\" . spaces . nl
139145
end = stripPrefix "\\" . spaces . stripSuffix "\\n\""
140146
spaces = Text.dropWhile (==' ')
141147
nl = stripSuffix "\\n\\"
@@ -154,11 +160,11 @@ addLines = map3 add1 addn end . map quote . dedent
154160
end line = addn line <> "\n" <> indentation <> "]"
155161

156162
removeLines :: [Text] -> [Text]
157-
removeLines = map unquote . map3 remove1 removen id . dropLast
163+
removeLines = map unquote . map3 start middle id . dropLast
158164
where
159165
remove = Text.dropWhile (==' ') . stripSuffix "\""
160-
remove1 = stripPrefix "[ \"" . remove
161-
removen = stripPrefix ", \"" . remove
166+
start = stripPrefix "[ \"" . remove
167+
middle = stripPrefix ", \"" . remove
162168
-- The last line should be ']'
163169
dropLast [] = []
164170
dropLast xs = List.init xs
@@ -175,22 +181,15 @@ dedent lines = map (Text.drop indentation) lines
175181
map (Text.length . Text.takeWhile Char.isSpace) $
176182
filter (not . Text.all Char.isSpace) lines
177183

184+
dedentAll :: [Text] -> [Text]
185+
dedentAll = map (Text.dropWhile (==' ') . Text.stripEnd)
186+
178187
quote :: Text -> Text
179188
quote = Text.replace "\"" "\\\""
180189

181190
unquote :: Text -> Text
182191
unquote = Text.replace "\\\"" "\""
183192

184-
-- | Apply separate transformations to initial, middle, and final elements.
185-
-- If there are 2 elements, middle loses out, and if there is 1, both
186-
-- initial and final functions are applied.
187-
-- map3 :: (a -> b) -> (a -> b) -> (a -> Maybe b) -> [a] -> [b]
188-
-- map3 initial middle final xs = case xs of
189-
-- [] -> []
190-
-- [x] -> [initial (final x)]
191-
-- x : xs -> initial x : go xs
192-
-- where
193-
194193
map3 :: (a -> b) -> (a -> b) -> (a -> b) -> [a] -> [b]
195194
map3 _ _ _ [] = []
196195
map3 initial middle end (x:xs) = initial x : go xs
@@ -199,6 +198,38 @@ map3 initial middle end (x:xs) = initial x : go xs
199198
go [x] = [end x]
200199
go (x:xs) = middle x : go xs
201200

201+
-- | Apply separate transformations to start, middle, end, or only elements.
202+
-- If there are 2 elements, middle loses out, and if there is 1, the @only@
203+
-- function is applied.
204+
mapAround :: (a -> b) -> (a -> b) -> (a -> b) -> (a -> b) -> [a] -> [b]
205+
mapAround start middle end only xs = case xs of
206+
[] -> []
207+
[x] -> [only x]
208+
x : xs -> start x : go xs
209+
where
210+
go xs = case xs of
211+
[] -> []
212+
[x] -> [end x]
213+
x : xs -> middle x : go xs
214+
215+
surround :: Text -> Text -> Text -> Text
216+
surround s e x = s <> x <> e
217+
218+
strip :: HasCallStack => Text -> Text -> Text -> Text
219+
strip s e = stripPrefix s . stripSuffix e
220+
221+
-- surround :: (Text, Text) -> (Text, Text) -> (Text, Text) -> (Text, Text)
222+
-- -> [Text] -> [Text]
223+
-- surround start middle end only xs = case xs of
224+
-- [x] -> [add only x]
225+
-- x : xs -> add start x : go xs
226+
-- where
227+
-- go xs = case xs of
228+
-- [] -> []
229+
-- [x] -> [add end x]
230+
-- x : xs -> add middle x : go xs
231+
-- add (s, e) x = s <> x <> e
232+
202233
stripSuffix :: HasCallStack => Text -> Text -> Text
203234
stripSuffix s text =
204235
maybe (error $ "expected suffix " <> show s <> " on " <> show text) id $

src/StringLiteral_test.hs

Lines changed: 33 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,17 @@ import Data.Text (Text)
77
import qualified StringLiteral
88

99

10-
(===) :: (Eq a, Show a) => a -> a -> HUnit.Assertion
11-
(===) = (HUnit.@?=)
12-
13-
run = Tasty.defaultMain
14-
1510
main :: IO ()
1611
main = Tasty.defaultMain $ Tasty.testGroup "tests"
17-
[ test_backslashExplicit
18-
, test_roundTrip
12+
[ test_backslashWrapped
13+
, test_backslashWrapped_roundTrip
1914
]
2015

21-
test_backslashExplicit :: Tasty.TestTree
22-
test_backslashExplicit = Tasty.testGroup "backslashExplicit"
16+
run :: Tasty.TestTree -> IO ()
17+
run = Tasty.defaultMain
18+
19+
test_backslashWrapped :: Tasty.TestTree
20+
test_backslashWrapped = Tasty.testGroup "backslashWrapped"
2321
[ [" one line"] ==> [" \"one line\""]
2422
, [" two", " lines"] ==>
2523
[ " \"two\\"
@@ -35,22 +33,37 @@ test_backslashExplicit = Tasty.testGroup "backslashExplicit"
3533
, " \\ explicit\\"
3634
, " \\\\nnewline\""
3735
]
36+
,
37+
[ " with"
38+
, " explicit"
39+
, " indent"
40+
] ==>
41+
[ " \"with\\"
42+
, " \\ explicit\\"
43+
, " \\ indent\""
44+
]
3845
]
3946
where
4047
(==>) :: Stack.HasCallStack => [Text] -> [Text] -> Tasty.TestTree
41-
(==>) = test StringLiteral.addBackslashExplicit
48+
(==>) = test StringLiteral.addBackslashWrapped
4249

43-
-- I think I don't need to test removeBackslashExplicit because add ensures
50+
-- I think I don't need to test removeBackslashWrapped because add ensures
4451
-- it goes from reasonable input to the right haskell, and round trip will
4552
-- ensure it goes back to the original state.
46-
test_roundTrip :: Tasty.TestTree
47-
test_roundTrip = Tasty.testGroup "roundTrip"
48-
[ backslashExplicit [" one line"]
49-
, backslashExplicit [" two", " lines"]
53+
test_backslashWrapped_roundTrip :: Tasty.TestTree
54+
test_backslashWrapped_roundTrip = Tasty.testGroup "roundTrip"
55+
[ backslashWrapped [" one line"]
56+
, backslashWrapped [" two", " lines"]
57+
, backslashWrapped
58+
[ " with an"
59+
, " explicit"
60+
, ""
61+
, " newline"
62+
]
5063
]
5164
where
52-
backslashExplicit = trip
53-
StringLiteral.addBackslashExplicit StringLiteral.removeBackslashExplicit
65+
backslashWrapped = trip
66+
StringLiteral.addBackslashWrapped StringLiteral.removeBackslashWrapped
5467

5568
trip :: (Stack.HasCallStack, Show a, Eq a) => (a -> b) -> (b -> a) -> a
5669
-> Tasty.TestTree
@@ -60,17 +73,6 @@ test :: (Stack.HasCallStack, Show a, Eq b, Show b) => (a -> b) -> a -> b
6073
-> Tasty.TestTree
6174
test f x expected = HUnit.testCase (take 70 $ show x) $ f x HUnit.@?= expected
6275

63-
-- -- TODO real tests
64-
-- _test_addBackslashExplicit = Text.unlines $ addBackslashExplicit
65-
-- [ "this is"
66-
-- , "raw"
67-
-- , ""
68-
-- , "text"
69-
-- ]
70-
--
71-
-- _test_removeBackslashExplicit = Text.unlines $ removeBackslashExplicit $
72-
-- Text.lines _test_addBackslashExplicit
73-
--
7476
-- _test_addBackslash = Text.IO.putStr $ Text.unlines $ addBackslash
7577
-- [ " foo"
7678
-- , ""
@@ -92,3 +94,6 @@ test f x expected = HUnit.testCase (take 70 $ show x) $ f x HUnit.@?= expected
9294
-- [1, 2, 3] `compare` [1,2,2] @?= LT
9395
-- ]
9496

97+
98+
(===) :: (Eq a, Show a) => a -> a -> HUnit.Assertion
99+
(===) = (HUnit.@?=)

0 commit comments

Comments
 (0)