Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

'cache destroy' command #81

Merged
merged 23 commits into from
Jun 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
7e42341
feat: cache destroy
CodeMouse92 May 18, 2022
fe8c8fe
feat: add prompt and --force to 'cache destroy' command
CodeMouse92 May 19, 2022
57c64e3
test: 'cache destroy'
CodeMouse92 May 19, 2022
0170540
refactor: RunnableCommand<> abstract base class
CodeMouse92 May 19, 2022
d8e7798
refactor: eliminate goto and improve style for 'cache' functionality
CodeMouse92 May 19, 2022
3d3a17e
refactor: Add ToExitCode extension on booleans
CodeMouse92 May 19, 2022
a1ad2db
style: change Ruby identiation to 2 characters
CodeMouse92 May 19, 2022
d65389b
build(deps): bump Moq from 4.17.2 to 4.18.1
dependabot[bot] May 19, 2022
f04f3c9
build(deps): bump actions/checkout from 2 to 3
dependabot[bot] May 19, 2022
6599de0
build(deps): bump actions/upload-artifact from 2 to 3
dependabot[bot] May 19, 2022
211aedf
build(deps): bump FluentAssertions from 6.6.0 to 6.7.0
dependabot[bot] May 19, 2022
ab66894
fix: improve cache directory override
CodeMouse92 May 20, 2022
7405c33
test: add test for non-standard cache-dir
CodeMouse92 May 20, 2022
09d278f
refactor: moved cache validation into dedicated function
CodeMouse92 May 23, 2022
c49c5de
refactor: move Console calls to runner
CodeMouse92 May 23, 2022
b64ad60
style: superficially shorten function to make codeclimate happy
CodeMouse92 May 23, 2022
7ff560b
Removes duplication between `CacheException` and `CacheWarningException`
mscottford Jun 1, 2022
a8be6b0
Merge branch 'main' into cache_destroy
mscottford Jun 1, 2022
3a799a3
refactor: split subcommand classes into separate files
CodeMouse92 Jun 6, 2022
df909e7
refactor: make runnable command truly optional
CodeMouse92 Jun 6, 2022
69893d5
refactor/test: resolve Aruba paths to simulated home directory
CodeMouse92 Jun 6, 2022
0eb0a57
refactor/test: resolve Aruba paths to simulated home directory
CodeMouse92 Jun 6, 2022
4707eda
test: add cache destroy acceptance tests w/o --force
CodeMouse92 Jun 6, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 21 additions & 10 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ Follow below steps if you want to contribute with a new command:
Example: CustomCommandOptions

```
public class ScanCommandOptions : CommandOptions
public class CustomCommandOptions : CommandOptions
{
public string YourArgument { get ; set; }
public string YourOption { get ; set; }
Expand All @@ -69,28 +69,39 @@ Example: CustomCommandOptions
}
```

2) Add a new class called _**YourNewCommandNameCommand**_ into the **Commands folder** and inherit it from the base class _**Command**_. You do not need to implement anything to allow the **cache-dir** option, as this is added globally.
2) Add a new class called _**YourNewCommandNameCommand**_ into the **Commands folder** and inherit it from either the generic base class _**RunnableCommand<>**_ (for commands that be executed) or the base class _**Command**_ (for commands that only store subcommands). You do not need to implement anything to allow the **cache-dir** option, as this is added globally.

Example: CustomCommand

```
public class CustomCommand : Command
public class CustomCommand : RunnableCommand<CustomCommandOptions>
{
public CustomCommand() : base("custom", "Custom command description")
{
// add your arguments and/or options definitioins here. For detailed information
// you have to reference the command-line-api library documentation. Below are just
// examples. See the Commands/ScanCommand.cs for an example or go to the Command Line Api (https://github.com/dotnet/command-line-api) repository for detailed information.
// add your arguments and/or options definitioins here. For detailed information
// you have to reference the command-line-api library documentation. Below are just
// examples. See the Commands/ScanCommand.cs for an example or go to the Command Line Api (https://github.com/dotnet/command-line-api) repository for detailed information.

Option<string> yourOption= new(new[] { "--alias1", "alias2" }, description: "Option Description");
AddOption(yourOption);
Option<string> yourOption= new(new[] { "--alias1", "alias2" }, description: "Option Description");
AddOption(yourOption);

Argument<string> yourArgument = new("yourargumentname", "Argument Description")
AddArgument(yourArgument);
Argument<string> yourArgument = new("yourargumentname", "Argument Description")
AddArgument(yourArgument);
}
}
```

