Merge pull request #531 from zspitz/main

Some dotnet script improvements; Animal (VB.NET)
This commit is contained in:
Jeff Atwood
2022-01-20 19:08:05 -08:00
committed by GitHub
18 changed files with 500 additions and 22 deletions

View File

@@ -10,6 +10,11 @@ public static class Extensions {
src.Select(x => selector(x.Item1, x.Item2, x.Item3)); src.Select(x => selector(x.Item1, x.Item2, x.Item3));
public static IEnumerable<(T1, T2, int)> WithIndex<T1, T2>(this IEnumerable<(T1, T2)> src) => src.Select((x, index) => (x.Item1, x.Item2, index)); public static IEnumerable<(T1, T2, int)> WithIndex<T1, T2>(this IEnumerable<(T1, T2)> src) => src.Select((x, index) => (x.Item1, x.Item2, index));
public static bool None<T>(this IEnumerable<T> src, Func<T, bool>? predicate = null) =>
predicate is null ?
!src.Any() :
!src.Any(predicate);
public static bool IsNullOrWhitespace([NotNullWhen(false)] this string? s) => string.IsNullOrWhiteSpace(s); public static bool IsNullOrWhitespace([NotNullWhen(false)] this string? s) => string.IsNullOrWhiteSpace(s);
[return: NotNullIfNotNull("path")] [return: NotNullIfNotNull("path")]

View File

@@ -17,6 +17,7 @@ var actions = new (Action action, string description)[] {
(multipleProjs, "Output multiple project files"), (multipleProjs, "Output multiple project files"),
(checkProjects, "Check .csproj/.vbproj files for target framework, nullability etc."), (checkProjects, "Check .csproj/.vbproj files for target framework, nullability etc."),
(checkExecutableProject, "Check that there is at least one executable project per port"), (checkExecutableProject, "Check that there is at least one executable project per port"),
(noCodeFiles, "Output ports without any code files"),
(printPortInfo, "Print info about a single port"), (printPortInfo, "Print info about a single port"),
(generateMissingSlns, "Generate solution files when missing"), (generateMissingSlns, "Generate solution files when missing"),
@@ -87,7 +88,7 @@ void printInfos() {
} }
void missingSln() { void missingSln() {
var data = infos.Where(x => !x.Slns.Any()).ToArray(); var data = infos.Where(x => x.Slns.None()).ToArray();
foreach (var item in data) { foreach (var item in data) {
WriteLine(item.LangPath); WriteLine(item.LangPath);
} }
@@ -98,7 +99,7 @@ void missingSln() {
void unexpectedSlnName() { void unexpectedSlnName() {
var counter = 0; var counter = 0;
foreach (var item in infos) { foreach (var item in infos) {
if (!item.Slns.Any()) { continue; } if (item.Slns.None()) { continue; }
var expectedSlnName = $"{item.GameName}.sln"; var expectedSlnName = $"{item.GameName}.sln";
if (item.Slns.Contains(Combine(item.LangPath, expectedSlnName), StringComparer.InvariantCultureIgnoreCase)) { continue; } if (item.Slns.Contains(Combine(item.LangPath, expectedSlnName), StringComparer.InvariantCultureIgnoreCase)) { continue; }
@@ -125,7 +126,7 @@ void multipleSlns() {
} }
void missingProj() { void missingProj() {
var data = infos.Where(x => !x.Projs.Any()).ToArray(); var data = infos.Where(x => x.Projs.None()).ToArray();
foreach (var item in data) { foreach (var item in data) {
WriteLine(item.LangPath); WriteLine(item.LangPath);
} }
@@ -136,7 +137,7 @@ void missingProj() {
void unexpectedProjName() { void unexpectedProjName() {
var counter = 0; var counter = 0;
foreach (var item in infos) { foreach (var item in infos) {
if (!item.Projs.Any()) { continue; } if (item.Projs.None()) { continue; }
var expectedProjName = $"{item.GameName}.{item.ProjExt}"; var expectedProjName = $"{item.GameName}.{item.ProjExt}";
if (item.Projs.Contains(Combine(item.LangPath, expectedProjName))) { continue; } if (item.Projs.Contains(Combine(item.LangPath, expectedProjName))) { continue; }
@@ -164,7 +165,7 @@ void multipleProjs() {
} }
void generateMissingSlns() { void generateMissingSlns() {
foreach (var item in infos.Where(x => !x.Slns.Any())) { foreach (var item in infos.Where(x => x.Slns.None())) {
var result = RunProcess("dotnet", $"new sln -n {item.GameName} -o {item.LangPath}"); var result = RunProcess("dotnet", $"new sln -n {item.GameName} -o {item.LangPath}");
WriteLine(result); WriteLine(result);
@@ -177,7 +178,7 @@ void generateMissingSlns() {
} }
void generateMissingProjs() { void generateMissingProjs() {
foreach (var item in infos.Where(x => !x.Projs.Any())) { foreach (var item in infos.Where(x => x.Projs.None())) {
// We can't use the dotnet command to create a new project using the built-in console template, because part of that template // We can't use the dotnet command to create a new project using the built-in console template, because part of that template
// is a Program.cs / Program.vb file. If there already are code files, there's no need to add a new empty one; and // is a Program.cs / Program.vb file. If there already are code files, there's no need to add a new empty one; and
// if there's already such a file, it might try to overwrite it. // if there's already such a file, it might try to overwrite it.
@@ -216,8 +217,8 @@ void generateMissingProjs() {
void checkProjects() { void checkProjects() {
foreach (var info in infos) { foreach (var info in infos) {
WriteLine(info.LangPath);
printProjectWarnings(info); printProjectWarnings(info);
WriteLine();
} }
} }
@@ -231,13 +232,15 @@ void printProjectWarnings(PortInfo info) {
nullable, nullable,
implicitUsing, implicitUsing,
rootNamespace, rootNamespace,
langVersion langVersion,
optionStrict
) = ( ) = (
getValue(parent, "TargetFramework", "TargetFrameworks"), getValue(parent, "TargetFramework", "TargetFrameworks"),
getValue(parent, "Nullable"), getValue(parent, "Nullable"),
getValue(parent, "ImplicitUsings"), getValue(parent, "ImplicitUsings"),
getValue(parent, "RootNamespace"), getValue(parent, "RootNamespace"),
getValue(parent, "LangVersion") getValue(parent, "LangVersion"),
getValue(parent, "OptionStrict")
); );
if (framework != "net6.0") { if (framework != "net6.0") {
@@ -266,6 +269,9 @@ void printProjectWarnings(PortInfo info) {
if (langVersion != "16.9") { if (langVersion != "16.9") {
warnings.Add($"LangVersion: {langVersion}"); warnings.Add($"LangVersion: {langVersion}");
} }
if (optionStrict != "On") {
warnings.Add($"OptionStrict: {optionStrict}");
}
} }
if (warnings.Any()) { if (warnings.Any()) {
@@ -284,6 +290,15 @@ void checkExecutableProject() {
} }
} }
void noCodeFiles() {
var qry = infos
.Where(x => x.CodeFiles.None())
.OrderBy(x => x.Lang);
foreach (var item in qry) {
WriteLine(item.LangPath);
}
}
void tryBuild() { void tryBuild() {
// if has code files, try to build // if has code files, try to build
} }

View File

@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<RootNamespace>Animal.Tests</RootNamespace>
<TargetFramework>net6.0</TargetFramework>
<IsPackable>false</IsPackable>
<OptionStrict>On</OptionStrict>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="3.1.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Animal\Animal.vbproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,65 @@
Imports System.IO
Public Class MockConsole
Inherits ConsoleAdapterBase
Private inputs As Queue(Of String)
Public ReadOnly Lines As New List(Of (line As String, centered As Boolean)) From {
("", False)
}
' TODO it's possible to clear all the lines, and we'd have to check once again in WriteString and WriteCenteredLine if there are any lines
Sub New(Inputs As IEnumerable(Of String))
Me.inputs = New Queue(Of String)(Inputs)
End Sub
Private Sub CheckLinesInitialized()
If Lines.Count = 0 Then Lines.Add(("", False))
End Sub
Private Sub WriteString(s As String, Optional centered As Boolean = False)
If s Is Nothing Then Return
CheckLinesInitialized()
s.Split(Environment.NewLine).ForEach(Sub(line, index)
If index = 0 Then
Dim currentLast = Lines(Lines.Count - 1)
' centered should never come from the current last line
' if WriteCenteredLine is called, it immediately creates a new line
Lines(Lines.Count - 1) = (currentLast.line + line, centered)
Else
Lines.Add((line, centered))
End If
End Sub)
End Sub
Public Overrides Sub Write(value As Object)
WriteString(value?.ToString)
End Sub
Public Overrides Sub WriteLine(value As Object)
WriteString(value?.ToString)
WriteLine()
End Sub
Public Overrides Sub WriteLine()
Lines.Add(("", False))
End Sub
Public Overrides Sub WriteCenteredLine(value As Object)
If Lines.Count = 0 Then Lines.Add(("", False))
Dim currentLast = Lines(Lines.Count - 1).line
If currentLast.Length > 0 Then Throw New InvalidOperationException("Can only write centered line if cursor is at start of line.")
WriteString(value?.ToString, True)
WriteLine()
End Sub
Public Overrides Function ReadLine() As String
' Indicates the end of a test run, for programs which loop endlessly
If inputs.Count = 0 Then Throw New EndOfStreamException("End of inputs")
Dim nextInput = inputs.Dequeue.Trim
WriteLine(nextInput)
Return nextInput
End Function
End Class

View File

@@ -0,0 +1,80 @@
Imports Xunit
Imports Animal
Imports System.IO
Public Class TestContainer
Private Shared Function ResponseVariantExpander(src As IEnumerable(Of String)) As TheoryData(Of String)
Dim theoryData = New TheoryData(Of String)
src.
SelectMany(Function(x) {x, x.Substring(0, 1)}).
SelectMany(Function(x) {
x,
x.ToUpperInvariant,
x.ToLowerInvariant,
x.ToTitleCase,
x.ToReverseCase
}).
Distinct.
ForEach(Sub(x) theoryData.Add(x))
Return theoryData
End Function
Private Shared YesVariantsThepryData As TheoryData(Of String) = ResponseVariantExpander({"yes", "true", "1"})
Private Shared Function YesVariants() As TheoryData(Of String)
Return YesVariantsThepryData
End Function
Private Shared NoVariantsThepryData As TheoryData(Of String) = ResponseVariantExpander({"no", "false", "0"})
Private Shared Function NoVariants() As TheoryData(Of String)
Return NoVariantsThepryData
End Function
''' <summary>Test LIST variants</summary>
<Theory>
<InlineData("LIST")>
<InlineData("list")>
<InlineData("List")>
<InlineData("lIST")>
Sub List(listResponse As String)
Dim console As New MockConsole({listResponse})
Dim game As New Game(console)
Assert.Throws(Of EndOfStreamException)(Sub() game.BeginLoop())
Assert.Equal(
{
"ANIMALS I ALREADY KNOW ARE:",
"FISH BIRD "
},
console.Lines.Slice(-4, -2).Select(Function(x) x.line)
)
End Sub
'' <summary>Test YES variants</summary>
<Theory>
<MemberData(NameOf(YesVariants))>
Sub YesVariant(yesVariant As String)
Dim console As New MockConsole({yesVariant})
Dim game As New Game(console)
Assert.Throws(Of EndOfStreamException)(Sub() game.BeginLoop())
Assert.Equal(
{
$"ARE YOU THINKING OF AN ANIMAL? {yesVariant}",
"DOES IT SWIM? "
},
console.Lines.Slice(-2, 0).Select(Function(x) x.line)
)
End Sub
'' <summary>Test NO variants</summary>
<Theory>
<MemberData(NameOf(NoVariants))>
Sub NoVariant(noVariant As String)
Dim console As New MockConsole({"y", noVariant})
Dim game As New Game(console)
Assert.Throws(Of EndOfStreamException)(Sub() game.BeginLoop())
Assert.Equal(
{
$"DOES IT SWIM? {noVariant}",
"IS IT A BIRD? "
},
console.Lines.Slice(-2, 0).Select(Function(x) x.line)
)
End Sub
End Class

View File

@@ -1,22 +1,31 @@
 
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16 # Visual Studio Version 17
VisualStudioVersion = 16.0.30114.105 VisualStudioVersion = 17.0.32112.339
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "Animal", "Animal.vbproj", "{147D66D5-D817-4024-9447-9F5B9A6D2B7D}" Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "Animal", "Animal\Animal.vbproj", "{5517E4CE-BCF9-4D1F-9A17-B620C1B96B0D}"
EndProject
Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "Animal.Tests", "Animal.Tests\Animal.Tests.vbproj", "{3986C6A2-77D4-4F00-B3CF-F5736C623B1E}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU Release|Any CPU = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{5517E4CE-BCF9-4D1F-9A17-B620C1B96B0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5517E4CE-BCF9-4D1F-9A17-B620C1B96B0D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5517E4CE-BCF9-4D1F-9A17-B620C1B96B0D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5517E4CE-BCF9-4D1F-9A17-B620C1B96B0D}.Release|Any CPU.Build.0 = Release|Any CPU
{3986C6A2-77D4-4F00-B3CF-F5736C623B1E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3986C6A2-77D4-4F00-B3CF-F5736C623B1E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3986C6A2-77D4-4F00-B3CF-F5736C623B1E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3986C6A2-77D4-4F00-B3CF-F5736C623B1E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
EndGlobalSection EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
{147D66D5-D817-4024-9447-9F5B9A6D2B7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU SolutionGuid = {88469A47-E30C-4763-A325-074101D16608}
{147D66D5-D817-4024-9447-9F5B9A6D2B7D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{147D66D5-D817-4024-9447-9F5B9A6D2B7D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{147D66D5-D817-4024-9447-9F5B9A6D2B7D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
EndGlobal EndGlobal

View File

@@ -4,5 +4,6 @@
<RootNamespace>Animal</RootNamespace> <RootNamespace>Animal</RootNamespace>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<LangVersion>16.9</LangVersion> <LangVersion>16.9</LangVersion>
<OptionStrict>On</OptionStrict>
</PropertyGroup> </PropertyGroup>
</Project> </Project>

View File

@@ -0,0 +1,29 @@
Public Class Branch
Public Property Text As String
Public ReadOnly Property IsEnd As Boolean
Get
Return Yes Is Nothing AndAlso No Is Nothing
End Get
End Property
Public Property Yes As Branch
Public Property No As Branch
' Allows walking all the descendants recursively
Public Iterator Function DescendantTexts() As IEnumerable(Of String)
If Yes IsNot Nothing Then
Yield Yes.Text
For Each childText In Yes.DescendantTexts
Yield childText
Next
End If
If No IsNot Nothing Then
Yield No.Text
For Each childText In No.DescendantTexts
Yield childText
Next
End If
End Function
End Class

View File

@@ -0,0 +1,152 @@
Option Compare Text
Public Class Game
' This Dictionary holds the corresponding value for each of the variants of "YES" and "NO" we accept
' Note that the Dictionary is case-insensitive, meaning it maps "YES", "yes" and even "yEs" to True
Private Shared ReadOnly YesNoResponses As New Dictionary(Of String, Boolean)(StringComparer.InvariantCultureIgnoreCase) From {
{"yes", True},
{"y", True},
{"true", True},
{"t", True},
{"1", True},
{"no", False},
{"n", False},
{"false", False},
{"f", False},
{"0", False}
}
ReadOnly console As ConsoleAdapterBase
' The pre-initialized root branch
ReadOnly root As New Branch With {
.Text = "DOES IT SWIM?",
.Yes = New Branch With {.Text = "FISH"},
.No = New Branch With {.Text = "BIRD"}
}
''' <summary>Reduces a string or console input to True, False or Nothing. Case-insensitive.</summary>
''' <param name="s">Optional String to reduce via the same logic. If not passed in, will use console.ReadLine</param>
''' <returns>
''' Returns True for a "yes" response (yes, y, true, t, 1) and False for a "no" response (no, n, false, f, 0).<br/>
''' Returns Nothing if the response doesn't match any of these.
''' </returns>
Private Function GetYesNo(Optional s As String = Nothing) As Boolean?
s = If(s, console.ReadLine)
Dim ret As Boolean
If YesNoResponses.TryGetValue(s, ret) Then Return ret
Return Nothing
End Function
Sub New(console As ConsoleAdapterBase)
If console Is Nothing Then Throw New ArgumentNullException(NameOf(console))
Me.console = console
End Sub
Sub BeginLoop()
' Print the program heading
console.WriteCenteredLine("ANIMAL")
console.WriteCenteredLine("CREATIVE COMPUTING MORRISTOWN, NEW JERSEY")
console.Write(
"
PLAY 'GUESS THE ANIMAL'
THINK OF AN ANIMAL AND THE COMPUTER WILL TRY TO GUESS IT.
")
Do
console.Write("ARE YOU THINKING OF AN ANIMAL? ")
Dim response = console.ReadLine
If response = "list" Then
' List all the stored animals
console.WriteLine(
"
ANIMALS I ALREADY KNOW ARE:")
' We're using a ForEach extension method instead of the regular For Each loop to provide the index alongside the text
root.DescendantTexts.ForEach(Sub(text, index)
' We want to move to the next line after every four animals
' But for the first animal, where the index is 0, 0 Mod 4 will also return 0
' So we have to explicitly exclude the first animal
If index > 0 AndAlso index Mod 4 = 0 Then console.WriteLine()
console.Write($"{text.MaxLength(15),-15}")
End Sub)
console.WriteLine(
"
")
Continue Do
End If
Dim ynResponse = GetYesNo(response)
If ynResponse Is Nothing OrElse Not ynResponse Then Continue Do
Dim currentBranch = root
Do While Not currentBranch.IsEnd
' Branches can either be questions, or end branches
' We have to walk the questions, prompting each time for "yes" or "no"
console.Write($"{currentBranch.Text} ")
Do
ynResponse = GetYesNo()
Loop While ynResponse Is Nothing
' Depending on the answer, we'll follow either the branch at "Yes" or "No"
currentBranch = If(
ynResponse,
currentBranch.Yes,
currentBranch.No
)
Loop
' Now we're at an end branch
console.Write($"IS IT A {currentBranch.Text}? ")
ynResponse = GetYesNo()
If ynResponse Then ' Only if ynResponse = True will we go into this If Then
console.WriteLine("WHY NOT TRY ANOTHER ANIMAL?")
Continue Do
End If
' Get the new animal
console.Write("THE ANIMAL YOU WERE THINKING OF WAS A ? ")
Dim newAnimal = console.ReadLine
' Get the question used to distinguish the new animal from the current end branch
console.WriteLine(
$"PLEASE TYPE IN A QUESTION THAT WOULD DISTINGUISH A
{newAnimal} FROM A {currentBranch.Text}")
Dim newQuestion = console.ReadLine
' Get the answer to that question, for the new animal
' for the old animal, the answer would be the opposite
console.Write(
$"FOR A {newAnimal} THE ANSWER WOULD BE ? ")
Do
ynResponse = GetYesNo()
Loop While ynResponse Is Nothing
' Create the new end branch for the new animal
Dim newBranch = New Branch With {.Text = newAnimal}
' Copy over the current animal to another new end branch
Dim currentBranchCopy = New Branch With {.Text = currentBranch.Text}
' Make the current branch into the distinguishing question
currentBranch.Text = newQuestion
' Set the Yes and No branches of the current branch according to the answer
If ynResponse Then
currentBranch.Yes = newBranch
currentBranch.No = currentBranchCopy
Else
currentBranch.No = newBranch
currentBranch.Yes = currentBranchCopy
End If
' TODO how do we exit?
Loop
End Sub
End Class

View File

@@ -0,0 +1,6 @@
Module Program
Sub Main()
Dim game As New Game(New ConsoleAdapter)
game.BeginLoop()
End Sub
End Module

View File

@@ -0,0 +1,29 @@
Public Class ConsoleAdapter
Inherits ConsoleAdapterBase
Public Overrides Sub Write(value As Object)
Console.Write(value)
End Sub
Public Overrides Sub WriteLine(value As Object)
Console.WriteLine(value)
End Sub
Public Overrides Sub WriteLine()
Console.WriteLine()
End Sub
Public Overrides Sub WriteCenteredLine(value As Object)
If Console.CursorLeft <> 0 Then Throw New InvalidOperationException("Can only write centered line if cursor is at start of line.")
Dim toWrite = If(value?.ToString, "")
Console.WriteLine($"{Space((Console.WindowWidth - toWrite.Length) \ 2)}{toWrite}")
End Sub
Public Overrides Function ReadLine() As String
Dim response As String
Do
response = Console.ReadLine
Loop While response Is Nothing
Return response.Trim
End Function
End Class

View File

@@ -0,0 +1,9 @@
Public MustInherit Class ConsoleAdapterBase
Public MustOverride Sub Write(value As Object)
Public MustOverride Sub WriteLine(value As Object)
Public MustOverride Sub WriteLine()
Public MustOverride Sub WriteCenteredLine(value As Object)
''' <summary>Implementations should always return a String without leading or trailing whitespace, never Nothng</summary>
Public MustOverride Function ReadLine() As String
End Class

View File

@@ -0,0 +1,50 @@
Imports System.Runtime.CompilerServices
Public Module Extensions
<Extension> Public Sub ForEach(Of T)(src As IEnumerable(Of T), action As Action(Of T))
For Each x In src
action(x)
Next
End Sub
<Extension> Public Sub ForEach(Of T)(src As IEnumerable(Of T), action As Action(Of T, Integer))
Dim index As Integer
For Each x In src
action(x, index)
index += 1
Next
End Sub
<Extension> Public Function MaxLength(s As String, value As Integer) As String
If s Is Nothing Then Return Nothing
Return s.Substring(0, Math.Min(s.Length, value))
End Function
<Extension> Public Function ForceEndsWith(s As String, toAppend As String) As String
If Not s.EndsWith(toAppend, StringComparison.OrdinalIgnoreCase) Then s += toAppend
Return s
End Function
<Extension> Public Function ToTitleCase(s As String) As String
If s Is Nothing Then Return Nothing
Return Char.ToUpperInvariant(s(0)) + s.Substring(1).ToUpperInvariant
End Function
' https://stackoverflow.com/a/3681580/111794
<Extension> Public Function ToReverseCase(s As String) As String
If s Is Nothing Then Return Nothing
Return New String(s.Select(Function(c) If(
Not Char.IsLetter(c),
c,
If(
Char.IsUpper(c), Char.ToLowerInvariant(c), Char.ToUpperInvariant(c)
)
)).ToArray)
End Function
' https://stackoverflow.com/a/58132204/111794
<Extension> Public Function Slice(Of T)(lst As IList(Of T), start As Integer, [end] As Integer) As T()
start = If(start >= 0, start, lst.Count + start)
[end] = If([end] > 0, [end], lst.Count + [end])
Return lst.Skip(start).Take([end] - start).ToArray
End Function
End Module

View File

@@ -3,7 +3,6 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework> <TargetFramework>net5.0</TargetFramework>
<RootNamespace>BasicComputerGames.Bagels</RootNamespace>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@@ -2,8 +2,9 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<RootNamespace>banner</RootNamespace> <RootNamespace>Banner</RootNamespace>
<TargetFramework>netcoreapp3.1</TargetFramework> <TargetFramework>netcoreapp3.1</TargetFramework>
<LangVersion>16.9</LangVersion>
</PropertyGroup> </PropertyGroup>
</Project> </Project>

View File

@@ -2,8 +2,9 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<RootNamespace>batnum</RootNamespace> <RootNamespace>Batnum</RootNamespace>
<TargetFramework>netcoreapp3.1</TargetFramework> <TargetFramework>netcoreapp3.1</TargetFramework>
<LangVersion>16.9</LangVersion>
</PropertyGroup> </PropertyGroup>
</Project> </Project>

View File

@@ -3,7 +3,6 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework> <TargetFramework>net5.0</TargetFramework>
<RootNamespace>_21_calendar</RootNamespace>
</PropertyGroup> </PropertyGroup>
</Project> </Project>

View File

@@ -2,8 +2,9 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<RootNamespace>word</RootNamespace> <RootNamespace>Word</RootNamespace>
<TargetFramework>netcoreapp3.1</TargetFramework> <TargetFramework>netcoreapp3.1</TargetFramework>
<LangVersion>16.9</LangVersion>
</PropertyGroup> </PropertyGroup>
</Project> </Project>