diff --git a/src/BlackFox.ColoredPrintf/BlackFox.ColoredPrintf.fsproj b/src/BlackFox.ColoredPrintf/BlackFox.ColoredPrintf.fsproj index 49078dd..1591cb0 100644 --- a/src/BlackFox.ColoredPrintf/BlackFox.ColoredPrintf.fsproj +++ b/src/BlackFox.ColoredPrintf/BlackFox.ColoredPrintf.fsproj @@ -57,9 +57,10 @@ - + + diff --git a/src/BlackFox.ColoredPrintf/ColoredPrintf.fs b/src/BlackFox.ColoredPrintf/ColoredPrintf.fs index c3db0ce..6397ba7 100644 --- a/src/BlackFox.ColoredPrintf/ColoredPrintf.fs +++ b/src/BlackFox.ColoredPrintf/ColoredPrintf.fs @@ -1,120 +1,61 @@ -[] -module BlackFox.ColoredPrintf.ColoredWriter +[] +[] +module BlackFox.ColoredPrintf.ColoredPrintf open System -open System.Text - -type IColoredPrinterEnv = - abstract Write : string -> unit - abstract Foreground : ConsoleColor with get,set - abstract Background : ConsoleColor with get,set - -type WriterStatus = | Normal | Foreground | Background | Escaping -type WriterState = { - mutable Colors: (ConsoleColor * ConsoleColor) list - mutable Status: WriterStatus - CurrentText: StringBuilder - mutable WipForeground: ConsoleColor option -} - -let getEmptyState (foreground: ConsoleColor) (background: ConsoleColor) = { - Colors = [foreground, background] - Status = WriterStatus.Normal - CurrentText = StringBuilder() - WipForeground = None -} - -module private StateHelpers = - open ColorStrings - let inline clearText (state: WriterState) = ignore(state.CurrentText.Clear()) - let inline appendChar (c: char) (state: WriterState) = ignore(state.CurrentText.Append(c)) - - let inline writeCurrentTextToEnv (env: IColoredPrinterEnv) (state: WriterState) = - if state.CurrentText.Length > 0 then - env.Write (state.CurrentText.ToString()) - state |> clearText - - let inline getColor (state: WriterState) = - let colorText = state.CurrentText.ToString() - state |> clearText - colorNameToColor colorText - -open StateHelpers - -let inline writeChar (env: IColoredPrinterEnv) (c: char) (state: WriterState) = - match state.Status with - | WriterStatus.Normal when c = '$' -> - writeCurrentTextToEnv env state - state.Status <- WriterStatus.Foreground - | WriterStatus.Normal when c = ']' -> - match state.Colors with - | [] -> failwith "Unexpected, no colors in stack" - | [_] -> state |> appendChar c - | (currentFg, currentBg) :: (previousFg, previousBg) :: rest -> - writeCurrentTextToEnv env state - state.Colors <- (previousFg, previousBg) :: rest - if currentFg <> previousFg then env.Foreground <- previousFg - if currentBg <> previousBg then env.Background <- previousBg - | WriterStatus.Normal when c = '\\' -> state.Status <- WriterStatus.Escaping - | WriterStatus.Normal -> state |> appendChar c - | WriterStatus.Escaping when c = '$' || c = ']' -> - state |> appendChar c - state.Status <- WriterStatus.Normal - | WriterStatus.Escaping -> - state |> appendChar '\\' - state |> appendChar c - state.Status <- WriterStatus.Normal - | WriterStatus.Foreground when c = ';' -> - match getColor state with - | Some c -> state.WipForeground <- Some c - | None -> () - state.Status <- WriterStatus.Background - | WriterStatus.Foreground when c = '[' -> - let (currentFg, currentBg) = state.Colors.Head - match getColor state with - | Some c -> - if currentFg <> c then env.Foreground <- c - state.Colors <- (c, currentBg) :: state.Colors - | None -> - state.Colors <- state.Colors.Head :: state.Colors - state.Status <- WriterStatus.Normal - | WriterStatus.Foreground -> state |> appendChar c - | WriterStatus.Background when c = '[' -> - let (currentFg, currentBg) = state.Colors.Head - - let fg = defaultArg state.WipForeground currentFg - let bg = defaultArg (getColor state) currentBg - - state.WipForeground <- None +open BlackFox.MasterOfFoo +open BlackFox.ColoredPrintf.ColoredWriter + +type private ConsoleColoredPrinterEnv() = + let mutable fg = ConsoleColor.White + let mutable bg = ConsoleColor.Black + let mutable colorDisabled = false + let wrap f = + if not colorDisabled then + try f() + with | _ -> colorDisabled <- true + + do + wrap (fun _ -> + fg <- Console.ForegroundColor + bg <- Console.BackgroundColor) + + interface ColoredWriter.IColoredPrinterEnv with + member __.Write (s: string) = Console.Write(s) + member __.Foreground + with get () = fg + and set c = + fg <- c + wrap(fun _ -> Console.ForegroundColor <- c) + member __.Background + with get () = bg + and set c = + bg <- c + wrap(fun _ -> Console.BackgroundColor <- c) + +type private ColoredConsolePrintEnv<'Result>(k) = + inherit PrintfEnv() + + let env = ConsoleColoredPrinterEnv() :> ColoredWriter.IColoredPrinterEnv + let state = getEmptyState env.Foreground env.Background + + override __.Finalize() : 'Result = + state |> finish env + k() + + override __.Write(s : PrintableElement) = + match s.ElementType with + | PrintableElementType.FromFormatSpecifier -> env.Write(s.FormatAsPrintF()) + | _ -> state |> writeString env (s.FormatAsPrintF()) + + override __.WriteT(s : string) = + env.Write(s) - if currentFg <> fg then env.Foreground <- fg - if currentBg <> bg then env.Background <- bg +type ColorPrintFormat<'T> = Format<'T, unit, string, unit> - state.Colors <- (fg, bg) :: state.Colors - state.Status <- WriterStatus.Normal - | WriterStatus.Background -> state |> appendChar c - -let inline writeString (env: IColoredPrinterEnv) (s: string) (state: WriterState) = - for i in 0..s.Length-1 do - state |> writeChar env (s.[i]) +let colorprintf<'T> (format: ColorPrintFormat<'T>) = + doPrintfFromEnv format (ColoredConsolePrintEnv(id)) -let inline finish (env: IColoredPrinterEnv) (state: WriterState) = - match state.Status with - | WriterStatus.Normal -> - writeCurrentTextToEnv env state - | WriterStatus.Escaping -> - state |> appendChar '\\' - writeCurrentTextToEnv env state - | WriterStatus.Foreground -> () - | WriterStatus.Background -> () - -let writeCompleteString (env: IColoredPrinterEnv) (s: string) = - let initialFg = env.Foreground - let initialBg = env.Background - - let state = getEmptyState initialFg initialBg - state |> writeString env s - - state |> finish env - if initialFg <> env.Foreground then env.Foreground <- initialFg - if initialBg <> env.Background then env.Background <- initialBg +let colorprintfn<'T> (format: ColorPrintFormat<'T>) = + let writeLine () = Console.WriteLine() + doPrintfFromEnv format (ColoredConsolePrintEnv(writeLine)) diff --git a/src/BlackFox.ColoredPrintf/ColoredWriter.fs b/src/BlackFox.ColoredPrintf/ColoredWriter.fs new file mode 100644 index 0000000..77d0ee8 --- /dev/null +++ b/src/BlackFox.ColoredPrintf/ColoredWriter.fs @@ -0,0 +1,117 @@ +module BlackFox.ColoredPrintf.ColoredWriter + +open System +open System.Text + +type IColoredPrinterEnv = + abstract Write : string -> unit + abstract Foreground : ConsoleColor with get,set + abstract Background : ConsoleColor with get,set + +type WriterStatus = | Normal | Foreground | Background | Escaping +type WriterState = { + mutable Colors: (ConsoleColor * ConsoleColor) list + mutable Status: WriterStatus + CurrentText: StringBuilder + mutable WipForeground: ConsoleColor option +} + +let getEmptyState (foreground: ConsoleColor) (background: ConsoleColor) = { + Colors = [foreground, background] + Status = WriterStatus.Normal + CurrentText = StringBuilder() + WipForeground = None +} + +module private StateHelpers = + open ColorStrings + let inline clearText (state: WriterState) = ignore(state.CurrentText.Clear()) + let inline appendChar (c: char) (state: WriterState) = ignore(state.CurrentText.Append(c)) + + let inline writeCurrentTextToEnv (env: IColoredPrinterEnv) (state: WriterState) = + if state.CurrentText.Length > 0 then + env.Write (state.CurrentText.ToString()) + state |> clearText + + let inline getColor (state: WriterState) = + let colorText = state.CurrentText.ToString() + state |> clearText + colorNameToColor colorText + +open StateHelpers + +let inline writeChar (env: IColoredPrinterEnv) (c: char) (state: WriterState) = + match state.Status with + | WriterStatus.Normal when c = '$' -> + writeCurrentTextToEnv env state + state.Status <- WriterStatus.Foreground + | WriterStatus.Normal when c = ']' -> + match state.Colors with + | [] -> failwith "Unexpected, no colors in stack" + | [_] -> state |> appendChar c + | (currentFg, currentBg) :: (previousFg, previousBg) :: rest -> + writeCurrentTextToEnv env state + state.Colors <- (previousFg, previousBg) :: rest + if currentFg <> previousFg then env.Foreground <- previousFg + if currentBg <> previousBg then env.Background <- previousBg + | WriterStatus.Normal when c = '\\' -> state.Status <- WriterStatus.Escaping + | WriterStatus.Normal -> state |> appendChar c + | WriterStatus.Escaping when c = '$' || c = ']' -> + state |> appendChar c + state.Status <- WriterStatus.Normal + | WriterStatus.Escaping -> + state |> appendChar '\\' + state |> appendChar c + state.Status <- WriterStatus.Normal + | WriterStatus.Foreground when c = ';' -> + match getColor state with + | Some c -> state.WipForeground <- Some c + | None -> () + state.Status <- WriterStatus.Background + | WriterStatus.Foreground when c = '[' -> + let (currentFg, currentBg) = state.Colors.Head + match getColor state with + | Some c -> + if currentFg <> c then env.Foreground <- c + state.Colors <- (c, currentBg) :: state.Colors + | None -> + state.Colors <- state.Colors.Head :: state.Colors + state.Status <- WriterStatus.Normal + | WriterStatus.Foreground -> state |> appendChar c + | WriterStatus.Background when c = '[' -> + let (currentFg, currentBg) = state.Colors.Head + + let fg = defaultArg state.WipForeground currentFg + let bg = defaultArg (getColor state) currentBg + + state.WipForeground <- None + + if currentFg <> fg then env.Foreground <- fg + if currentBg <> bg then env.Background <- bg + + state.Colors <- (fg, bg) :: state.Colors + state.Status <- WriterStatus.Normal + | WriterStatus.Background -> state |> appendChar c + +let inline writeString (env: IColoredPrinterEnv) (s: string) (state: WriterState) = + for i in 0..s.Length-1 do + state |> writeChar env (s.[i]) + +let inline finish (env: IColoredPrinterEnv) (state: WriterState) = + match state.Status with + | WriterStatus.Normal -> + writeCurrentTextToEnv env state + | WriterStatus.Escaping -> + state |> appendChar '\\' + writeCurrentTextToEnv env state + | WriterStatus.Foreground -> () + | WriterStatus.Background -> () + + let (initialFg, initialBg) = state.Colors |> List.last + if initialFg <> env.Foreground then env.Foreground <- initialFg + if initialBg <> env.Background then env.Background <- initialBg + +let writeCompleteString (env: IColoredPrinterEnv) (s: string) = + let state = getEmptyState (env.Foreground) (env.Background) + state |> writeString env s + state |> finish env \ No newline at end of file diff --git a/src/TestApp/Program.fs b/src/TestApp/Program.fs index 2bcf7f9..a533f14 100644 --- a/src/TestApp/Program.fs +++ b/src/TestApp/Program.fs @@ -1,7 +1,8 @@ -// Learn more about F# at http://fsharp.org -// See the 'F# Tutorial' project for more help. +open System +open BlackFox.ColoredPrintf [] let main argv = - printfn "%A" argv + colorprintfn "$white;blue[%s ]$black;white[%s ]$white;red[%s]" "La vie" "est" "belle" + ignore(Console.ReadLine()) 0 // return an integer exit code diff --git a/src/TestApp/TestApp.fsproj b/src/TestApp/TestApp.fsproj index dba095d..ed78392 100644 --- a/src/TestApp/TestApp.fsproj +++ b/src/TestApp/TestApp.fsproj @@ -52,6 +52,13 @@ + + + BlackFox.ColoredPrintf + {860e1cc6-a7f8-4bc2-9a6c-ebe93360ece1} + True + + 11