@@ -0,0 +1,379 @@
1+{-# OPTIONS_GHC -Wall #-}
2+{-# LANGUAGE OverloadedStrings #-}
3+module Reporting.Doc
4+ ( P.Doc
5+ , (P.<+>), (<>)
6+ , P.cat, P.empty, P.fillSep, P.hang, P.hcat, P.hsep, P.indent, P.sep, P.vcat
7+ , P.black, P.blue, P.green, P.magenta, P.dullyellow, P.red, P.dullred
8+
9+ , fromString, fromText, fromName
10+ , toAnsi, toString, toLine
11+ , encode
12+
13+ , stack, reflow, commaSep
14+ , toSimpleNote, toSimpleHint, toFancyHint
15+ , link, fancyLink, reflowLink, makeLink
16+ , args, moreArgs, ordinalize
17+ , cycle
18+ )
19+ where
20+
21+
22+import Prelude hiding (cycle)
23+import qualified Data.List as List
24+import Data.Monoid ((<>))
25+import qualified Data.Text as Text
26+import qualified Data.Text.Lazy as TL
27+import qualified Data.Text.Lazy.Builder as TB
28+import qualified Elm.Name as N
29+import qualified System.Console.ANSI.Types as Ansi
30+import System.IO (Handle)
31+import qualified Text.PrettyPrint.ANSI.Leijen as P
32+
33+import qualified Elm.Compiler.Version as Compiler
34+import qualified Elm.Package as Pkg
35+import qualified Json.Encode as E
36+
37+
38+
39+-- FROM
40+
41+
42+fromString :: String -> P.Doc
43+fromString =
44+ P.text
45+
46+
47+fromText :: Text.Text -> P.Doc
48+fromText txt =
49+ P.text (Text.unpack txt)
50+
51+
52+fromName :: N.Name -> P.Doc
53+fromName name =
54+ P.text (N.toString name)
55+
56+
57+
58+-- TO STRING
59+
60+
61+toAnsi :: Handle -> P.Doc -> IO ()
62+toAnsi handle doc =
63+ P.displayIO handle (P.renderPretty 1 80 doc)
64+
65+
66+toString :: P.Doc -> String
67+toString doc =
68+ P.displayS (P.renderPretty 1 80 doc) ""
69+
70+
71+toLine :: P.Doc -> String
72+toLine doc =
73+ P.displayS (P.renderPretty 1 (div maxBound 2) doc) ""
74+
75+
76+
77+-- FORMATTING
78+
79+
80+stack :: [P.Doc] -> P.Doc
81+stack docs =
82+ P.vcat (List.intersperse "" docs)
83+
84+
85+reflow :: String -> P.Doc
86+reflow paragraph =
87+ P.fillSep (map P.text (words paragraph))
88+
89+
90+commaSep :: P.Doc -> (P.Doc -> P.Doc) -> [P.Doc] -> [P.Doc]
91+commaSep conjunction addStyle names =
92+ case names of
93+ [name] ->
94+ [ addStyle name ]
95+
96+ [name1,name2] ->
97+ [ addStyle name1, conjunction, addStyle name2 ]
98+
99+ _ ->
100+ map (\name -> addStyle name <> ",") (init names)
101+ ++
102+ [ conjunction
103+ , addStyle (last names)
104+ ]
105+
106+
107+
108+-- HINTS
109+
110+
111+toSimpleNote :: String -> P.Doc
112+toSimpleNote message =
113+ P.fillSep ((P.underline "Note" <> ":") : map P.text (words message))
114+
115+
116+toSimpleHint :: String -> P.Doc
117+toSimpleHint message =
118+ toFancyHint (map P.text (words message))
119+
120+
121+toFancyHint :: [P.Doc] -> P.Doc
122+toFancyHint chunks =
123+ P.fillSep (P.underline "Hint" <> ":" : chunks)
124+
125+
126+
127+-- LINKS
128+
129+
130+link :: P.Doc -> String -> String -> String -> P.Doc
131+link word before fileName after =
132+ P.fillSep $
133+ (P.underline word <> ":")
134+ : map P.text (words before)
135+ ++ P.text (makeLink fileName)
136+ : map P.text (words after)
137+
138+
139+fancyLink :: P.Doc -> [P.Doc] -> String -> [P.Doc] -> P.Doc
140+fancyLink word before fileName after =
141+ P.fillSep $
142+ (P.underline word <> ":") : before ++ P.text (makeLink fileName) : after
143+
144+
145+makeLink :: String -> String
146+makeLink fileName =
147+ "<https://elm-lang.org/hints/" <> Pkg.versionToString Compiler.version <> "/" <> fileName <> ">"
148+
149+
150+reflowLink :: String -> String -> String -> P.Doc
151+reflowLink before fileName after =
152+ P.fillSep $
153+ map P.text (words before)
154+ ++ P.text (makeLink fileName)
155+ : map P.text (words after)
156+
157+
158+
159+-- HELPERS
160+
161+
162+args :: Int -> String
163+args n =
164+ show n <> if n == 1 then " argument" else " arguments"
165+
166+
167+moreArgs :: Int -> String
168+moreArgs n =
169+ show n <> " more" <> if n == 1 then " argument" else " arguments"
170+
171+
172+ordinalize :: Int -> String
173+ordinalize number =
174+ let
175+ remainder10 =
176+ number `mod` 10
177+
178+ remainder100 =
179+ number `mod` 100
180+
181+ ending
182+ | remainder100 `elem` [11..13] = "th"
183+ | remainder10 == 1 = "st"
184+ | remainder10 == 2 = "nd"
185+ | remainder10 == 3 = "rd"
186+ | otherwise = "th"
187+ in
188+ show number <> ending
189+
190+
191+
192+cycle :: Int -> [N.Name] -> P.Doc
193+cycle indent names =
194+ let
195+ topLine = "┌─────┐"
196+ nameLine name = "│ " <> P.dullyellow (fromName name)
197+ midLine = "│ ↓"
198+ bottomLine = "└─────┘"
199+ in
200+ P.indent indent $ P.vcat $
201+ topLine : List.intersperse midLine (map nameLine names) ++ [ bottomLine ]
202+
203+
204+
205+-- JSON
206+
207+
208+encode :: P.Doc -> E.Value
209+encode doc =
210+ E.array (toJsonHelp noStyle [] (P.renderPretty 1 80 doc))
211+
212+
213+data Style =
214+ Style
215+ { _bold :: Bool
216+ , _underline :: Bool
217+ , _color :: Maybe Color
218+ }
219+
220+
221+noStyle :: Style
222+noStyle =
223+ Style False False Nothing
224+
225+
226+data Color
227+ = D_Red
228+ | V_Red
229+ | D_Magenta
230+ | V_Magenta
231+ | D_Yellow
232+ | V_Yellow
233+ | D_Green
234+ | V_Green
235+ | D_Cyan
236+ | V_Cyan
237+ | D_Blue
238+ | V_Blue
239+ | D_Black
240+ | V_Black
241+ | D_White
242+ | V_White
243+
244+
245+toJsonHelp :: Style -> [TB.Builder] -> P.SimpleDoc -> [E.Value]
246+toJsonHelp style revChunks simpleDoc =
247+ case simpleDoc of
248+ P.SFail ->
249+ error $
250+ "according to the main implementation, @SFail@ can not\
251+ \ appear uncaught in a rendered @SimpleDoc@"
252+
253+ P.SEmpty ->
254+ [ encodeChunks style revChunks ]
255+
256+ P.SChar char rest ->
257+ toJsonHelp style (TB.singleton char : revChunks) rest
258+
259+ P.SText _ string rest ->
260+ toJsonHelp style (TB.fromString string : revChunks) rest
261+
262+ P.SLine indent rest ->
263+ toJsonHelp style (spaces indent : "\n" : revChunks) rest
264+
265+ P.SSGR sgrs rest ->
266+ encodeChunks style revChunks : toJsonHelp (sgrToStyle sgrs style) [] rest
267+
268+
269+spaces :: Int -> TB.Builder
270+spaces n =
271+ TB.fromText (Text.replicate n " ")
272+
273+
274+sgrToStyle :: [Ansi.SGR] -> Style -> Style
275+sgrToStyle sgrs style@(Style bold underline color) =
276+ case sgrs of
277+ [] ->
278+ style
279+
280+ sgr : rest ->
281+ sgrToStyle rest $
282+ case sgr of
283+ Ansi.Reset -> noStyle
284+ Ansi.SetConsoleIntensity i -> Style (isBold i) underline color
285+ Ansi.SetItalicized _ -> style
286+ Ansi.SetUnderlining u -> Style bold (isUnderline u) color
287+ Ansi.SetBlinkSpeed _ -> style
288+ Ansi.SetVisible _ -> style
289+ Ansi.SetSwapForegroundBackground _ -> style
290+ Ansi.SetColor l i c -> Style bold underline (toColor l i c)
291+ Ansi.SetRGBColor _ _ -> style
292+
293+
294+isBold :: Ansi.ConsoleIntensity -> Bool
295+isBold intensity =
296+ case intensity of
297+ Ansi.BoldIntensity -> True
298+ Ansi.FaintIntensity -> False
299+ Ansi.NormalIntensity -> False
300+
301+
302+isUnderline :: Ansi.Underlining -> Bool
303+isUnderline underlining =
304+ case underlining of
305+ Ansi.SingleUnderline -> True
306+ Ansi.DoubleUnderline -> False
307+ Ansi.NoUnderline -> False
308+
309+
310+toColor :: Ansi.ConsoleLayer -> Ansi.ColorIntensity -> Ansi.Color -> Maybe Color
311+toColor layer intensity color =
312+ case layer of
313+ Ansi.Background ->
314+ Nothing
315+
316+ Ansi.Foreground ->
317+ let
318+ pick dull vivid =
319+ case intensity of
320+ Ansi.Dull -> dull
321+ Ansi.Vivid -> vivid
322+ in
323+ Just $
324+ case color of
325+ Ansi.Red -> pick D_Red V_Red
326+ Ansi.Magenta -> pick D_Magenta V_Magenta
327+ Ansi.Yellow -> pick D_Yellow V_Yellow
328+ Ansi.Green -> pick D_Green V_Green
329+ Ansi.Cyan -> pick D_Cyan V_Cyan
330+ Ansi.Blue -> pick D_Blue V_Blue
331+ Ansi.White -> pick D_White V_White
332+ Ansi.Black -> pick D_Black V_Black
333+
334+
335+encodeChunks :: Style -> [TB.Builder] -> E.Value
336+encodeChunks (Style bold underline color) revChunks =
337+ let
338+ text =
339+ case revChunks of
340+ [] ->
341+ Text.empty
342+
343+ c:cs ->
344+ TL.toStrict $ TB.toLazyText $
345+ List.foldl' (\builder chunk -> chunk <> builder) c cs
346+ in
347+ case color of
348+ Nothing | not bold && not underline ->
349+ E.text text
350+
351+ _ ->
352+ E.object
353+ [ ("bold", E.bool bold)
354+ , ("underline", E.bool underline)
355+ , ("color", maybe E.null encodeColor color)
356+ , ("string", E.text text)
357+ ]
358+
359+
360+encodeColor :: Color -> E.Value
361+encodeColor color =
362+ E.text $
363+ case color of
364+ D_Red -> "red"
365+ V_Red -> "RED"
366+ D_Magenta -> "magenta"
367+ V_Magenta -> "MAGENTA"
368+ D_Yellow -> "yellow"
369+ V_Yellow -> "YELLOW"
370+ D_Green -> "green"
371+ V_Green -> "GREEN"
372+ D_Cyan -> "cyan"
373+ V_Cyan -> "CYAN"
374+ D_Blue -> "blue"
375+ V_Blue -> "BLUE"
376+ D_Black -> "black"
377+ V_Black -> "BLACK"
378+ D_White -> "white"
379+ V_White -> "WHITE"