Skip to content

Commit

Permalink
feat(diff): add 'rpa diff' command to compare 2 files
Browse files Browse the repository at this point in the history
Signed-off-by: JobaDiniz <[email protected]>
  • Loading branch information
JobaDiniz committed Oct 24, 2023
1 parent c682c4c commit 52e6a73
Show file tree
Hide file tree
Showing 9 changed files with 146 additions and 85 deletions.
34 changes: 34 additions & 0 deletions src/Joba.IBM.RPA.Cli/Framework/TempFile.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
namespace Joba.IBM.RPA.Cli
{
internal class TempFile : IDisposable
{
private readonly FileInfo file;

private TempFile(FileInfo file)
{
this.file = file;
}

internal FileInfo Info => file;

internal static async Task<TempFile> CreateAsync(WalFile wal, string prefix, CancellationToken cancellation)
{
var tempDir = new DirectoryInfo(Path.GetTempPath());
if (!tempDir.Exists)
tempDir.Create();

var file = new FileInfo(Path.Combine(tempDir.FullName, $"[{prefix}] {wal.Info.Name}"));
await File.WriteAllTextAsync(file.FullName, wal.ToString(), cancellation);
return new TempFile(file);
}

internal async Task<string> ReadAsync(CancellationToken cancellation) => await File.ReadAllTextAsync(file.FullName, cancellation);
public static implicit operator FileInfo(TempFile temp) => temp.Info;

void IDisposable.Dispose()
{
if (file.Exists)
file.Delete();
}
}
}
1 change: 1 addition & 0 deletions src/Joba.IBM.RPA.Cli/RpaCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public RpaCommand() : base("Provides features to manage RPA through the command
AddCommand(new PackageCommand());
AddCommand(new BuildCommand());
AddCommand(new GitCommand());
AddCommand(new DiffCommand());

this.SetHandler(ShowHelp, Bind.FromServiceProvider<InvocationContext>());
}
Expand Down
47 changes: 47 additions & 0 deletions src/Joba.IBM.RPA.Cli/SourceControl/DiffCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using Microsoft.Extensions.Logging;

