/// <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
                                }
Exemple #3
0
 /// <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));
 }