Skip to content

Commit

Permalink
Add option to automatically generate parameter value using external s…
Browse files Browse the repository at this point in the history
…cript
  • Loading branch information
cezarypiatek committed Sep 1, 2024
1 parent ff4e7e0 commit a9b7d8e
Show file tree
Hide file tree
Showing 8 changed files with 130 additions and 73 deletions.
3 changes: 3 additions & 0 deletions schema/v1/ScriptRunnerSchema.json
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@
"autoParameterBuilderPattern":{
"type": "string"
},
"valueGeneratorCommand":{
"type": "string"
},
"prompt": {
"type":"string",
"enum": [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
using System;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Platform;
using Microsoft.Extensions.DependencyInjection;
using Splat.Microsoft.Extensions.DependencyInjection;

Expand Down
31 changes: 29 additions & 2 deletions src/ScriptRunner/ScriptRunner.GUI/ParamsPanelFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Data.Converters;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Threading;
using Projektanker.Icons.Avalonia;
using ScriptRunner.GUI.Infrastructure;
using ScriptRunner.GUI.ScriptConfigs;
using ScriptRunner.GUI.Settings;
Expand All @@ -27,7 +30,7 @@ public ParamsPanelFactory(VaultProvider vaultProvider)
_vaultProvider = vaultProvider;
}

public ParamsPanel Create(ScriptConfig action, Dictionary<string, string> values)
public ParamsPanel Create(ScriptConfig action, Dictionary<string, string> values, Func<string, string, Task<string?>> commandExecutor)
{
var paramsPanel = new StackPanel
{
Expand Down Expand Up @@ -68,7 +71,31 @@ public ParamsPanel Create(ScriptConfig action, Dictionary<string, string> values
"paramRow"
}
};

if (string.IsNullOrWhiteSpace(param.ValueGeneratorCommand) == false)
{
var generateButton = new Button()
{
Margin = new(5,0,5,0),
Width = 50,
VerticalAlignment = VerticalAlignment.Stretch,
HorizontalContentAlignment = HorizontalAlignment.Center
};
generateButton.Click += async(sender, args) =>
{
var result = await commandExecutor($"Generate parameter for '{param.Name}'", param.ValueGeneratorCommand);
Dispatcher.UIThread.Post(() =>
{
//TODO: Handle other controls
if (controlRecord is { Control: TextBox tb })
{
tb.Text = result?.Trim() ?? string.Empty;
}
});
};
Attached.SetIcon(generateButton, "fas fa-wand-magic-sparkles");
ToolTip.SetTip(generateButton, "Auto fill");
actionPanel.Children.Add(generateButton);
}
paramsPanel.Children.Add(actionPanel);
controlRecords.Add(controlRecord);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ public class ScriptParam
public string Default { get; set; }
public Dictionary<string, string> PromptSettings { get; set; } = new();
public string? AutoParameterBuilderPattern { get; set; }
public string? ValueGeneratorCommand { get; set; }

public bool GetPromptSettings(string name, [NotNullWhen(true)] out string? value)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Data.SqlTypes;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Text;
Expand Down Expand Up @@ -126,10 +127,11 @@ private static IEnumerable<ScriptConfig> LoadFileSource(string fileName,
foreach (var action in scriptConfig.Actions)
{
action.Source = fileName;

var parameterBuilder = CreateBuilder(action);

var actionDir = Path.GetDirectoryName(fileName);

string ResolveAbsolutePath(string path)
{
if (string.IsNullOrWhiteSpace(path) == false)
Expand All @@ -143,17 +145,20 @@ string ResolveAbsolutePath(string path)
return path;
}

if (string.IsNullOrWhiteSpace(action.Command) == false && (action.Command.StartsWith(".")))
[return:NotNullIfNotNull("command")]
string? AdjustCommandPath(string? command)
{
var (commandPath, args) = MainWindowViewModel.SplitCommandAndArgs(action.Command);
action.Command = (ResolveAbsolutePath(commandPath) + " " + args).Trim();
if (string.IsNullOrWhiteSpace(command) == false && (command.StartsWith(".")))
{
var (commandPath, args) = MainWindowViewModel.SplitCommandAndArgs(command);
return (ResolveAbsolutePath(commandPath) + " " + args).Trim();
}

return command;
}

if (string.IsNullOrWhiteSpace(action.InstallCommand) == false && (action.Command.StartsWith(".")))
{
var (commandPath, args) = MainWindowViewModel.SplitCommandAndArgs(action.InstallCommand);
action.InstallCommand = (ResolveAbsolutePath(commandPath) + " " + args).Trim();
}
action.Command = AdjustCommandPath(action.Command);
action.InstallCommand = AdjustCommandPath(action.InstallCommand);

var autoGeneratedParameters = action.Params.Select(param => parameterBuilder.Build(param)).Where(paramString => string.IsNullOrWhiteSpace(paramString) == false);
action.Command += " "+string.Join(" ", autoGeneratedParameters);
Expand All @@ -169,23 +174,8 @@ string ResolveAbsolutePath(string path)
}
}

if (string.IsNullOrWhiteSpace(action.WorkingDirectory))
{
action.WorkingDirectory = actionDir;
}
else
{
action.WorkingDirectory = ResolveAbsolutePath(action.WorkingDirectory);
}

if (string.IsNullOrWhiteSpace(action.InstallCommandWorkingDirectory))
{
action.InstallCommandWorkingDirectory = actionDir;
}
else
{
action.InstallCommandWorkingDirectory = ResolveAbsolutePath(action.InstallCommandWorkingDirectory);
}
action.WorkingDirectory = string.IsNullOrWhiteSpace(action.WorkingDirectory) ? actionDir : ResolveAbsolutePath(action.WorkingDirectory);
action.InstallCommandWorkingDirectory = string.IsNullOrWhiteSpace(action.InstallCommandWorkingDirectory) ? actionDir : ResolveAbsolutePath(action.InstallCommandWorkingDirectory);

var defaultSet = new ArgumentSet()
{
Expand All @@ -194,6 +184,7 @@ string ResolveAbsolutePath(string path)

foreach (var param in action.Params)
{
param.ValueGeneratorCommand = AdjustCommandPath(param.ValueGeneratorCommand);
defaultSet.Arguments[param.Name] = param.Default;
}

Expand Down Expand Up @@ -248,9 +239,6 @@ string ResolveAbsolutePath(string path)
{
if (set.Arguments.TryGetValue(param.Name, out var defaultValue))
{



set.Arguments[param.Name] = ResolveAbsolutePath(defaultValue);
}
}
Expand Down
96 changes: 62 additions & 34 deletions src/ScriptRunner/ScriptRunner.GUI/ViewModels/MainWindowViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -425,7 +425,27 @@ private void RenderParameterForm(ScriptConfig action, Dictionary<string, string>
//var actionPanel = new StackPanel();

// Create IPanel with controls for all parameters
var paramsPanel = _paramsPanelFactory.Create(action, parameterValues);
var paramsPanel = _paramsPanelFactory.Create(action, parameterValues, (title, command) =>
{
if (SelectedAction != null)
{
var taskCompletionSource = new TaskCompletionSource<string>();
try
{
ExecuteCommand(command, this.SelectedAction, title, s =>
{
taskCompletionSource.SetResult(s);
});
}
catch (Exception e)
{
taskCompletionSource.SetException(e);
}
return taskCompletionSource.Task;
}

return Task.FromResult("");
});

// Add panel with param controls to action panel
//actionPanel.Children.Add(paramsPanel.Panel);
Expand Down Expand Up @@ -470,8 +490,7 @@ public void InstallScript()
var (commandPath, args) = SplitCommandAndArgs(installCommand);
var job = new RunningJobViewModel
{
Tile = "#" + jobCounter++,
CommandName = $"Install {selectedAction.Name}",
Tile = $"#{jobCounter++} Install {selectedAction.Name}",
ExecutedCommand = installCommand,
EnvironmentVariables = new Dictionary<string, string?>()
};
Expand Down Expand Up @@ -745,46 +764,55 @@ public void RunScript()
}


RegisterExecution(selectedAction);
AddExecutionAudit(selectedAction);

var (commandPath, args) = SplitCommandAndArgs(selectedAction.Command);
var maskedArgs = args;
var selectedActionCommand = selectedAction.Command;

ExecuteCommand(selectedActionCommand, selectedAction);

// Some audit staff
var usedParams = HarvestCurrentParameters(vaultPrefixForNewEntries: $"{selectedAction.Name}_{Guid.NewGuid():N}");
var executionLogAction = new ExecutionLogAction(DateTime.Now, selectedAction.SourceName, selectedAction.Name, usedParams);
ExecutionLog.Insert(0, executionLogAction);
SelectedRecentExecution = executionLogAction;
AppSettingsService.UpdateExecutionLog(ExecutionLog.ToList());
}

}

var envVariables = new Dictionary<string, string?>(selectedAction.EnvironmentVariables);
private void ExecuteCommand(string command, ScriptConfig selectedAction, string? title = null, Action<string>? onComplete = null)
{
var (commandPath, args) = SplitCommandAndArgs(command);
var envVariables = new Dictionary<string, string?>(selectedAction.EnvironmentVariables);
var maskedArgs = args;
foreach (var controlRecord in _controlRecords)
{
var controlValue = controlRecord.GetFormattedValue();
args = args.Replace($"{{{controlRecord.Name}}}", controlValue);
maskedArgs = maskedArgs.Replace($"{{{controlRecord.Name}}}", controlRecord.MaskingRequired? "*****": controlValue);

foreach (var controlRecord in _controlRecords)
foreach (var (key, val) in envVariables)
{
// This is definitely not pretty, should be using some ReactiveUI observables to read values?
var controlValue = controlRecord.GetFormattedValue();
args = args.Replace($"{{{controlRecord.Name}}}", controlValue);
maskedArgs = maskedArgs.Replace($"{{{controlRecord.Name}}}", controlRecord.MaskingRequired? "*****": controlValue);

foreach (var (key, val) in envVariables)
{
if(string.IsNullOrWhiteSpace(val) == false)
envVariables[key] = val.Replace($"{{{controlRecord.Name}}}", controlValue);
}
if(string.IsNullOrWhiteSpace(val) == false)
envVariables[key] = val.Replace($"{{{controlRecord.Name}}}", controlValue);
}
}

var job = new RunningJobViewModel
{
Tile = "#"+jobCounter++,
CommandName = selectedAction.Name,
ExecutedCommand = $"{commandPath} {maskedArgs}",
EnvironmentVariables = envVariables
};
this.RunningJobs.Add(job);
SelectedRunningJob = job;
job.RunJob(commandPath, args, selectedAction.WorkingDirectory, selectedAction.InteractiveInputs, selectedAction.Troubleshooting);
var job = new RunningJobViewModel
{
Tile = $"#{jobCounter++} {title ?? selectedAction.Name}",
ExecutedCommand = $"{commandPath} {maskedArgs}",
EnvironmentVariables = envVariables
};
this.RunningJobs.Add(job);
SelectedRunningJob = job;

var usedParams = HarvestCurrentParameters(vaultPrefixForNewEntries: $"{selectedAction.Name}_{Guid.NewGuid():N}");
var executionLogAction = new ExecutionLogAction(DateTime.Now, selectedAction.SourceName, selectedAction.Name, usedParams);
ExecutionLog.Insert(0, executionLogAction);
SelectedRecentExecution = executionLogAction;
AppSettingsService.UpdateExecutionLog(ExecutionLog.ToList());
if (onComplete != null)
{
job.ExecutionCompleted += (sender, args) => onComplete(job.RawOutput);
}

job.RunJob(commandPath, args, selectedAction.WorkingDirectory, selectedAction.InteractiveInputs, selectedAction.Troubleshooting);
}

public ObservableCollection<ExecutionLogAction> ExecutionLog { get; set; } = new ();
Expand All @@ -806,7 +834,7 @@ public ExecutionLogAction SelectedRecentExecution



private void RegisterExecution(ScriptConfig selectedAction)
private void AddExecutionAudit(ScriptConfig selectedAction)
{
AppSettingsService.UpdateRecent(recent =>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ public RunningJobStatus Status
set => this.RaiseAndSetIfChanged(ref _status, value);
}

public string CommandName { get; set; }
public string ExecutedCommand { get; set; }
public void CancelExecution() => ExecutionCancellation.Cancel();
public void DismissTroubleshootingMessage()
Expand Down Expand Up @@ -92,10 +91,12 @@ public void RunJob(string commandPath, string args, string? workingDirectory,
_troubleshooting = troubleshooting;
CurrentRunOutput = "";
ExecutionPending = true;

Task.Factory.StartNew(async () =>
{
var stopWatch = new Stopwatch();
stopWatch.Start();
var rawOutput = new StringBuilder();
try
{
await using var inputStream = new MultiplexerStream();
Expand All @@ -107,13 +108,16 @@ await Cli.Wrap(commandPath)
//TODO: Working dir should be read from the config with the fallback set to the config file dir
.WithWorkingDirectory(workingDirectory ?? "Scripts/")
.WithStandardInputPipe(PipeSource.FromStream(inputStream,autoFlush:true))
.WithStandardOutputPipe(PipeTarget.ToDelegate(s => AppendToOutput(s, ConsoleOutputLevel.Normal)))
.WithStandardOutputPipe(PipeTarget.ToDelegate(s =>
{
rawOutput.Append(s);
AppendToOutput(s, ConsoleOutputLevel.Normal);
}))
.WithStandardErrorPipe(PipeTarget.ToDelegate(s => AppendToOutput(s, ConsoleOutputLevel.Error)))
.WithValidation(CommandResultValidation.None)
.WithEnvironmentVariables(EnvironmentVariables)
.ExecuteAsync(ExecutionCancellation.Token);
ChangeStatus(RunningJobStatus.Finished);
Dispatcher.UIThread.Post(RaiseExecutionCompleted);
}
catch (Exception e)
{
Expand All @@ -136,7 +140,14 @@ await Cli.Wrap(commandPath)
stopWatch.Stop();
AppendToOutput("---------------------------------------------", ConsoleOutputLevel.Normal);
AppendToOutput($"Execution finished after {stopWatch.Elapsed}", ConsoleOutputLevel.Normal);
Dispatcher.UIThread.Post(() => { ExecutionPending = false; });


Dispatcher.UIThread.Post(() =>
{
ExecutionPending = false;
RawOutput = rawOutput.ToString();
RaiseExecutionCompleted();
});
_logForwarder.Finish();
}
}, TaskCreationOptions.LongRunning);
Expand Down Expand Up @@ -686,6 +697,8 @@ public bool ExecutionPending
set => this.RaiseAndSetIfChanged(ref _executionPending, value);
}

public string RawOutput { get; set; }

private int _outputIndex;
private bool _executionPending;
private StreamWriter? inputWriter;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@
<StackPanel Orientation="Horizontal">
<StackPanel >
<StackPanel Orientation="Horizontal">
<TextBlock FontSize="16" Text="{Binding Tile}" Margin="0,0,5,0" />
<TextBlock FontSize="16" Text="{Binding CommandName}" />
<TextBlock FontSize="16" Text="{Binding Tile}" />
</StackPanel>
<TextBlock FontSize="12" Text="{Binding Status, StringFormat='({0})'}" Foreground="{Binding Status, Converter={x:Static gui:JobStatusToColorConverter.Instance}}"></TextBlock>
</StackPanel>
Expand Down

0 comments on commit a9b7d8e

Please sign in to comment.