Skip to content

Commit

Permalink
Merge pull request #3 from Pedrokostam/autoreset-by-historyid
Browse files Browse the repository at this point in the history
Autoreset by historyid ( 1.0.0 release )
  • Loading branch information
Pedrokostam authored Feb 21, 2024
2 parents 49d36bd + 6b8b786 commit 6b60852
Show file tree
Hide file tree
Showing 13 changed files with 336 additions and 108 deletions.
12 changes: 12 additions & 0 deletions WriteProgressPlus/Components/IdConflictException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace WriteProgressPlus.Components;
internal class IdConflictException:Exception
{
public IdConflictException():base($"{nameof(WriteProgressPlusCommand.ParentID)} cannot be the same as {nameof(WriteProgressPlusCommand.ID)}")
{

}
}
50 changes: 44 additions & 6 deletions WriteProgressPlus/Components/ItemFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public partial class ItemFormatter

public string PropertiesSeparator { get; set; } = DefaultSeparator;

// Saves given values inside this instance, so that they can be used when formatting a future item.
public void Update(ScriptBlock? script, string[]? props, string? sep)
{
if (script is null)
Expand Down Expand Up @@ -57,6 +58,7 @@ public void Update(ScriptBlock? script, string[]? props, string? sep)
return null;
}

// Script has priority
if (Script is not null)
{
return Script.InvokeReturnAsIs(objects)?.ToString();
Expand All @@ -73,17 +75,23 @@ public void Update(ScriptBlock? script, string[]? props, string? sep)
}
}

private string? GetFormattedProperties(object firstObject)
/// <summary>
/// Return a string made of values of selected <see cref="Properties"/>, concatenated with <see cref="PropertiesSeparator"/>.
/// If no property was selected, returns null.
/// </summary>
/// <param name="objectToFormat"></param>
/// <returns>String of property values or null</returns>
private string? GetFormattedProperties(object objectToFormat)
{
// items from pipeline will actually be PSObjects
// only specifying it as -InputObject will give the raw object
if (firstObject is PSObject pso)
if (objectToFormat is PSObject pso)
{
GetPropertyOfPsObject(pso);
}
else
{
GetPropertyOfNormalObject(firstObject);
GetPropertyOfNormalObject(objectToFormat);
}

if (Components.Count == 0)
Expand All @@ -94,6 +102,11 @@ public void Update(ScriptBlock? script, string[]? props, string? sep)
return string.Join(PropertiesSeparator, Components);
}

/// <summary>
/// Given a PSObject, selects properties matching given pattern (possible with wildcards).
/// Fills <see cref="Components"/> with matching properties' values.
/// </summary>
/// <param name="pso"></param>
void GetPropertyOfPsObject(PSObject pso)
{
// Uses PSObject's built in Match method for handling wildcards
Expand All @@ -110,8 +123,18 @@ void GetPropertyOfPsObject(PSObject pso)
}
}

/// <summary>
/// Given a non-PSObject, goes through each of its properties
/// and selects those matching given pattern (possible with wildcards).
/// Fills <see cref="Components"/> with matching properties' values.
/// </summary>
/// <param name="obj"></param>
void GetPropertyOfNormalObject(object obj)
{
if(obj is PSObject)
{
throw new InvalidOperationException($"Method {nameof(GetPropertyOfNormalObject)} cannot be used on a PSObject");
}
Type t = obj.GetType();
var allProperties = t.GetProperties();
foreach (string propertyName in Properties!)
Expand All @@ -132,6 +155,13 @@ void GetPropertyOfNormalObject(object obj)
}
}

/// <summary>
/// Given a non-wildcard pattern compares each property's name to the pattern (case insensitive).
/// If the property matches, its value is added to <see cref="Components"/>
/// </summary>
/// <param name="obj"></param>
/// <param name="allProperties"></param>
/// <param name="name"></param>
private void MatchNormalProp(object obj, PropertyInfo[] allProperties, string name)
{
foreach (var property in allProperties)
Expand All @@ -143,18 +173,24 @@ private void MatchNormalProp(object obj, PropertyInfo[] allProperties, string na
}
}