Typically you will not need to implement the **Run()** method for your **CustomCommand** class, as this is provided by **RunnableCommand<>**. However, if you want to augment this behavior
apart from the **CommandRunner** (see step 3), you can override that method:

```
protected override int Run(IHost host, InvocationContext context, CustomCommandOptions options)
{
// your custom logic here
return base.Run(host, context, options);
}
```

3) Add a new class called _**YourNewCommandNameCommandRunner**_ into the **CommandRunners** folder and inherit it from **CommandRunner**.
Implement the Run method.

Expand Down
22 changes: 16 additions & 6 deletions Corgibytes.Freshli.Cli.Test/Commands/CacheCommandTest.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
using System.CommandLine.Builder;
using System.CommandLine.IO;
using System.CommandLine.Parsing;
using System.Threading.Tasks;
using System.CommandLine;
using Corgibytes.Freshli.Cli.Commands;
using Corgibytes.Freshli.Cli.Commands.Cache;
using Corgibytes.Freshli.Cli.Test.Common;
using FluentAssertions;
using Xunit;
Expand All @@ -12,8 +10,6 @@ namespace Corgibytes.Freshli.Cli.Test.Commands
{
public class CacheCommandTest : FreshliTest
{
private readonly TestConsole _console = new();

public CacheCommandTest(ITestOutputHelper output) : base(output) { }

[Fact]
Expand All @@ -29,5 +25,19 @@ public void Verify_prepare_handler_configuration()
CachePrepareCommand cachePrepareCommand = new();
cachePrepareCommand.Handler.Should().NotBeNull();
}

[Fact]
public void Verify_destroy_handler_configuration()
{
CacheDestroyCommand cacheDestroyCommand = new();
cacheDestroyCommand.Handler.Should().NotBeNull();
}

[Theory]
[InlineData("--force")]
public void Verify_force_option_configuration(string alias)
{
TestHelpers.VerifyAlias<CacheDestroyCommand>(alias, ArgumentArity.ZeroOrOne, false);
}
}
}
5 changes: 5 additions & 0 deletions Corgibytes.Freshli.Cli/CommandOptions/CacheCommandOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,9 @@ public class CacheCommandOptions : CommandOptions
public class CachePrepareCommandOptions : CommandOptions
{
}

public class CacheDestroyCommandOptions : CommandOptions
{
public bool Force { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using System;
using System.CommandLine.Invocation;
using System.CommandLine.IO;
using Corgibytes.Freshli.Cli.CommandOptions;
using Corgibytes.Freshli.Cli.Extensions;
using Corgibytes.Freshli.Cli.Functionality;
using Corgibytes.Freshli.Lib;

namespace Corgibytes.Freshli.Cli.CommandRunners.Cache
{
public class CacheDestroyCommandRunner : CommandRunner<CacheDestroyCommandOptions>
{
public CacheDestroyCommandRunner(IServiceProvider serviceProvider, Runner runner)
: base(serviceProvider, runner)
{

}

public override int Run(CacheDestroyCommandOptions options, InvocationContext context)
{
// Unless the --force flag is passed, prompt the user whether they want to destroy the cache
if (!options.Force && !Confirm($"Do you want to completely DELETE the directory {options.CacheDir.FullName}?", context))
{
context.Console.Out.WriteLine("Operation aborted. Cache not destroyed.");
return true.ToExitCode();
}

// Destroy the cache
context.Console.Out.WriteLine($"Destroying cache at {options.CacheDir}");
try
{
return Functionality.Cache.Destroy(options.CacheDir).ToExitCode();
}
// Catch errors
catch (CacheException error)
{
context.Console.Error.WriteLine(error.Message);
return error.IsWarning.ToExitCode();
}

}
}
}
33 changes: 33 additions & 0 deletions Corgibytes.Freshli.Cli/CommandRunners/Cache/PrepareCacheRunner.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System;
using System.CommandLine.Invocation;
using System.CommandLine.IO;
using Corgibytes.Freshli.Cli.CommandOptions;
using Corgibytes.Freshli.Cli.Extensions;
using Corgibytes.Freshli.Cli.Functionality;
using Corgibytes.Freshli.Lib;

namespace Corgibytes.Freshli.Cli.CommandRunners.Cache
{
public class CachePrepareCommandRunner : CommandRunner<CachePrepareCommandOptions>
{
public CachePrepareCommandRunner(IServiceProvider serviceProvider, Runner runner)
: base(serviceProvider, runner)
{

}

public override int Run(CachePrepareCommandOptions options, InvocationContext context)
{
context.Console.Out.WriteLine($"Preparing cache at {options.CacheDir}");
try
{
return Functionality.Cache.Prepare(options.CacheDir).ToExitCode();
}
catch (CacheException e)
{
context.Console.Error.WriteLine(e.Message);
return false.ToExitCode();
}
}
}
}
20 changes: 2 additions & 18 deletions Corgibytes.Freshli.Cli/CommandRunners/CacheCommandRunner.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System;
using System.CommandLine.Invocation;
using Corgibytes.Freshli.Cli.CommandOptions;
using Corgibytes.Freshli.Cli.Functionality;
using Corgibytes.Freshli.Lib;

namespace Corgibytes.Freshli.Cli.CommandRunners
Expand All @@ -13,25 +13,9 @@ public CacheCommandRunner(IServiceProvider serviceProvider, Runner runner)

}

public override int Run(CacheCommandOptions options)
public override int Run(CacheCommandOptions options, InvocationContext context)
{
return 0;
}
}