namespace Joba.IBM.RPA.Cli
{
internal partial class DiffCommand : Command
{
internal const string CommandName = "diff";

internal DiffCommand() : base(CommandName, "Allows comparing two wal files")
{
var leftFile = new Argument<FileInfo>("leftFilePath", "The left file to compare. It accepts the full path or relative to the working directory.");
var rightFile = new Argument<FileInfo>("rightFilePath", "The right file to compare. It accepts the full path or relative to the working directory.");

AddArgument(leftFile);
AddArgument(rightFile);

this.SetHandler(HandleAsync, leftFile, rightFile, Bind.FromLogger<DiffCommand>(), Bind.FromServiceProvider<InvocationContext>());
}

private async Task HandleAsync(FileInfo leftFile, FileInfo rightFile, ILogger<DiffCommand> logger, InvocationContext context)
{
var cancellation = context.GetCancellationToken();
var workingDirectory = System.Environment.CurrentDirectory;
var leftPath = leftFile.FullName;
var rightPath = rightFile.FullName;
if (Path.IsPathFullyQualified(leftPath) is false)
leftPath = Path.Combine(workingDirectory, leftPath);
if (Path.IsPathFullyQualified(rightPath) is false)
rightPath = Path.Combine(workingDirectory, rightPath);

leftFile = new FileInfo(leftPath);
rightFile = new FileInfo(rightPath);

var leftWal = WalFile.Read(leftFile);
using var leftTxt = await TempFile.CreateAsync(leftWal, "left", cancellation);
logger.LogDebug("Temp created for left {File}", leftTxt.Info);

var rightWal = WalFile.Read(rightFile);
using var rightTxt = await TempFile.CreateAsync(rightWal, "right", cancellation);
logger.LogDebug("Temp created for right {File}", rightTxt.Info);

var vsCode = new VsCode();
logger.LogDebug("Launching Vs Code...");
await vsCode.DiffAsync(leftTxt, rightTxt, cancellation);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ private async Task HandleAsync(bool remove, ILogger<GitCommand> logger, Invocati
throw new Exception("Git is not initialized in this directory");

var cancellation = context.GetCancellationToken();
var cliFile = new FileInfo(System.Environment.ProcessPath!);
var configurator = new GitConfigurator(logger, new DirectoryInfo(System.Environment.CurrentDirectory),
RpaCommand.CommandName, GitDiffCommandLine, GitMergeCommandLine);
cliFile, GitDiffCommandLine, GitMergeCommandLine);

if (remove)
await configurator.RemoveAsync(cancellation);
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using Microsoft.Extensions.Logging;
using System.Diagnostics;

namespace Joba.IBM.RPA.Cli
{
Expand Down Expand Up @@ -31,18 +30,18 @@ public MergeCommand() : base(CommandName, "Help git merging wal files. If confli
private async Task HandleAsync(FileInfo baseFile, FileInfo localFile, FileInfo remoteFile, FileInfo? mergedFile,
ILogger<GitCommand> logger, InvocationContext context)
{
var cancellation = context.GetCancellationToken();
logger.LogDebug("Files: base={base} | local={local} | remote={remote} | merged={merged}", baseFile, localFile, remoteFile, mergedFile?.FullName ?? "<null>");

var cancellation = context.GetCancellationToken();
mergedFile ??= localFile; //if 'merged' was not provided, then save the merge result back to 'local'
baseFile = new FileInfo(Path.GetFullPath(baseFile.FullName));
localFile = new FileInfo(Path.GetFullPath(localFile.FullName));
remoteFile = new FileInfo(Path.GetFullPath(remoteFile.FullName));
mergedFile = new FileInfo(Path.GetFullPath(mergedFile.FullName));

//TODO: not working... the 'base' file is corrupted by git :(
logger.LogDebug("Reading base {File} (exists={Exists})", baseFile, baseFile.Exists);
//File.Copy(baseFile.FullName, @"C:\Users\002742631\Desktop\base.wal", true);
File.Copy(baseFile.FullName, @"C:\Users\002742631\Desktop\base.wal", true);
var baseWal = WalFile.Read(baseFile);
logger.LogDebug("Reading local {File}", localFile);
var localWal = WalFile.Read(localFile);
Expand All @@ -65,64 +64,6 @@ private async Task HandleAsync(FileInfo baseFile, FileInfo localFile, FileInfo r

mergedWal.Overwrite(new WalContent(await mergedTxt.ReadAsync(cancellation)));
}

class VsCode
{
private const string ExeName = "code";

/// <summary>
/// Launches a new session of VSCode to 3-way merge files, according to the <a href="https://code.visualstudio.com/docs/editor/command-line#_core-cli-options">documentation</a>.
/// </summary>
/// <param name="leftFile"></param>
/// <param name="rightFile"></param>
/// <param name="baseFile"></param>
/// <param name="resultFile"></param>
/// <param name="cancellation"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
internal async Task MergeAsync(FileInfo leftFile, FileInfo rightFile, FileInfo baseFile, FileInfo resultFile, CancellationToken cancellation)
{
var arguments = $"-n -m \"{leftFile.FullName}\" \"{rightFile.FullName}\" \"{baseFile.FullName}\" \"{resultFile.FullName}\" --wait";
var info = new ProcessStartInfo(ExeName, arguments) { UseShellExecute = true, CreateNoWindow = true };
using var process = Process.Start(info);
if (process == null)
throw new Exception($"Could not start '{ExeName}' tool.");

await process.WaitForExitAsync(cancellation);
}
}

class TempFile : IDisposable
{
private readonly FileInfo file;

private TempFile(FileInfo file)
{
this.file = file;
}

internal FileInfo Info => file;

internal static async Task<TempFile> CreateAsync(WalFile wal, string prefix, CancellationToken cancellation)
{
var tempDir = new DirectoryInfo(Path.GetTempPath());
if (!tempDir.Exists)
tempDir.Create();

var file = new FileInfo(Path.Combine(tempDir.FullName, $"[{prefix}] {wal.Info.Name}"));
await File.WriteAllTextAsync(file.FullName, wal.ToString(), cancellation);
return new TempFile(file);
}

internal async Task<string> ReadAsync(CancellationToken cancellation) => await File.ReadAllTextAsync(file.FullName, cancellation);
public static implicit operator FileInfo(TempFile temp) => temp.Info;

void IDisposable.Dispose()
{
if (file.Exists)
file.Delete();
}
}
}
}
}
File renamed without changes.
37 changes: 37 additions & 0 deletions src/Joba.IBM.RPA.Cli/SourceControl/VsCode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System.Diagnostics;

namespace Joba.IBM.RPA.Cli
{
internal class VsCode
{
private const string ExeName = "code";

/// <summary>
/// Launches a new session of VSCode to 3-way merge files, according to the <a href="https://code.visualstudio.com/docs/editor/command-line#_core-cli-options">documentation</a>.
/// </summary>
internal async Task MergeAsync(FileInfo leftFile, FileInfo rightFile, FileInfo baseFile, FileInfo resultFile, CancellationToken cancellation)
{
var arguments = $"-n -m \"{leftFile.FullName}\" \"{rightFile.FullName}\" \"{baseFile.FullName}\" \"{resultFile.FullName}\" --wait";
var info = new ProcessStartInfo(ExeName, arguments) { UseShellExecute = true, CreateNoWindow = true };
using var process = Process.Start(info);
if (process == null)
throw new Exception($"Could not start '{ExeName}' tool.");

await process.WaitForExitAsync(cancellation);
}

/// <summary>
/// Launches a new session of VSCode to show differences between two files, according to the <a href="https://code.visualstudio.com/docs/editor/command-line#_core-cli-options">documentation</a>.
/// </summary>
internal async Task DiffAsync(FileInfo leftFile, FileInfo rightFile, CancellationToken cancellation)
{
var arguments = $"-n -d \"{leftFile.FullName}\" \"{rightFile.FullName}\" --wait";
var info = new ProcessStartInfo(ExeName, arguments) { UseShellExecute = true, CreateNoWindow = true };
using var process = Process.Start(info);
if (process == null)
throw new Exception($"Could not start '{ExeName}' tool.");

await process.WaitForExitAsync(cancellation);
}
}
}
44 changes: 22 additions & 22 deletions src/Joba.IBM.RPA/Git/GitConfigurator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ public class GitConfigurator
{
private readonly ILogger logger;
private readonly DirectoryInfo workingDir;
private readonly string cliName;
private readonly FileInfo cliFile;
private readonly string gitDiffCommandLine;
private readonly string gitMergeCommandLine;

public GitConfigurator(ILogger logger, DirectoryInfo workingDir, string cliName, string gitDiffCommandLine, string gitMergeCommandLine)
public GitConfigurator(ILogger logger, DirectoryInfo workingDir, FileInfo cliFile, string gitDiffCommandLine, string gitMergeCommandLine)
{
this.logger = logger;
this.workingDir = workingDir;
this.cliName = cliName;
this.cliFile = cliFile;
this.gitDiffCommandLine = gitDiffCommandLine;
this.gitMergeCommandLine = gitMergeCommandLine;
var gitDir = new DirectoryInfo(Path.Combine(workingDir.FullName, ".git"));
Expand All @@ -25,32 +25,32 @@ public GitConfigurator(ILogger logger, DirectoryInfo workingDir, string cliName,

public async Task ConfigureAsync(CancellationToken cancellation)
{
var gitAttributes = new GitAttributes(workingDir, cliName);
var gitAttributes = new GitAttributes(workingDir, Path.GetFileNameWithoutExtension(cliFile.Name));
await gitAttributes.ConfigureAsync(cancellation);

var gitIgnore = new GitIgnore(workingDir,cliName);
var gitIgnore = new GitIgnore(workingDir, Path.GetFileNameWithoutExtension(cliFile.Name));
await gitIgnore.ConfigureAsync(cancellation);

var diffConfigurator = new DiffConfigurator(logger, cliName, gitDiffCommandLine);
var diffConfigurator = new DiffConfigurator(logger, cliFile, gitDiffCommandLine);
await diffConfigurator.CreateConversionWrapperAsync(cancellation);
await diffConfigurator.ConfigureAsync(cancellation);

var mergeConfigurator = new MergeConfigurator(logger, cliName, gitMergeCommandLine);
var mergeConfigurator = new MergeConfigurator(logger, Path.GetFileNameWithoutExtension(cliFile.Name), gitMergeCommandLine);
await mergeConfigurator.ConfigureAsync(cancellation);
}

public async Task RemoveAsync(CancellationToken cancellation)
{
var mergeConfigurator = new MergeConfigurator(logger, cliName, gitMergeCommandLine);
var mergeConfigurator = new MergeConfigurator(logger, Path.GetFileNameWithoutExtension(cliFile.Name), gitMergeCommandLine);
await mergeConfigurator.DeleteAsync(cancellation);

var diffConfigurator = new DiffConfigurator(logger, cliName, gitDiffCommandLine);
var diffConfigurator = new DiffConfigurator(logger, cliFile, gitDiffCommandLine);
await diffConfigurator.DeleteAsync(cancellation);

var gitIgnore = new GitIgnore(workingDir, cliName);
var gitIgnore = new GitIgnore(workingDir, Path.GetFileNameWithoutExtension(cliFile.Name));
await gitIgnore.RemoveAsync(cancellation);

var gitAttributes = new GitAttributes(workingDir, cliName);
var gitAttributes = new GitAttributes(workingDir, Path.GetFileNameWithoutExtension(cliFile.Name));
await gitAttributes.RemoveAsync(cancellation);
}

Expand Down Expand Up @@ -143,21 +143,21 @@ private async Task CreateFileAsync(CancellationToken cancellation)

internal class DiffConfigurator
{
private readonly FileInfo file;
private readonly FileInfo converterFile;
private readonly ILogger logger;
private readonly string cliName;
private readonly string gitDiffCommandLine;

public DiffConfigurator(ILogger logger, string cliName, string gitDiffCommandLine)
public DiffConfigurator(ILogger logger, FileInfo cliFile, string gitDiffCommandLine)
{
this.logger = logger;
this.cliName = cliName;
this.gitDiffCommandLine = gitDiffCommandLine;
file = new FileInfo(Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.LocalApplicationData), cliName, "wal2txt"));
cliName = Path.GetFileNameWithoutExtension(cliFile.Name);
converterFile = new FileInfo(Path.Combine(cliFile.Directory!.FullName, "wal2txt"));
}

internal string Name => file.Name;
internal DirectoryInfo Directory => file.Directory!;
internal string Name => converterFile.Name;
internal DirectoryInfo Directory => converterFile.Directory!;

/// <summary>
/// Provides an intermediate file that tells git how to call 'rpa git diff [fileName]', since git does not know how to call that directly.
Expand All @@ -166,8 +166,8 @@ public DiffConfigurator(ILogger logger, string cliName, string gitDiffCommandLin
/// <returns></returns>
internal async Task CreateConversionWrapperAsync(CancellationToken cancellation)
{
file.Directory!.Create();
await File.WriteAllTextAsync(file.FullName, GetContents(), cancellation);
converterFile.Directory!.Create();
await File.WriteAllTextAsync(converterFile.FullName, GetContents(), cancellation);
AppendToPathEnvironmentVariable();
}

Expand Down Expand Up @@ -221,7 +221,7 @@ private async Task<bool> NeedsConfigurationAsync(CancellationToken cancellation)
var fileName = "git";
var arguments = $"config --global --get-regexp diff.{cliName}.*";
logger.LogDebug("Executing {FileName} {Arguments}", fileName, arguments);

var info = new ProcessStartInfo(fileName, arguments) { UseShellExecute = false, RedirectStandardError = true, RedirectStandardOutput = true };
using var process = Process.Start(info) ?? throw new Exception($"Could not start '{info.FileName} {info.Arguments}'");
process.ErrorDataReceived += OnGitError;
Expand Down Expand Up @@ -276,8 +276,8 @@ private void OnGitError(object sender, DataReceivedEventArgs e)

internal async Task DeleteAsync(CancellationToken cancellation)
{
if (file.Exists)
file.Delete();
if (converterFile.Exists)
converterFile.Delete();

var fileName = "git";
var arguments = $"config --global --remove-section diff.{cliName}";
Expand Down

0 comments on commit 52e6a73

Please sign in to comment.