mirror of
https://github.com/nsnail/spectre.console.git
synced 2025-04-16 17:02:51 +08:00
parent
0a0380ae0a
commit
3f2ca49071
1
.github/workflows/ci.yaml
vendored
1
.github/workflows/ci.yaml
vendored
@ -71,6 +71,7 @@ jobs:
|
|||||||
dotnet example colors
|
dotnet example colors
|
||||||
dotnet example emojis
|
dotnet example emojis
|
||||||
dotnet example exceptions
|
dotnet example exceptions
|
||||||
|
dotnet example calendars
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
shell: bash
|
shell: bash
|
||||||
|
15
examples/Calendars/Calendars.csproj
Normal file
15
examples/Calendars/Calendars.csproj
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
<Title>Calendars</Title>
|
||||||
|
<Description>Demonstrates how to render calendars.</Description>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\src\Spectre.Console\Spectre.Console.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
68
examples/Calendars/Program.cs
Normal file
68
examples/Calendars/Program.cs
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Spectre.Console;
|
||||||
|
using Spectre.Console.Rendering;
|
||||||
|
|
||||||
|
namespace Calendars
|
||||||
|
{
|
||||||
|
public static class Program
|
||||||
|
{
|
||||||
|
public static void Main(string[] args)
|
||||||
|
{
|
||||||
|
AnsiConsole.WriteLine();
|
||||||
|
AnsiConsole.Render(
|
||||||
|
new Columns(GetCalendars())
|
||||||
|
.Collapse());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<IRenderable> GetCalendars()
|
||||||
|
{
|
||||||
|
yield return EmbedInPanel(
|
||||||
|
"Invariant calendar",
|
||||||
|
new Calendar(2020, 10)
|
||||||
|
.SimpleHeavyBorder()
|
||||||
|
.SetHighlightStyle(Style.Parse("red"))
|
||||||
|
.AddCalendarEvent("An event", 2020, 9, 22)
|
||||||
|
.AddCalendarEvent("Another event", 2020, 10, 2)
|
||||||
|
.AddCalendarEvent("A third event", 2020, 10, 13));
|
||||||
|
|
||||||
|
yield return EmbedInPanel(
|
||||||
|
"Swedish calendar (sv-SE)",
|
||||||
|
new Calendar(2020, 10)
|
||||||
|
.RoundedBorder()
|
||||||
|
.SetHighlightStyle(Style.Parse("blue"))
|
||||||
|
.SetCulture("sv-SE")
|
||||||
|
.AddCalendarEvent("An event", 2020, 9, 22)
|
||||||
|
.AddCalendarEvent("Another event", 2020, 10, 2)
|
||||||
|
.AddCalendarEvent("A third event", 2020, 10, 13));
|
||||||
|
|
||||||
|
yield return EmbedInPanel(
|
||||||
|
"German calendar (de-DE)",
|
||||||
|
new Calendar(2020, 10)
|
||||||
|
.MarkdownBorder()
|
||||||
|
.SetHighlightStyle(Style.Parse("yellow"))
|
||||||
|
.SetCulture("de-DE")
|
||||||
|
.AddCalendarEvent("An event", 2020, 9, 22)
|
||||||
|
.AddCalendarEvent("Another event", 2020, 10, 2)
|
||||||
|
.AddCalendarEvent("A third event", 2020, 10, 13));
|
||||||
|
|
||||||
|
yield return EmbedInPanel(
|
||||||
|
"Italian calendar (de-DE)",
|
||||||
|
new Calendar(2020, 10)
|
||||||
|
.DoubleBorder()
|
||||||
|
.SetHighlightStyle(Style.Parse("green"))
|
||||||
|
.SetCulture("it-IT")
|
||||||
|
.AddCalendarEvent("An event", 2020, 9, 22)
|
||||||
|
.AddCalendarEvent("Another event", 2020, 10, 2)
|
||||||
|
.AddCalendarEvent("A third event", 2020, 10, 13));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IRenderable EmbedInPanel(string title, Calendar calendar)
|
||||||
|
{
|
||||||
|
return new Panel(calendar)
|
||||||
|
.Expand()
|
||||||
|
.RoundedBorder()
|
||||||
|
.SetBorderStyle(Style.Parse("grey"))
|
||||||
|
.SetHeader($" {title} ", Style.Parse("yellow"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
92
src/Spectre.Console.Tests/Unit/CalendarTests.cs
Normal file
92
src/Spectre.Console.Tests/Unit/CalendarTests.cs
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
using System;
|
||||||
|
using Shouldly;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Spectre.Console.Tests.Unit
|
||||||
|
{
|
||||||
|
public sealed class CalendarTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Should_Render_Calendar_Correctly()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var console = new PlainConsole(width: 80);
|
||||||
|
var calendar = new Calendar(2020, 10)
|
||||||
|
.AddCalendarEvent(new DateTime(2020, 9, 1))
|
||||||
|
.AddCalendarEvent(new DateTime(2020, 10, 3))
|
||||||
|
.AddCalendarEvent(new DateTime(2020, 10, 12));
|
||||||
|
|
||||||
|
// When
|
||||||
|
console.Render(calendar);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
console.Lines.Count.ShouldBe(10);
|
||||||
|
console.Lines[0].ShouldBe("┌─────┬─────┬─────┬─────┬─────┬─────┬─────┐");
|
||||||
|
console.Lines[1].ShouldBe("│ Sun │ Mon │ Tue │ Wed │ Thu │ Fri │ Sat │");
|
||||||
|
console.Lines[2].ShouldBe("├─────┼─────┼─────┼─────┼─────┼─────┼─────┤");
|
||||||
|
console.Lines[3].ShouldBe("│ │ │ │ │ 1 │ 2 │ 3* │");
|
||||||
|
console.Lines[4].ShouldBe("│ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ 10 │");
|
||||||
|
console.Lines[5].ShouldBe("│ 11 │ 12* │ 13 │ 14 │ 15 │ 16 │ 17 │");
|
||||||
|
console.Lines[6].ShouldBe("│ 18 │ 19 │ 20 │ 21 │ 22 │ 23 │ 24 │");
|
||||||
|
console.Lines[7].ShouldBe("│ 25 │ 26 │ 27 │ 28 │ 29 │ 30 │ 31 │");
|
||||||
|
console.Lines[8].ShouldBe("│ │ │ │ │ │ │ │");
|
||||||
|
console.Lines[9].ShouldBe("└─────┴─────┴─────┴─────┴─────┴─────┴─────┘");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Render_Calendar_Correctly_For_Specific_Culture()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var console = new PlainConsole(width: 80);
|
||||||
|
var calendar = new Calendar(2020, 10, 15)
|
||||||
|
.SetCulture("de-DE")
|
||||||
|
.AddCalendarEvent(new DateTime(2020, 9, 1))
|
||||||
|
.AddCalendarEvent(new DateTime(2020, 10, 3))
|
||||||
|
.AddCalendarEvent(new DateTime(2020, 10, 12));
|
||||||
|
|
||||||
|
// When
|
||||||
|
console.Render(calendar);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
console.Lines.Count.ShouldBe(10);
|
||||||
|
console.Lines[0].ShouldBe("┌─────┬────┬────┬────┬────┬────┬────┐");
|
||||||
|
console.Lines[1].ShouldBe("│ Mo │ Di │ Mi │ Do │ Fr │ Sa │ So │");
|
||||||
|
console.Lines[2].ShouldBe("├─────┼────┼────┼────┼────┼────┼────┤");
|
||||||
|
console.Lines[3].ShouldBe("│ │ │ │ 1 │ 2 │ 3* │ 4 │");
|
||||||
|
console.Lines[4].ShouldBe("│ 5 │ 6 │ 7 │ 8 │ 9 │ 10 │ 11 │");
|
||||||
|
console.Lines[5].ShouldBe("│ 12* │ 13 │ 14 │ 15 │ 16 │ 17 │ 18 │");
|
||||||
|
console.Lines[6].ShouldBe("│ 19 │ 20 │ 21 │ 22 │ 23 │ 24 │ 25 │");
|
||||||
|
console.Lines[7].ShouldBe("│ 26 │ 27 │ 28 │ 29 │ 30 │ 31 │ │");
|
||||||
|
console.Lines[8].ShouldBe("│ │ │ │ │ │ │ │");
|
||||||
|
console.Lines[9].ShouldBe("└─────┴────┴────┴────┴────┴────┴────┘");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Render_List_Of_Events_If_Enabled()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var console = new PlainConsole(width: 80);
|
||||||
|
var calendar = new Calendar(2020, 10, 15)
|
||||||
|
.SetCulture("de-DE")
|
||||||
|
.AddCalendarEvent(new DateTime(2020, 9, 1))
|
||||||
|
.AddCalendarEvent(new DateTime(2020, 10, 3))
|
||||||
|
.AddCalendarEvent(new DateTime(2020, 10, 12));
|
||||||
|
|
||||||
|
// When
|
||||||
|
console.Render(calendar);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
console.Lines.Count.ShouldBe(10);
|
||||||
|
console.Lines[0].ShouldBe("┌─────┬────┬────┬────┬────┬────┬────┐");
|
||||||
|
console.Lines[1].ShouldBe("│ Mo │ Di │ Mi │ Do │ Fr │ Sa │ So │");
|
||||||
|
console.Lines[2].ShouldBe("├─────┼────┼────┼────┼────┼────┼────┤");
|
||||||
|
console.Lines[3].ShouldBe("│ │ │ │ 1 │ 2 │ 3* │ 4 │");
|
||||||
|
console.Lines[4].ShouldBe("│ 5 │ 6 │ 7 │ 8 │ 9 │ 10 │ 11 │");
|
||||||
|
console.Lines[5].ShouldBe("│ 12* │ 13 │ 14 │ 15 │ 16 │ 17 │ 18 │");
|
||||||
|
console.Lines[6].ShouldBe("│ 19 │ 20 │ 21 │ 22 │ 23 │ 24 │ 25 │");
|
||||||
|
console.Lines[7].ShouldBe("│ 26 │ 27 │ 28 │ 29 │ 30 │ 31 │ │");
|
||||||
|
console.Lines[8].ShouldBe("│ │ │ │ │ │ │ │");
|
||||||
|
console.Lines[9].ShouldBe("└─────┴────┴────┴────┴────┴────┴────┘");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -44,6 +44,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GitHub", "GitHub", "{C3E2CB
|
|||||||
..\.github\workflows\publish.yaml = ..\.github\workflows\publish.yaml
|
..\.github\workflows\publish.yaml = ..\.github\workflows\publish.yaml
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Calendars", "..\examples\Calendars\Calendars.csproj", "{57691C7D-683D-46E6-AA4F-57A8C5F65D25}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@ -198,6 +200,18 @@ Global
|
|||||||
{90C081A7-7C1D-4A4A-82B6-8FF473C3EA32}.Release|x64.Build.0 = Release|Any CPU
|
{90C081A7-7C1D-4A4A-82B6-8FF473C3EA32}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{90C081A7-7C1D-4A4A-82B6-8FF473C3EA32}.Release|x86.ActiveCfg = Release|Any CPU
|
{90C081A7-7C1D-4A4A-82B6-8FF473C3EA32}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{90C081A7-7C1D-4A4A-82B6-8FF473C3EA32}.Release|x86.Build.0 = Release|Any CPU
|
{90C081A7-7C1D-4A4A-82B6-8FF473C3EA32}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{57691C7D-683D-46E6-AA4F-57A8C5F65D25}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{57691C7D-683D-46E6-AA4F-57A8C5F65D25}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{57691C7D-683D-46E6-AA4F-57A8C5F65D25}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{57691C7D-683D-46E6-AA4F-57A8C5F65D25}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{57691C7D-683D-46E6-AA4F-57A8C5F65D25}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{57691C7D-683D-46E6-AA4F-57A8C5F65D25}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{57691C7D-683D-46E6-AA4F-57A8C5F65D25}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{57691C7D-683D-46E6-AA4F-57A8C5F65D25}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{57691C7D-683D-46E6-AA4F-57A8C5F65D25}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{57691C7D-683D-46E6-AA4F-57A8C5F65D25}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{57691C7D-683D-46E6-AA4F-57A8C5F65D25}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{57691C7D-683D-46E6-AA4F-57A8C5F65D25}.Release|x86.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@ -214,6 +228,7 @@ Global
|
|||||||
{1EABB956-957F-4C1A-8AC0-FD19C8F3C2F2} = {F0575243-121F-4DEE-9F6B-246E26DC0844}
|
{1EABB956-957F-4C1A-8AC0-FD19C8F3C2F2} = {F0575243-121F-4DEE-9F6B-246E26DC0844}
|
||||||
{90C081A7-7C1D-4A4A-82B6-8FF473C3EA32} = {F0575243-121F-4DEE-9F6B-246E26DC0844}
|
{90C081A7-7C1D-4A4A-82B6-8FF473C3EA32} = {F0575243-121F-4DEE-9F6B-246E26DC0844}
|
||||||
{C3E2CB5C-1517-4C75-B59A-93D4E22BEC8D} = {20595AD4-8D75-4AF8-B6BC-9C38C160423F}
|
{C3E2CB5C-1517-4C75-B59A-93D4E22BEC8D} = {20595AD4-8D75-4AF8-B6BC-9C38C160423F}
|
||||||
|
{57691C7D-683D-46E6-AA4F-57A8C5F65D25} = {F0575243-121F-4DEE-9F6B-246E26DC0844}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {5729B071-67A0-48FB-8B1B-275E6822086C}
|
SolutionGuid = {5729B071-67A0-48FB-8B1B-275E6822086C}
|
||||||
|
83
src/Spectre.Console/Extensions/CalendarExtensions.cs
Normal file
83
src/Spectre.Console/Extensions/CalendarExtensions.cs
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Spectre.Console
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Contains extension methods for <see cref="Calendar"/>.
|
||||||
|
/// </summary>
|
||||||
|
public static class CalendarExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a calendar event.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="calendar">The calendar to add the calendar event to.</param>
|
||||||
|
/// <param name="date">The calendar event date.</param>
|
||||||
|
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||||
|
public static Calendar AddCalendarEvent(this Calendar calendar, DateTime date)
|
||||||
|
{
|
||||||
|
return AddCalendarEvent(calendar, string.Empty, date.Year, date.Month, date.Day);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a calendar event.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="calendar">The calendar to add the calendar event to.</param>
|
||||||
|
/// <param name="description">The calendar event description.</param>
|
||||||
|
/// <param name="date">The calendar event date.</param>
|
||||||
|
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||||
|
public static Calendar AddCalendarEvent(this Calendar calendar, string description, DateTime date)
|
||||||
|
{
|
||||||
|
return AddCalendarEvent(calendar, description, date.Year, date.Month, date.Day);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a calendar event.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="calendar">The calendar to add the calendar event to.</param>
|
||||||
|
/// <param name="year">The year of the calendar event.</param>
|
||||||
|
/// <param name="month">The month of the calendar event.</param>
|
||||||
|
/// <param name="day">The day of the calendar event.</param>
|
||||||
|
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||||
|
public static Calendar AddCalendarEvent(this Calendar calendar, int year, int month, int day)
|
||||||
|
{
|
||||||
|
return AddCalendarEvent(calendar, string.Empty, year, month, day);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a calendar event.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="calendar">The calendar.</param>
|
||||||
|
/// <param name="description">The calendar event description.</param>
|
||||||
|
/// <param name="year">The year of the calendar event.</param>
|
||||||
|
/// <param name="month">The month of the calendar event.</param>
|
||||||
|
/// <param name="day">The day of the calendar event.</param>
|
||||||
|
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||||
|
public static Calendar AddCalendarEvent(this Calendar calendar, string description, int year, int month, int day)
|
||||||
|
{
|
||||||
|
if (calendar is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(calendar));
|
||||||
|
}
|
||||||
|
|
||||||
|
calendar.CalendarEvents.Add(new CalendarEvent(description, year, month, day));
|
||||||
|
return calendar;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the calendar's highlight <see cref="Style"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="calendar">The calendar.</param>
|
||||||
|
/// <param name="style">The highlight style.</param>
|
||||||
|
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||||
|
public static Calendar SetHighlightStyle(this Calendar calendar, Style? style)
|
||||||
|
{
|
||||||
|
if (calendar is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(calendar));
|
||||||
|
}
|
||||||
|
|
||||||
|
calendar.HightlightStyle = style ?? Style.Plain;
|
||||||
|
return calendar;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
73
src/Spectre.Console/Extensions/HasCultureExtensions.cs
Normal file
73
src/Spectre.Console/Extensions/HasCultureExtensions.cs
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
|
namespace Spectre.Console
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Contains extension methods for <see cref="IHasCulture"/>.
|
||||||
|
/// </summary>
|
||||||
|
public static class HasCultureExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the culture.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">An object type with a culture.</typeparam>
|
||||||
|
/// <param name="obj">The object to set the culture for.</param>
|
||||||
|
/// <param name="culture">The culture to set.</param>
|
||||||
|
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||||
|
public static T SetCulture<T>(this T obj, CultureInfo culture)
|
||||||
|
where T : class, IHasCulture
|
||||||
|
{
|
||||||
|
if (obj is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (culture is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(culture));
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.Culture = culture;
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the culture.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">An object type with a culture.</typeparam>
|
||||||
|
/// <param name="obj">The object to set the culture for.</param>
|
||||||
|
/// <param name="name">The culture to set.</param>
|
||||||
|
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||||
|
public static T SetCulture<T>(this T obj, string name)
|
||||||
|
where T : class, IHasCulture
|
||||||
|
{
|
||||||
|
if (obj is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.Culture = CultureInfo.GetCultureInfo(name);
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the culture.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">An object type with a culture.</typeparam>
|
||||||
|
/// <param name="obj">The object to set the culture for.</param>
|
||||||
|
/// <param name="name">The culture to set.</param>
|
||||||
|
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||||
|
public static T SetCulture<T>(this T obj, int name)
|
||||||
|
where T : class, IHasCulture
|
||||||
|
{
|
||||||
|
if (obj is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.Culture = CultureInfo.GetCultureInfo(name);
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
src/Spectre.Console/IHasCulture.cs
Normal file
15
src/Spectre.Console/IHasCulture.cs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
using System.Globalization;
|
||||||
|
|
||||||
|
namespace Spectre.Console
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents something that has a culture.
|
||||||
|
/// </summary>
|
||||||
|
public interface IHasCulture
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the culture.
|
||||||
|
/// </summary>
|
||||||
|
CultureInfo Culture { get; set; }
|
||||||
|
}
|
||||||
|
}
|
88
src/Spectre.Console/Internal/Collections/ListWithCallback.cs
Normal file
88
src/Spectre.Console/Internal/Collections/ListWithCallback.cs
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Spectre.Console.Internal.Collections
|
||||||
|
{
|
||||||
|
internal sealed class ListWithCallback<T> : IList<T>
|
||||||
|
{
|
||||||
|
private readonly List<T> _list;
|
||||||
|
private readonly Action _callback;
|
||||||
|
|
||||||
|
public T this[int index]
|
||||||
|
{
|
||||||
|
get => _list[index];
|
||||||
|
set => _list[index] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Count => _list.Count;
|
||||||
|
public bool IsReadOnly => false;
|
||||||
|
|
||||||
|
public ListWithCallback(Action callback)
|
||||||
|
{
|
||||||
|
_list = new List<T>();
|
||||||
|
_callback = callback ?? throw new ArgumentNullException(nameof(callback));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Add(T item)
|
||||||
|
{
|
||||||
|
_list.Add(item);
|
||||||
|
_callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
_list.Clear();
|
||||||
|
_callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Contains(T item)
|
||||||
|
{
|
||||||
|
return _list.Contains(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CopyTo(T[] array, int arrayIndex)
|
||||||
|
{
|
||||||
|
_list.CopyTo(array, arrayIndex);
|
||||||
|
_callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerator<T> GetEnumerator()
|
||||||
|
{
|
||||||
|
return _list.GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int IndexOf(T item)
|
||||||
|
{
|
||||||
|
return _list.IndexOf(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Insert(int index, T item)
|
||||||
|
{
|
||||||
|
_list.Insert(index, item);
|
||||||
|
_callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Remove(T item)
|
||||||
|
{
|
||||||
|
var result = _list.Remove(item);
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
_callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveAt(int index)
|
||||||
|
{
|
||||||
|
_list.RemoveAt(index);
|
||||||
|
_callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
{
|
||||||
|
return GetEnumerator();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
|
namespace Spectre.Console.Internal
|
||||||
|
{
|
||||||
|
internal static class DayOfWeekExtensions
|
||||||
|
{
|
||||||
|
public static string GetAbbreviatedDayName(this DayOfWeek day, CultureInfo culture)
|
||||||
|
{
|
||||||
|
culture ??= CultureInfo.InvariantCulture;
|
||||||
|
var name = culture.DateTimeFormat.GetAbbreviatedDayName(day);
|
||||||
|
|
||||||
|
if (name.Length > 0 && char.IsLower(name[0]))
|
||||||
|
{
|
||||||
|
name = string.Format(culture, "{0}{1}", char.ToUpper(name[0], culture), name.Substring(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DayOfWeek GetNextWeekDay(this DayOfWeek day)
|
||||||
|
{
|
||||||
|
var next = (int)day + 1;
|
||||||
|
if (next > (int)DayOfWeek.Saturday)
|
||||||
|
{
|
||||||
|
return DayOfWeek.Sunday;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (DayOfWeek)next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
|
||||||
using Spectre.Console.Internal;
|
using Spectre.Console.Internal;
|
||||||
|
|
||||||
namespace Spectre.Console
|
namespace Spectre.Console
|
||||||
|
274
src/Spectre.Console/Widgets/Calendar.cs
Normal file
274
src/Spectre.Console/Widgets/Calendar.cs
Normal file
@ -0,0 +1,274 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using Spectre.Console.Internal;
|
||||||
|
using Spectre.Console.Internal.Collections;
|
||||||
|
using Spectre.Console.Rendering;
|
||||||
|
|
||||||
|
namespace Spectre.Console
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A renderable calendar.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class Calendar : Renderable, IHasCulture, IHasTableBorder
|
||||||
|
{
|
||||||
|
private const int NumberOfWeekDays = 7;
|
||||||
|
private const int ExpectedRowCount = 6;
|
||||||
|
|
||||||
|
private readonly ListWithCallback<CalendarEvent> _calendarEvents;
|
||||||
|
|
||||||
|
private int _year;
|
||||||
|
private int _month;
|
||||||
|
private int _day;
|
||||||
|
private IRenderable? _table;
|
||||||
|
private TableBorder _border;
|
||||||
|
private bool _useSafeBorder;
|
||||||
|
private Style? _borderStyle;
|
||||||
|
private bool _dirty;
|
||||||
|
private CultureInfo _culture;
|
||||||
|
private Style _highlightStyle;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the calendar year.
|
||||||
|
/// </summary>
|
||||||
|
public int Year
|
||||||
|
{
|
||||||
|
get => _year;
|
||||||
|
set => MarkAsDirty(() => _year = value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the calendar month.
|
||||||
|
/// </summary>
|
||||||
|
public int Month
|
||||||
|
{
|
||||||
|
get => _month;
|
||||||
|
set => MarkAsDirty(() => _month = value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the calendar day.
|
||||||
|
/// </summary>
|
||||||
|
public int Day
|
||||||
|
{
|
||||||
|
get => _day;
|
||||||
|
set => MarkAsDirty(() => _day = value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public TableBorder Border
|
||||||
|
{
|
||||||
|
get => _border;
|
||||||
|
set => MarkAsDirty(() => _border = value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool UseSafeBorder
|
||||||
|
{
|
||||||
|
get => _useSafeBorder;
|
||||||
|
set => MarkAsDirty(() => _useSafeBorder = value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public Style? BorderStyle
|
||||||
|
{
|
||||||
|
get => _borderStyle;
|
||||||
|
set => MarkAsDirty(() => _borderStyle = value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the calendar's <see cref="CultureInfo"/>.
|
||||||
|
/// </summary>
|
||||||
|
public CultureInfo Culture
|
||||||
|
{
|
||||||
|
get => _culture;
|
||||||
|
set => MarkAsDirty(() => _culture = value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the calendar's highlight <see cref="Style"/>.
|
||||||
|
/// </summary>
|
||||||
|
public Style HightlightStyle
|
||||||
|
{
|
||||||
|
get => _highlightStyle;
|
||||||
|
set => MarkAsDirty(() => _highlightStyle = value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a list containing all calendar events.
|
||||||
|
/// </summary>
|
||||||
|
public IList<CalendarEvent> CalendarEvents => _calendarEvents;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="Calendar"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="date">The calendar date.</param>
|
||||||
|
public Calendar(DateTime date)
|
||||||
|
: this(date.Year, date.Month, date.Day)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="Calendar"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="year">The calendar year.</param>
|
||||||
|
/// <param name="month">The calendar month.</param>
|
||||||
|
public Calendar(int year, int month)
|
||||||
|
: this(year, month, 1)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="Calendar"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="year">The calendar year.</param>
|
||||||
|
/// <param name="month">The calendar month.</param>
|
||||||
|
/// <param name="day">The calendar day.</param>
|
||||||
|
public Calendar(int year, int month, int day)
|
||||||
|
{
|
||||||
|
_year = year;
|
||||||
|
_month = month;
|
||||||
|
_day = day;
|
||||||
|
_table = null;
|
||||||
|
_border = TableBorder.Square;
|
||||||
|
_useSafeBorder = true;
|
||||||
|
_borderStyle = null;
|
||||||
|
_dirty = true;
|
||||||
|
_culture = CultureInfo.InvariantCulture;
|
||||||
|
_highlightStyle = new Style(foreground: Color.Blue);
|
||||||
|
_calendarEvents = new ListWithCallback<CalendarEvent>(() => _dirty = true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
protected override Measurement Measure(RenderContext context, int maxWidth)
|
||||||
|
{
|
||||||
|
var table = GetTable();
|
||||||
|
return table.Measure(context, maxWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
|
||||||
|
{
|
||||||
|
return GetTable().Render(context, maxWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IRenderable GetTable()
|
||||||
|
{
|
||||||
|
// Table needs to be built?
|
||||||
|
if (_dirty || _table == null)
|
||||||
|
{
|
||||||
|
_table = BuildTable();
|
||||||
|
_dirty = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _table;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IRenderable BuildTable()
|
||||||
|
{
|
||||||
|
var culture = Culture ?? CultureInfo.InvariantCulture;
|
||||||
|
|
||||||
|
var table = new Table
|
||||||
|
{
|
||||||
|
Border = _border,
|
||||||
|
UseSafeBorder = _useSafeBorder,
|
||||||
|
BorderStyle = _borderStyle,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add columns
|
||||||
|
foreach (var order in GetWeekdays())
|
||||||
|
{
|
||||||
|
table.AddColumn(new TableColumn(order.GetAbbreviatedDayName(culture)));
|
||||||
|
}
|
||||||
|
|
||||||
|
var row = new List<IRenderable>();
|
||||||
|
|
||||||
|
var currentDay = 1;
|
||||||
|
var weekday = culture.DateTimeFormat.FirstDayOfWeek;
|
||||||
|
var weekdays = BuildWeekDayTable();
|
||||||
|
|
||||||
|
var daysInMonth = DateTime.DaysInMonth(Year, Month);
|
||||||
|
while (currentDay <= daysInMonth)
|
||||||
|
{
|
||||||
|
if (weekdays[currentDay - 1] == weekday)
|
||||||
|
{
|
||||||
|
if (_calendarEvents.Any(e => e.Month == Month && e.Day == currentDay))
|
||||||
|
{
|
||||||
|
row.Add(new Markup(currentDay.ToString(CultureInfo.InvariantCulture) + "*", _highlightStyle));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
row.Add(new Text(currentDay.ToString(CultureInfo.InvariantCulture)));
|
||||||
|
}
|
||||||
|
|
||||||
|
currentDay++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Add empty cell
|
||||||
|
row.Add(Text.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (row.Count == NumberOfWeekDays)
|
||||||
|
{
|
||||||
|
// Flush row
|
||||||
|
table.AddRow(row.ToArray());
|
||||||
|
row.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
weekday = weekday.GetNextWeekDay();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (row.Count > 0)
|
||||||
|
{
|
||||||
|
// Flush row
|
||||||
|
table.AddRow(row.ToArray());
|
||||||
|
row.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// We want all calendars to have the same height.
|
||||||
|
if (table.RowCount < ExpectedRowCount)
|
||||||
|
{
|
||||||
|
var diff = Math.Max(0, ExpectedRowCount - table.RowCount);
|
||||||
|
for (var i = 0; i < diff; i++)
|
||||||
|
{
|
||||||
|
table.AddEmptyRow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return table;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MarkAsDirty(Action action)
|
||||||
|
{
|
||||||
|
action();
|
||||||
|
_dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private DayOfWeek[] GetWeekdays()
|
||||||
|
{
|
||||||
|
var culture = Culture ?? CultureInfo.InvariantCulture;
|
||||||
|
|
||||||
|
var days = new DayOfWeek[7];
|
||||||
|
days[0] = culture.DateTimeFormat.FirstDayOfWeek;
|
||||||
|
for (var i = 1; i < 7; i++)
|
||||||
|
{
|
||||||
|
days[i] = days[i - 1].GetNextWeekDay();
|
||||||
|
}
|
||||||
|
|
||||||
|
return days;
|
||||||
|
}
|
||||||
|
|
||||||
|
private DayOfWeek[] BuildWeekDayTable()
|
||||||
|
{
|
||||||
|
var result = new List<DayOfWeek>();
|
||||||
|
for (var day = 0; day < DateTime.DaysInMonth(Year, Month); day++)
|
||||||
|
{
|
||||||
|
result.Add(new DateTime(Year, Month, day + 1).DayOfWeek);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
54
src/Spectre.Console/Widgets/CalendarEvent.cs
Normal file
54
src/Spectre.Console/Widgets/CalendarEvent.cs
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
namespace Spectre.Console
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a calendar event.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class CalendarEvent
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the description of the calendar event.
|
||||||
|
/// </summary>
|
||||||
|
public string Description { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the year of the calendar event.
|
||||||
|
/// </summary>
|
||||||
|
public int Year { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the month of the calendar event.
|
||||||
|
/// </summary>
|
||||||
|
public int Month { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the day of the calendar event.
|
||||||
|
/// </summary>
|
||||||
|
public int Day { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="CalendarEvent"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="year">The year of the calendar event.</param>
|
||||||
|
/// <param name="month">The month of the calendar event.</param>
|
||||||
|
/// <param name="day">The day of the calendar event.</param>
|
||||||
|
public CalendarEvent(int year, int month, int day)
|
||||||
|
: this(string.Empty, year, month, day)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="CalendarEvent"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="description">The calendar event description.</param>
|
||||||
|
/// <param name="year">The year of the calendar event.</param>
|
||||||
|
/// <param name="month">The month of the calendar event.</param>
|
||||||
|
/// <param name="day">The day of the calendar event.</param>
|
||||||
|
public CalendarEvent(string description, int year, int month, int day)
|
||||||
|
{
|
||||||
|
Description = description ?? string.Empty;
|
||||||
|
Year = year;
|
||||||
|
Month = month;
|
||||||
|
Day = day;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -50,6 +50,29 @@ namespace Spectre.Console
|
|||||||
_items = new List<IRenderable>(items.Select(item => new Markup(item)));
|
_items = new List<IRenderable>(items.Select(item => new Markup(item)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
protected override Measurement Measure(RenderContext context, int maxWidth)
|
||||||
|
{
|
||||||
|
var maxPadding = Math.Max(Padding.Left, Padding.Right);
|
||||||
|
|
||||||
|
var itemWidths = _items.Select(item => item.Measure(context, maxWidth).Max).ToArray();
|
||||||
|
var columnCount = CalculateColumnCount(maxWidth, itemWidths, _items.Count, maxPadding);
|
||||||
|
|
||||||
|
var rows = _items.Count / columnCount;
|
||||||
|
var greatestWidth = 0;
|
||||||
|
for (var row = 0; row < rows; row += columnCount)
|
||||||
|
{
|
||||||
|
var widths = itemWidths.Skip(row * columnCount).Take(columnCount).ToList();
|
||||||
|
var totalWidth = widths.Sum() + (maxPadding * (widths.Count - 1));
|
||||||
|
if (totalWidth > greatestWidth)
|
||||||
|
{
|
||||||
|
greatestWidth = totalWidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Measurement(greatestWidth, greatestWidth);
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
|
protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user