public class CachePrepareCommandRunner : CommandRunner<CachePrepareCommandOptions>
{
public CachePrepareCommandRunner(IServiceProvider serviceProvider, Runner runner)
: base(serviceProvider, runner)
{

}

public override int Run(CachePrepareCommandOptions options)
{
bool success = Cache.Prepare(options.CacheDir);
return success ? 0 : 1;
}
}
}

19 changes: 17 additions & 2 deletions Corgibytes.Freshli.Cli/CommandRunners/CommandRunner.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.CommandLine.Invocation;
using Corgibytes.Freshli.Lib;

namespace Corgibytes.Freshli.Cli.CommandRunners
Expand All @@ -8,13 +10,26 @@ public abstract class CommandRunner<T> : ICommandRunner<T> where T : CommandOpti
protected Runner Runner { get; }
protected IServiceProvider Services { get; }

public CommandRunner(IServiceProvider serviceProvider, Runner runner)
protected CommandRunner(IServiceProvider serviceProvider, Runner runner)
{
Runner = runner;
Services = serviceProvider;
}

public abstract int Run(T options);
protected static bool Confirm(string message, InvocationContext context, bool defaultYes = false)
{
// Prompt the user whether they want to proceed
string prompt = defaultYes ? "[Y/n]" : "[y/N]";
context.Console.Out.Write($"{message} {prompt} ");
string choice = Console.In.ReadLine();

var yesChoices = new List<string> {"y", "Y"};
var noChoices = new List<string> {"n", "N"};

return (defaultYes ? !noChoices.Contains(choice) : yesChoices.Contains(choice));
}

public abstract int Run(T options, InvocationContext context);

}
}
4 changes: 2 additions & 2 deletions Corgibytes.Freshli.Cli/CommandRunners/ICommandRunner.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
using Corgibytes.Freshli.Cli.CommandOptions;
using System.CommandLine.Invocation;

namespace Corgibytes.Freshli.Cli.CommandRunners
{
public interface ICommandRunner<T> where T : CommandOptions.CommandOptions
{
public int Run(T options);
public int Run(T options, InvocationContext context);
}
}
3 changes: 2 additions & 1 deletion Corgibytes.Freshli.Cli/CommandRunners/ScanCommandRunner.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.CommandLine.Invocation;
using Corgibytes.Freshli.Cli.CommandOptions;
using Corgibytes.Freshli.Cli.Formatters;
using Corgibytes.Freshli.Cli.OutputStrategies;
Expand All @@ -16,7 +17,7 @@ public ScanCommandRunner(IServiceProvider serviceProvider, Runner runner): base(

}

