using System.Globalization; using System.Text; namespace Geekeey.Extensions.Process; /// /// Builder that helps format command-line arguments into a string. /// public sealed partial class ArgumentsBuilder { private static readonly IFormatProvider DefaultFormatProvider = CultureInfo.InvariantCulture; private readonly StringBuilder _buffer = new(); /// /// Adds the specified value to the list of arguments. /// public ArgumentsBuilder Add(string value, bool escape = true) { if (_buffer.Length > 0) _buffer.Append(' '); _buffer.Append(escape ? Escape(value) : value); return this; } /// /// Adds the specified values to the list of arguments. /// public ArgumentsBuilder Add(IEnumerable values, bool escape = true) { foreach (var value in values) { Add(value, escape); } return this; } /// /// Adds the specified value to the list of arguments. /// public ArgumentsBuilder Add(IFormattable value, IFormatProvider formatProvider, bool escape = true) => Add(value.ToString(null, formatProvider), escape); /// /// Adds the specified value to the list of arguments. /// The value is converted to string using invariant culture. /// public ArgumentsBuilder Add(IFormattable value, bool escape = true) => Add(value, DefaultFormatProvider, escape); /// /// Adds the specified values to the list of arguments. /// public ArgumentsBuilder Add(IEnumerable values, IFormatProvider formatProvider, bool escape = true) { foreach (var value in values) { Add(value, formatProvider, escape); } return this; } /// /// Adds the specified values to the list of arguments. /// The values are converted to string using invariant culture. /// public ArgumentsBuilder Add(IEnumerable values, bool escape = true) => Add(values, DefaultFormatProvider, escape); /// /// Builds the resulting arguments string. /// public string Build() => _buffer.ToString(); } public partial class ArgumentsBuilder { private static string Escape(string argument) { // Short circuit if the argument is clean and doesn't need escaping if (argument.Length > 0 && argument.All(c => !char.IsWhiteSpace(c) && c is not '"')) return argument; var buffer = new StringBuilder(); buffer.Append('"'); for (var i = 0; i < argument.Length;) { var c = argument[i++]; switch (c) { case '\\': { var backslashCount = 1; while (i < argument.Length && argument[i] == '\\') { backslashCount++; i++; } if (i == argument.Length) { buffer.Append('\\', backslashCount * 2); } else if (argument[i] == '"') { buffer.Append('\\', backslashCount * 2 + 1).Append('"'); i++; } else { buffer.Append('\\', backslashCount); } break; } case '"': buffer.Append('\\').Append('"'); break; default: buffer.Append(c); break; } } buffer.Append('"'); return buffer.ToString(); } }