using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using CliFx.Infrastructure; using CliFx.Schema; using CliFx.Utils; using CliFx.Utils.Extensions; namespace CliFx; /// /// Builder for . /// public partial class CliApplicationBuilder { private readonly HashSet _commandSchemas = []; private bool _isDebugModeAllowed = true; private bool _isPreviewModeAllowed = true; private string? _title; private string? _executableName; private string? _version; private string? _description; private IConsole? _console; private ITypeActivator? _typeActivator; /// /// Adds a command to the application. /// public CliApplicationBuilder AddCommand(CommandSchema commandSchema) { _commandSchemas.Add(commandSchema); return this; } /// /// Specifies whether debug mode (enabled with the [debug] directive) is allowed in the application. /// public CliApplicationBuilder AllowDebugMode(bool isAllowed = true) { _isDebugModeAllowed = isAllowed; return this; } /// /// Specifies whether preview mode (enabled with the [preview] directive) is allowed in the application. /// public CliApplicationBuilder AllowPreviewMode(bool isAllowed = true) { _isPreviewModeAllowed = isAllowed; return this; } /// /// Sets the application title, which is shown in the help text. /// /// /// By default, application title is inferred from the assembly name. /// public CliApplicationBuilder SetTitle(string title) { _title = title; return this; } /// /// Sets the application executable name, which is shown in the help text. /// /// /// By default, application executable name is inferred from the assembly file name. /// public CliApplicationBuilder SetExecutableName(string executableName) { _executableName = executableName; return this; } /// /// Sets the application version, which is shown in the help text or when the user specifies the version option. /// /// /// By default, application version is inferred from the assembly version. /// public CliApplicationBuilder SetVersion(string version) { _version = version; return this; } /// /// Sets the application description, which is shown in the help text. /// public CliApplicationBuilder SetDescription(string? description) { _description = description; return this; } /// /// Configures the application to use the specified implementation of . /// public CliApplicationBuilder UseConsole(IConsole console) { _console = console; return this; } /// /// Configures the application to use the specified implementation of . /// public CliApplicationBuilder UseTypeActivator(ITypeActivator typeActivator) { _typeActivator = typeActivator; return this; } /// /// Configures the application to use the specified delegate for activating types. /// public CliApplicationBuilder UseTypeActivator(Func createInstance) => UseTypeActivator(new DelegateTypeActivator(createInstance)); /// /// Configures the application to use the specified service provider for activating types. /// public CliApplicationBuilder UseTypeActivator(IServiceProvider serviceProvider) => // Null returns are handled by DelegateTypeActivator UseTypeActivator(serviceProvider.GetService!); /// /// Creates a configured instance of . /// public CliApplication Build() { var metadata = new ApplicationMetadata( _title ?? GetDefaultTitle(), _executableName ?? GetDefaultExecutableName(), _version ?? GetDefaultVersionText(), _description ); var configuration = new ApplicationConfiguration( new ApplicationSchema(_commandSchemas.ToArray()), _isDebugModeAllowed, _isPreviewModeAllowed ); return new CliApplication( metadata, configuration, _console ?? new SystemConsole(), _typeActivator ?? new DefaultTypeActivator() ); } } public partial class CliApplicationBuilder { private static string GetDefaultTitle() { var entryAssemblyName = EnvironmentEx.EntryAssembly?.GetName().Name; if (string.IsNullOrWhiteSpace(entryAssemblyName)) { throw new InvalidOperationException( "Failed to infer the default application title. " + $"Please specify it explicitly using `{nameof(SetTitle)}()`." ); } return entryAssemblyName; } [UnconditionalSuppressMessage( "SingleFile", "IL3000:Avoid accessing Assembly file path when publishing as a single file", Justification = "The file path is checked to ensure the assembly location is available." )] private static string GetDefaultExecutableName() { var processFilePath = EnvironmentEx.ProcessPath; // Process file path should generally always be available if (string.IsNullOrWhiteSpace(processFilePath)) { throw new InvalidOperationException( "Failed to infer the default application executable name. " + $"Please specify it explicitly using `{nameof(SetExecutableName)}()`." ); } var entryAssemblyFilePath = EnvironmentEx.EntryAssembly?.Location; // Single file application: entry assembly is not on disk and doesn't have a file path if (string.IsNullOrWhiteSpace(entryAssemblyFilePath)) { return Path.GetFileNameWithoutExtension(processFilePath); } // Legacy .NET Framework application: entry assembly has the same file path as the process if (PathEx.AreEqual(entryAssemblyFilePath, processFilePath)) { return Path.GetFileNameWithoutExtension(entryAssemblyFilePath); } // .NET Core application launched through the native application host: // entry assembly has the same file path as the process, but with a different extension. if ( PathEx.AreEqual(Path.ChangeExtension(entryAssemblyFilePath, "exe"), processFilePath) || PathEx.AreEqual( Path.GetFileNameWithoutExtension(entryAssemblyFilePath), processFilePath ) ) { return Path.GetFileNameWithoutExtension(entryAssemblyFilePath); } // .NET Core application launched through the .NET CLI return "dotnet " + Path.GetFileName(entryAssemblyFilePath); } private static string GetDefaultVersionText() { var entryAssemblyVersion = EnvironmentEx.EntryAssembly?.GetName().Version; if (entryAssemblyVersion is null) { throw new InvalidOperationException( "Failed to infer the default application version. " + $"Please specify it explicitly using `{nameof(SetVersion)}()`." ); } return "v" + entryAssemblyVersion.ToSemanticString(); } }