private void MatchNormapProp_Wild(object obj, PropertyInfo[] allProperties, string pattern)
/// <summary>
/// Uses the given pattern as a case-insensitive regex pattern and applies it to each property.
/// If the property matches, its value is added to <see cref="Components"/>
/// </summary>
/// <param name="obj"></param>
/// <param name="allProperties"></param>
/// <param name="regexPattern">Valid regex pattern</param>
private void MatchNormapProp_Wild(object obj, PropertyInfo[] allProperties, string regexPattern)
{
foreach (var property in allProperties)
{
if (Regex.IsMatch(property.Name, pattern, RegexOptions.IgnoreCase, RegexTimeout))
if (Regex.IsMatch(property.Name, regexPattern, RegexOptions.IgnoreCase, RegexTimeout))
{
Components.Add(property.GetValue(obj));
}
}
}

private readonly static MatchEvaluator AliasReplacer = new(ReplaceAlias);

/// <summary>
/// There are 3 possible wildcard: *, ?, []. The brackets can be left as they are, because they regex-compliant
Expand All @@ -166,6 +202,8 @@ private void MatchNormapProp_Wild(object obj, PropertyInfo[] allProperties, stri

private readonly static MatchEvaluator Evaluator = new(ReplaceWildcard);

private readonly static MatchEvaluator AliasReplacer = new(ReplaceAlias);

private static string ReplaceAlias(Match match)
{
int aliasIndex = match.Groups["alias"].Value switch
Expand Down
48 changes: 0 additions & 48 deletions WriteProgressPlus/Components/ProgressBase.cs

This file was deleted.

93 changes: 93 additions & 0 deletions WriteProgressPlus/Components/ProgressBaseCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
using System.Management.Automation;
using System.Diagnostics;
using static System.FormattableString;
namespace WriteProgressPlus.Components;
public class ProgressBaseCommand : PSCmdlet
{
internal const int Offset = 2137;

internal static readonly Dictionary<int, ProgressState> ProgressDict = [];

/// <summary>
/// Looks for the state associated with the given instance (by its ID).
/// If no matching state is found, creates a new one and returns it.
/// <para/>
/// If the state comes from different historyID, it may be removed and created anew (depending on settings of <paramref name="current"/>)
/// </summary>
/// <param name="current"></param>
/// <returns></returns>
public static ProgressState GetProgressInner(WriteProgressPlusCommand current)
{
if (ProgressDict.TryGetValue(current.ID, out ProgressState? existingProgressInner))
{
if (current.KeepState.IsPresent || existingProgressInner.HistoryId == current.HistoryId)
{
// If the history ids match, or if user requested state keeping, return the existing state
return existingProgressInner;
}
else // HistoryId is different and user does not want to keep state
{
// Do not write the comlete bar - due to pwsh7 throttling the complete update of bar
// will make the new bar not display
// From powershell point of view the bar never went away, just changed activity, etc...
RemoveProgressInner(current.ID, false);
return AddNewProgressInner(current);
}
}
else
{
return AddNewProgressInner(current);
}
}

private static ProgressState AddNewProgressInner(WriteProgressPlusCommand current)
{
ProgressState p = new(current);
ProgressDict.Add(current.ID, p);
return p;
}

/// <summary>
/// Removes progress bar state associated with the given id. Does nothing if id is not associated with anything.
/// <para/>
/// If state is removed, its bar will be updated once with RecordType set to complete.
/// </summary>
/// <param name="id">ID of ProgressState</param>
/// <returns></returns>
public static bool RemoveProgressInner(int id) => RemoveProgressInner(id, writeCompleted: true);

private static bool RemoveProgressInner(int id, bool writeCompleted)
{
if (!ProgressDict.TryGetValue(id, out ProgressState? progressInner))
{
return false;
}
if (writeCompleted)
{
// Set recordtype to completed
// According to documentation, each bar should be written once with RecordType set to Completed
// This ensures that the bar will be removed
progressInner.AssociatedRecord.RecordType = ProgressRecordType.Completed;
progressInner.AssociatedRecord.PercentComplete = 100;
progressInner.WriteProgress();
// However, if the state is being removed, because a new bar from different command is requested
// We should not complete it - the bar should be reused
// Thus we skip this section altogether if writeCompleted is false
}
Debug.WriteLine(Invariant($"Removed state for progress bar {id - Offset} (was {progressInner.ActualCurrentIteration} iteration)"));
ProgressDict.Remove(id);
return true;
}

/// <summary>
/// Removes all progress bar states
/// </summary>
public void ClearProgressInners()
{
var keys = ProgressDict.Keys.ToArray();
foreach (int id in keys)
{
RemoveProgressInner(id);
}
}
}
Loading

0 comments on commit 6b60852

Please sign in to comment.