wwauth/Google.Solutions.WWAuth/Util/CommandLineParser.cs (101 lines of code) (raw):
//
// Copyright 2022 Google LLC
//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
//
using Google.Solutions.WWAuth.Interop;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;
namespace Google.Solutions.WWAuth.Util
{
public interface ICommandLineOptions
{
string Executable { get; set; }
}
[AttributeUsage(AttributeTargets.Property)]
public class CommandLineArgumentAttribute : Attribute
{ }
/// <summary>
/// Generate and parse command line arguments based on
/// properties of a class.
/// </summary>
internal static class CommandLineParser
{
private static IEnumerable<PropertyInfo> GetSupportedProperties<TOptions>()
=> typeof(TOptions)
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => p.CustomAttributes
.EnsureNotNull()
.Any(a => a.AttributeType == typeof(CommandLineArgumentAttribute)))
.Where(p => p.PropertyType.IsEnum || p.PropertyType == typeof(string));
public static TOptions Parse<TOptions>(string commandLine)
where TOptions : class, ICommandLineOptions, new()
{
var commandLineParts = commandLine
.SplitQuotedString(' ')
.Select(arg => arg.Trim())
.Where(arg => !string.IsNullOrWhiteSpace(arg));
var options = new TOptions()
{
Executable = commandLineParts.FirstOrDefault()
};
var arguments = commandLineParts
.Skip(1) // Ignore the executable path
.ToList();
if ((arguments.Count % 2) != 0)
{
throw new ArgumentException(
"Command line contains too many options");
}
var properties = GetSupportedProperties<TOptions>();
for (int i = 0; i < arguments.Count / 2; i++)
{
var key = arguments[i * 2];
var value = arguments[i * 2 + 1];
if (!key.StartsWith("/"))
{
throw new ArgumentException(
"Unrecognized command line option: " + key);
}
var property = properties.FirstOrDefault(
p => p.Name.Equals(key.Substring(1), StringComparison.OrdinalIgnoreCase));
if (property == null)
{
throw new ArgumentException(
"Unrecognized command line option: " + key);
}
if (property.PropertyType.IsEnum)
{
property.SetValue(options, Enum.Parse(property.PropertyType, value));
}
else
{
property.SetValue(options, value);
}
}
return options;
}
public static string ToString<TOptions>(TOptions options, bool useQuotes = true)
where TOptions : ICommandLineOptions
{
var quote = useQuotes ? "\"" : string.Empty;
string executablePath = options.Executable;
if (executablePath.Contains(" ") && !useQuotes)
{
//
// Some client libraries don't support quoted command
// lines (b/238143555, b/237606033). That's a problem if
// the executable path itself contains spaces, which is
// reasonably likely on Windows.
//
// As a workaround, convert the executable path to
// 8.3 notation, which is guaranteed not to include
// spaces. Then it's safe to drop the quotes.
//
var shortNameBuffer = new StringBuilder(260);
if (NativeMethods.GetShortPathName(
executablePath,
shortNameBuffer,
shortNameBuffer.Capacity) == 0)
{
throw new Win32Exception("Failed to create short-path name for file");
}
executablePath = shortNameBuffer.ToString();
Debug.Assert(!executablePath.Contains(" "));
}
return $"{quote}{executablePath}{quote} " + string.Join(
" ",
GetSupportedProperties<TOptions>()
.Where(p => p.PropertyType.IsEnum ||
p.GetValue(options) is string s && !string.IsNullOrEmpty(s))
.Select(p => $"/{p.Name} {quote}{p.GetValue(options)}{quote}"));
}
}
}