mirror of
https://github.com/coding-horror/basic-computer-games.git
synced 2025-12-22 23:26:40 -08:00
364 lines
10 KiB
C#
364 lines
10 KiB
C#
using System.Xml.Linq;
|
|
using DotnetUtils;
|
|
using static System.Console;
|
|
using static System.IO.Path;
|
|
using static DotnetUtils.Methods;
|
|
using static DotnetUtils.Functions;
|
|
|
|
var infos = PortInfos.Get;
|
|
|
|
var actions = new (Action action, string description)[] {
|
|
(printInfos, "Output information -- solution, project, and code files"),
|
|
(missingSln, "Output missing sln"),
|
|
(unexpectedSlnName, "Output misnamed sln"),
|
|
(multipleSlns, "Output multiple sln files"),
|
|
(missingProj, "Output missing project file"),
|
|
(unexpectedProjName, "Output misnamed project files"),
|
|
(multipleProjs, "Output multiple project files"),
|
|
(checkProjects, "Check .csproj/.vbproj files for target framework, nullability etc."),
|
|
(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"),
|
|
|
|
(generateMissingSlns, "Generate solution files when missing"),
|
|
(generateMissingProjs, "Generate project files when missing")
|
|
};
|
|
|
|
foreach (var (_, description, index) in actions.WithIndex()) {
|
|
WriteLine($"{index}: {description}");
|
|
}
|
|
|
|
WriteLine();
|
|
|
|
actions[getChoice(actions.Length - 1)].action();
|
|
|
|
void printSlns(PortInfo pi) {
|
|
switch (pi.Slns.Length) {
|
|
case 0:
|
|
WriteLine("No sln");
|
|
break;
|
|
case 1:
|
|
WriteLine($"Solution: {pi.Slns[0].RelativePath(pi.LangPath)}");
|
|
break;
|
|
case > 1:
|
|
WriteLine("Solutions:");
|
|
foreach (var sln in pi.Slns) {
|
|
Write(sln.RelativePath(pi.LangPath));
|
|
WriteLine();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void printProjs(PortInfo pi) {
|
|
switch (pi.Projs.Length) {
|
|
case 0:
|
|
WriteLine("No project");
|
|
break;
|
|
case 1:
|
|
WriteLine($"Project: {pi.Projs[0].RelativePath(pi.LangPath)}");
|
|
break;
|
|
case > 1:
|
|
WriteLine("Projects:");
|
|
foreach (var proj in pi.Projs) {
|
|
Write(proj.RelativePath(pi.LangPath));
|
|
WriteLine();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void printInfos() {
|
|
foreach (var item in infos) {
|
|
WriteLine(item.LangPath);
|
|
WriteLine();
|
|
|
|
printSlns(item);
|
|
WriteLine();
|
|
|
|
printProjs(item);
|
|
WriteLine();
|
|
|
|
// get code files
|
|
foreach (var file in item.CodeFiles) {
|
|
WriteLine(file.RelativePath(item.LangPath));
|
|
}
|
|
WriteLine(new string('-', 50));
|
|
}
|
|
}
|
|
|
|
void missingSln() {
|
|
var data = infos.Where(x => x.Slns.None()).ToArray();
|
|
foreach (var item in data) {
|
|
WriteLine(item.LangPath);
|
|
}
|
|
WriteLine();
|
|
WriteLine($"Count: {data.Length}");
|
|
}
|
|
|
|
void unexpectedSlnName() {
|
|
var counter = 0;
|
|
foreach (var item in infos) {
|
|
if (item.Slns.None()) { continue; }
|
|
|
|
var expectedSlnName = $"{item.GameName}.sln";
|
|
if (item.Slns.Contains(Combine(item.LangPath, expectedSlnName), StringComparer.InvariantCultureIgnoreCase)) { continue; }
|
|
|
|
counter += 1;
|
|
WriteLine(item.LangPath);
|
|
WriteLine($"Expected: {expectedSlnName}");
|
|
|
|
printSlns(item);
|
|
|
|
WriteLine();
|
|
}
|
|
WriteLine($"Count: {counter}");
|
|
}
|
|
|
|
void multipleSlns() {
|
|
var data = infos.Where(x => x.Slns.Length > 1).ToArray();
|
|
foreach (var item in data) {
|
|
WriteLine(item.LangPath);
|
|
printSlns(item);
|
|
}
|
|
WriteLine();
|
|
WriteLine($"Count: {data.Length}");
|
|
}
|
|
|
|
void missingProj() {
|
|
var data = infos.Where(x => x.Projs.None()).ToArray();
|
|
foreach (var item in data) {
|
|
WriteLine(item.LangPath);
|
|
}
|
|
WriteLine();
|
|
WriteLine($"Count: {data.Length}");
|
|
}
|
|
|
|
void unexpectedProjName() {
|
|
var counter = 0;
|
|
foreach (var item in infos) {
|
|
if (item.Projs.None()) { continue; }
|
|
|
|
var expectedProjName = $"{item.GameName}.{item.ProjExt}";
|
|
if (item.Projs.Contains(Combine(item.LangPath, expectedProjName))) { continue; }
|
|
|
|
counter += 1;
|
|
WriteLine(item.LangPath);
|
|
WriteLine($"Expected: {expectedProjName}");
|
|
|
|
printProjs(item);
|
|
|
|
WriteLine();
|
|
}
|
|
WriteLine($"Count: {counter}");
|
|
}
|
|
|
|
void multipleProjs() {
|
|
var data = infos.Where(x => x.Projs.Length > 1).ToArray();
|
|
foreach (var item in data) {
|
|
WriteLine(item.LangPath);
|
|
WriteLine();
|
|
printProjs(item);
|
|
}
|
|
WriteLine();
|
|
WriteLine($"Count: {data.Length}");
|
|
}
|
|
|
|
void generateMissingSlns() {
|
|
foreach (var item in infos.Where(x => x.Slns.None())) {
|
|
var result = RunProcess("dotnet", $"new sln -n {item.GameName} -o {item.LangPath}");
|
|
WriteLine(result);
|
|
|
|
var slnFullPath = Combine(item.LangPath, $"{item.GameName}.sln");
|
|
foreach (var proj in item.Projs) {
|
|
result = RunProcess("dotnet", $"sln {slnFullPath} add {proj}");
|
|
WriteLine(result);
|
|
}
|
|
}
|
|
}
|
|
|
|
void generateMissingProjs() {
|
|
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
|
|
// 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.
|
|
|
|
var projText = item.Lang switch {
|
|
"csharp" => @"<Project Sdk=""Microsoft.NET.Sdk"">
|
|
<PropertyGroup>
|
|
<OutputType>Exe</OutputType>
|
|
<TargetFramework>net6.0</TargetFramework>
|
|
<LangVersion>10</LangVersion>
|
|
<ImplicitUsings>enable</ImplicitUsings>
|
|
<Nullable>enable</Nullable>
|
|
</PropertyGroup>
|
|
</Project>
|
|
",
|
|
"vbnet" => @$"<Project Sdk=""Microsoft.NET.Sdk"">
|
|
<PropertyGroup>
|
|
<OutputType>Exe</OutputType>
|
|
<RootNamespace>{item.GameName}</RootNamespace>
|
|
<TargetFramework>net6.0</TargetFramework>
|
|
<LangVersion>16.9</LangVersion>
|
|
</PropertyGroup>
|
|
</Project>
|
|
",
|
|
_ => throw new InvalidOperationException()
|
|
};
|
|
var projFullPath = Combine(item.LangPath, $"{item.GameName}.{item.ProjExt}");
|
|
File.WriteAllText(projFullPath, projText);
|
|
|
|
if (item.Slns.Length == 1) {
|
|
var result = RunProcess("dotnet", $"sln {item.Slns[0]} add {projFullPath}");
|
|
WriteLine(result);
|
|
}
|
|
}
|
|
}
|
|
|
|
void checkProjects() {
|
|
foreach (var info in infos) {
|
|
WriteLine(info.LangPath);
|
|
printProjectWarnings(info);
|
|
}
|
|
}
|
|
|
|
void printProjectWarnings(PortInfo info) {
|
|
foreach (var proj in info.Projs) {
|
|
var warnings = new List<string>();
|
|
var parent = XDocument.Load(proj).Element("Project")?.Element("PropertyGroup");
|
|
|
|
var (
|
|
framework,
|
|
nullable,
|
|
implicitUsing,
|
|
rootNamespace,
|
|
langVersion,
|
|
optionStrict
|
|
) = (
|
|
getValue(parent, "TargetFramework", "TargetFrameworks"),
|
|
getValue(parent, "Nullable"),
|
|
getValue(parent, "ImplicitUsings"),
|
|
getValue(parent, "RootNamespace"),
|
|
getValue(parent, "LangVersion"),
|
|
getValue(parent, "OptionStrict")
|
|
);
|
|
|
|
if (framework != "net6.0") {
|
|
warnings.Add($"Target: {framework}");
|
|
}
|
|
|
|
if (info.Lang == "csharp") {
|
|
if (nullable != "enable") {
|
|
warnings.Add($"Nullable: {nullable}");
|
|
}
|
|
if (implicitUsing != "enable") {
|
|
warnings.Add($"ImplicitUsings: {implicitUsing}");
|
|
}
|
|
if (rootNamespace != null && rootNamespace != info.GameName) {
|
|
warnings.Add($"RootNamespace: {rootNamespace}");
|
|
}
|
|
if (langVersion != "10") {
|
|
warnings.Add($"LangVersion: {langVersion}");
|
|
}
|
|
}
|
|
|
|
if (info.Lang == "vbnet") {
|
|
if (rootNamespace != info.GameName) {
|
|
warnings.Add($"RootNamespace: {rootNamespace}");
|
|
}
|
|
if (langVersion != "16.9") {
|
|
warnings.Add($"LangVersion: {langVersion}");
|
|
}
|
|
if (optionStrict != "On") {
|
|
warnings.Add($"OptionStrict: {optionStrict}");
|
|
}
|
|
}
|
|
|
|
if (warnings.Any()) {
|
|
WriteLine(proj.RelativePath(info.LangPath));
|
|
WriteLine(string.Join("\n", warnings));
|
|
WriteLine();
|
|
}
|
|
}
|
|
}
|
|
|
|
void checkExecutableProject() {
|
|
foreach (var item in infos) {
|
|
if (item.Projs.All(proj => getValue(proj, "OutputType") != "Exe")) {
|
|
WriteLine($"{item.LangPath}");
|
|
}
|
|
}
|
|
}
|
|
|
|
void noCodeFiles() {
|
|
var qry = infos
|
|
.Where(x => x.CodeFiles.None())
|
|
.OrderBy(x => x.Lang);
|
|
foreach (var item in qry) {
|
|
WriteLine(item.LangPath);
|
|
}
|
|
}
|
|
|
|
void tryBuild() {
|
|
// if has code files, try to build
|
|
}
|
|
|
|
void printPortInfo() {
|
|
// prompt for port number
|
|
Write("Enter number from 1 to 96 ");
|
|
var index = getChoice(1, 96);
|
|
|
|
Write("Enter 0 for C#, 1 for VB ");
|
|
var lang = getChoice(1) switch {
|
|
0 => "csharp",
|
|
1 => "vbnet",
|
|
_ => throw new InvalidOperationException()
|
|
};
|
|
|
|
WriteLine();
|
|
|
|
var info = infos.Single(x => x.Index == index && x.Lang == lang);
|
|
|
|
WriteLine(info.LangPath);
|
|
WriteLine(new string('-', 50));
|
|
|
|
// print solutions
|
|
printSlns(info);
|
|
|
|
// mismatched solution name/location? (expected x)
|
|
var expectedSlnName = Combine(info.LangPath, $"{info.GameName}.sln");
|
|
if (!info.Slns.Contains(expectedSlnName)) {
|
|
WriteLine($"Expected name/path: {expectedSlnName.RelativePath(info.LangPath)}");
|
|
}
|
|
|
|
// has executable project?
|
|
if (info.Projs.All(proj => getValue(proj, "OutputType") != "Exe")) {
|
|
WriteLine("No executable project");
|
|
}
|
|
|
|
WriteLine();
|
|
|
|
// print projects
|
|
printProjs(info);
|
|
|
|
// mimsatched project name/location? (expected x)
|
|
var expectedProjName = Combine(info.LangPath, $"{info.GameName}.{info.ProjExt}");
|
|
if (info.Projs.Length < 2 && !info.Projs.Contains(expectedProjName)) {
|
|
WriteLine($"Expected name/path: {expectedProjName.RelativePath(info.LangPath)}");
|
|
}
|
|
|
|
WriteLine();
|
|
|
|
// verify project properties
|
|
printProjectWarnings(info);
|
|
|
|
WriteLine("Code files:");
|
|
|
|
// list code files
|
|
foreach (var codeFile in info.CodeFiles) {
|
|
WriteLine(codeFile.RelativePath(info.LangPath));
|
|
}
|
|
|
|
// try build
|
|
}
|