/// <summary> /// Discovers and caches job types from the <paramref name="jobAssemblies"/>, creating a singleton service <see cref="JobCache"/>. /// </summary> /// <param name="services">The service collection being modified.</param> /// <param name="jobAssemblies">The assemblies where jobs can be found.</param> /// <returns>A <see cref="ConfigReader"/> that can read JSON configs for the job types in the <see cref="JobCache"/>.</returns> static (JobCache, ConfigReader) AddJobCache(this IServiceCollection services, IEnumerable <Assembly> jobAssemblies) { // need to use this stuff now; create it then register it var cache = new JobCache(jobAssemblies); var cfgReader = new ConfigReader(cache); services.AddSingleton(cache); services.AddSingleton(cfgReader); foreach (var job in cache.Jobs) { if (job.IsValid) { services.AddTransient(job.JobType); } } return(cache, cfgReader); }
static CommandLineBuilder GetCommandLineBuilder(IServiceCollection services, JobCache cache, ConfigReader reader) { var root = new RootCommand(); var list = new Command(ListCommand, "Lists the jobs available to run.") { new Option <bool>(new[] { "--verbose", "-v" }, "Optional. Changes the config serialization to include all properties."), new Option <bool>(new[] { "--json", "-j" }, "Optional. Changes the output format to JSON.") }; list.Handler = CommandHandler.Create <bool, bool>((verbose, json) => services.AddListAction(verbose, json)); root.AddCommand(list); var get = new Command(GetCommand, "Writes the default config for the specified type to the file path.") { new Argument <string>("type", "The type of job to get the default config for."), new Argument <string>("filePath", () => "", "Optional. The file path to write the config to."), new Option <bool>(new[] { "--verbose", "-v" }, "Optional. Changes the config serialization to include all properties.") }; get.Handler = CommandHandler.Create <string, string, bool>((type, filePath, verbose) => services.AddGetAction(type, filePath, verbose)); root.AddCommand(get); var run = new Command(RunCommand, "Runs a job from a JSON config file.") { new Argument <FileInfo>(ConfigPathArgument, "The JSON serialized config for the job to run.").ExistingOnly(), new Argument <FileInfo>("resultsPath", () => null, "Optional. The file path to write the results of the job to."), new Option <bool>(new[] { "--debug", "-d" }, "Optional. Prompts the user to attach a debugger when the job starts."), new Option <bool>(new[] { "--silent", "-s" }, "Optional. Silences console output.") }; run.Handler = CommandHandler.Create <FileInfo, FileInfo, bool, bool>((configPath, resultsPath, debug, silent) => services.AddRunAction(reader, configPath, resultsPath, debug, silent)); root.AddCommand(run); // Add a command for each job, with each config path as an option foreach (var job in cache.Jobs) { var jobCmd = new Command(job.JobType.Name.ToLowerInvariant(), $"Runs the job {job.JobType.FullName}."); jobCmd.AddAlias(job.JobType.Name); AddConfigOptions(jobCmd, job.ConfigType, null); root.AddCommand(jobCmd); } void AddConfigOptions(Command command, Type type, string prefix) { foreach (var prop in type.GetProperties()) { var name = prefix == null ? $"--{prop.Name}" : $"{prefix}.{prop.Name}"; // The job type comes from the command and the package/version cannot be set (it's whatever's executing) if (name == "--Job") { continue; } bool isArray = prop.PropertyType.IsArray; Type propType = isArray ? prop.PropertyType.GetElementType() : prop.PropertyType; if (propType.IsValueType || propType == typeof(string)) { if (prop.CanWrite) { var desc = prop.GetCustomAttributes <DescriptionAttribute>().FirstOrDefault()?.Description; var option = new Option(name.ToLowerInvariant(), desc) { Argument = new Argument(propType.Name) { Arity = (propType == typeof(bool), isArray) switch { (true, false) => ArgumentArity.ZeroOrOne, (_, true) => ArgumentArity.ZeroOrMore, (_, _) => ArgumentArity.ExactlyOne }, ArgumentType = prop.PropertyType }
/// <summary> /// Initializes a new <see cref="ConfigReader"/>. /// </summary> /// <param name="cache">The <see cref="JobCache"/> containing the config types that can be read.</param> public ConfigReader(JobCache cache) { this.cache = cache ?? throw new ArgumentNullException(nameof(cache)); }