Skip to content

Commit

Permalink
Add colorprintf function
Browse files Browse the repository at this point in the history
  • Loading branch information
vbfox committed Oct 8, 2016
1 parent 9827067 commit 46fa554
Show file tree
Hide file tree
Showing 5 changed files with 186 additions and 119 deletions.
3 changes: 2 additions & 1 deletion src/BlackFox.ColoredPrintf/BlackFox.ColoredPrintf.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,10 @@
<ItemGroup>
<Compile Include="AssemblyInfo.fs" />
<Compile Include="ColorStrings.fs" />
<Compile Include="ColoredPrintf.fs" />
<Compile Include="ColoredWriter.fs" />
<None Include="paket.references" />
<None Include="paket.template" />
<Compile Include="ColoredPrintf.fs" />
</ItemGroup>
<ItemGroup>
<Reference Include="mscorlib" />
Expand Down
171 changes: 56 additions & 115 deletions src/BlackFox.ColoredPrintf/ColoredPrintf.fs
Original file line number Diff line number Diff line change
@@ -1,120 +1,61 @@
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module BlackFox.ColoredPrintf.ColoredWriter
[<AutoOpen>]
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
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<unit, string, 'Result>()

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))
117 changes: 117 additions & 0 deletions src/BlackFox.ColoredPrintf/ColoredWriter.fs
Original file line number Diff line number Diff line change
@@ -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
7 changes: 4 additions & 3 deletions src/TestApp/Program.fs
Original file line number Diff line number Diff line change
@@ -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

[<EntryPoint>]
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
7 changes: 7 additions & 0 deletions src/TestApp/TestApp.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@
<None Include="App.config" />
<None Include="paket.references" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\BlackFox.ColoredPrintf\BlackFox.ColoredPrintf.fsproj">
<Name>BlackFox.ColoredPrintf</Name>
<Project>{860e1cc6-a7f8-4bc2-9a6c-ebe93360ece1}</Project>
<Private>True</Private>
</ProjectReference>
</ItemGroup>
<PropertyGroup>
<MinimumVisualStudioVersion Condition="'$(MinimumVisualStudioVersion)' == ''">11</MinimumVisualStudioVersion>
</PropertyGroup>
Expand Down

0 comments on commit 46fa554

Please sign in to comment.