diff --git a/README.MD b/README.MD index 115ee792..041bce85 100644 --- a/README.MD +++ b/README.MD @@ -85,6 +85,7 @@ Below are a list of the included namespaces with links to their README files: - [SabreTools.Metadata.DatFiles](https://github.com/SabreTools/SabreTools.Serialization/tree/main/SabreTools.Metadata.DatFiles) - [SabreTools.Metadata.DatItems](https://github.com/SabreTools/SabreTools.Serialization/tree/main/SabreTools.Metadata.Datitems) - [SabreTools.Metadata.Filter](https://github.com/SabreTools/SabreTools.Serialization/tree/main/SabreTools.Metadata.Filter) +- [SabreTools.Metadata.Reports](https://github.com/SabreTools/SabreTools.Serialization/tree/main/SabreTools.Metadata.Reports) - [SabreTools.ObjectIdentifier](https://github.com/SabreTools/SabreTools.Serialization/tree/main/SabreTools.ObjectIdentifier) - [SabreTools.Serialization.CrossModel](https://github.com/SabreTools/SabreTools.Serialization/tree/main/SabreTools.Serialization.CrossModel) - [SabreTools.Serialization.Readers](https://github.com/SabreTools/SabreTools.Serialization/tree/main/SabreTools.Serialization.Readers) diff --git a/SabreTools.Metadata.Reports/BaseReport.cs b/SabreTools.Metadata.Reports/BaseReport.cs new file mode 100644 index 00000000..e361abc3 --- /dev/null +++ b/SabreTools.Metadata.Reports/BaseReport.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using System.IO; +using SabreTools.Logging; +using SabreTools.Metadata.DatFiles; + +#pragma warning disable IDE0290 // Use primary constructor +namespace SabreTools.Metadata.Reports +{ + /// + /// Base class for a report output format + /// + public abstract class BaseReport + { + #region Logging + + /// + /// Logging object + /// + protected readonly Logger _logger = new(); + + #endregion + + /// + /// Set of DatStatistics objects to use for formatting + /// + protected List _statistics; + + /// + /// Create a new report from the filename + /// + /// List of statistics objects to set + public BaseReport(List statsList) + { + _statistics = statsList; + } + + /// + /// Create and open an output file for writing direct from a set of statistics + /// + /// Name of the file to write to + /// True if baddumps should be included in output, false otherwise + /// True if nodumps should be included in output, false otherwise + /// True if the error that is thrown should be thrown back to the caller, false otherwise + /// True if the report was written correctly, false otherwise + public bool WriteToFile(string? outfile, bool baddumpCol, bool nodumpCol, bool throwOnError = false) + { + InternalStopwatch watch = new($"Writing statistics to '{outfile}"); + + try + { + // Try to create the output file + FileStream stream = File.Create(outfile ?? string.Empty); + if (stream is null) + { + _logger.Warning($"File '{outfile}' could not be created for writing! Please check to see if the file is writable"); + return false; + } + + // Write to the stream + bool result = WriteToStream(stream, baddumpCol, nodumpCol, throwOnError); + + // Dispose of the stream + stream.Dispose(); + } + catch (Exception ex) when (!throwOnError) + { + _logger.Error(ex); + return false; + } + finally + { + watch.Stop(); + } + + return true; + } + + /// + /// Write a set of statistics to an input stream + /// + /// Stream to write to + /// True if baddumps should be included in output, false otherwise + /// True if nodumps should be included in output, false otherwise + /// True if the error that is thrown should be thrown back to the caller, false otherwise + /// True if the report was written correctly, false otherwise + public abstract bool WriteToStream(Stream stream, bool baddumpCol, bool nodumpCol, bool throwOnError = false); + } +} diff --git a/SabreTools.Metadata.Reports/Enums.cs b/SabreTools.Metadata.Reports/Enums.cs new file mode 100644 index 00000000..2ffdae60 --- /dev/null +++ b/SabreTools.Metadata.Reports/Enums.cs @@ -0,0 +1,38 @@ +namespace SabreTools.Metadata.Reports +{ + /// + /// Determine which format to output Stats to + /// + public enum StatReportFormat + { + /// + /// Only output to the console + /// + None, + + /// + /// Console-formatted + /// + Textfile, + + /// + /// ClrMamePro HTML + /// + HTML, + + /// + /// Comma-Separated Values (Standardized) + /// + CSV, + + /// + /// Semicolon-Separated Values (Standardized) + /// + SSV, + + /// + /// Tab-Separated Values (Standardized) + /// + TSV, + } +} diff --git a/SabreTools.Metadata.Reports/Formats/CommaSeparatedValue.cs b/SabreTools.Metadata.Reports/Formats/CommaSeparatedValue.cs new file mode 100644 index 00000000..d8aa8db2 --- /dev/null +++ b/SabreTools.Metadata.Reports/Formats/CommaSeparatedValue.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using SabreTools.Metadata.DatFiles; + +namespace SabreTools.Metadata.Reports.Formats +{ + /// + /// Represents a comma-separated value file + /// + public sealed class CommaSeparatedValue : SeparatedValue + { + /// + /// Create a new report from the filename + /// + /// List of statistics objects to set + public CommaSeparatedValue(List statsList) : base(statsList) + { + _delim = ','; + } + } +} diff --git a/SabreTools.Metadata.Reports/Formats/Html.cs b/SabreTools.Metadata.Reports/Formats/Html.cs new file mode 100644 index 00000000..6a4fa834 --- /dev/null +++ b/SabreTools.Metadata.Reports/Formats/Html.cs @@ -0,0 +1,329 @@ +using System; +using System.Collections.Generic; +using System.IO; +#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD +using System.Net; +#endif +using System.Text; +using System.Xml; +using SabreTools.Hashing; +using SabreTools.Metadata.DatFiles; +using SabreTools.Text.Extensions; +using ItemStatus = SabreTools.Data.Models.Metadata.ItemStatus; +using ItemType = SabreTools.Data.Models.Metadata.ItemType; + +#pragma warning disable IDE0290 // Use primary constructor +namespace SabreTools.Metadata.Reports.Formats +{ + /// + /// HTML report format + /// + /// TODO: Make output standard width, without making the entire thing a table + public class Html : BaseReport + { + /// + /// Create a new report from the filename + /// + /// List of statistics objects to set + public Html(List statsList) + : base(statsList) + { + } + + /// + public override bool WriteToStream(Stream stream, bool baddumpCol, bool nodumpCol, bool throwOnError = false) + { + try + { + XmlTextWriter xtw = new(stream, Encoding.UTF8) + { + Formatting = Formatting.Indented, + IndentChar = '\t', + Indentation = 1 + }; + + // Write out the header + WriteHeader(xtw, baddumpCol, nodumpCol); + + // Now process each of the statistics + for (int i = 0; i < _statistics.Count; i++) + { + // Get the current statistic + DatStatistics stat = _statistics[i]; + + // If we have a directory statistic + if (stat.IsDirectory) + { + WriteMidSeparator(xtw, baddumpCol, nodumpCol); + WriteIndividual(xtw, stat, baddumpCol, nodumpCol); + + // If we have anything but the last value, write the separator + if (i < _statistics.Count - 1) + { + WriteFooterSeparator(xtw, baddumpCol, nodumpCol); + WriteMidHeader(xtw, baddumpCol, nodumpCol); + } + } + + // If we have a normal statistic + else + { + WriteIndividual(xtw, stat, baddumpCol, nodumpCol); + } + } + + WriteFooter(xtw); +#if NET452_OR_GREATER + xtw.Dispose(); +#endif + } + catch (Exception ex) when (!throwOnError) + { + _logger.Error(ex); + return false; + } + + return true; + } + + /// + /// Write out the header to the stream, if any exists + /// + /// XmlTextWriter to write to + /// True if baddumps should be included in output, false otherwise + /// True if nodumps should be included in output, false otherwise + private static void WriteHeader(XmlTextWriter xtw, bool baddumpCol, bool nodumpCol) + { + xtw.WriteDocType("html", null, null, null); + xtw.WriteStartElement("html"); + + xtw.WriteStartElement("header"); + xtw.WriteElementString("title", "DAT Statistics Report"); + xtw.WriteElementString("style", @" +body { + background-color: lightgray; +} +.dir { + color: #0088FF; +}"); + xtw.WriteEndElement(); // header + + xtw.WriteStartElement("body"); + + xtw.WriteElementString("h2", $"DAT Statistics Report ({DateTime.Now:d})"); + + xtw.WriteStartElement("table"); + xtw.WriteAttributeString("border", "1"); + xtw.WriteAttributeString("cellpadding", "5"); + xtw.WriteAttributeString("cellspacing", "0"); + xtw.Flush(); + + // Now write the mid header for those who need it + WriteMidHeader(xtw, baddumpCol, nodumpCol); + } + + /// + /// Write out the mid-header to the stream, if any exists + /// + /// XmlTextWriter to write to + /// True if baddumps should be included in output, false otherwise + /// True if nodumps should be included in output, false otherwise + private static void WriteMidHeader(XmlTextWriter xtw, bool baddumpCol, bool nodumpCol) + { + xtw.WriteStartElement("tr"); + xtw.WriteAttributeString("bgcolor", "gray"); + + xtw.WriteElementString("th", "File Name"); + + xtw.WriteStartElement("th"); + xtw.WriteAttributeString("align", "right"); + xtw.WriteString("Total Size"); + xtw.WriteEndElement(); // th + + xtw.WriteStartElement("th"); + xtw.WriteAttributeString("align", "right"); + xtw.WriteString("Games"); + xtw.WriteEndElement(); // th + + xtw.WriteStartElement("th"); + xtw.WriteAttributeString("align", "right"); + xtw.WriteString("Roms"); + xtw.WriteEndElement(); // th + + xtw.WriteStartElement("th"); + xtw.WriteAttributeString("align", "right"); + xtw.WriteString("Disks"); + xtw.WriteEndElement(); // th + + xtw.WriteStartElement("th"); + xtw.WriteAttributeString("align", "right"); + xtw.WriteString("# with CRC"); + xtw.WriteEndElement(); // th + + xtw.WriteStartElement("th"); + xtw.WriteAttributeString("align", "right"); + xtw.WriteString("# with MD5"); + xtw.WriteEndElement(); // th + + xtw.WriteStartElement("th"); + xtw.WriteAttributeString("align", "right"); + xtw.WriteString("# with SHA-1"); + xtw.WriteEndElement(); // th + + xtw.WriteStartElement("th"); + xtw.WriteAttributeString("align", "right"); + xtw.WriteString("# with SHA-256"); + xtw.WriteEndElement(); // th + + if (baddumpCol) + { + xtw.WriteStartElement("th"); + xtw.WriteAttributeString("align", "right"); + xtw.WriteString("Baddumps"); + xtw.WriteEndElement(); // th + } + + if (nodumpCol) + { + xtw.WriteStartElement("th"); + xtw.WriteAttributeString("align", "right"); + xtw.WriteString("Nodumps"); + xtw.WriteEndElement(); // th + } + + xtw.WriteEndElement(); // tr + xtw.Flush(); + } + + /// + /// Write a single set of statistics + /// + /// XmlTextWriter to write to + /// DatStatistics object to write out + /// True if baddumps should be included in output, false ot + /// herwise + /// True if nodumps should be included in output, false otherwise + private static void WriteIndividual(XmlTextWriter xtw, DatStatistics stat, bool baddumpCol, bool nodumpCol) + { + bool isDirectory = stat.DisplayName!.StartsWith("DIR: "); + + xtw.WriteStartElement("tr"); + if (isDirectory) + xtw.WriteAttributeString("class", "dir"); + +#if NET20 || NET35 + xtw.WriteElementString("td", isDirectory ? stat.DisplayName.Remove(0, 5) : stat.DisplayName); +#else + xtw.WriteElementString("td", isDirectory ? WebUtility.HtmlEncode(stat.DisplayName.Remove(0, 5)) : WebUtility.HtmlEncode(stat.DisplayName)); +#endif + + xtw.WriteStartElement("td"); + xtw.WriteAttributeString("align", "right"); + xtw.WriteString(NumberHelper.GetBytesReadable(stat.TotalSize)); + xtw.WriteEndElement(); // td + + xtw.WriteStartElement("td"); + xtw.WriteAttributeString("align", "right"); + xtw.WriteString(stat.MachineCount.ToString()); + xtw.WriteEndElement(); // td + + xtw.WriteStartElement("td"); + xtw.WriteAttributeString("align", "right"); + xtw.WriteString(stat.GetItemCount(ItemType.Rom).ToString()); + xtw.WriteEndElement(); // td + + xtw.WriteStartElement("td"); + xtw.WriteAttributeString("align", "right"); + xtw.WriteString(stat.GetItemCount(ItemType.Disk).ToString()); + xtw.WriteEndElement(); // td + + xtw.WriteStartElement("td"); + xtw.WriteAttributeString("align", "right"); + xtw.WriteString(stat.GetHashCount(HashType.CRC32).ToString()); + xtw.WriteEndElement(); // td + + xtw.WriteStartElement("td"); + xtw.WriteAttributeString("align", "right"); + xtw.WriteString(stat.GetHashCount(HashType.MD5).ToString()); + xtw.WriteEndElement(); // td + + xtw.WriteStartElement("td"); + xtw.WriteAttributeString("align", "right"); + xtw.WriteString(stat.GetHashCount(HashType.SHA1).ToString()); + xtw.WriteEndElement(); // td + + xtw.WriteStartElement("td"); + xtw.WriteAttributeString("align", "right"); + xtw.WriteString(stat.GetHashCount(HashType.SHA256).ToString()); + xtw.WriteEndElement(); // td + + if (baddumpCol) + { + xtw.WriteStartElement("td"); + xtw.WriteAttributeString("align", "right"); + xtw.WriteString(stat.GetStatusCount(ItemStatus.BadDump).ToString()); + xtw.WriteEndElement(); // td + } + + if (nodumpCol) + { + xtw.WriteStartElement("td"); + xtw.WriteAttributeString("align", "right"); + xtw.WriteString(stat.GetStatusCount(ItemStatus.Nodump).ToString()); + xtw.WriteEndElement(); // td + } + + xtw.WriteEndElement(); // tr + xtw.Flush(); + } + + /// + /// Write out the separator to the stream, if any exists + /// + /// XmlTextWriter to write to + /// True if baddumps should be included in output, false otherwise + /// True if nodumps should be included in output, false otherwise + private static void WriteMidSeparator(XmlTextWriter xtw, bool baddumpCol, bool nodumpCol) + { + xtw.WriteStartElement("tr"); + + xtw.WriteStartElement("td"); + xtw.WriteAttributeString("colspan", baddumpCol && nodumpCol ? "12" : (baddumpCol ^ nodumpCol ? "11" : "10")); + xtw.WriteEndElement(); // td + + xtw.WriteEndElement(); // tr + xtw.Flush(); + } + + /// + /// Write out the footer-separator to the stream, if any exists + /// + /// XmlTextWriter to write to + /// True if baddumps should be included in output, false otherwise + /// True if nodumps should be included in output, false otherwise + private static void WriteFooterSeparator(XmlTextWriter xtw, bool baddumpCol, bool nodumpCol) + { + xtw.WriteStartElement("tr"); + xtw.WriteAttributeString("border", "0"); + + xtw.WriteStartElement("td"); + xtw.WriteAttributeString("colspan", baddumpCol && nodumpCol ? "12" : (baddumpCol ^ nodumpCol ? "11" : "10")); + xtw.WriteEndElement(); // td + + xtw.WriteEndElement(); // tr + xtw.Flush(); + } + + /// + /// Write out the footer to the stream, if any exists + /// + /// XmlTextWriter to write to + private static void WriteFooter(XmlTextWriter xtw) + { + xtw.WriteEndElement(); // table + xtw.WriteEndElement(); // body + xtw.WriteEndElement(); // html + xtw.Flush(); + } + } +} diff --git a/SabreTools.Metadata.Reports/Formats/SemicolonSeparatedValue.cs b/SabreTools.Metadata.Reports/Formats/SemicolonSeparatedValue.cs new file mode 100644 index 00000000..870858c5 --- /dev/null +++ b/SabreTools.Metadata.Reports/Formats/SemicolonSeparatedValue.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using SabreTools.Metadata.DatFiles; + +namespace SabreTools.Metadata.Reports.Formats +{ + /// + /// Represents a semicolon-separated value file + /// + public sealed class SemicolonSeparatedValue : SeparatedValue + { + /// + /// Create a new report from the filename + /// + /// List of statistics objects to set + public SemicolonSeparatedValue(List statsList) : base(statsList) + { + _delim = ';'; + } + } +} diff --git a/SabreTools.Metadata.Reports/Formats/SeparatedValue.cs b/SabreTools.Metadata.Reports/Formats/SeparatedValue.cs new file mode 100644 index 00000000..f0fff99f --- /dev/null +++ b/SabreTools.Metadata.Reports/Formats/SeparatedValue.cs @@ -0,0 +1,148 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using SabreTools.Hashing; +using SabreTools.Metadata.DatFiles; +using SabreTools.Text.SeparatedValue; +using ItemStatus = SabreTools.Data.Models.Metadata.ItemStatus; +using ItemType = SabreTools.Data.Models.Metadata.ItemType; + +#pragma warning disable IDE0290 // Use primary constructor +namespace SabreTools.Metadata.Reports.Formats +{ + /// + /// Separated-Value report format + /// + public abstract class SeparatedValue : BaseReport + { + /// + /// Represents the delimiter between fields + /// + protected char _delim; + + /// + /// Create a new report from the filename + /// + /// List of statistics objects to set + public SeparatedValue(List statsList) + : base(statsList) + { + } + + /// + public override bool WriteToStream(Stream stream, bool baddumpCol, bool nodumpCol, bool throwOnError = false) + { + try + { + Writer svw = new(stream, Encoding.UTF8) + { + Separator = _delim, + Quotes = true, + }; + + // Write out the header + WriteHeader(svw, baddumpCol, nodumpCol); + + // Now process each of the statistics + for (int i = 0; i < _statistics.Count; i++) + { + // Get the current statistic + DatStatistics stat = _statistics[i]; + + // If we have a directory statistic + if (stat.IsDirectory) + { + WriteIndividual(svw, stat, baddumpCol, nodumpCol); + + // If we have anything but the last value, write the separator + if (i < _statistics.Count - 1) + WriteFooterSeparator(svw); + } + + // If we have a normal statistic + else + { + WriteIndividual(svw, stat, baddumpCol, nodumpCol); + } + } + + svw.Dispose(); + } + catch (Exception ex) when (!throwOnError) + { + _logger.Error(ex); + return false; + } + + return true; + } + + /// + /// Write out the header to the stream, if any exists + /// + /// Writer to write to + /// True if baddumps should be included in output, false otherwise + /// True if nodumps should be included in output, false otherwise + private static void WriteHeader(Writer svw, bool baddumpCol, bool nodumpCol) + { + string[] headers = + [ + "File Name", + "Total Size", + "Games", + "Roms", + "Disks", + "# with CRC", + "# with MD5", + "# with SHA-1", + "# with SHA-256", + "# with SHA-384", + "# with SHA-512", + baddumpCol ? "BadDumps" : string.Empty, + nodumpCol ? "Nodumps" : string.Empty, + ]; + svw.WriteHeader(headers); + svw.Flush(); + } + + /// + /// Write a single set of statistics + /// + /// Writer to write to + /// DatStatistics object to write out + /// True if baddumps should be included in output, false otherwise + /// True if nodumps should be included in output, false otherwise + private static void WriteIndividual(Writer svw, DatStatistics stat, bool baddumpCol, bool nodumpCol) + { + string[] values = + [ + stat.DisplayName!, + stat.TotalSize.ToString(), + stat.MachineCount.ToString(), + stat.GetItemCount(ItemType.Rom).ToString(), + stat.GetItemCount(ItemType.Disk).ToString(), + stat.GetHashCount(HashType.CRC32).ToString(), + stat.GetHashCount(HashType.MD5).ToString(), + stat.GetHashCount(HashType.SHA1).ToString(), + stat.GetHashCount(HashType.SHA256).ToString(), + stat.GetHashCount(HashType.SHA384).ToString(), + stat.GetHashCount(HashType.SHA512).ToString(), + baddumpCol ? stat.GetStatusCount(ItemStatus.BadDump).ToString() : string.Empty, + nodumpCol ? stat.GetStatusCount(ItemStatus.Nodump).ToString() : string.Empty, + ]; + svw.WriteValues(values); + svw.Flush(); + } + + /// + /// Write out the footer-separator to the stream, if any exists + /// + /// Writer to write to + private static void WriteFooterSeparator(Writer svw) + { + svw.WriteString("\n"); + svw.Flush(); + } + } +} diff --git a/SabreTools.Metadata.Reports/Formats/TabSeparatedValue.cs b/SabreTools.Metadata.Reports/Formats/TabSeparatedValue.cs new file mode 100644 index 00000000..23537777 --- /dev/null +++ b/SabreTools.Metadata.Reports/Formats/TabSeparatedValue.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using SabreTools.Metadata.DatFiles; + +namespace SabreTools.Metadata.Reports.Formats +{ + /// + /// Represents a tab-separated value file + /// + public sealed class TabSeparatedValue : SeparatedValue + { + /// + /// Create a new report from the filename + /// + /// List of statistics objects to set + public TabSeparatedValue(List statsList) : base(statsList) + { + _delim = '\t'; + } + } +} diff --git a/SabreTools.Metadata.Reports/Formats/Textfile.cs b/SabreTools.Metadata.Reports/Formats/Textfile.cs new file mode 100644 index 00000000..44b1fd77 --- /dev/null +++ b/SabreTools.Metadata.Reports/Formats/Textfile.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using SabreTools.Hashing; +using SabreTools.Metadata.DatFiles; +using SabreTools.Text.Extensions; +using ItemStatus = SabreTools.Data.Models.Metadata.ItemStatus; +using ItemType = SabreTools.Data.Models.Metadata.ItemType; + +#pragma warning disable IDE0290 // Use primary constructor +namespace SabreTools.Metadata.Reports.Formats +{ + /// + /// Textfile report format + /// + public class Textfile : BaseReport + { + /// + /// Create a new report from the filename + /// + /// List of statistics objects to set + public Textfile(List statsList) + : base(statsList) + { + } + + /// + public override bool WriteToStream(Stream stream, bool baddumpCol, bool nodumpCol, bool throwOnError = false) + { + try + { + StreamWriter sw = new(stream); + + // Now process each of the statistics + for (int i = 0; i < _statistics.Count; i++) + { + // Get the current statistic + DatStatistics stat = _statistics[i]; + + // If we have a directory statistic + if (stat.IsDirectory) + { + WriteIndividual(sw, stat, baddumpCol, nodumpCol); + + // If we have anything but the last value, write the separator + if (i < _statistics.Count - 1) + WriteFooterSeparator(sw); + } + + // If we have a normal statistic + else + { + WriteIndividual(sw, stat, baddumpCol, nodumpCol); + } + } + + sw.Dispose(); + } + catch (Exception ex) when (!throwOnError) + { + _logger.Error(ex); + return false; + } + + return true; + } + + /// + /// Write a single set of statistics + /// + /// StreamWriter to write to + /// DatStatistics object to write out + /// True if baddumps should be included in output, false otherwise + /// True if nodumps should be included in output, false otherwise + private static void WriteIndividual(StreamWriter sw, DatStatistics stat, bool baddumpCol, bool nodumpCol) + { + var line = new StringBuilder(); + + line.AppendLine($"'{stat.DisplayName}':"); + line.AppendLine($"--------------------------------------------------"); + line.AppendLine($" Uncompressed size: {NumberHelper.GetBytesReadable(stat!.TotalSize)}"); + line.AppendLine($" Games found: {stat.MachineCount}"); + line.AppendLine($" Roms found: {stat.GetItemCount(ItemType.Rom)}"); + line.AppendLine($" Disks found: {stat.GetItemCount(ItemType.Disk)}"); + line.AppendLine($" Roms with CRC-32: {stat.GetHashCount(HashType.CRC32)}"); + line.AppendLine($" Roms with MD5: {stat.GetHashCount(HashType.MD5)}"); + line.AppendLine($" Roms with SHA-1: {stat.GetHashCount(HashType.SHA1)}"); + line.AppendLine($" Roms with SHA-256: {stat.GetHashCount(HashType.SHA256)}"); + line.AppendLine($" Roms with SHA-384: {stat.GetHashCount(HashType.SHA384)}"); + line.AppendLine($" Roms with SHA-512: {stat.GetHashCount(HashType.SHA512)}"); + + if (baddumpCol) + line.AppendLine($" Roms with BadDump status: {stat.GetStatusCount(ItemStatus.BadDump)}"); + + if (nodumpCol) + line.AppendLine($" Roms with Nodump status: {stat.GetStatusCount(ItemStatus.Nodump)}"); + + // For spacing between DATs + line.AppendLine(); + line.AppendLine(); + + sw.Write(line.ToString()); + sw.Flush(); + } + + /// + /// Write out the footer-separator to the stream, if any exists + /// + /// StreamWriter to write to + private static void WriteFooterSeparator(StreamWriter sw) + { + sw.Write("\n"); + sw.Flush(); + } + } +} diff --git a/SabreTools.Metadata.Reports/README.MD b/SabreTools.Metadata.Reports/README.MD new file mode 100644 index 00000000..111302e6 --- /dev/null +++ b/SabreTools.Metadata.Reports/README.MD @@ -0,0 +1,3 @@ +# SabreTools.Metadata.Reports + +This library contains report generating functionality used by metadata format processing. diff --git a/SabreTools.Metadata.Reports/SabreTools.Metadata.Reports.csproj b/SabreTools.Metadata.Reports/SabreTools.Metadata.Reports.csproj new file mode 100644 index 00000000..a80bbd43 --- /dev/null +++ b/SabreTools.Metadata.Reports/SabreTools.Metadata.Reports.csproj @@ -0,0 +1,44 @@ + + + + + net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0;net9.0;net10.0;netstandard2.0;netstandard2.1 + true + false + false + true + latest + NU1902;NU1903 + enable + true + snupkg + true + 2.3.0 + + + Matt Nadareski + Report functionality for metadata file processing + Copyright (c) Matt Nadareski 2016-2026 + https://github.com/SabreTools/ + README.md + https://github.com/SabreTools/SabreTools.Serialization + git + metadata dat datfile report + MIT + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SabreTools.Serialization.sln b/SabreTools.Serialization.sln index 5c811755..044ddb2d 100644 --- a/SabreTools.Serialization.sln +++ b/SabreTools.Serialization.sln @@ -47,6 +47,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SabreTools.Metadata.DatFile EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SabreTools.Metadata.DatFiles.Test", "SabreTools.Metadata.DatFiles.Test\SabreTools.Metadata.DatFiles.Test.csproj", "{EFF85ED6-27D6-4076-A935-9792D2975078}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SabreTools.Metadata.Reports", "SabreTools.Metadata.Reports\SabreTools.Metadata.Reports.csproj", "{6CDE2071-784B-4072-9EAE-33F0AE00566C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -321,6 +323,18 @@ Global {EFF85ED6-27D6-4076-A935-9792D2975078}.Release|x64.Build.0 = Release|Any CPU {EFF85ED6-27D6-4076-A935-9792D2975078}.Release|x86.ActiveCfg = Release|Any CPU {EFF85ED6-27D6-4076-A935-9792D2975078}.Release|x86.Build.0 = Release|Any CPU + {6CDE2071-784B-4072-9EAE-33F0AE00566C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6CDE2071-784B-4072-9EAE-33F0AE00566C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6CDE2071-784B-4072-9EAE-33F0AE00566C}.Debug|x64.ActiveCfg = Debug|Any CPU + {6CDE2071-784B-4072-9EAE-33F0AE00566C}.Debug|x64.Build.0 = Debug|Any CPU + {6CDE2071-784B-4072-9EAE-33F0AE00566C}.Debug|x86.ActiveCfg = Debug|Any CPU + {6CDE2071-784B-4072-9EAE-33F0AE00566C}.Debug|x86.Build.0 = Debug|Any CPU + {6CDE2071-784B-4072-9EAE-33F0AE00566C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6CDE2071-784B-4072-9EAE-33F0AE00566C}.Release|Any CPU.Build.0 = Release|Any CPU + {6CDE2071-784B-4072-9EAE-33F0AE00566C}.Release|x64.ActiveCfg = Release|Any CPU + {6CDE2071-784B-4072-9EAE-33F0AE00566C}.Release|x64.Build.0 = Release|Any CPU + {6CDE2071-784B-4072-9EAE-33F0AE00566C}.Release|x86.ActiveCfg = Release|Any CPU + {6CDE2071-784B-4072-9EAE-33F0AE00566C}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/SabreTools.Serialization/SabreTools.Serialization.csproj b/SabreTools.Serialization/SabreTools.Serialization.csproj index 0e55eabf..4db86006 100644 --- a/SabreTools.Serialization/SabreTools.Serialization.csproj +++ b/SabreTools.Serialization/SabreTools.Serialization.csproj @@ -46,6 +46,8 @@ + + diff --git a/publish-nix.sh b/publish-nix.sh index 43ef7555..a53062e7 100755 --- a/publish-nix.sh +++ b/publish-nix.sh @@ -84,6 +84,7 @@ if [ $NO_BUILD = false ]; then dotnet pack SabreTools.Metadata.DatFiles/SabreTools.Metadata.DatFiles.csproj --output $BUILD_FOLDER dotnet pack SabreTools.Metadata.DatItems/SabreTools.Metadata.DatItems.csproj --output $BUILD_FOLDER dotnet pack SabreTools.Metadata.Filter/SabreTools.Metadata.Filter.csproj --output $BUILD_FOLDER + dotnet pack SabreTools.Metadata.Reports/SabreTools.Metadata.Reports.csproj --output $BUILD_FOLDER dotnet pack SabreTools.ObjectIdentifier/SabreTools.ObjectIdentifier.csproj --output $BUILD_FOLDER dotnet pack SabreTools.Serialization.CrossModel/SabreTools.Serialization.CrossModel.csproj --output $BUILD_FOLDER dotnet pack SabreTools.Serialization.Readers/SabreTools.Serialization.Readers.csproj --output $BUILD_FOLDER diff --git a/publish-win.ps1 b/publish-win.ps1 index e3ef7611..2a869e1a 100644 --- a/publish-win.ps1 +++ b/publish-win.ps1 @@ -75,6 +75,7 @@ if (!$NO_BUILD.IsPresent) { dotnet pack SabreTools.Metadata.DatFiles\SabreTools.Metadata.DatFiles.csproj --output $BUILD_FOLDER dotnet pack SabreTools.Metadata.DatItems\SabreTools.Metadata.DatItems.csproj --output $BUILD_FOLDER dotnet pack SabreTools.Metadata.Filter\SabreTools.Metadata.Filter.csproj --output $BUILD_FOLDER + dotnet pack SabreTools.Metadata.Reports\SabreTools.Metadata.Reports.csproj --output $BUILD_FOLDER dotnet pack SabreTools.ObjectIdentifier\SabreTools.ObjectIdentifier.csproj --output $BUILD_FOLDER dotnet pack SabreTools.Serialization.CrossModel\SabreTools.Serialization.CrossModel.csproj --output $BUILD_FOLDER dotnet pack SabreTools.Serialization.Readers\SabreTools.Serialization.Readers.csproj --output $BUILD_FOLDER