From 72c04a69973a88b549f677085597da26f5a8fa31 Mon Sep 17 00:00:00 2001 From: Enrico Galassi Date: Mon, 6 Feb 2023 22:34:03 +0100 Subject: [PATCH] Refactor of CEs internal --- README.md | 8 +- Showcase.fsx | 25 ++-- .../Prompts/MultiSelectionPromptBuilder.fs | 85 +++++++++--- .../Prompts/SelectionPromptBuilder.fs | 50 +++++-- src/FsSpectre/Prompts/TextPromptBuilder.fs | 95 ++++++++++--- src/FsSpectre/Syle.fs | 39 ++++-- src/FsSpectre/Widgets/BarChartBuilder.fs | 67 ++++++--- .../Widgets/BreakdownChartBuilder.fs | 48 +++++-- src/FsSpectre/Widgets/CalendarBuilder.fs | 66 ++++----- src/FsSpectre/Widgets/CalendarEventBuilder.fs | 35 ++--- src/FsSpectre/Widgets/ColumnsBuilder.fs | 21 ++- src/FsSpectre/Widgets/GridBuilder.fs | 62 +++++++-- src/FsSpectre/Widgets/GridColumnBuilder.fs | 61 ++++++--- src/FsSpectre/Widgets/LayoutBuilder.fs | 50 ++++--- src/FsSpectre/Widgets/MarkupBuilder.fs | 37 ++++- src/FsSpectre/Widgets/PanelBuilder.fs | 64 ++++++--- src/FsSpectre/Widgets/PanelHeaderBuilder.fs | 31 ++++- src/FsSpectre/Widgets/RowsBuilder.fs | 24 +++- src/FsSpectre/Widgets/RuleBuilder.fs | 41 ++++-- src/FsSpectre/Widgets/TableBuilder.fs | 127 +++++++++++++----- src/FsSpectre/Widgets/TableColumnBuilder.fs | 100 ++++++++++---- src/FsSpectre/Widgets/TableTitleBuilder.fs | 19 ++- src/FsSpectre/Widgets/TextBuilder.fs | 36 +++-- src/FsSpectre/Widgets/TreeBuilder.fs | 43 +++--- src/FsSpectre/Widgets/TreeNodeBuilder.fs | 45 ++++--- 25 files changed, 923 insertions(+), 356 deletions(-) diff --git a/README.md b/README.md index cdf3c05..9b92fa1 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ With F# + FsSpectre: ```fsharp table { column_text "" - column (tableColumn { header "Feature"; centerd }) + column (tableColumn { header_text "Feature"; centerd }) row_text [| "Baz"; "[green]Qux[/]" |] row [| markup { text "[blue]Corgi[/]" }; panel { content "Waldo" } |] } |> AnsiConsole.Write @@ -68,9 +68,9 @@ barChart { width 60 label "[green bold underline]Number of fruits[/]" center_label - item "Apple" 12 Color.Yellow - item "Oranges" 54 Color.Green - item "Bananas" 33 Color.Red + item ("Apple", 12, Color.Yellow) + item ("Oranges", 54, Color.Green) + item ("Bananas", 33, Color.Red) } |> AnsiConsole.Write ``` diff --git a/Showcase.fsx b/Showcase.fsx index ca74271..5e12c4e 100644 --- a/Showcase.fsx +++ b/Showcase.fsx @@ -12,7 +12,7 @@ table { column ( tableColumn { - header "Feature" + header_text "Feature" no_wrap right_aligned width 10 @@ -22,7 +22,7 @@ table { column ( tableColumn { - header "Demonstration" + header_text "Demonstration" pad_right 0 } ) @@ -35,8 +35,8 @@ table { no_headers no_border - column (tableColumn { header "Desc"; pad_right 3 }) - column (tableColumn { header "Colors"; pad_right 0 }) + column (tableColumn { header_text "Desc"; pad_right 3 }) + column (tableColumn { header_text "Colors"; pad_right 0 }) row [| markup { text ("✓ [bold grey]NO_COLOR support[/]\n"+ @@ -111,8 +111,8 @@ table { simple_border border_color Color.Grey columns [| - tableColumn { header "Overview" } - tableColumn { footer "[grey]3 Files, 225 KiB[/]" } + tableColumn { header_text "Overview" } + tableColumn { footer_text "[grey]3 Files, 225 KiB[/]" } |] row [| @@ -149,7 +149,6 @@ table { row [| markup { text "[red]Tables and Trees[/]" } grid { - collapse number_of_columns 2 row [| panel { @@ -157,17 +156,17 @@ table { content_renderable (breakdownChart { show_percentage full_size - item "F#" 82 Color.Violet - item "PowerShell" 13 Color.Red - item "Bash" 5 Color.Blue + item ("F#", 82, Color.Violet) + item ("PowerShell", 13, Color.Red) + item ("Bash", 5, Color.Blue) }) } panel { border_color Color.Grey content_renderable (barChart { - item "Apple" 32 Color.Green - item "Oranges" 13 Color.Orange1 - item "Bananas" 22 Color.Yellow + item ("Apple", 32, Color.Green) + item ("Oranges", 13, Color.Orange1) + item ("Bananas", 22, Color.Yellow) }) } |] diff --git a/src/FsSpectre/Prompts/MultiSelectionPromptBuilder.fs b/src/FsSpectre/Prompts/MultiSelectionPromptBuilder.fs index ad45637..1b6fe43 100644 --- a/src/FsSpectre/Prompts/MultiSelectionPromptBuilder.fs +++ b/src/FsSpectre/Prompts/MultiSelectionPromptBuilder.fs @@ -1,47 +1,90 @@ namespace FsSpectre +open System +open System.Collections.Generic open Spectre.Console [] module MultiSelectionPromptBuilder = + type MulitSelectionPromptConfig<'T> = + { Title: string + Comparer: IEqualityComparer<'T> + Choices: ('T option * 'T array) array + PageSize: int + Required: bool + MoreChoicesText: string + InstructionsText: string + Converter: Option<'T -> string> } + + static member Default = + { Title = String.Empty + Comparer = EqualityComparer<'T>.Default + Choices = Array.empty<('T option * 'T array)> + PageSize = 10 + Required = true + MoreChoicesText = "[grey](Move up and down to reveal more choices)[/]" + InstructionsText = "[grey](Press to select, to accept)[/]" + Converter = None } + + type MultiSelectionPromptBuilder<'T>() = - member __.Yield _ = MultiSelectionPrompt<'T>() + member __.Yield _ = MulitSelectionPromptConfig<'T>.Default + + member __.Run(config: MulitSelectionPromptConfig<'T>) = + let result = MultiSelectionPrompt<'T>(config.Comparer) + result.Title <- config.Title + + config.Choices + |> Array.iter (fun (grpOpt, choices) -> + match grpOpt with + | Some grp -> result.AddChoiceGroup(grp, choices) |> ignore + | None -> result.AddChoices(choices) |> ignore) + + result.PageSize <- config.PageSize + result.Required <- config.Required + result.MoreChoicesText <- config.MoreChoicesText + result.InstructionsText <- config.InstructionsText + config.Converter |> Option.iter (fun c -> result.Converter <- c) + result [] - member __.Title(multiSelectionPrompt: MultiSelectionPrompt<'T>, title: string) = - multiSelectionPrompt.Title <- title - multiSelectionPrompt + member __.Title(config: MulitSelectionPromptConfig<'T>, title: string) = { config with Title = title } + + [] + member __.Title(config: MulitSelectionPromptConfig<'T>, comparer: IEqualityComparer<'T>) = + { config with Comparer = comparer } [] - member __.Choices(multiSelectionPrompt: MultiSelectionPrompt<'T>, choices: 'T array) = - multiSelectionPrompt.AddChoices(choices) + member __.Choices(config: MulitSelectionPromptConfig<'T>, choices: 'T array) = + { config with + Choices = Array.append config.Choices [| (None, choices) |] } [] - member __.ChoiceGroup(multiSelectionPrompt: MultiSelectionPrompt<'T>, choiceGroup: 'T, choices: 'T array) = - multiSelectionPrompt.AddChoiceGroup(choiceGroup, choices) + member __.ChoiceGroup(config: MulitSelectionPromptConfig<'T>, choiceGroup: 'T, choices: 'T array) = + { config with + Choices = Array.append config.Choices [| (Some choiceGroup, choices) |] } [] - member __.PageSize(multiSelectionPrompt: MultiSelectionPrompt<'T>) = multiSelectionPrompt.NotRequired() + member __.PageSize(config: MulitSelectionPromptConfig<'T>) = { config with Required = false } [] - member __.PageSize(multiSelectionPrompt: MultiSelectionPrompt<'T>, pageSize: int) = - multiSelectionPrompt.PageSize <- pageSize - multiSelectionPrompt + member __.PageSize(config: MulitSelectionPromptConfig<'T>, pageSize: int) = { config with PageSize = pageSize } [] - member __.MoreChoicesText(multiSelectionPrompt: MultiSelectionPrompt<'T>, moreChoicesText: string) = - multiSelectionPrompt.MoreChoicesText <- moreChoicesText - multiSelectionPrompt + member __.MoreChoicesText(config: MulitSelectionPromptConfig<'T>, moreChoicesText: string) = + { config with + MoreChoicesText = moreChoicesText } [] - member __.InstructionsText(multiSelectionPrompt: MultiSelectionPrompt<'T>, instructionsText: string) = - multiSelectionPrompt.InstructionsText <- instructionsText - multiSelectionPrompt + member __.InstructionsText(config: MulitSelectionPromptConfig<'T>, instructionsText: string) = + { config with + InstructionsText = instructionsText } [] - member __.Converter(multiSelectionPrompt: MultiSelectionPrompt<'T>, converter: 'T -> string) = - multiSelectionPrompt.Converter <- converter - multiSelectionPrompt + member __.Converter(config: MulitSelectionPromptConfig<'T>, converter: 'T -> string) = + { config with + Converter = Some converter } + let multiSelectionPrompt<'T> = MultiSelectionPromptBuilder<'T>() diff --git a/src/FsSpectre/Prompts/SelectionPromptBuilder.fs b/src/FsSpectre/Prompts/SelectionPromptBuilder.fs index 22e8ee8..ec7b537 100644 --- a/src/FsSpectre/Prompts/SelectionPromptBuilder.fs +++ b/src/FsSpectre/Prompts/SelectionPromptBuilder.fs @@ -1,34 +1,56 @@ namespace FsSpectre +open System open Spectre.Console [] module SelectionPromptBuilder = + type SelectionPromptConfig<'T> = + { Title: string + Choices: 'T array + PageSize: int + MoreChoicesText: string + Converter: Option<'T -> string> } + + static member Default = + { Title = String.Empty + Choices = Array.empty<'T> + PageSize = 10 + MoreChoicesText = "[grey](Move up and down to reveal more choices)[/]" + Converter = None } + type SelectionPromptBuilder<'T>() = - member __.Yield _ = SelectionPrompt<'T>() + member __.Yield _ = SelectionPromptConfig<'T>.Default + + member __.Run(config: SelectionPromptConfig<'T>) = + let result = SelectionPrompt<'T>() + result.Title <- config.Title + result.AddChoices(config.Choices) |> ignore + result.PageSize <- config.PageSize + result.MoreChoicesText <- config.MoreChoicesText + config.Converter |> Option.iter (fun c -> result.Converter <- c) + result [] - member __.Title(selectionPrompt: SelectionPrompt<'T>, title: string) = - selectionPrompt.Title <- title - selectionPrompt + member __.Title(config: SelectionPromptConfig<'T>, title: string) = { config with Title = title } [] - member __.Choices(selectionPrompt: SelectionPrompt<'T>, choices: 'T array) = selectionPrompt.AddChoices(choices) + member __.Choices(config: SelectionPromptConfig<'T>, choices: 'T array) = + { config with + Choices = Array.append config.Choices choices } [] - member __.PageSize(selectionPrompt: SelectionPrompt<'T>, pageSize: int) = - selectionPrompt.PageSize <- pageSize - selectionPrompt + member __.PageSize(config: SelectionPromptConfig<'T>, pageSize: int) = { config with PageSize = pageSize } [] - member __.MoreChoicesText(selectionPrompt: SelectionPrompt<'T>, moreChoicesText: string) = - selectionPrompt.MoreChoicesText <- moreChoicesText - selectionPrompt + member __.MoreChoicesText(config: SelectionPromptConfig<'T>, moreChoicesText: string) = + { config with + MoreChoicesText = moreChoicesText } [] - member __.Converter(selectionPrompt: SelectionPrompt<'T>, converter: 'T -> string) = - selectionPrompt.Converter <- converter - selectionPrompt + member __.Converter(config: SelectionPromptConfig<'T>, converter: 'T -> string) = + { config with + Converter = Some converter } let selectionPrompt<'T> = SelectionPromptBuilder<'T>() diff --git a/src/FsSpectre/Prompts/TextPromptBuilder.fs b/src/FsSpectre/Prompts/TextPromptBuilder.fs index 56859f9..3daa7b1 100644 --- a/src/FsSpectre/Prompts/TextPromptBuilder.fs +++ b/src/FsSpectre/Prompts/TextPromptBuilder.fs @@ -6,39 +6,92 @@ open Spectre.Console [] module TextPromptBuilder = + type TextPromptConfig<'T> = + { Text: string + Comparer: StringComparer option + PromptStyle: Style + IsSecret: bool + Mask: char option + HasHiddenInput: bool + AllowEmpty: bool + ValidationErrorMessage: string + Validator: Option<'T -> ValidationResult> + Converter: Option<'T -> string> } + + static member Default = + { Text = String.Empty + Comparer = None + PromptStyle = Style.Plain + IsSecret = false + Mask = None + HasHiddenInput = false + AllowEmpty = false + ValidationErrorMessage = "[red]Invalid input[/]" + Validator = None + Converter = None } + type TextPromptBuilder<'T>() = - member __.Yield _ = TextPrompt<'T>(String.Empty) + member __.Yield _ = TextPromptConfig<'T>.Default + + member __.Run(config: TextPromptConfig<'T>) = + let result = TextPrompt<'T>(config.Text, Option.toObj config.Comparer) + result.IsSecret <- config.IsSecret + + if config.HasHiddenInput then + result.Mask <- Nullable() + else + config.Mask |> Option.iter (fun m -> result.Mask <- m) + + result.AllowEmpty <- config.AllowEmpty + result.ValidationErrorMessage <- config.ValidationErrorMessage + config.Validator |> Option.iter (fun v -> result.Validator <- v) + config.Converter |> Option.iter (fun c -> result.Converter <- c) + + result [] - member __.Title(_, text: string) = TextPrompt<'T>(text) + member __.Text(config: TextPromptConfig<'T>, text: string) = { config with Text = text } + + [] + member __.Comparer(config: TextPromptConfig<'T>, comparer: StringComparer) = + { config with Comparer = Some comparer } [] - member __.PromptStyle(textPrompt: TextPrompt<'T>, style: Style) = - textPrompt.PromptStyle <- style - textPrompt + member __.PromptStyle(config: TextPromptConfig<'T>, style: Style) = { config with PromptStyle = style } [] - member __.Secret(textPrompt: TextPrompt<'T>) = textPrompt.Secret() + member __.Secret(config: TextPromptConfig<'T>) = { config with IsSecret = true } - [] - member __.Mask(textPrompt: TextPrompt<'T>, mask: char) = textPrompt.Secret(mask) + [] + member __.SecretWithMask(config: TextPromptConfig<'T>, mask: char) = + { config with + IsSecret = true + Mask = Some mask + HasHiddenInput = false } - [] - member __.HideInput(textPrompt: TextPrompt<'T>) = textPrompt.Secret(Nullable()) + [] + member __.SecretWithHiddenInput(config: TextPromptConfig<'T>) = + { config with + IsSecret = true + Mask = None + HasHiddenInput = true } [] - member __.AllowEmpty(textPrompt: TextPrompt<'T>) = - textPrompt.AllowEmpty <- true - textPrompt + member __.AllowEmpty(config: TextPromptConfig<'T>) = { config with AllowEmpty = true } [] - member __.ValidationErrorMessage(textPrompt: TextPrompt<'T>, validationErrorMessage: string) = - textPrompt.ValidationErrorMessage <- validationErrorMessage - textPrompt - - [] - member __.Validate(textPrompt: TextPrompt<'T>, validator: 'T -> ValidationResult) = - textPrompt.Validator <- validator - textPrompt + member __.ValidationErrorMessage(config: TextPromptConfig<'T>, validationErrorMessage: string) = + { config with + ValidationErrorMessage = validationErrorMessage } + + [] + member __.Validator(config: TextPromptConfig<'T>, validator: 'T -> ValidationResult) = + { config with + Validator = Some validator } + + [] + member __.Converter(config: TextPromptConfig<'T>, converter: 'T -> string) = + { config with + Converter = Some converter } let textPrompt<'T> = TextPromptBuilder<'T>() diff --git a/src/FsSpectre/Syle.fs b/src/FsSpectre/Syle.fs index c098daf..c6ce702 100644 --- a/src/FsSpectre/Syle.fs +++ b/src/FsSpectre/Syle.fs @@ -6,23 +6,44 @@ open Spectre.Console [] module StyleBuilder = + type StyleConfig = + { Foreground: Color + Background: Color + Decoration: Decoration + Link: string option } + + static member Default = + { Foreground = Color.Default + Background = Color.Default + Decoration = Decoration.None + Link = None } + type StyleBuilder() = - member __.Yield _ = Style() + member __.Yield _ = StyleConfig.Default + + member __.Run(config: StyleConfig) = + Style(config.Foreground, config.Background, config.Decoration, Option.toObj config.Link) + + [] + member __.Plain(config: StyleConfig) = + let plain = Style.Plain + + { config with + Foreground = plain.Foreground + Background = plain.Background + Decoration = plain.Decoration + Link = None } [] - member __.Foreground(style: Style, foreground: Color) = - Style(foreground, style.Background, style.Decoration, style.Link) + member __.Foreground(config: StyleConfig, foreground: Color) = { config with Foreground = foreground } [] - member __.Background(style: Style, background: Color) = - Style(style.Foreground, background, style.Decoration, style.Link) + member __.Background(config: StyleConfig, background: Color) = { config with Background = background } [] - member __.Decoration(style: Style, decoration: Decoration) = - Style(style.Foreground, style.Background, decoration, style.Link) + member __.Decoration(config: StyleConfig, decoration: Decoration) = { config with Decoration = decoration } [] - member __.Link(style: Style, link: string) = - Style(style.Foreground, style.Background, style.Decoration, link) + member __.Link(config: StyleConfig, link: string) = { config with Link = Some link } let style = StyleBuilder() diff --git a/src/FsSpectre/Widgets/BarChartBuilder.fs b/src/FsSpectre/Widgets/BarChartBuilder.fs index 770fb56..6f51900 100644 --- a/src/FsSpectre/Widgets/BarChartBuilder.fs +++ b/src/FsSpectre/Widgets/BarChartBuilder.fs @@ -1,41 +1,72 @@ namespace FsSpectre +open System open Spectre.Console [] module BarChartBuilder = + type BarChartConfig = + { Label: string + LabelAlignment: Justify + Width: int option + ShowValues: bool + Items: (string * float * Color) array } + + static member Default = + { Label = String.Empty + LabelAlignment = Justify.Center + Width = None + ShowValues = true + Items = Array.empty<(string * float * Color)> } + type BarChartBuilder() = - member __.Yield _ = BarChart() + member __.Yield _ = BarChartConfig.Default + + member __.Run(config: BarChartConfig) = + let result = BarChart() + result.Label <- config.Label + result.LabelAlignment <- config.LabelAlignment + config.Width |> Option.iter (fun w -> result.Width <- w) + result.ShowValues <- config.ShowValues + + config.Items + |> Array.iter (fun (label, value, color) -> result.AddItem(label, value, color) |> ignore) + + result [] - member __.FullSize(barChart: BarChart, label: string) = - barChart.Label <- label - barChart + member __.FullSize(config: BarChartConfig, label: string) = { config with Label = label } - [] - member __.LeftAlignLabel(barChart: BarChart) = barChart.LeftAlignLabel() + [] + member __.LeftAlignedLabel(config: BarChartConfig) = + { config with + LabelAlignment = Justify.Left } - [] - member __.CenterLabel(barChart: BarChart) = barChart.CenterLabel() + [] + member __.CenteredLabel(config: BarChartConfig) = + { config with + LabelAlignment = Justify.Center } - [] - member __.RightAlignLabel(barChart: BarChart) = barChart.RightAlignLabel() + [] + member __.RightAlignedLabel(config: BarChartConfig) = + { config with + LabelAlignment = Justify.Right } [] - member __.Width(barChart: BarChart, width: int) = - barChart.Width <- width - barChart + member __.Width(config: BarChartConfig, width: int) = { config with Width = Some width } [] - member __.HideValues(barChart: BarChart) = barChart.HideValues() + member __.HideValues(config: BarChartConfig) = { config with ShowValues = false } [] - member __.Item(barChart: BarChart, label: string, value: float, color: Color) = - barChart.AddItem(label, value, color) + member __.Item(config: BarChartConfig, item: (string * float * Color)) = + { config with + Items = Array.append config.Items [| item |] } [] - member __.Items(barChart: BarChart, elements: 'T array, converter: 'T -> BarChartItem) = - barChart.AddItems(elements, converter) + member __.Items(config: BarChartConfig, items: 'T array, converter: 'T -> (string * float * Color)) = + { config with + Items = items |> Array.map converter |> Array.append config.Items } let barChart = BarChartBuilder() diff --git a/src/FsSpectre/Widgets/BreakdownChartBuilder.fs b/src/FsSpectre/Widgets/BreakdownChartBuilder.fs index 946036b..de92309 100644 --- a/src/FsSpectre/Widgets/BreakdownChartBuilder.fs +++ b/src/FsSpectre/Widgets/BreakdownChartBuilder.fs @@ -5,27 +5,57 @@ open Spectre.Console [] module BreakdownChartBuilder = + type BreakdownChartConfig = + { Compact: bool + ShowPercentage: bool + ShowTags: bool + ShowTagValues: bool + Items: (string * float * Color) array } + + static member Default = + { Compact = true + ShowPercentage = false + ShowTags = true + ShowTagValues = true + Items = Array.empty<(string * float * Color)> } + type BreakdownChartBuilder() = - member __.Yield _ = BreakdownChart() + member __.Yield _ = BreakdownChartConfig.Default + + member __.Run(config: BreakdownChartConfig) = + let result = BreakdownChart() + result.Compact <- config.Compact + result.ShowTags <- config.ShowTags + result.ShowTagValues <- config.ShowTagValues + + if config.ShowPercentage then + result.ShowPercentage() |> ignore + + config.Items + |> Array.iter (fun (label, value, color) -> result.AddItem(label, value, color) |> ignore) + + result [] - member __.FullSize(breakdownChart: BreakdownChart) = breakdownChart.FullSize() + member __.FullSize(config: BreakdownChartConfig) = { config with Compact = false } [] - member __.ShowPercentage(breakdownChart: BreakdownChart) = breakdownChart.ShowPercentage() + member __.ShowPercentage(config: BreakdownChartConfig) = { config with ShowPercentage = true } [] - member __.HideTags(breakdownChart: BreakdownChart) = breakdownChart.HideTags() + member __.HideTags(config: BreakdownChartConfig) = { config with ShowTags = false } [] - member __.HideTagValues(breakdownChart: BreakdownChart) = breakdownChart.HideTagValues() + member __.HideTagValues(config: BreakdownChartConfig) = { config with ShowTagValues = false } [] - member __.Item(breakdownChart: BreakdownChart, label: string, value: float, color: Color) = - breakdownChart.AddItem(label, value, color) + member __.Item(config: BreakdownChartConfig, item: (string * float * Color)) = + { config with + Items = Array.append config.Items [| item |] } [] - member __.Items(breakdownChart: BreakdownChart, elements: 'T array, converter: 'T -> IBreakdownChartItem) = - breakdownChart.AddItems(elements, converter) + member __.Items(config: BreakdownChartConfig, items: 'T array, converter: 'T -> (string * float * Color)) = + { config with + Items = items |> Array.map converter |> Array.append config.Items } let breakdownChart = BreakdownChartBuilder() diff --git a/src/FsSpectre/Widgets/CalendarBuilder.fs b/src/FsSpectre/Widgets/CalendarBuilder.fs index c44db6e..7008dee 100644 --- a/src/FsSpectre/Widgets/CalendarBuilder.fs +++ b/src/FsSpectre/Widgets/CalendarBuilder.fs @@ -7,46 +7,52 @@ open Spectre.Console [] module CalendarBuilder = - type CalendarBuilder() = - member __.Yield _ = Calendar(DateTime.Now) - - [] - member __.Default(calendar: Calendar) = calendar + type CalendarConfig = + { DateTime: DateTime + CultureInfo: CultureInfo + ShowHeader: bool + HeaderStyle: Style + HighlightStyle: Style + Events: CalendarEvent array } + + static member Default(dateTime: DateTime) = + { DateTime = dateTime + CultureInfo = CultureInfo.CurrentCulture + ShowHeader = true + HeaderStyle = Style.Plain + HighlightStyle = Style.Plain + Events = Array.empty } - [] - member __.Year(calendar: Calendar, year: int) = - calendar.Year <- year - calendar + type CalendarBuilder() = + member __.Yield _ = CalendarConfig.Default(DateTime.Now) - [] - member __.Month(calendar: Calendar, month: int) = - calendar.Month <- month - calendar + member __.Run(config: CalendarConfig) = + let result = Calendar(config.DateTime) + result.Culture <- config.CultureInfo + result.HeaderStyle <- config.HeaderStyle + result.HightlightStyle <- config.HighlightStyle + config.Events |> Array.map result.CalendarEvents.Add |> ignore + result - [] - member __.Day(calendar: Calendar, day: int) = - calendar.Day <- day - calendar + [] + member __.Default(config: CalendarConfig) = config [] - member __.DateTime(_, dateTime: DateTime) = Calendar(dateTime) + member __.DateTime(config: CalendarConfig, dateTime: DateTime) = { config with DateTime = dateTime } [] - member __.Culture(calendar: Calendar, cultureInfo: CultureInfo) = - calendar.Culture <- cultureInfo - calendar + member __.Culture(config: CalendarConfig, cultureInfo: CultureInfo) = + { config with + CultureInfo = cultureInfo } [] - member __.HideHeader(calendar: Calendar) = calendar.HideHeader() - - [] - member __.Event(calendar: Calendar, event: CalendarEvent) = - calendar.CalendarEvents.Add(event) - calendar + member __.HideHeader(config: CalendarConfig) = + { config with + ShowHeader = false } [] - member __.Events(calendar: Calendar, events: CalendarEvent array) = - events |> Array.map calendar.CalendarEvents.Add |> ignore - calendar + member __.Events(config: CalendarConfig, events: CalendarEvent array) = + { config with + Events = Array.append config.Events events } let calendar = CalendarBuilder() diff --git a/src/FsSpectre/Widgets/CalendarEventBuilder.fs b/src/FsSpectre/Widgets/CalendarEventBuilder.fs index 77a7ee7..45926de 100644 --- a/src/FsSpectre/Widgets/CalendarEventBuilder.fs +++ b/src/FsSpectre/Widgets/CalendarEventBuilder.fs @@ -6,29 +6,30 @@ open Spectre.Console [] module CalendarEventBuilder = + type CalendarEventConfig = + { DateTime: DateTime + Description: string } + + static member Default(dateTime: DateTime) = + { DateTime = dateTime + Description = String.Empty } + type CalendarEventBuilder() = member __.Yield _ = - let now = DateTime.Now - CalendarEvent(now.Year, now.Month, now.Day) - - [] - member __.Year(calendarEvent: CalendarEvent, year: int) = - CalendarEvent(calendarEvent.Description, year, calendarEvent.Month, calendarEvent.Day) + CalendarEventConfig.Default(DateTime.Now) - [] - member __.Month(calendarEvent: CalendarEvent, month: int) = - CalendarEvent(calendarEvent.Description, calendarEvent.Year, month, calendarEvent.Day) + member __.Run(config: CalendarEventConfig) = + CalendarEvent(config.Description, config.DateTime.Year, config.DateTime.Month, config.DateTime.Day) - [] - member __.Day(calendarEvent: CalendarEvent, day: int) = - CalendarEvent(calendarEvent.Description, calendarEvent.Year, calendarEvent.Month, day) + [] + member __.Default(config: CalendarEventConfig) = config - [] - member __.Year(calendarEvent: CalendarEvent, year: int, month: int, day: int) = - CalendarEvent(calendarEvent.Description, year, month, day) + [] + member __.Year(config: CalendarEventConfig, dateTime: DateTime) = { config with DateTime = dateTime } [] - member __.Description(calendarEvent: CalendarEvent, description: string) = - CalendarEvent(description, calendarEvent.Year, calendarEvent.Month, calendarEvent.Day) + member __.Description(config: CalendarEventConfig, description: string) = + { config with + Description = description } let calendarEvent = CalendarEventBuilder() diff --git a/src/FsSpectre/Widgets/ColumnsBuilder.fs b/src/FsSpectre/Widgets/ColumnsBuilder.fs index 7059afd..7b4915d 100644 --- a/src/FsSpectre/Widgets/ColumnsBuilder.fs +++ b/src/FsSpectre/Widgets/ColumnsBuilder.fs @@ -6,13 +6,26 @@ open Spectre.Console.Rendering [] module ColumnsBuilder = + type ColumnsConfig = + { Items: IRenderable array } + + static member Default = { Items = Array.empty } + type ColumnsBuilder() = - member __.Yield _ = Columns(Array.empty) + member __.Yield _ = ColumnsConfig.Default + + member __.Run(config: ColumnsConfig) = Columns(config.Items) + + [] + member __.ItemsText(config: ColumnsConfig, items: string array) = + let markups = items |> Array.map Markup |> Array.map (fun x -> x :> IRenderable) - [] - member __.Items(_, items: string array) = Columns(items) + { config with + Items = Array.append config.Items markups } [] - member __.ItemsRenderable(_, items: IRenderable array) = Columns(items) + member __.ItemsRenderable(config: ColumnsConfig, items: IRenderable array) = + { config with + Items = Array.append config.Items items } let columns = ColumnsBuilder() diff --git a/src/FsSpectre/Widgets/GridBuilder.fs b/src/FsSpectre/Widgets/GridBuilder.fs index 4913315..bdb309a 100644 --- a/src/FsSpectre/Widgets/GridBuilder.fs +++ b/src/FsSpectre/Widgets/GridBuilder.fs @@ -6,27 +6,67 @@ open Spectre.Console.Rendering [] module GridBuilder = + type GridConfig = + { Expand: bool + ExplicitNumberOfColumns: int option + Columns: GridColumn array + Rows: IRenderable array array } + + static member Default = + { Expand = false + ExplicitNumberOfColumns = None + Columns = Array.empty + Rows = Array.empty } + type GridBuilder() = - member __.Yield _ = Grid() + member __.Yield _ = GridConfig.Default - [] - member __.Expand(grid: Grid) = - grid.Expand <- true - grid + member __.Run(config: GridConfig) = + let result = Grid() + result.Expand <- config.Expand - [] - member __.Collapse(grid: Grid) = grid.Collapse() + if Option.isSome config.ExplicitNumberOfColumns then + config.ExplicitNumberOfColumns + |> Option.iter (fun n -> result.AddColumns(n) |> ignore) + else + result.AddColumns(config.Columns) |> ignore + + config.Rows |> Array.iter (fun row -> result.AddRow(row) |> ignore) + result + + [] + member __.Expand(config: GridConfig) = { config with Expand = true } [] - member __.NodeText(grid: Grid, count: int) = grid.AddColumns(count) + member __.NodeText(config: GridConfig, count: int) = + { config with + ExplicitNumberOfColumns = Some count } + + [] + member __.Column(config: GridConfig) = + { config with + Columns = Array.append config.Columns [| GridColumn() |] } [] - member __.Column(grid: Grid, column: GridColumn) = grid.AddColumn(column) + member __.Column(config: GridConfig, column: GridColumn) = + { config with + Columns = Array.append config.Columns [| column |] } + + [] + member __.Row(config: GridConfig) = + { config with + Rows = Array.append config.Rows [| [||] |] } [] - member __.Row(grid: Grid, columns: IRenderable array) = grid.AddRow(columns) + member __.Row(config: GridConfig, columns: IRenderable array) = + { config with + Rows = Array.append config.Rows [| columns |] } [] - member __.RowText(grid: Grid, columns: string array) = grid.AddRow(columns) + member __.RowText(config: GridConfig, columns: string array) = + let markups = columns |> Array.map Markup |> Array.map (fun x -> x :> IRenderable) + + { config with + Rows = Array.append config.Rows [| markups |] } let grid = GridBuilder() diff --git a/src/FsSpectre/Widgets/GridColumnBuilder.fs b/src/FsSpectre/Widgets/GridColumnBuilder.fs index 56013cd..bb612ce 100644 --- a/src/FsSpectre/Widgets/GridColumnBuilder.fs +++ b/src/FsSpectre/Widgets/GridColumnBuilder.fs @@ -5,38 +5,67 @@ open Spectre.Console [] module GridColumnBuilder = + type GridColumnConfig = + { NoWrap: bool + Alignment: Justify + Width: int option + Padding: Padding } + + static member Default = + { NoWrap = false + Alignment = Justify.Left + Width = None + Padding = Padding(0, 0, 0, 0) } + type GridColumnBuilder() = - member __.Yield _ = GridColumn() + member __.Yield _ = GridColumnConfig.Default + + member __.Run(config: GridColumnConfig) = + let result = GridColumn() + result.NoWrap <- config.NoWrap + result.Alignment <- config.Alignment + config.Width |> Option.iter (fun w -> result.Width <- w) + result.Padding <- config.Padding + result [] - member __.NoWrap(gridColumn: GridColumn) = - gridColumn.NoWrap <- true - gridColumn + member __.NoWrap(config: GridColumnConfig) = { config with NoWrap = true } [] - member __.LeftAligned(gridColumn: GridColumn) = gridColumn.LeftAligned() + member __.LeftAligned(config: GridColumnConfig) = + { config with Alignment = Justify.Left } [] - member __.Centered(gridColumn: GridColumn) = gridColumn.Centered() + member __.Centered(config: GridColumnConfig) = + { config with + Alignment = Justify.Center } [] - member __.RightAligned(gridColumn: GridColumn) = gridColumn.RightAligned() + member __.RightAligned(config: GridColumnConfig) = + { config with + Alignment = Justify.Right } [] - member __.Width(gridColumn: GridColumn, width: int) = - gridColumn.Width <- width - gridColumn - - [] - member __.PadRight(gridColumn: GridColumn, pad: int) = gridColumn.PadRight(pad) + member __.Width(config: GridColumnConfig, width: int) = { config with Width = Some width } [] - member __.PadLeft(gridColumn: GridColumn, pad: int) = gridColumn.PadLeft(pad) + member __.PadLeft(config: GridColumnConfig, pad: int) = + { config with + Padding = Padding(pad, config.Padding.Top, config.Padding.Right, config.Padding.Bottom) } [] - member __.PadTop(gridColumn: GridColumn, pad: int) = gridColumn.PadTop(pad) + member __.PadTop(config: GridColumnConfig, pad: int) = + { config with + Padding = Padding(config.Padding.Left, pad, config.Padding.Right, config.Padding.Bottom) } + + [] + member __.PadRight(config: GridColumnConfig, pad: int) = + { config with + Padding = Padding(config.Padding.Left, config.Padding.Top, pad, config.Padding.Bottom) } [] - member __.PadBottom(gridColumn: GridColumn, pad: int) = gridColumn.PadBottom(pad) + member __.PadBottom(config: GridColumnConfig, pad: int) = + { config with + Padding = Padding(config.Padding.Left, config.Padding.Top, config.Padding.Right, pad) } let gridColumn = GridColumnBuilder() diff --git a/src/FsSpectre/Widgets/LayoutBuilder.fs b/src/FsSpectre/Widgets/LayoutBuilder.fs index 0edf6e1..a4fbe50 100644 --- a/src/FsSpectre/Widgets/LayoutBuilder.fs +++ b/src/FsSpectre/Widgets/LayoutBuilder.fs @@ -1,39 +1,57 @@ namespace FsSpectre open System -open System.Globalization open Spectre.Console open Spectre.Console.Rendering [] module LayoutBuilder = + type LayoutConfig = + { Name: string + SplitColumns: Layout array + SplitRows: Layout array + Ratio: int + Width: int option + MinimumWidth: int option + Content: IRenderable option } + + static member Default = + { Name = String.Empty + SplitColumns = Array.empty + SplitRows = Array.empty + Ratio = 1 + Width = None + MinimumWidth = None + Content = None } + type LayoutBuilder() = - member __.Yield _ = Layout(String.Empty) + member __.Yield _ = LayoutConfig.Default + + member __.Run(config: LayoutConfig) = + let result = Layout(config.Name, Option.toObj config.Content) + config.Width |> Option.iter (fun w -> result.Size <- (Nullable w)) + config.MinimumWidth |> Option.iter (fun w -> result.MinimumSize <- w) + result.SplitColumns(config.SplitColumns).SplitRows(config.SplitRows) [] - member __.SplitColumns(layout: Layout, columns: Layout array) = layout.SplitColumns(columns) + member __.SplitColumns(config: LayoutConfig, columns: Layout array) = { config with SplitColumns = columns } [] - member __.SplitRows(layout: Layout, rows: Layout array) = layout.SplitRows(rows) + member __.SplitRows(config: LayoutConfig, rows: Layout array) = { config with SplitRows = rows } [] - member __.Ratio(layout: Layout, ratio: int) = - layout.Ratio <- ratio - layout + member __.Ratio(config: LayoutConfig, ratio: int) = { config with Ratio = ratio } [] - member __.Width(layout: Layout, width: int) = - layout.Size <- width - layout + member __.Width(config: LayoutConfig, width: int) = { config with Width = Some width } [] - member __.MinimumWidth(layout: Layout, width: int) = - layout.MinimumSize <- width - layout + member __.MinimumWidth(config: LayoutConfig, width: int) = + { config with + MinimumWidth = Some width } [] - member __.Content(layout: Layout, content: IRenderable) = - layout.Update(content) - + member __.Content(config: LayoutConfig, content: IRenderable) = { config with Content = Some content } + let layout = LayoutBuilder() diff --git a/src/FsSpectre/Widgets/MarkupBuilder.fs b/src/FsSpectre/Widgets/MarkupBuilder.fs index dc5ecf8..917f238 100644 --- a/src/FsSpectre/Widgets/MarkupBuilder.fs +++ b/src/FsSpectre/Widgets/MarkupBuilder.fs @@ -6,22 +6,45 @@ open Spectre.Console [] module MarkupBuilder = + type MarkupConfig = + { Text: string + Style: Style + Justification: Justify } + + static member Default = + { Text = String.Empty + Style = Style.Plain + Justification = Justify.Left } + type MarkupBuilder() = - member __.Yield _ = Markup(String.Empty) + member __.Yield _ = MarkupConfig.Default + + member __.Run(config: MarkupConfig) = + let result = Markup(config.Text, config.Style) + result.Justify(config.Justification) + + [] + member __.Empty(config: MarkupConfig) = { config with Text = String.Empty } [] - member __.Text(_, text: string) = Markup(text) + member __.Text(config: MarkupConfig, text: string) = { config with Text = text } - [] - member __.TextWithStyle(_, text: string, style: Style) = Markup(text, style) + [] + member __.Style(config: MarkupConfig, style: Style) = { config with Style = style } [] - member __.LeftJustified(markup: Markup) = markup.LeftJustified() + member __.LeftJustified(config: MarkupConfig) = + { config with + Justification = Justify.Left } [] - member __.RightJustified(markup: Markup) = markup.RightJustified() + member __.RightJustified(config: MarkupConfig) = + { config with + Justification = Justify.Right } [] - member __.Centered(markup: Markup) = markup.Centered() + member __.Centered(config: MarkupConfig) = + { config with + Justification = Justify.Center } let markup = MarkupBuilder() diff --git a/src/FsSpectre/Widgets/PanelBuilder.fs b/src/FsSpectre/Widgets/PanelBuilder.fs index d0c95eb..80c4a1c 100644 --- a/src/FsSpectre/Widgets/PanelBuilder.fs +++ b/src/FsSpectre/Widgets/PanelBuilder.fs @@ -7,41 +7,65 @@ open Spectre.Console.Rendering [] module PanelBuilder = + type PanelConfig = + { Content: IRenderable + BorderStyle: Style option + BorderColor: Color option + Header: PanelHeader + Expand: bool + Width: int option + Height: int option } + + static member Default = + { Content = Markup(String.Empty) + BorderStyle = None + BorderColor = None + Header = PanelHeader(String.Empty) + Expand = false + Width = None + Height = None } + type PanelBuilder() = - member __.Yield _ = Panel(String.Empty) + member __.Yield _ = PanelConfig.Default - [] - member __.Content(_, text: string) = Panel(text) + member __.Run(config: PanelConfig) = + let result = Panel(config.Content) + config.BorderStyle |> Option.iter (fun s -> result.BorderStyle <- s) + config.BorderColor |> Option.iter (fun c -> result.BorderColor(c) |> ignore) + result.Header <- config.Header + config.Width |> Option.iter (fun w -> result.Width <- (Nullable w)) + config.Height |> Option.iter (fun h -> result.Height <- (Nullable h)) + result + + [] + member __.ContentText(config: PanelConfig, text: string) = { config with Content = Markup(text) } [] - member __.ContentRenderable(_, renderable: IRenderable) = Panel(renderable) + member __.ContentRenderable(config: PanelConfig, renderable: IRenderable) = { config with Content = renderable } + + [] + member __.BorderStyle(config: PanelConfig, style: Style) = + { config with BorderStyle = Some style } [] - member __.BorderColor(panel: Panel, color: Color) = panel.BorderColor(color) + member __.BorderColor(config: PanelConfig, color: Color) = + { config with BorderColor = Some color } [] - member __.Header(panel: Panel, header: PanelHeader) = - panel.Header <- header - panel + member __.Header(config: PanelConfig, header: PanelHeader) = { config with Header = header } [] - member __.HeaderText(panel: Panel, text: string) = - panel.Header <- PanelHeader(text) - panel + member __.HeaderText(config: PanelConfig, text: string) = + { config with + Header = PanelHeader(text) } [] - member __.Expand(panel: Panel) = - panel.Expand <- true - panel + member __.Expand(config: PanelConfig) = { config with Expand = true } [] - member __.Width(panel: Panel, width: int) = - panel.Width <- width - panel + member __.Width(config: PanelConfig, width: int) = { config with Width = Some width } [] - member __.Height(panel: Panel, height: int) = - panel.Height <- height - panel + member __.Height(config: PanelConfig, height: int) = { config with Height = Some height } let panel = PanelBuilder() diff --git a/src/FsSpectre/Widgets/PanelHeaderBuilder.fs b/src/FsSpectre/Widgets/PanelHeaderBuilder.fs index fe130e0..49d754d 100644 --- a/src/FsSpectre/Widgets/PanelHeaderBuilder.fs +++ b/src/FsSpectre/Widgets/PanelHeaderBuilder.fs @@ -6,19 +6,40 @@ open Spectre.Console [] module PanelHeaderBuilder = + type PanelHeaderConfig = + { Text: string + Justification: Justify } + + static member Default = + { Text = String.Empty + Justification = Justify.Left } + type PanelHeaderBuilder() = - member __.Yield _ = PanelHeader(String.Empty) + member __.Yield _ = PanelHeaderConfig.Default + + member __.Run(config: PanelHeaderConfig) = + let result = PanelHeader(config.Text) + result.Justify(config.Justification) + + [] + member __.Empty(config: PanelHeaderConfig) = { config with Text = String.Empty } [] - member __.Text(_, text: string) = PanelHeader(text) + member __.Text(config: PanelHeaderConfig, text: string) = { config with Text = text } [] - member __.LeftJustified(panelHeader: PanelHeader) = panelHeader.LeftJustified() + member __.LeftJustified(config: PanelHeaderConfig) = + { config with + Justification = Justify.Left } [] - member __.RightJustified(panelHeader: PanelHeader) = panelHeader.RightJustified() + member __.RightJustified(config: PanelHeaderConfig) = + { config with + Justification = Justify.Right } [] - member __.Centered(panelHeader: PanelHeader) = panelHeader.Centered() + member __.Centered(config: PanelHeaderConfig) = + { config with + Justification = Justify.Center } let panelHeader = PanelHeaderBuilder() diff --git a/src/FsSpectre/Widgets/RowsBuilder.fs b/src/FsSpectre/Widgets/RowsBuilder.fs index 61957ab..c34cfed 100644 --- a/src/FsSpectre/Widgets/RowsBuilder.fs +++ b/src/FsSpectre/Widgets/RowsBuilder.fs @@ -6,17 +6,27 @@ open Spectre.Console.Rendering [] module RowsBuilder = + type RowsConfig = + { Items: IRenderable array } + + static member Default = { Items = Array.empty } + type RowsBuilder() = - member __.Yield _ = Rows(Array.empty) + member __.Yield _ = RowsConfig.Default - [] - member this.Items(rows: Rows, items: string array) = - let renderables = - items |> Array.map (Markup) |> Array.map (fun x -> x :> IRenderable) + member __.Run(config: RowsConfig) = Rows(config.Items) - this.ItemsRenderable(rows, renderables) + [] + member __.ItemsText(config: RowsConfig, items: string array) = + let markups = items |> Array.map Markup |> Array.map (fun x -> x :> IRenderable) + + { config with + Items = Array.append config.Items markups } [] - member __.ItemsRenderable(_, items: IRenderable array) = Rows(items) + member __.ItemsRenderable(config: RowsConfig, items: IRenderable array) = + { config with + Items = Array.append config.Items items } + let rows = RowsBuilder() diff --git a/src/FsSpectre/Widgets/RuleBuilder.fs b/src/FsSpectre/Widgets/RuleBuilder.fs index 1a8bc0e..2b9f9a2 100644 --- a/src/FsSpectre/Widgets/RuleBuilder.fs +++ b/src/FsSpectre/Widgets/RuleBuilder.fs @@ -1,24 +1,45 @@ namespace FsSpectre +open System open Spectre.Console [] module RuleBuilder = + type RuleConfig = + { Title: string + Justification: Justify } + + static member Default = + { Title = String.Empty + Justification = Justify.Left } + type RuleBuilder() = - member __.Yield _ = Rule() + member __.Yield _ = RuleConfig.Default + + member __.Run(config: RuleConfig) = + let result = Rule(config.Title) + result.Justify(config.Justification) [] - member __.Empty(rule: Rule) = rule + member __.Empty(config: RuleConfig) = { config with Title = String.Empty } [] - member __.Title(rule: Rule, title: string) = - rule.Title <- title - rule - - [] - member __.Justification(rule: Rule, justify: Justify) = - rule.Justification <- justify - rule + member __.Title(config: RuleConfig, title: string) = { config with Title = title } + + [] + member __.LeftJustified(config: RuleConfig) = + { config with + Justification = Justify.Left } + + [] + member __.RightJustified(config: RuleConfig) = + { config with + Justification = Justify.Right } + + [] + member __.Centered(config: RuleConfig) = + { config with + Justification = Justify.Center } let rule = RuleBuilder() diff --git a/src/FsSpectre/Widgets/TableBuilder.fs b/src/FsSpectre/Widgets/TableBuilder.fs index 177b69a..23d51cf 100644 --- a/src/FsSpectre/Widgets/TableBuilder.fs +++ b/src/FsSpectre/Widgets/TableBuilder.fs @@ -6,71 +6,130 @@ open Spectre.Console.Rendering [] module TableBuilder = - type TableBuilder() = + type TableConfig = + { Title: TableTitle option + Columns: TableColumn array + Rows: IRenderable array array + ShowHeaders: bool + ShowFooters: bool + Width: int option + Expand: bool + Border: TableBorder + BorderColor: Color option } + + static member Default = + { Title = None + Columns = Array.empty + Rows = Array.empty + ShowHeaders = true + ShowFooters = true + Width = None + Expand = true + Border = TableBorder.Square + BorderColor = None } - member __.Yield _ = Table() + type TableBuilder() = + member __.Yield _ = TableConfig.Default + + member __.Run(config: TableConfig) = + let result = Table() + config.Title |> Option.iter (fun t -> result.Title <- t) + result.AddColumns(config.Columns) |> ignore + config.Rows |> Array.iter (fun row -> result.AddRow(row) |> ignore) + result.ShowHeaders <- config.ShowHeaders + result.ShowFooters <- config.ShowFooters + config.Width |> Option.iter (fun w -> result.Width <- w) + result.Border <- config.Border + config.BorderColor |> Option.iter (fun c -> result.BorderColor(c) |> ignore) + result [] - member __.Title(table: Table, title: TableTitle) = - table.Title <- title - table + member __.Title(config: TableConfig, title: TableTitle) = { config with Title = Some title } [] - member __.TitleText(table: Table, text: string) = - table.Title <- TableTitle(text) - table + member __.TitleText(config: TableConfig, text: string) = + let title = TableTitle(text) + { config with Title = Some title } + + [] + member __.NoHeaders(config: TableConfig) = { config with ShowHeaders = false } + + [] + member __.NoFooters(config: TableConfig) = { config with ShowFooters = false } [] - member __.Width(table: Table, width: int) = - table.Width <- width - table + member __.Width(config: TableConfig, width: int) = { config with Width = Some width } [] - member __.Collapse(table: Table) = table.Collapse() + member __.Collapse(config: TableConfig) = { config with Expand = false } [] - member __.SimpleBorder(table: Table) = table.SimpleBorder() + member __.SimpleBorder(config: TableConfig) = + { config with + Border = TableBorder.Simple } [] - member __.RoundedBorder(table: Table) = table.RoundedBorder() + member __.RoundedBorder(config: TableConfig) = + { config with + Border = TableBorder.Rounded } - [] - member __.BorderColor(table: Table, color: Color) = table.BorderColor(color) + [] + member __.AsciiBorder(config: TableConfig) = + { config with + Border = TableBorder.Ascii } - [] - member __.NoHeaders(table: Table) = table.HideHeaders() - - [] - member __.NoFooters(table: Table) = table.HideFooters() - - [] - member __.Border(table: Table, tableBorder: TableBorder) = - table.Border <- tableBorder - table + [] + member __.DoubleEdgeBorder(config: TableConfig) = + { config with + Border = TableBorder.DoubleEdge } [] - member __.NoBorder(table: Table) = table.NoBorder() + member __.NoBorder(config: TableConfig) = + { config with + Border = TableBorder.None } + + [] + member __.BorderColor(config: TableConfig, color: Color) = + { config with BorderColor = Some color } [] - member __.Column(table: Table, column: TableColumn) = table.AddColumn(column) + member __.Column(config: TableConfig, column: TableColumn) = + { config with + Columns = Array.append config.Columns [| column |] } [] - member __.Columns(table: Table, column: TableColumn array) = table.AddColumns(column) + member __.Columns(config: TableConfig, columns: TableColumn array) = + { config with + Columns = Array.append config.Columns columns } [] - member __.ColumnText(table: Table, text: string) = table.AddColumn(text) + member __.ColumnText(config: TableConfig, text: string) = + { config with + Columns = Array.append config.Columns [| TableColumn(text) |] } [] - member __.ColumnsText(table: Table, texts: string array) = table.AddColumns(texts) + member __.ColumnsText(config: TableConfig, texts: string array) = + let columns = texts |> Array.map (fun t -> TableColumn(t)) + + { config with + Columns = Array.append config.Columns columns } [] - member __.EmptyRow(table: Table) = table.AddEmptyRow() + member __.EmptyRow(config: TableConfig) = + { config with + Rows = Array.append config.Rows [| [||] |] } [] - member __.Row(table: Table, columns: IRenderable array) = table.AddRow(columns) + member __.Row(config: TableConfig, columns: IRenderable array) = + { config with + Rows = Array.append config.Rows [| columns |] } [] - member __.RowText(table: Table, columns: string array) = table.AddRow(columns) + member __.RowText(config: TableConfig, columns: string array) = + let markups = columns |> Array.map Markup |> Array.map (fun x -> x :> IRenderable) + + { config with + Rows = Array.append config.Rows [| markups |] } let table = TableBuilder() diff --git a/src/FsSpectre/Widgets/TableColumnBuilder.fs b/src/FsSpectre/Widgets/TableColumnBuilder.fs index 68fd8db..e7bfcc3 100644 --- a/src/FsSpectre/Widgets/TableColumnBuilder.fs +++ b/src/FsSpectre/Widgets/TableColumnBuilder.fs @@ -7,55 +7,99 @@ open Spectre.Console.Rendering [] module TableColumnBuilder = - type TableColumnBuilder() = - - member __.Yield _ = TableColumn(String.Empty) + type TableColumnConfig = + { HeaderText: string + HeaderRenderable: IRenderable option + Footer: IRenderable option + NoWrap: bool + Width: int option + Alignment: Justify option + Padding: Padding } + + static member Default = + { HeaderText = String.Empty + HeaderRenderable = None + Footer = None + NoWrap = false + Width = None + Alignment = None + Padding = Padding(1, 0, 1, 0) } - [] - member __.Header(_, header: string) = TableColumn(header) + type TableColumnBuilder() = + member __.Yield _ = TableColumnConfig.Default + + member __.Run(config: TableColumnConfig) = + let result = + match config.HeaderRenderable with + | Some h -> TableColumn(h) + | None -> TableColumn(config.HeaderText) + + config.Footer |> Option.iter (fun f -> result.Footer <- f) + result.NoWrap <- config.NoWrap + config.Alignment |> Option.iter (fun a -> result.Alignment <- a) + result.Padding <- config.Padding + result + + [] + member __.HeaderText(config: TableColumnConfig, text: string) = + { config with + HeaderText = text + HeaderRenderable = None } [] - member __.HeaderRenderable(_, renderable: IRenderable) = TableColumn(renderable) + member __.HeaderRenderable(config: TableColumnConfig, renderable: IRenderable) = + { config with + HeaderText = String.Empty + HeaderRenderable = Some renderable } - [] - member __.Footer(tableColumn: TableColumn, footer: string) = - tableColumn.Footer <- Markup(footer) - tableColumn + [] + member __.FooterText(config: TableColumnConfig, text: string) = + let renderable = Markup(text) + { config with Footer = Some renderable } [] - member __.FooterRenderable(tableColumn: TableColumn, renderable: IRenderable) = - tableColumn.Footer <- renderable - tableColumn + member __.FooterRenderable(config: TableColumnConfig, renderable: IRenderable) = + { config with Footer = Some renderable } [] - member __.NoWrap(tableColumn: TableColumn) = - tableColumn.NoWrap <- true - tableColumn + member __.NoWrap(config: TableColumnConfig) = { config with NoWrap = true } [] - member __.LeftAligned(tableColumn: TableColumn) = tableColumn.LeftAligned() + member __.LeftAligned(config: TableColumnConfig) = + { config with + Alignment = Some Justify.Left } [] - member __.Centered(tableColumn: TableColumn) = tableColumn.Centered() + member __.Centered(config: TableColumnConfig) = + { config with + Alignment = Some Justify.Center } [] - member __.RightAligned(tableColumn: TableColumn) = tableColumn.RightAligned() + member __.RightAligned(config: TableColumnConfig) = + { config with + Alignment = Some Justify.Right } [] - member __.Width(tableColumn: TableColumn, width: int) = - tableColumn.Width <- width - tableColumn - - [] - member __.PadRight(tableColumn: TableColumn, pad: int) = tableColumn.PadRight(pad) + member __.Width(config: TableColumnConfig, width: int) = { config with Width = Some width } [] - member __.PadLeft(tableColumn: TableColumn, pad: int) = tableColumn.PadLeft(pad) + member __.PadLeft(config: TableColumnConfig, pad: int) = + { config with + Padding = Padding(pad, config.Padding.Top, config.Padding.Right, config.Padding.Bottom) } [] - member __.PadTop(tableColumn: TableColumn, pad: int) = tableColumn.PadTop(pad) + member __.PadTop(config: TableColumnConfig, pad: int) = + { config with + Padding = Padding(config.Padding.Left, pad, config.Padding.Right, config.Padding.Bottom) } + + [] + member __.PadRight(config: TableColumnConfig, pad: int) = + { config with + Padding = Padding(config.Padding.Left, config.Padding.Top, pad, config.Padding.Bottom) } [] - member __.PadBottom(tableColumn: TableColumn, pad: int) = tableColumn.PadBottom(pad) + member __.PadBottom(config: TableColumnConfig, pad: int) = + { config with + Padding = Padding(config.Padding.Left, config.Padding.Top, config.Padding.Right, pad) } let tableColumn = TableColumnBuilder() diff --git a/src/FsSpectre/Widgets/TableTitleBuilder.fs b/src/FsSpectre/Widgets/TableTitleBuilder.fs index 5a0885c..1606075 100644 --- a/src/FsSpectre/Widgets/TableTitleBuilder.fs +++ b/src/FsSpectre/Widgets/TableTitleBuilder.fs @@ -6,14 +6,27 @@ open Spectre.Console [] module TableTitleBuilder = + type TableTitleConfig = + { Text: string + Style: Style } + + static member Default = + { Text = String.Empty + Style = Style.Plain } + type TableTitleBuilder() = - member __.Yield _ = TableTitle(String.Empty) + member __.Yield _ = TableTitleConfig.Default + + member __.Run(config: MarkupConfig) = TableTitle(config.Text, config.Style) + + [] + member __.Empty(config: TableTitleConfig) = { config with Text = String.Empty } [] - member __.Text(_, text: string) = TableTitle(text) + member __.Text(config: TableTitleConfig, text: string) = { config with Text = text } [] - member __.Style(tableTitle: TableTitle, style: Style) = tableTitle.SetStyle(style) + member __.Style(config: TableTitleConfig, style: Style) = { config with Style = style } let tableTitle = TableTitleBuilder() diff --git a/src/FsSpectre/Widgets/TextBuilder.fs b/src/FsSpectre/Widgets/TextBuilder.fs index 3214644..cf005b8 100644 --- a/src/FsSpectre/Widgets/TextBuilder.fs +++ b/src/FsSpectre/Widgets/TextBuilder.fs @@ -6,25 +6,45 @@ open Spectre.Console [] module TextBuilder = + type TextConfig = + { Text: string + Style: Style + Justification: Justify } + + static member Default = + { Text = String.Empty + Style = Style.Plain + Justification = Justify.Left } + type TextBuilder() = - member __.Yield _ = Text(String.Empty) + member __.Yield _ = TextConfig.Default + + member __.Run(config: TextConfig) = + let result = Text(config.Text, config.Style) + result.Justify(config.Justification) [] - member __.Text(text: Text) = text + member __.Empty(config: TextConfig) = { config with Text = String.Empty } [] - member __.Text(_, text: string) = Text(text) + member __.Text(config: TextConfig, text: string) = { config with Text = text } - [] - member __.TextWithStyle(_, text: string, style: Style) = Text(text, style) + [] + member __.Style(config: TextConfig, style: Style) = { config with Style = style } [] - member __.LeftJustified(text: Text) = text.LeftJustified() + member __.LeftJustified(config: TextConfig) = + { config with + Justification = Justify.Left } [] - member __.RightJustified(text: Text) = text.RightJustified() + member __.RightJustified(config: TextConfig) = + { config with + Justification = Justify.Right } [] - member __.Centered(text: Text) = text.Centered() + member __.Centered(config: TextConfig) = + { config with + Justification = Justify.Center } let text = TextBuilder() diff --git a/src/FsSpectre/Widgets/TreeBuilder.fs b/src/FsSpectre/Widgets/TreeBuilder.fs index a9148c0..fa7c192 100644 --- a/src/FsSpectre/Widgets/TreeBuilder.fs +++ b/src/FsSpectre/Widgets/TreeBuilder.fs @@ -7,33 +7,46 @@ open Spectre.Console.Rendering [] module TreeBuilder = + type TreeConfig = + { Label: IRenderable + Nodes: TreeNode array } + + static member Default = + { Label = Markup(String.Empty) + Nodes = Array.empty } + type TreeBuilder() = - member __.Yield _ = Tree(String.Empty) + member __.Yield _ = TreeConfig.Default + + member __.Run(config: TreeConfig) = + let result = Tree(config.Label) + result.AddNodes(config.Nodes) + result [] - member __.Label(_, label: string) = Tree(label) + member __.RootText(config: TreeConfig, text: string) = { config with Label = Markup(text) } [] - member __.LabelRenderable(_, renderable: IRenderable) = Tree(renderable) + member __.LabelRenderable(config: TreeConfig, renderable: IRenderable) = { config with Label = renderable } [] - member __.Node(tree: Tree, subNode: TreeNode) = - tree.AddNode(subNode) |> ignore - tree + member __.Node(config: TreeConfig, subNode: TreeNode) = + { config with + Nodes = Array.append config.Nodes [| subNode |] } [] - member __.Nodes(tree: Tree, subNodes: TreeNode array) = - tree.AddNodes(subNodes) |> ignore - tree + member __.Nodes(config: TreeConfig, subNodes: TreeNode array) = + { config with + Nodes = Array.append config.Nodes subNodes } [] - member __.NodeRenderable(tree: Tree, renderable: IRenderable) = - tree.AddNode(renderable) |> ignore - tree + member __.NodeRenderable(config: TreeConfig, renderable: IRenderable) = + { config with + Nodes = Array.append config.Nodes [| TreeNode(renderable) |] } [] - member __.NodeText(tree: Tree, text: string) = - tree.AddNode(text) |> ignore - tree + member __.NodeText(config: TreeConfig, text: string) = + { config with + Nodes = Array.append config.Nodes [| TreeNode(Markup(text)) |] } let tree = TreeBuilder() diff --git a/src/FsSpectre/Widgets/TreeNodeBuilder.fs b/src/FsSpectre/Widgets/TreeNodeBuilder.fs index d462d63..75cfdef 100644 --- a/src/FsSpectre/Widgets/TreeNodeBuilder.fs +++ b/src/FsSpectre/Widgets/TreeNodeBuilder.fs @@ -7,33 +7,46 @@ open Spectre.Console.Rendering [] module TreeNodeBuilder = + type TreeNodeConfig = + { Label: IRenderable + Nodes: TreeNode array } + + static member Default = + { Label = Markup(String.Empty) + Nodes = Array.empty } + type TreeNodeBuilder() = - member __.Yield _ = TreeNode(Markup(String.Empty)) + member __.Yield _ = TreeNodeConfig.Default - [] - member __.LabelRenderable(_, renderable: IRenderable) = TreeNode(renderable) + member __.Run(config: TreeNodeConfig) = + let result = TreeNode(config.Label) + result.AddNodes(config.Nodes) + result [] - member __.RootText(_, text: string) = TreeNode(Markup(text)) + member __.RootText(config: TreeNodeConfig, text: string) = { config with Label = Markup(text) } + + [] + member __.LabelRenderable(config: TreeNodeConfig, renderable: IRenderable) = { config with Label = renderable } [] - member __.Node(node: TreeNode, subNode: TreeNode) = - node.AddNode(subNode) |> ignore - node + member __.Node(config: TreeNodeConfig, subNode: TreeNode) = + { config with + Nodes = Array.append config.Nodes [| subNode |] } [] - member __.Nodes(node: TreeNode, subNodes: TreeNode array) = - node.AddNodes(subNodes) |> ignore - node + member __.Nodes(config: TreeNodeConfig, subNodes: TreeNode array) = + { config with + Nodes = Array.append config.Nodes subNodes } [] - member __.NodeRenderable(node: TreeNode, renderable: IRenderable) = - node.AddNode(renderable) |> ignore - node + member __.NodeRenderable(config: TreeNodeConfig, renderable: IRenderable) = + { config with + Nodes = Array.append config.Nodes [| TreeNode(renderable) |] } [] - member __.NodeText(node: TreeNode, text: string) = - node.AddNode(text) |> ignore - node + member __.NodeText(config: TreeNodeConfig, text: string) = + { config with + Nodes = Array.append config.Nodes [| TreeNode(Markup(text)) |] } let treeNode: TreeNodeBuilder = TreeNodeBuilder()