Skip to content

ECF v1.0.1 - Async commands, DevOps tasks and more

Compare
Choose a tag to compare
@Aviuz Aviuz released this 13 Aug 18:55
· 21 commits to master since this release

Note: Changes are backward compatible for standard usage scenarios (i.e. using ECFHostBuilder and CommandBase classes).

1. Linux users, I've got something for you major

  • problematic system32.CommandLineToArgW was rewritten to fully support Linux builds

2. ECF is now fully asynchronous major

  • added new base class AsyncCommandBase, which allow to utilize async behaviour
    • use CancellationToken for graceful interruption
    • CommandBase is now wrapping AsyncCommandBase to reflect old behavior
  • ICommand has new interface breaking change
    • definition of Execute changed to Task ExecuteAsync(CommandArguments args, CancellationToken cancellationToken)
    • definition ApplyArguments has been removed (it is still used internally inside AsyncCommandBase)
    • CancellationToken need to be handled for graceful interruption, for generic use I recommend putting cancellationToken.Register(() => Environment.Exit(1))
  • CTRL + C now tries to interrupt current command instead of interrupting whole program
    • For ICommand and AsyncCommandBase token will be cancelled. If token is not handled, command will run despite cancel signal.
    • For CommandBase this will invoke Environment.Exit(1)
    • Pressing CTRL + C twice will result in calling Environment.Exit(1) in any scenario
  • Updated BaseKit commands to new async behavior, including LoadScriptCommand, along with ScriptLoader.

3. Now ECF is more suitable for DevOps tasks with arrival of Default Commands major

  • this new approach is designed to modify ECF console application, to be used as a part of pipeline process, which does not support passing CLI arguments (or it's just really inconvenient)
    • yes, you've guessed it: I've encountered this scenario during one of my projects :)
    • the main advantage of this approach, in contrast to using simple console application, is that you can utilize IoC and already constructed ECF commands for those tasks
  • added ECFHostBuilder.UseSingleCommand<TCommand>(), which will enforce program to run only command you've specified (default command + no prompt mode + no commands in registry)
    • example configuration should looks like this
      await new ECFHostBuilder()
      #if ENV_build_for_very_annoying_devops
          .UseSingleCommand<Example.Commands.TestProgressBar_Asynchronous>()
      #else 
          .UseDefaultCommands()
      #endif
          .RunAsync(args);
      
  • added DefaultCommand option in InterfaceContext (not necessary when using UseSingleCommand)
    • note: keep in mind that command mapping comes first, so program.exe hello john will first check if command hello exits and tries to invoke it with args ["john"], if there is no such command it will invoke default command with args ["hello", "john"]
    • CommandArguments now have new property ExecutedAsDefaultCommand which will indicate if command was invoked via fallback mechanism: when no arguments were passed or when command name was not found by matching first argument
  • added DisablePrompting inside InterfaceContext (true when using UseSingleCommand), which will invoke default command, instead of prompt mode, when program started without arguments
  • removed not found message from core engine breaking change
    • NotFoundCommand was merged into HelpCommand, it now utilize ExecutedAsDefaultCommand to whenever display command not found or not
    • UseDefaultCommands() now sets HelpCommand as default command (if not set already)
    • if your ECF program does not invoke UseDefaultCommands() and you want to restore old NotFoundCommand behavior please set ctx.DefaultCommand = typeof(ECF.BaseKitCommands.HelpCommand) in your .Configure((ctx, services, _) => section

4. Help is now more helpful major

  • now every CommandBase and AsyncCommandBase will react to -h and --help with displaying help message (same as help commandname)
  • now command syntax, if not set by attribute, will be generated automatically based on argument bindings
  • some visual changes to reduce fuss and focus on actual help content

5. Other changes

  • added RequiredAttribute to use with [Argument]/[Parameter]/[Flag], which will prevent command from running if not set by user (CommandBase, AsyncCommandBase) major
  • added support for nullables in binded properties (CommandBase, AsyncCommandBase | [Argument], [Parameter], [Flag] )
  • added option for specifying StringComparison, defaults to StringComparison.InvariantCulture (CommandBase, AsyncCommandBase | [Argument], [Parameter], [Flag] )
  • new constructor for [Flag]s and [Paremeter]s major
    • [Flag(ShortName="f", LongName="flag")] => [Flag("-f --flag")]/[Flag("-f", "--flag")]
    • This will allow to change prefix aswell (eg. ~~tilde-flag)
  • added option to alter default behavior of ignoring values with prefixes (CommandBase, AsyncCommandBase | [Argument], [Parameter] )
    • by default all arguments and parameters with prefix - will be ignored
    • you can disable all prefixes by providing ForbiddenValuePrefixes = new string[0]
  • now all ECF exceptions inherit from ECFException which can help you managing exception better in your application
  • prefix from InterfaceContext now does not include space after, which can be added to maintain old behavior. (Marking this as breaking change because it can break some automated tests) breaking change
  • (advanced) AddECFCommandRegistry now uses configure Action inside extension method to register commands breaking change
    • New example for creating ICommandProcessor looks like this:
      public ICommandProcessor CreateDirectionCommandsProcessor(InterfaceContext interfaceContext)
      {
          // CommandProcessors hold IoC containers to maintain seperate collection of services        
      
          return new ServiceCollection()
              // when using alternative scope, we need to build command registry manually
              .AddECFCommandRegistry(interfaceContext, builder => builder
                  .RegisterCommands<DirectionCommandAttribute>(Assembly.GetExecutingAssembly()) // we can register all commands with  specified attribute in specified assembly
                  .RegisterCommands<CommandAttribute>(typeof(HelpCommand).Assembly) // this line will register basic commands as  HelpCommand, LoadCommand etc.
                  .Register<CommandAttribute>(typeof(ExitCommand)) // alternatively you can always register commands seperatly one by one
                  .Register(typeof(ExitCommand), "exit")) // or even register without attribute (it can cause issues with help command)
              .BuildAndCreateECFCommandProcessor(); // at the end we need to construct CommandProcesor which will process command  requests
      }