Skip to content

Commit

Permalink
Tyrrrz#13 - Add installation code for suggest mode.
Browse files Browse the repository at this point in the history
  • Loading branch information
mauricel committed Apr 12, 2021
1 parent e0acc16 commit 5e56901
Show file tree
Hide file tree
Showing 9 changed files with 280 additions and 9 deletions.
1 change: 1 addition & 0 deletions CliFx.Demo/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public static async Task<int> Main() =>
.SetDescription("Demo application showcasing CliFx features.")
.AddCommandsFromThisAssembly()
.UseTypeActivator(GetServiceProvider().GetRequiredService)
.AllowSuggestMode(true)
.Build()
.RunAsync();
}
Expand Down
17 changes: 13 additions & 4 deletions CliFx/CliApplication.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using CliFx.Infrastructure;
using CliFx.Input;
using CliFx.Schema;
using CliFx.Suggestions;
using CliFx.Utils;
using CliFx.Utils.Extensions;

Expand All @@ -32,6 +33,7 @@ public class CliApplication
private readonly ITypeActivator _typeActivator;

private readonly CommandBinder _commandBinder;
private readonly IFileSystem _fileSystem;

/// <summary>
/// Initializes an instance of <see cref="CliApplication"/>.
Expand All @@ -40,14 +42,16 @@ public CliApplication(
ApplicationMetadata metadata,
ApplicationConfiguration configuration,
IConsole console,
ITypeActivator typeActivator)
ITypeActivator typeActivator,
IFileSystem fileSystem)
{
Metadata = metadata;
Configuration = configuration;
_console = console;
_typeActivator = typeActivator;

_commandBinder = new CommandBinder(typeActivator);
_fileSystem = fileSystem;
}

private bool IsDebugModeEnabled(CommandInput commandInput) =>
Expand Down Expand Up @@ -107,11 +111,16 @@ private async ValueTask<int> RunAsync(ApplicationSchema applicationSchema, Comma
}

// Handle suggest directive
if (Configuration.IsSuggestModeAllowed)
{
new SuggestionService(applicationSchema, _fileSystem).EnsureInstalled(Metadata.Title);
}

if (IsSuggestModeEnabled(commandInput))
{
new SuggestionService(applicationSchema)
.GetSuggestions(commandInput).ToList()
.ForEach(p => _console.Output.WriteLine(p));
new SuggestionService(applicationSchema, _fileSystem)
.GetSuggestions(commandInput).ToList()
.ForEach(p => _console.Output.WriteLine(p));
return 0;
}

Expand Down
14 changes: 13 additions & 1 deletion CliFx/CliApplicationBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public partial class CliApplicationBuilder
private string? _description;
private IConsole? _console;
private ITypeActivator? _typeActivator;
private IFileSystem? _fileSystem;

/// <summary>
/// Adds a command to the application.
Expand Down Expand Up @@ -121,6 +122,7 @@ public CliApplicationBuilder AllowSuggestMode(bool isAllowed = true)
return this;
}


/// <summary>
/// Sets application title, which is shown in the help text.
/// </summary>
Expand Down Expand Up @@ -177,6 +179,15 @@ public CliApplicationBuilder UseConsole(IConsole console)
return this;
}

/// <summary>
/// Configures the application to use the specified implementation of <see cref="IFileSystem"/>.
/// </summary>
CliApplicationBuilder UseFileSystem(IFileSystem fileSystem)
{
_fileSystem = fileSystem;
return this;
}

/// <summary>
/// Configures the application to use the specified implementation of <see cref="ITypeActivator"/>.
/// </summary>
Expand Down Expand Up @@ -215,7 +226,8 @@ public CliApplication Build()
metadata,
configuration,
_console ?? new SystemConsole(),
_typeActivator ?? new DefaultTypeActivator()
_typeActivator ?? new DefaultTypeActivator(),
_fileSystem ?? new FileSystem()
);
}
}
Expand Down
27 changes: 27 additions & 0 deletions CliFx/Infrastructure/FileSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System.IO;