public override int Run(ScanCommandOptions options)
public override int Run(ScanCommandOptions options, InvocationContext context)
{
if (string.IsNullOrWhiteSpace(options.Path?.FullName))
{
Expand Down
20 changes: 20 additions & 0 deletions Corgibytes.Freshli.Cli/Commands/Cache/DestroyCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System.CommandLine;
using Corgibytes.Freshli.Cli.CommandOptions;

namespace Corgibytes.Freshli.Cli.Commands.Cache
{
public class CacheDestroyCommand : RunnableCommand<CacheDestroyCommandOptions>
{
public CacheDestroyCommand()
: base("destroy", "Deletes the Freshli cache.")
{
Option<bool> forceOption = new("--force", "Don't prompt to confirm destruction of cache.")
{
Arity = ArgumentArity.ZeroOrOne
};

AddOption(forceOption);
}
}

}
11 changes: 11 additions & 0 deletions Corgibytes.Freshli.Cli/Commands/Cache/PrepareCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using Corgibytes.Freshli.Cli.CommandOptions;

namespace Corgibytes.Freshli.Cli.Commands.Cache
{
public class CachePrepareCommand : RunnableCommand<CachePrepareCommandOptions>
{
public CachePrepareCommand()
: base("prepare", "Ensures the cache directory exists and contains a valid cache database.")
{}
}
}
27 changes: 6 additions & 21 deletions Corgibytes.Freshli.Cli/Commands/CacheCommand.cs
Original file line number Diff line number Diff line change
@@ -1,36 +1,21 @@
using System.CommandLine;
using System.CommandLine.Invocation;
using System.CommandLine.NamingConventionBinder;
using Corgibytes.Freshli.Cli.CommandOptions;
using Corgibytes.Freshli.Cli.CommandRunners;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Corgibytes.Freshli.Cli.Commands.Cache;

namespace Corgibytes.Freshli.Cli.Commands
{
public class CacheCommand : Command
{
public CacheCommand() : base("cache", "Manages the local cache database")
public CacheCommand()
: base("cache", "Manages the local cache database")
{
CachePrepareCommand prepare = new();
AddCommand(prepare);

CacheDestroyCommand destroy = new();
AddCommand(destroy);
Comment on lines 11 to +15
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These variables can be inlined:

Suggested change
CachePrepareCommand prepare = new();
AddCommand(prepare);
CacheDestroyCommand destroy = new();
AddCommand(destroy);
AddCommand(new CachePrepareCommand());
AddCommand(new CacheDestroyCommand());

}
}

public class CachePrepareCommand : Command
{
public CachePrepareCommand() : base("prepare",
"Ensures the cache directory exists and contains a valid cache database.")
{
Handler = CommandHandler.Create<IHost, InvocationContext, CachePrepareCommandOptions>(Run);
}

private int Run(IHost host, InvocationContext context, CachePrepareCommandOptions options)
{
using IServiceScope scope = host.Services.CreateScope();
ICommandRunner<CachePrepareCommandOptions> runner = scope.ServiceProvider.GetRequiredService<ICommandRunner<CachePrepareCommandOptions>>();
return runner.Run(options);
}
}

}
25 changes: 25 additions & 0 deletions Corgibytes.Freshli.Cli/Commands/RunnableCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System.CommandLine;
using System.CommandLine.Invocation;
using System.CommandLine.NamingConventionBinder;
using Corgibytes.Freshli.Cli.CommandRunners;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace Corgibytes.Freshli.Cli.Commands;

#nullable enable
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of just enabling nullable types in this file, I think it makes sense to enable it for the entire project. I think that belongs in a separate pull request, though.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you agree with making it project-wide, then please create an issue for it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd considered it, and even tried it briefly, but it would require a fair bit of refactoring. I'll create an issue to spike it.

public abstract class RunnableCommand<T> : Command where T: CommandOptions.CommandOptions
{
protected RunnableCommand(string name, string? description = null) : base(name, description)
{
Handler = CommandHandler.Create<IHost, InvocationContext, T>(Run);
}

protected virtual int Run(IHost host, InvocationContext context, T options)
{
using IServiceScope scope = host.Services.CreateScope();
ICommandRunner<T> runner = scope.ServiceProvider.GetRequiredService<ICommandRunner<T>>();
return runner.Run(options, context);
}
}
#nullable restore
Loading