From 39fa26ac60643b84880d315c0d34e3a8b37ff115 Mon Sep 17 00:00:00 2001 From: David Hall Date: Mon, 16 Jul 2018 13:14:28 -0600 Subject: [PATCH] Added composite formatter framework and a string formatter to handle Byte Size references. --- Core/ByteSizeFormatter.cs | 99 +++++++++++++++++++++++++++++++++++ Core/CompositeFormatter.cs | 125 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 224 insertions(+) create mode 100644 Core/ByteSizeFormatter.cs create mode 100644 Core/CompositeFormatter.cs diff --git a/Core/ByteSizeFormatter.cs b/Core/ByteSizeFormatter.cs new file mode 100644 index 00000000..7eade816 --- /dev/null +++ b/Core/ByteSizeFormatter.cs @@ -0,0 +1,99 @@ +using System; + +namespace Vanara +{ + /// + /// A custom formatter for byte sizes (things like files, network bandwidth, etc.) that will automatically determine the best abbreviation. + /// + public class ByteSizeFormatter : Formatter + { + private static readonly string[] suffixes = { " B", " KB", " MB", " GB", " TB", " PB", " EB" }; + + /// + /// Converts the value of a specified object to an equivalent string representation using specified format and culture-specific + /// formatting information. + /// + /// A format string containing formatting specifications. + /// An object to format. + /// An object that supplies format information about the current instance. + /// + /// The string representation of the value of , formatted as specified by and + /// . + /// + public override string Format(string format, object arg, IFormatProvider formatProvider) + { + long bytes = 0; + try { bytes = Convert.ToInt64(arg); } + catch { return HandleOtherFormats(format, arg); } + if (bytes == 0) return "0" + suffixes[0]; + var m = System.Text.RegularExpressions.Regex.Match(format, "^[B|b](?\\d+)$"); + if (!m.Success) return HandleOtherFormats(format, arg); + var prec = m.Groups["prec"].Success ? byte.Parse(m.Groups["prec"].Value) : 0; + var place = Convert.ToInt32(Math.Floor(Math.Log(bytes, 1024))); + if (place >= suffixes.Length) place = suffixes.Length - 1; + var num = Math.Round(bytes / Math.Pow(1024, place), 1); + return $"{num.ToString("F" + prec)}{suffixes[place]}"; + } + + /// + /// Converts the string representation of a byte size to its 64-bit signed integer equivalent. A return value indicates whether the + /// conversion succeeded. + /// + /// A string containing a byte size to convert. + /// + /// When this method returns, contains the 64-bit signed integer value equivalent of the value contained in , + /// if the conversion succeeded, or zero if the conversion failed. The conversion fails if the parameter is + /// null or Empty, or is not of the correct format. This parameter is passed uninitialized; any value originally supplied in result + /// will be overwritten. + /// + /// if was converted successfully; otherwise, . + /// + public static bool TryParse(string input, out long bytes) + { + const string expr = @"^\s*(?\d+(?:\.\d+)?)\s*(?[kKmMgGtTpPeEyY]?[bB])?\s*$"; + var match = System.Text.RegularExpressions.Regex.Match(input, expr); + bytes = 0; + if (!match.Success) return false; + long mult = 1; + switch (match.Groups["mod"].Value.ToUpper()) + { + case "B": + case "": + break; + + case "KB": + mult = 1024; + break; + + case "MB": + mult = (long)Math.Pow(1024, 2); + break; + + case "GB": + mult = (long)Math.Pow(1024, 3); + break; + + case "TB": + mult = (long)Math.Pow(1024, 4); + break; + + case "PB": + mult = (long)Math.Pow(1024, 5); + break; + + case "EB": + mult = (long)Math.Pow(1024, 6); + break; + + case "YB": + mult = (long)Math.Pow(1024, 7); + break; + + default: + throw new InvalidOperationException(); + } + bytes = (long)Math.Round(float.Parse(match.Groups["num"].Value) * mult); + return true; + } + } +} \ No newline at end of file diff --git a/Core/CompositeFormatter.cs b/Core/CompositeFormatter.cs new file mode 100644 index 00000000..8d28ea84 --- /dev/null +++ b/Core/CompositeFormatter.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Generic; +using System.Globalization; + +namespace Vanara +{ + // Leveraged code from t3chb0t and Bogdan Yarema at https://codereview.stackexchange.com/questions/138747/custom-string-formatters + + /// Binds multiple formatters together. + /// + internal sealed class CompositeFormatter : Formatter + { + private readonly List _formatters; + + /// Initializes a new instance of the class. + /// The culture. + /// The formatters. + public CompositeFormatter(CultureInfo culture = null, params Formatter[] formatters) : base(culture) + { + _formatters = new List(formatters); + } + + /// Adds the specified formatter. + /// The formatter. + public void Add(Formatter formatter) => _formatters.Add(formatter); + + /// + /// Converts the value of a specified object to an equivalent string representation using specified format and culture-specific + /// formatting information. + /// + /// A format string containing formatting specifications. + /// An object to format. + /// An object that supplies format information about the current instance. + /// + /// The string representation of the value of , formatted as specified by and + /// . + /// + public override string Format(string format, object arg, IFormatProvider formatProvider) + { + foreach (var formatter in _formatters) + { + var result = formatter.Format(format, arg, formatProvider); + if (result != null) + return result; + } + return null; + } + } + + /// Extension method to combine formatter instances. + public static class FormatterComposer + { + /// Adds a chain of formatters with specific cultures to make a composite. + /// A derived type. + /// The formatter instance to start the chain. + /// The culture. + /// A composite formatter. + /// + /// + /// // Build composite formatter from custom formatters derived from Formatter + /// var formatter = Formatter.Default().Add<CustomFormatter1>().Add<CustomFormatter2>(); + /// // Use custom format extensions defined in the custom formatters to format the string + /// var output = string.Format(formatter, "{0:cf1} = {0:cf2}", 512); + /// + /// + public static Formatter Add(this Formatter formatter, CultureInfo culture = null) where T : Formatter, new() + { + var newFormatter = new T(); + if (!(formatter is CompositeFormatter compositeFormatter)) + return new CompositeFormatter(culture, formatter, newFormatter); + compositeFormatter.Add(newFormatter); + return compositeFormatter; + } + } + + /// Base class for expandable formatters. + public abstract class Formatter : IFormatProvider, ICustomFormatter + { + /// Initializes a new instance of the class. + /// The culture. + protected Formatter(CultureInfo culture = null) + { + Culture = culture ?? CultureInfo.InvariantCulture; + } + + /// Gets a default instance of a composite formatter. + /// The culture. + /// A composite formatter. + public static Formatter Default(CultureInfo culture = null) => new CompositeFormatter(culture); + + /// Gets the culture. + /// The culture. + public CultureInfo Culture { get; } + + /// Returns an object that provides formatting services for the specified type. + /// An object that specifies the type of format object to return. + /// + /// An instance of the object specified by , if the IFormatProvider implementation can supply that type + /// of object; otherwise, . + /// + public virtual object GetFormat(Type formatType) => formatType == typeof(ICustomFormatter) ? this : null; + + /// + /// Converts the value of a specified object to an equivalent string representation using specified format and culture-specific + /// formatting information. + /// + /// A format string containing formatting specifications. + /// An object to format. + /// An object that supplies format information about the current instance. + /// + /// The string representation of the value of , formatted as specified by and + /// . + /// + public abstract string Format(string format, object arg, IFormatProvider formatProvider); + + /// Helper method that can be used inside the Format method to handle unrecognized formats. + /// A format string containing formatting specifications. + /// An object to format. + /// + /// The string representation of the value of , formatted as specified by and + /// . + /// + protected string HandleOtherFormats(string format, object arg) => (arg as IFormattable)?.ToString(format, Culture) ?? (arg?.ToString() ?? string.Empty); + } +} \ No newline at end of file