namespace CliFx.Infrastructure
{
class FileSystem : IFileSystem
{
public void Copy(string sourceFileName, string destFileName)
{
File.Copy(sourceFileName, destFileName);
}

public bool Exists(string path)
{
return File.Exists(path);
}

public string ReadAllText(string path)
{
return File.ReadAllText(path);
}

public void WriteAllText(string path, string content)
{
File.WriteAllText(path, content);
}
}
}
35 changes: 35 additions & 0 deletions CliFx/Infrastructure/IFileSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

namespace CliFx.Infrastructure
{
/// <summary>
/// Abstraction for the file system
/// </summary>
public interface IFileSystem
{
/// <summary>
/// Determines whether the specified file exists.
/// </summary>
bool Exists(string filePath);

/// <summary>
/// Opens a text file, reads all the text in the file, and then closes the file.
/// </summary>
string ReadAllText(string filePath);

/// <summary>
/// Creates a new file, writes the specified string to the file, and then closes
/// the file. If the target file already exists, it is overwritten.
/// </summary>
void WriteAllText(string filePath, string content);

/// <summary>
/// Copies an existing file to a new file. Overwriting a file of the same name is
/// not allowed.
/// </summary>
void Copy(string path, string backupPath);
}
}
60 changes: 60 additions & 0 deletions CliFx/Suggestions/BashSuggestEnvironment.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

