-- Credit goes to wintvelt. -- Copy pasted from https://github.com/wintvelt/elm-print-any since it hasn't been updated for 0.18 module PrintAny exposing ( view, log , config, viewWithConfig ) {-| A tiny library for debugging purposes. It prints any record to the console or to the DOM. You can simply call `PrintAny.view myRecord` inside your `view` function, to print `myRecord` to the DOM. Or use `PrintAny.log myRecord` anywhere, to get a (somewhat) prettified version of the record in the console. _PS: You may not want to use this with large records. Performance is not optimal. This module iterates over a string version of the record, which may take long time._ # Basics @docs view, log # Advanced @docs config, viewWithConfig -} import Html exposing (Html, p, pre, text) import Html.Attributes exposing (class, style) import String {- Library constants -} type alias Constants = { quote : String , indentChars : String , outdentChars : String , newLineChars : String } constants : Constants constants = { quote = "\"" , indentChars = "[{(" , outdentChars = "}])" , newLineChars = "," } type Config = Config { increment : Int , className : String } {-| Custom configuration of output to DOM. With the `viewWithConfig` function, you can configure - `Int` indentation in pixels of individual lines - `String` class name for rendering the `
` wrapper
Usage:
`viewWithConfig (config 20 "debug-record") myRecord`
Prints `record` to the DOM with the wrapper provide with class "debug-record",
and each line indented with increments of 20px.
The classname allows you to style the wrapper as well as the children elements in css.
-}
config : Int -> String -> Config
config increment className =
Config
{ increment = increment
, className = className
}
{-| Default configuration
```Elm
defaultConfig =
{ increment = 20 -- 20 pixels indentation at each level
, className = "" -- no className supplied to wrapper
}```
```
-}
defaultConfig : Config
defaultConfig =
Config
{ increment = 20
, className = ""
}
{-| renders any record to the Dom.
Usage:
```Elm
view model =
div []
[ text model.whatEverYouWant
, PrintAny.view model
]
```
The output is a `` element.
Inside is a set of indented `` elements representing your record.
-}
view : a -> Html msg
view record =
viewWithConfig defaultConfig record
{-| renders any record to the Dom, with custom configuration.
Usage:
```Elm
view model =
div []
[ text model.whatEverYouWant
, PrintAny.viewWithConfig
PrintAny.config
20
"debug-record"
model
]
```
-}
viewWithConfig : Config -> a -> Html msg
viewWithConfig (Config config_) record =
let
lines =
record
|> Debug.toString
|> splitWithQuotes
|> splitUnquotedWithChars
|> List.concat
|> mergeQuoted
|> addIndents
in
pre
(if config_.className == "" then
[]
else
[ class config_.className ]
)
<|
List.map (viewLine <| Config config_) lines
{- render a single formatted line to DOM -}
viewLine : Config -> ( Int, String ) -> Html msg
viewLine (Config config_) ( indent, string ) =
p
[ style "paddingLeft" (px (indent * config_.increment))
, style "marginTop" "0px"
, style "marginBottom" "0px"
]
[ text string ]
{-| Prints a stylized version of any record to the DOM.
So if you have:
```Elm
record =
{ name = "Bill"
, friends = [ "Casey", "Dave", "Eve", "Fred" ]
, coordinates = ( 125, 33 )
}
```
Then `PrintAny.log record` will log to the console:
```Elm
{ name = "Bill"
, friends =
..[ "Casey"
.., "Dave"
.., "Eve"
.., "Fred"
..]
, coordinates =
..(125
..,33
..)
}
```
The function will output the original record passed, so you can do:
`myNewRecord = PrintAny.log { record | item = somethingNew }`
-}
log : a -> a
log record =
let
lines =
record
|> Debug.toString
|> splitWithQuotes
|> splitUnquotedWithChars
|> List.concat
|> mergeQuoted
|> addIndents
|> List.reverse
_ =
List.map logLine lines
in
record
{- print a single line to the console -}
logLine : ( Int, String ) -> ()
logLine ( indent, string ) =
let
logIndent =
String.padLeft indent ' ' ""
_ =
Debug.log "" <| logIndent ++ string
in
()
-- helpers
px : Int -> String
px int =
String.fromInt int
++ "px"
type alias IndentedString =
{ indentBefore : Int
, string : String
, indentAfter : Int
}
{- take list of strings and add indentation, based on the first character in each string -}
addIndents : List String -> List ( Int, String )
addIndents stringList =
stringList
|> List.foldl addIndent []
|> List.map (\r -> ( r.indentBefore, r.string ))
{- add indent to a single Item in a list -}
addIndent : String -> List IndentedString -> List IndentedString
addIndent string startList =
case List.reverse startList of
{ indentAfter } :: other ->
let
firstChar =
String.left 1 string
( newIndentBefore, newIndentAfter ) =
if String.contains firstChar constants.indentChars then
( indentAfter + 1, indentAfter + 1 )
else if String.contains firstChar constants.outdentChars then
( indentAfter, indentAfter - 1 )
else
( indentAfter, indentAfter )
in
startList
++ [ { indentBefore = newIndentBefore
, string = string
, indentAfter = newIndentAfter
}
]
[] ->
[ { indentBefore = 0
, string = string
, indentAfter = 0
}
]
{- If string is not in quotes, split based on characters,
otherwise return unsplit string in a list
-}
splitUnquotedWithChars : List String -> List (List String)
splitUnquotedWithChars stringList =
let
splitString string =
if String.left 1 string == constants.quote then
[ string ]
else
splitWithChars
(constants.indentChars ++ constants.newLineChars ++ constants.outdentChars)
string
in
List.map splitString stringList
{- split a string with each of a set of characters, keeping the characters used to split -}
splitWithChars : String -> String -> List String
splitWithChars splitters string =
case String.left 1 splitters of
"" ->
[ string ]
char ->
string
|> splitWithChar char
|> List.map (splitWithChars <| String.dropLeft 1 splitters)
|> List.concat
|> List.filter (\s -> s /= "")
{- split a string with a character, but keep the string or character used in splitting
So:
`splitWithChar "," "Apples, Bananas, Coconuts" == ["Apples", ", Bananas", ", Coconuts"]`
-}
splitWithChar : String -> String -> List String
splitWithChar splitter string =
String.split splitter string
|> List.indexedMap
(\ind str ->
if ind > 0 then
splitter ++ str
else
str
)
{- split a string with quoted parts, but keep quotes -}
splitWithQuotes : String -> List String
splitWithQuotes string =
String.split "\"" string
|> List.indexedMap
(\i str ->
if remainderBy 2 i == 1 then
"\"" ++ str ++ "\""
else
str
)
{- in a list of strings, add all quoted list items to the previous item in the list -}
mergeQuoted : List String -> List String
mergeQuoted =
List.foldl mergeOneQuote []
mergeOneQuote : String -> List String -> List String
mergeOneQuote string startList =
if String.left 1 string == constants.quote then
-- append the string to the last line in the list
case List.reverse startList of
x :: xs ->
((x ++ string) :: xs)
|> List.reverse
[] ->
[ string ]
else
-- simply add string as line to the list
startList ++ [ string ]
-- helpers
pad : Int -> String
pad indent =
String.padLeft 5 '0' <| String.fromInt indent
splitLine : String -> ( Int, String )
splitLine line =
let
indent =
String.left 5 line
|> String.toInt
|> Maybe.withDefault 0
newLine =
String.dropLeft 5 line
in
( indent, newLine )