namespace CliFx.Suggestions
{
class BashSuggestEnvironment : ISuggestEnvironment
{
public string Version => "V1";

public bool ShouldInstall()
{
if (Environment.OSVersion.Platform == PlatformID.Unix || Environment.OSVersion.Platform == PlatformID.MacOSX)
{
return File.Exists(GetInstallPath());
}
return false;
}

public string GetInstallPath()
{
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".bashrc");
}

public string GetInstallCommand(string commandName)
{
var safeName = commandName.Replace(" ", "_");
return $@"
### clifx-suggest-begins-here-{safeName}-{Version}
# this block provides auto-complete for the {commandName} command
# and assumes that {commandName} is on the path
_{safeName}_complete()
{{
local word=${{COMP_WORDS[COMP_CWORD]}}
# generate unique environment variable
CLIFX_CMD_CACHE=""clifx-suggest-$(uuidgen)""
# replace hyphens with underscores to make it valid
CLIFX_CMD_CACHE=${{CLIFX_CMD_CACHE//\-/_}}
export $CLIFX_CMD_CACHE=${{COMP_LINE}}
local completions
completions=""$({commandName} ""[suggest]"" --cursor ""${{COMP_POINT}}"" --envvar $CLIFX_CMD_CACHE 2>/dev/null)""
if [ $? -ne 0]; then
completions=""""
fi
unset $CLIFX_CMD_CACHE
COMPREPLY=( $(compgen -W ""$completions"" -- ""$word"") )
}}
complete -f -F _{safeName}_complete ""{commandName}""
### clifx-suggest-ends-here-{safeName}";
}
}
}
17 changes: 17 additions & 0 deletions CliFx/Suggestions/ISuggestEnvironment.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace CliFx.Suggestions
{
interface ISuggestEnvironment
{
bool ShouldInstall();

string Version { get; }

string GetInstallPath();

string GetInstallCommand(string command);
}
}
64 changes: 64 additions & 0 deletions CliFx/Suggestions/PowershellSuggestEnvironment.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

namespace CliFx.Suggestions
{
class PowershellSuggestEnvironment : ISuggestEnvironment
{
public string Version => "V1";

public bool ShouldInstall()
{
if (Environment.OSVersion.Platform == PlatformID.Unix || Environment.OSVersion.Platform == PlatformID.MacOSX)
{
return File.Exists("/usr/bin/pwsh");

}
return true;
}

public string GetInstallPath()
{
var baseDir = "";
if (Environment.OSVersion.Platform == PlatformID.Unix || Environment.OSVersion.Platform == PlatformID.MacOSX)
{
baseDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".config", ".powershell");
}
else
{
var myDocuments = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments, Environment.SpecialFolderOption.DoNotVerify);
baseDir = Path.Combine(myDocuments, "WindowsPowerShell");
}

return Path.Combine(baseDir, "Microsoft.PowerShell_profile.ps1");
}

public string GetInstallCommand(string commandName)
{
var safeName = commandName.Replace(" ", "_");
return $@"
### clifx-suggest-begins-here-{safeName}-{Version}
# this block provides auto-complete for the {commandName} command
# and assumes that {commandName} is on the path
$scriptblock = {{
param($wordToComplete, $commandAst, $cursorPosition)
$command = ""{commandName}""
$commandCacheId = ""clifx-suggest-"" + (new-guid).ToString()
Set-Content -path ""ENV:\$commandCacheId"" -value $commandAst
$result = &$command `[suggest`] --envvar $commandCacheId --cursor $cursorPosition | ForEach-Object {{
[System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
}}
Remove-Item -Path ""ENV:\$commandCacheId""
$result
}}
Register-ArgumentCompleter -Native -CommandName ""{commandName}"" -ScriptBlock $scriptblock
### clifx-suggest-ends-here-{safeName}";
}
}
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
using CliFx.Input;
using CliFx.Infrastructure;
using CliFx.Input;
using CliFx.Schema;
using CliFx.Utils;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace CliFx
namespace CliFx.Suggestions
{
internal class SuggestionService
{
private ApplicationSchema _applicationSchema;
private readonly IFileSystem _fileSystem;

public SuggestionService(ApplicationSchema applicationSchema)
public SuggestionService(ApplicationSchema applicationSchema, IFileSystem fileSystem)
{
_applicationSchema = applicationSchema;
_fileSystem = fileSystem;
}

public IEnumerable<string> GetSuggestions(CommandInput commandInput)
Expand Down Expand Up @@ -72,5 +78,45 @@ private static List<string> NoSuggestions()
{
return new List<string>();
}

public void EnsureInstalled(string commandName)
{
foreach (var env in new ISuggestEnvironment[] { new BashSuggestEnvironment(), new PowershellSuggestEnvironment() })
{
var path = env.GetInstallPath();

if(!env.ShouldInstall())
{
continue;
}

if (!_fileSystem.Exists(path))
{
_fileSystem.WriteAllText(path, "");
}

var pattern = $"### clifx-suggest-begins-here-{Regex.Escape(commandName)}-{env.Version}.*### clifx-suggest-ends-here-{Regex.Escape(commandName)}";
var script = _fileSystem.ReadAllText(path);
var match = Regex.Match(script, pattern, RegexOptions.Singleline);
if (match.Success)
{
continue;
}

var uninstallPattern = $"### clifx-suggest-begins-here-{Regex.Escape(commandName)}.*### clifx-suggest-ends-here-{Regex.Escape(commandName)}";
var sb = new StringBuilder(Regex.Replace(script, uninstallPattern, "", RegexOptions.Singleline));
sb.AppendLine(env.GetInstallCommand(commandName));

// move backup to temp folder for OS to delete eventually (just in case something really bad happens)
var tempFile = Path.GetFileName(path);
var tempExtension = Path.GetExtension(tempFile) + $".backup_{DateTime.UtcNow.ToFileTime()}";
tempFile = Path.ChangeExtension(tempFile, tempExtension);
var backupPath = Path.Combine(Path.GetTempPath(), tempFile);

_fileSystem.Copy(path, backupPath);
_fileSystem.WriteAllText(path, sb.ToString());
}
}
}
}
}

0 comments on commit 5e56901

Please sign in to comment.