Giter VIP home page Giter VIP logo

loopz's Introduction

๐Ÿงฟ Elizium.Loopz

A B A B A B A B

PowerShell iteration utilities with additional goodies like Parameter Set Tools.

Introduction

When writing a suite of utilities/functions it can be difficult to develop them so that they behave in a consistent manner. Along with another dependent Powershell module Elizium.Krayola, Elizium.Loopz can be used to build PowerShell commands that are both more visually appealing and consistent particularly with regards to rendering repetitive content as a result of some kind of iteration process.

The module can be installed using the standard install-module command:

PS> install-module -Name Elizium.Loopz -Scope AllUsers

โš ๏ธ BREAKING: As of version 4.0, Rename-Many has been moved into a separate module: RexFS

Dependencies

Requires:

which will be installed automatically if not already present.

For best results on windows, it is recommended that the user installs and uses Microsoft's Windows Terminal, since it has better support for emojis when compared to the ageing Console app. Users can also try TerminalBuddy, another PowerShell module, to assist in setting up custom colour themes.

The ๐Ÿ“œ ChangeLog for this project is available here.

The Main Commands (for end users)

COMMAND-NAME DESCRIPTION
Format-Escape Escape Regex param
Show-Signals Show signals with overrides
Select-Patterns Find text inside files
Convert-Emojis Convert emoji short codes to HTML code points

Iteration functions (for end developers)

The following table shows the list of public commands exported from the Loopz module:

COMMAND-NAME DESCRIPTION
Invoke-ForeachFsItem Invoke a function foreach file system object
Invoke-MirrorDirectoryTree Copy a directory tree invoking a function
Invoke-TraverseDirectory Navigate a directory tree invoking a function
Show-Header Show iteration Header
Show-Summary Show iteration Summary
Write-HostFeItemDecorator Write output foreach file system object

Parameter Set Tools

This module includes a collection of commands/classes that comprise the parameter set tools. When building new commands that use the parameter set framework, it can be difficult to build them so they don't violate the established rules, particular when the command is complex and has a large number of parameters and parameter sets. These parameter sets tools aims to fill a void and give developers some additional tools that can be used to resolved common parameter issues. The following table shows the commands in this tool set:

COMMAND-NAME DESCRIPTION
Show-InvokeReport โœ”๏ธ Show command invoke report
Show-ParameterSetInfo โœ”๏ธ Show parameter set info
Show-ParameterSetReport โœ”๏ธ Show parameter set violations
CLASS-NAME DESCRIPTION
DryRunner Dry run a command
RuleController Parameter set rules
Syntax Command syntax

See Parameter Set Tools ๐Ÿ™

Emoji Converter

Included in this module is a command that converts files (typically markdown) which contains emoji short code references to HTML code points. The need for this command was discovered when it was found that external documentation services such as gitbook do not support emoji short codes. If the user is dealing with a lot of content that contain many short codes references, it would be impractical to convert these by hand. The command Convert-Emojis can perform bulk conversion of files using the github emoji api for reference.

Supporting Utilities (for developers)

COMMAND-NAME DESCRIPTION
Edit-RemoveSingleSubString Remove single substring
Format-StructuredLine Create Krayon line
Get-FormattedSignal Get formatted signal
Get-InverseSubstring Get inverse substring (the opposite of standard substring string method)
Get-IsLocked Get locked state of a command
Get-PaddedLabel Get space padded string
Get-PlatformName Get platform name (OS type)
Get-PsObjectField Get field from PSCustomObject
Get-Signals Get signals
Initialize-ShellOperant Init shell operation
Invoke-ByPlatform Invoke OS specific fn
New-RegularExpression Regex factory fn
Resolve-ByPlatform Resolve item by OS type
Resolve-PatternOccurrence Regex param helper
Select-FsItem A predicate fn used for filtering
Select-SignalContainer Select signal into a container
Split-Match Split regex match
Test-IsFileSystemSafe Test if string is FS safe
Update-GroupRefs Update named group refs
CLASS-NAME DESCRIPTION
bootstrap Command init helper

General Concepts

โœจ Exchange hashtable object

A common theme present in the main commands is the use of a Hash-table object called $Exchange. The scenarios in which the Exchange are as follows:

  • Allows calling code to send additional parameters to a Loopz command outside of its regular signature.
  • Allows invoked code to return information back to calling code.

Let's elaborate the above points...

โญ First point: Invoke-ForeachFsItem requires calling code to either specify a script-block or a function (collectively called the invokee). The invokee must have to conform to a signature accepting the following four common arguments:

  • Underscore: the current pipeline item
  • Index: an allocated numeric value indicating the sequence number in the pipeline
  • Exchange: the hash-table containing additional named items, and other information gathered throughout processing
  • Trigger: client controlled boolean flag that should be used to denote if update/write action was taken for a particular item in pipeline. (Relevant for state changing operations only).

When additional parameters need to be sent to the invokee, there is already a mechanism for passing these (either with BlockParams or FuncteeParams), this approach is generally preferred.

However, there is another commonly occurring pattern which would require the use of Exchange. This pattern is the adapter pattern. If there is an existing function that needs to be integrated to be used with Invoke-ForeachFsItem, but does not match the required signature, an intermediate adapter can be implemented. Calling code can put in any additional parameters (required by the non-conformant function) into the Exchange, which are picked up by the adapter and forwarded on as required. Using the adapter this way is much preferred than using additional parameters (BlockParams or FuncteeParams), because there could be confusion as to whom these parameters are required for, the adapter or the target function/script-block. Using parameters in Exchange can be made to be much clearer because very meaningful names can be used as hash-table keys; Eg, for internal Loopz command interaction (Invoke-MirrorDirectoryTree internally invokes Invoke-TraverseDirectory and uses keys like 'LOOPZ.MIRROR.INVOKEE', which means that, that value is only of importance to Invoke-TraverseDirectory, so any other function that sees this should ignore it).

๐Ÿ“Œ Note, users should use a similar namespaced style keys, for their own use, to avoid any chance of name clashes and users should not use any keys beginning with 'LOOPZ.' as these are reserved for internal Loopz operation.

โš ๏ธ Warning don't nest Invoke-ForeachFsItem calls, using the same Exchange instance. That is to say do not use a function/script-block already known to call 'Invoke-ForeachFsItem' with its own Invoke-ForeachFsItem request using the same Exchange instance. If you need to achieve this, then a new and separate Exchange instance should be created. However, recursive functions are fine, as long as it makes sense that different iterations use the same Exchange.

โญ Second point:

The Invoke-MirrorDirectoryTree command illustrates this well. Invoke-MirrorDirectoryTree needs to be able to present the invokee with multiple (actually, just 2) DirectoryInfo objects for each source directory encountered, one for the source directory and another for the mirrored directory. Since Invoke-ForeachFsItem is the command that under-pins this functionality, Invoke-MirrorDirectoryTree needs to conform to it's requirements, one of which is that a single DirectoryInfo is presented to the invokee. To get around this, it populates a new entry inside the Exchange: 'LOOPZ.MIRROR.ROOT-DESTINATION', which the invokee can now access. This same technique can be used by calling code.

โœจ The Trigger

If the script-block/function (invokee) to be invoked by Invoke-ForeachFsItem, Invoke-MirrorDirectoryTree or Invoke-TraverseDirectory (the compound function) is a state changing operation (such as renaming a file or a directory), it may be useful to know if the invokee actually performed the change or not, especially when a particular command is re-run. It may be that a rerun of a command results in no actual state change and it may be useful to know this after the batch has completed. (Please don't confuse this with WhatIf behaviour. An example of not performing an action being alluded to here, is an attempt to rename a file where the new name is the same as the existing one; this could happen in a re-run) In this scenario, the invokee must set the Trigger accordingly. If the write action was performed, then Trigger should be set (it's just a boolean value) on the PSCustomObject that it should return. The Trigger that the invokee receives as one of the fixed parameters passed to it by the compound function, reflects if any of the previous items in the pipeline set the Trigger.

If the user needs to write functionality that needs to be able to support re-runs, where the re-run should not produce overly verbose output, because no real action was performed for some items in the pipeline, then use of the 'LOOPZ.WH-FOREACH-DECORATOR.IF-TRIGGERED' setting in the Exchange should be made. It should be set to true (although in reality, just the existence of the IF-TRIGGERED key, sets this option):

  $Exchange['LOOPZ.WH-FOREACH-DECORATOR.IF-TRIGGERED'] = $true

๐Ÿ“Œ This indicates that for a particular item in the pipeline, no output should be written for that item, if the invokee has not set the Trigger to true, to indicate that action has been performed

If a Summary script-block is supplied to the compound function, then it will see if any of the pipeline items set the Trigger.

โœจ Write-HostFeItemDecorator

When using a custom function/script-block (invokee) with one of the compound functions it is considered good form not to write to the host within the command being written (PSScriptAnalyzer warning PSAvoidUsingWriteHost comes to mind). This is so that the command can be composed into a pipeline without generating convoluted output (plus other reasons). However, it isn't against the law to write output and command line utilities are made much the richer and user friendly when they receive feedback for the operations being performed.

This is where Write-HostFeItemDecorator comes in. It allows the development of commands that don't write to the host, leaving this to be taken over by Write-HostFeItemDecorator.

The following shows an example of using a named function with Invoke-ForeachFsItem

  function Resize-Image {
    param(
      [System.IO.FileInfo]$Underscore,
      [int]$Index,
      [System.Collections.Hashtable]$Exchange,
      [boolean]$Trigger
    )

    [PSCustomObject]@{ Product = $FileInfo; }
  }

  [string]$directoryPath = './Data/fefsi';
  Get-ChildItem $directoryPath -Recurse -File -Filter "*.jpg" | Invoke-ForeachFsItem -Functee 'Resize-Image'

The function does not write any output to the host. However, it might be desirable to do so. Rather than include that logic into Resize-Image, it can be modified to populate the returned PSCustomObject that it already creates with additional properties (although this part is optional) and then making use of Write-HostFeItemDecorator.

This can be achieved by defining our end function in the Exchange under key 'LOOPZ.WH-FOREACH-DECORATOR.FUNCTION-NAME' and selecting Write-HostFeItemDecorator to be the Functee on Invoke-ForeachFsItem.

  function Resize-Image {
    param(
      [System.IO.DirectoryInfo]$Underscore,
      [int]$Index,
      [System.Collections.Hashtable]$Exchange,
      [boolean]$Trigger,
    )
    ...
    $pairs = @(
      @('By', $percentage), @('Height', $height), @('Width', $width)
    );
    @{ Product = $Underscore; Pairs = $pairs; }
  }

  [Systems.Collection.Hashtable]$Exchange = @{
    'LOOPZ.WH-FOREACH-DECORATOR.FUNCTION-NAME' = 'Resize-Image';
  }

  [string]$directoryPath = './Data/fefsi';
  Get-ChildItem $directoryPath -Recurse -File -Filter "*.jpg" | Invoke-ForeachFsItem -Exchange $Exchange
    -Functee 'Write-HostFeItemDecorator'

This sets up a new calling chain, where Invoke-ForeachFsItem invokes the Write-HostFeItemDecorator function and it in turn invokes the function defined in 'LOOPZ.WH-FOREACH-DECORATOR.FUNCTION-NAME' in this case being Resize-Image. This technique can also be used with Invoke-MirrorDirectoryTree and Invoke-TraverseDirectory.

Helpers

Some global definitions have been exported as global variables as an aid to using the functions in this module.

๐ŸŽฏ Predefined Header script-block

$LoopzHelpers.HeaderBlock

The HeaderBlock can be used on any compound function that that has a Header parameter. The Header can be customised with the following Exchange entries:

  • 'LOOPZ.KRAYOLA-THEME': Krayola Theme generally in use
  • 'LOOPZ.HEADER-BLOCK.MESSAGE': message displayed as part of the header
  • 'LOOPZ.HEADER-BLOCK.CRUMB-SIGNAL': Lead text displayed in header, default: '[+] '
  • 'LOOPZ.HEADER.PROPERTIES': An array of Key/Value pairs of items to be displayed
  • 'LOOPZ.HEADER-BLOCK.LINE': A string denoting the line to be displayed. (There are predefined lines available to use in $LoopzUI, or a custom one can be used instead)

The HeaderBlock will generated either a single line or multi-line Header depending on whether custom properties have been defined. When properties have been defined under key LOOPZ.HEADER.PROPERTIES then a multi-line Header is generated, eg:

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The sound the wind makes in the pines // ["A" => "One", "B" => "Two", "C" => "Three"]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

If no properties have been defined then a single line Header will be generated, eg:

[+] ============================================================= [ What lies in the darkness ] ===

What is displayed in the Header is driven by what is defined in the Krayola theme or items in the Exchange, so in this example

  • 'CRUMB-A' (Exchange): 'LOOPZ.HEADER-BLOCK.CRUMB-SIGNAL'
  • 'What lies in the darkness' (Exchange): 'LOOPZ.HEADER-BLOCK.MESSAGE'
  • '[': (Theme): 'OPEN'
  • ']': (Theme): 'CLOSE'

The user can specify the pre-defined Header script block or can defined their own. The signature of the Header script-block is as follows:

  param(
    [System.Collections.Hashtable]$Exchange
  )

๐ŸŽฏ Predefined Summary script-block

$LoopzHelpers.SummaryBlock

The SummaryBlock can be used on any compound function that that has a Summary parameter. It can be customised by specifying a line string under key 'LOOPZ.SUMMARY-BLOCK.LINE'. Any string can be defined or one of the pre-defined lines (see below) can be specified.

A custom summary message may also be defined under key 'LOOPZ.SUMMARY-BLOCK.MESSAGE'; this is optional and if not specified, the word 'Summary' will be used.

A Krayola theme may be specified and as one may already have been defined for Write-HostFeFsItem under key 'LOOPZ.KRAYOLA-THEME', this will also be used by the Summary block.

The user can specify the pre-defined Summary script block or can defined their own. The signature of the Summary script-block is as follows:

  param(
    [int]$Count,
    [int]$Skipped,
    [boolean]$Triggered,
    [System.Collections.Hashtable]$Exchange
  )

๐ŸŽฏ Line definitions

To be set under key 'LOOPZ.HEADER-BLOCK.LINE' and/or 'LOOPZ.SUMMARY-BLOCK.LINE' of the Exchange as previously discussed.

$LoopzUI.UnderscoreLine
$LoopzUI.EqualsLine
$LoopzUI.DashLine
$LoopzUI.DotsLine
$LoopzUI.LightDashLine
$LoopzUI.LightDotsLine
$LoopzUI.TildeLine
$LoopzUI.SmallUnderscoreLine
$LoopzUI.SmallEqualsLine
$LoopzUI.SmallLightDashLine
$LoopzUI.SmallDashLine
$LoopzUI.SmallDotsLine
$LoopzUI.SmallLightDotsLine
$LoopzUI.SmallTildeLine

๐ŸŽฏ Predefined Write-HostFeFsItem decorator script-block

As the write host decorator is functionally the same used in different contents, it made sense not to force the user to keep re-defining this. Therefore, a predefined decorator is available for 3rd party use. Just pass this value as the Block parameter on the compound function being used.

$LoopzHelpers.WhItemDecoratorBlock

Acknowledgements

๐Ÿ™ I'd like to thank @KirkMunro and @JamesWTruher who wrote the original Get-CommandDetails function which formed the early roots of the design of Show-ParameterSetInfo. See this PowerShell issue for proposals on amending the syntax displayed from Get-Command.

loopz's People

Contributors

plastikfan avatar

Stargazers

 avatar  avatar  avatar

Watchers

 avatar  avatar

loopz's Issues

Add Hoist to Invoke-MirrorDirectory/Invoke-TraverseDirectory

Applying a directory filter can be too brutal. Currently, child directories that match the filter will not be selected if it contains any ancestors that do not also match the filter. We need to modify this behaviour and give the client an option to change this.

Invoke-TraverseDirectory contains the following offending code:

    [System.IO.DirectoryInfo[]]$directoryInfos = Get-ChildItem -Path $Path `
      -Directory | Where-Object { $Condition.Invoke($_) }

    if ($directoryInfos) {
      $directoryInfos | Invoke-ForeachFsItem -Directory -Block $adapter `
        -PassThru $PassThru -Condition $Condition;
    }

and Invoke-MirrorDirectoryTree contains:

  [scriptblock]$filterDirectories = {
    [OutputType([boolean])]
    param(
      [System.IO.DirectoryInfo]$directoryInfo
    )
    Select-Directory -DirectoryInfo $directoryInfo `
      -Includes $DirectoryIncludes -Excludes $DirectoryExcludes;
  }

  Invoke-TraverseDirectory -Path $resolvedSourcePath `
    -SourceDirectoryBlock $doMirrorBlock -PassThru $PassThru -Summary $summary `
    -Condition $filterDirectories;

We could introduce a new flag on Invoke-TraverseDirectory, lets say PluckDescendents (meaning that we can pluck matching descendents, even when its parents may not match the filter), which could be use whilst processing the top level directory. If the flag is set, we don't use the recurse functionality. Instead, we issue a separate Get-ChildItem request with the Recurse parameter set, then apply the filter on this resultant set which would allow us to see those matching descendents. The key here is that we only ever want to issue a single Invoke-ForeachFsItem request when PluckDescendents is set.

The existing functionlaity should still stand because that is what the client may want.

write-HostItemDecorator needs to support being able to write custom properties

Currently, the function writes a single key-value pair denoted by 'ITEM-LABEL' & 'ITEM-VALUE' in PassThru. This is too limiting for the client. The client needs to be able to defined any number of custom properties.

The custom properties should be defined in PassThru as 'PROPERTIES', which represents a collection of key/value pairs which are then just written out. We can leave in ITEM-LABEL/ITEM-VALUE if the client only needs to display a single property, but a pre-condition of the function is that either 'PROPERTIES' can exist or 'ITEM-LABEL' & 'ITEM-VALUE', but not both.

WHAT-IF should be universal

Currently WHAT-IF is scoped: LOOPZ.MIRROR.WHAT-IF. This does not make sense. WHAT-IF should be targeted at every command in the passThru chain. LOOPZ.MIRROR.WHAT-IF should simply be WHAT-IF.

This is a breaking change.

Intermittent error in build script

Plastikfan@JANUS ๎‚ฐ ~\dev\github\PoSh\Loopz ๎‚ฐ ๎‚  feature/header-block โ‰ข +1 ~1 -0 ! ๎‚ฐ                                                                                           [09:26]
ฮป cd .\Elizium.Loopz\
Plastikfan@JANUS ๎‚ฐ ~\dev\github\PoSh\Loopz\Elizium.Loopz ๎‚ฐ ๎‚  feature/header-block โ‰ข +2 ~1 -0 ! ๎‚ฐ                                                                             [09:29]
ฮป inv-bu
Build . C:\Users\Plastikfan\dev\github\PoSh\Loopz\Elizium.Loopz\Elizium.Loopz.build.ps1
Task /./Clean
Done /./Clean 00:00:00.0283399
Task /./Build/Compile
Missing output 'C:\Users\Plastikfan\dev\github\PoSh\Loopz\Elizium.Loopz\Output\Elizium.Loopz\Elizium.Loopz.psm1'.
Done /./Build/Compile 00:00:00.4513523
Task /./Build/CreateManifest/CopyPSD
VERBOSE: Performing the operation "Copy File" on target "Item: C:\Users\Plastikfan\dev\github\PoSh\Loopz\Elizium.Loopz\Elizium.Loopz.psd1 Destination: C:\Users\Plastikfan\dev\github\PoSh\Loopz\Elizium.Loopz\Output\Elizium.Loopz\Elizium.Loopz.psd1".
Done /./Build/CreateManifest/CopyPSD 00:00:00.0242980
Task /./Build/CreateManifest/UpdatePublicFunctionsToExport
Done /./Build/CreateManifest/UpdatePublicFunctionsToExport 00:00:00.0204947
Done /./Build/CreateManifest 00:00:00.0479371
Task /./Build/Ana/Analyse
ERROR: Object reference not set to an instance of an object.
At C:\Users\Plastikfan\dev\github\PoSh\Loopz\Elizium.Loopz\Elizium.Loopz.build.ps1:175 char:3
+   Invoke-ScriptAnalyzer -Path .\Output\ -Recurse
+   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
At C:\Users\Plastikfan\dev\github\PoSh\Loopz\Elizium.Loopz\Elizium.Loopz.build.ps1:174 char:1
+ task Analyse {
+ ~~~~~~~~~~~~~~
At C:\Users\Plastikfan\dev\github\PoSh\Loopz\Elizium.Loopz\Elizium.Loopz.build.ps1:7 char:1
+ task Ana Analyse
+ ~~~~~~~~~~~~~~~~
At C:\Users\Plastikfan\dev\github\PoSh\Loopz\Elizium.Loopz\Elizium.Loopz.build.ps1:5 char:1
+ task Build Compile, CreateManifest, Ana
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
At C:\Users\Plastikfan\dev\github\PoSh\Loopz\Elizium.Loopz\Elizium.Loopz.build.ps1:2 char:1
+ task . Clean, Build, Tests, Stats
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Build FAILED. 9 tasks, 1 errors, 0 warnings 00:00:01.1740783
Invoke-ScriptAnalyzer: C:\Users\Plastikfan\dev\github\PoSh\Loopz\Elizium.Loopz\Elizium.Loopz.build.ps1:175
Line |
 175 |    Invoke-ScriptAnalyzer -Path .\Output\ -Recurse
     |    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     | Object reference not set to an instance of an object.

โจฏ Plastikfan@JANUS ๎‚ฐ ~\dev\github\PoSh\Loopz\Elizium.Loopz ๎‚ฐ ๎‚  feature/header-block โ‰ข +2 ~1 -0 ! ๎‚ฐ                                                                           [09:29]
ฮป inv-bu

Line 175 is the invoke:

task Analyse {
  Invoke-ScriptAnalyzer -Path .\Output\ -Recurse
}

Make Write-HostFeFsItem ITEM-VALUE/PROPERTIES dynamic

Currently, ITEM-VALUE can only accept a static string, which makes this not as useful as desired. There is little to no value in having a static value that is displayed for each item in the pipleline, because it would be the same value for every item. The client should be able to provide a script block (stored in the passThru under key 'LOOPZ.WH-FOREACH-DECORATOR.GET-VALUE', when given a result and the underscore can determine what the item value should be.

The same criticsim can be made of 'LOOPZ.WH-FOREACH-DECORATOR.PROPERTIES'. However, this should be resolved by the invokee by populating the PSCustomObject returned with an addition property 'Properties' with an array of key/value pairs. Write-HostFeFsItem should pick up these values and add them to the themedPairs collection. Actually, instead of simply overriding 'LOOPZ.WH-FOREACH-DECORATOR.PROPERTIES', we can simply just add to the themedPairs collection.

Actually, on further consideration, should we just remove custom properties from the PassThru altogether? ITEM-VALUE could be replaced with a property on the PSCustomObject invokee result, or perhaps even better let's completely do away with ITEM-LABEL/ITEM-VALUE and just rely on PROPERTIES/Properties instead; this would make the framework simpler to use.

Fix Literal pattern/with/target in Rename-ForeachFsItem

Setting Literal currently throws up an error.

Remove the current Literal param and replace it with a positional parameter LiteralPattern, which should be used as the Literal version of Pattern, so we don't specify Literal as a separate flag. The literal version of With should be LiteralWith. There should also be a LiteralTarget.

  • Literal and Pattern are mutually exclusive.
  • With / LiteralWith/Target/LiteralTarget can be used with Literal & Pattern.
  • Literal parameters support wildcards (except LiteralTarget).

So the first 2 positional parameters should be LiteralPattern and LiteralWith. This way the user can specify

Rename-ForeachFsItem "old" "new"

and this would be the intuitive way to use the command.

If the user wants to use the more advanced functionality using dynamic regular expressions, then -Pattern and -With come into play and must be named explicitly.

EDIT: we can't allow a wildcard to be used with target/with/pattern, because it is too blunt an instrument. Wildcards are suitbale for narrowing result sets like you would with Get-ChildItem, but not for selecting sub strings; this is why regex is required.

Add invoke-ForeachFsItem

Combines the functionality of invoke-foreachFile and invoke-foreachDirectory, both of which are now redundant. Define an extra 2 switches, File and Directory, which work in the same way as they do on Get-ChildItem. If neither of these flags are present, then
invoke-ForeachFsItem will invoke the specified function/scriptblock for appropriate files and directories.

Also need to define a global Summary variable that can be used by the client.

Add an item count to the PassThru

This allows client code to access the number of processed items in compound functions, outside of the the Summary object if so provided.
All compound functions should implement this.
branch: add-count-to-passthru

Fix problem of the Cut indicator not appearing in Rename-Many

Cut indicator in missing when the user specifies a Pattern but does not specify either a Paste or LiteralWith. The outcome is correct, but the indicator ([โœ‚๏ธ] Cut) is missing from the output.

But its only a cut operation for Upate-Match action so doesnt apply to Move-Match, because Move-Match implies that either an Anchor has been specified or Start/End has been specified, meaning that its a move operation.

Write-HostFeItemDecorator: Make Product 'Affirmable'

Write-HostFeItemDecorator needs to be able to affirm the Product. Currently, the Product s written without any affrmation:

    # Get Product if it exists
    #
    [string]$productLabel = [string]::Empty;
    if ($invokeResult -and $invokeResult.psobject.properties.match('Product') -and $invokeResult.Product) {
      $productValue = $getResult.Invoke($invokeResult.Product);
      $productLabel = $PassThru.ContainsKey('LOOPZ.WH-FOREACH-DECORATOR.PRODUCT-LABEL') `
        ? $PassThru['LOOPZ.WH-FOREACH-DECORATOR.PRODUCT-LABEL'] : 'Product';

      if (-not([string]::IsNullOrWhiteSpace($productLabel))) {
        $themedPairs += , @($productLabel, $productValue);
      }
    }

In particular, its this line:

$themedPairs += , @($productLabel, $productValue);

that needs to be modified. A third parameter, its affirmation needs to ebe passed in here.

show-DefaultHeaderBlock header line display sometimes does not work properly

First, the crumb should not only be displayed if the user has defined a message in the PassThru as 'LOOPZ.HEADER-BLOCK.MESSAGE'

When a message is defined, sometimes that is displayed incorrectly eg:

[๐Ÿ›ก๏ธ] ---------------------------------------------------------------------------------------------- ------ [ Rename ] ---

In this example (Rename-ForeachFsItem), there is a gap in the dashes, why?

Rename-Many: Add context to the command

Add another parameter to the command called Context, which adds contextual info. This will allow other new higher-order commands to be built on top of Rename-Many and inject the higher-order info into it.

  • Title (goes into the header)
  • Item Message ('Rename Item')
  • Summary Message ('Rename Summary')

MethodInvocationException occurs randomly in Invoke-TraverseDirectoryTree adapter

Running Xatch periodically throws errors like this:

_________________________________________________________________________________
   [๐ŸŽถ] Conversion Summary (Gatecrasher- Global Sound System (Longitude)) | ("Count" -> "17", "Skipped" -> "0", "Triggered" -> "True", "From" -> "flac", "To" -> "mp3")
_________________________________________________________________________________
   [๐Ÿ“] Audio Directory // ["No" => "850", "Album" => "Gatecrasher- Global Sound System (Longitude)"]
MethodInvocationException: C:\Users\Plastikfan\Documents\PowerShell\Modules\Elizium.Loopz\Elizium.Loopz.psm1:1306
Line |
1306 |      $adapted.Invoke(
     |      ~~~~~~~~~~~~~~~~
     | Exception calling "Invoke" with "5" argument(s): "Exception calling "Invoke" with "1" argument(s): "Exception calling "Invoke" with "4" argument(s): "Exception calling "Invoke" with "1" argument(s): "Exception calling
     | ".ctor" with "2" argument(s): "Count cannot be less than zero. (Parameter 'count')"""""

However, processing that directory by itself, does not cause the same error to be thrown. Only happens in the full batch run.

Ocurring on this line in Invoke-TraverseDirectoryTree:

    $adapted.Invoke(
      $_underscore,
      $_passThru['LOOPZ.TRAVERSE.CONDITION'],
      $_passThru,
      $PassThru['LOOPZ.TRAVERSE.INVOKEE'],
      $_trigger
    );

Add Invoke-ForeachFile

Derive Invoke-ForeachFile from TerminalBuddy

Trigger needs to be clarified.
Summary needs to be resolved (this migt be a UI only thing )

Invalid validation script in Invoke-TraverseDirectory for FuncteeParams

This is possibly causing this error:

[-] invoke-ConversionBatch.given: blah.should:  204ms (203ms|1ms)
 ParameterBindingValidationException: Cannot bind argument to parameter 'Path' because it is null.
 MethodInvocationException: Exception calling "Invoke" with "4" argument(s): "Cannot bind argument to parameter 'Path' because it is null."
 CmdletInvocationException: Exception calling "Invoke" with "4" argument(s): "Cannot bind argument to parameter 'Path' because it is null."
 MethodInvocationException: Exception calling "Invoke" with "4" argument(s): "Exception calling "Invoke" with "4" argument(s): "Cannot bind argument to parameter 'Path' because it is null.""
 CmdletInvocationException: Exception calling "Invoke" with "4" argument(s): "Exception calling "Invoke" with "4" argument(s): "Cannot bind argument to parameter 'Path' because it is null.""
 MethodInvocationException: Exception calling "Invoke" with "1" argument(s): "Exception calling "Invoke" with "4" argument(s): "Exception calling "Invoke" with "4" argument(s): "Cannot bind argument to parameter 'Path' because it is null."""
 CmdletInvocationException: Exception calling "Invoke" with "1" argument(s): "Exception calling "Invoke" with "4" argument(s): "Exception calling "Invoke" with "4" argument(s): "Cannot bind argument to parameter 'Path' because it is null."""
 MethodInvocationException: Exception calling "Invoke" with "5" argument(s): "Exception calling "Invoke" with "1" argument(s): "Exception calling "Invoke" with "4" argument(s): "Exception calling "Invoke" with "4" argument(s): "Cannot bind argument to parameter 'Path' because it is null.""""
 CmdletInvocationException: Exception calling "Invoke" with "5" argument(s): "Exception calling "Invoke" with "1" argument(s): "Exception calling "Invoke" with "4" argument(s): "Exception calling "Invoke" with "4" argument(s): "Cannot bind argument to parameter 'Path' because it is null.""""
 MethodInvocationException: Exception calling "Invoke" with "5" argument(s): "Exception calling "Invoke" with "5" argument(s): "Exception calling "Invoke" with "1" argument(s): "Exception calling "Invoke" with "4" argument(s): "Exception calling "Invoke" with "4" argument(s): "Cannot bind argument to parameter 'Path' because it is null."""""
 at <ScriptBlock>, C:\Users\Plastikfan\Documents\PowerShell\Modules\Elizium.Loopz\Elizium.Loopz.psm1:1308
 at Invoke-ForeachFsItem<Process>, C:\Users\Plastikfan\Documents\PowerShell\Modules\Elizium.Loopz\Elizium.Loopz.psm1:404
 at Invoke-TraverseDirectory, C:\Users\Plastikfan\Documents\PowerShell\Modules\Elizium.Loopz\Elizium.Loopz.psm1:1457
 at Invoke-MirrorDirectoryTree, C:\Users\Plastikfan\Documents\PowerShell\Modules\Elizium.Loopz\Elizium.Loopz.psm1:944
 at invoke-ConversionBatch, C:\Users\Plastikfan\dev\github\PoSh\Xatch\Elizium.Xatch\Internal\invoke-ConversionBatch.ps1:212
 at <ScriptBlock>, C:\Users\Plastikfan\dev\github\PoSh\Xatch\Elizium.Xatch\Tests\Internal\invoke-ConversionBatch.tests.ps1:59
Tests completed in 713ms

This is happening in Xatch, when WHAT-IF set to true.

Also there is some ambiguity of the WHAT-IF entry in PassThru. Make sure they all use WHAT-IF, not LOOPZ.MIRROR.WHAT-IF

Add support for Regex global options in Rename-Many command

Currently, patterns provided by the user (for Pattern, Anchor & With parameters) provided to the RegEx constructor without any options. That means for examle that case sensitivity is on by default, without a means to change this by the user.

Pattern parameters should be able to support global flags at the end of the pattern preceded with a forward slash: eg 'abc/i'. The .Net flags are documented at https://docs.microsoft.com/en-us/dotnet/standard/base-types/regular-expression-options and should be expressed as a string of comma separated values (use codes as described at regex101).

Factory function new-RegularExpression, needs to be modified to support this functionality.

Remove all exception handling

This is a problem because errors are being absorbed and hidden which makes problem resolution difficult. Its better to crash and burn fast then to hide the errors.

Invoke-MirrorDirectoryTree, CopyFiles depends on CreateDirs

CopyFiles switch should be able to work independently from CreateDirs. currently, CopyFiles has not effect unless CreateDirs is also set. There is a useful usecase that requires this decoupling. If you want to mirror and directory tree, but in the destination tree, you only want to copy over some files that match a particluar filter (includes/excludes), then this won't work unless CreateDirs. If you also set CreateDirs, then this could result in empty directories in the destination, as a result of there being files that fail to pass the filter, but the directroy would be created anyway.

The much better way around this is to remove the need to also set the CreateDirs flag.

branch: feature/copyfiles-without-createdirs

Make message in Write-HostFeItemDecorator dynamic

The message as defined under 'LOOPZ.WH-FOREACH-DECORATOR.MESSAGE' is static. It needs to be made dynamic and be able to change depending on the result of invoking the invokee.

Define a new PassThru entry under key 'LOOPZ.WH-FOREACH-DECORATOR.GET-MESSAGE' that should be a scriptblock that calling side can define.

branch: dynamic-wh-fe-message

Add Invoke-ForeachDirectory

Derive Invoke-ForeachDirectory from XLDPlusPlus.Invoke-ForeachDirectory (iterate.ps1)

Trigger needs to be clarified.
Summary needs to be resolved (this migt be a UI only thing )

Use command references with get-command

This is a big change, but the right one to do, unfortunately for little payback. Use get-command/invoke on block rather than using the & op on the named function string. This will fix the problem using test functions in Pester tests.

See FuncitonInfo Class

branch: use-command-ref

Define multi-line format for Write-HostFeItemDecorator

For any command using Write-HostFeItemDecorator, the current display of just a single line for each item processed can result in too wide an output causing excessive wrapping ruining the resultant output. An example of this is Rename-ForeachFsItem (Rename-Many).

We need to define appropriate options for Write-HostFeItemDecorator that will enable its output to be properly formatted over multiple lines for each iterated item.

Using WhatIf on New-Item causing failures

WhatIf setting should not be applied to New-Item, because it causes other code to break as required directories have not been created, leading to $null being passes as Path parameter.

[-] invoke-ConversionBatch.given: blah.should:  204ms (203ms|1ms)
 ParameterBindingValidationException: Cannot bind argument to parameter 'Path' because it is null.
 MethodInvocationException: Exception calling "Invoke" with "4" argument(s): "Cannot bind argument to parameter 'Path' because it is null."
 CmdletInvocationException: Exception calling "Invoke" with "4" argument(s): "Cannot bind argument to parameter 'Path' because it is null."
 MethodInvocationException: Exception calling "Invoke" with "4" argument(s): "Exception calling "Invoke" with "4" argument(s): "Cannot bind argument to parameter 'Path' because it is null.""
 CmdletInvocationException: Exception calling "Invoke" with "4" argument(s): "Exception calling "Invoke" with "4" argument(s): "Cannot bind argument to parameter 'Path' because it is null.""
 MethodInvocationException: Exception calling "Invoke" with "1" argument(s): "Exception calling "Invoke" with "4" argument(s): "Exception calling "Invoke" with "4" argument(s): "Cannot bind argument to parameter 'Path' because it is null."""
 CmdletInvocationException: Exception calling "Invoke" with "1" argument(s): "Exception calling "Invoke" with "4" argument(s): "Exception calling "Invoke" with "4" argument(s): "Cannot bind argument to parameter 'Path' because it is null."""
 MethodInvocationException: Exception calling "Invoke" with "5" argument(s): "Exception calling "Invoke" with "1" argument(s): "Exception calling "Invoke" with "4" argument(s): "Exception calling "Invoke" with "4" argument(s): "Cannot bind argument to parameter 'Path' because it is null.""""
 CmdletInvocationException: Exception calling "Invoke" with "5" argument(s): "Exception calling "Invoke" with "1" argument(s): "Exception calling "Invoke" with "4" argument(s): "Exception calling "Invoke" with "4" argument(s): "Cannot bind argument to parameter 'Path' because it is null.""""
 MethodInvocationException: Exception calling "Invoke" with "5" argument(s): "Exception calling "Invoke" with "5" argument(s): "Exception calling "Invoke" with "1" argument(s): "Exception calling "Invoke" with "4" argument(s): "Exception calling "Invoke" with "4" argument(s): "Cannot bind argument to parameter 'Path' because it is null."""""
 at <ScriptBlock>, C:\Users\Plastikfan\Documents\PowerShell\Modules\Elizium.Loopz\Elizium.Loopz.psm1:1308
 at Invoke-ForeachFsItem<Process>, C:\Users\Plastikfan\Documents\PowerShell\Modules\Elizium.Loopz\Elizium.Loopz.psm1:404
 at Invoke-TraverseDirectory, C:\Users\Plastikfan\Documents\PowerShell\Modules\Elizium.Loopz\Elizium.Loopz.psm1:1457
 at Invoke-MirrorDirectoryTree, C:\Users\Plastikfan\Documents\PowerShell\Modules\Elizium.Loopz\Elizium.Loopz.psm1:944
 at invoke-ConversionBatch, C:\Users\Plastikfan\dev\github\PoSh\Xatch\Elizium.Xatch\Internal\invoke-ConversionBatch.ps1:212
 at <ScriptBlock>, C:\Users\Plastikfan\dev\github\PoSh\Xatch\Elizium.Xatch\Tests\Internal\invoke-ConversionBatch.tests.ps1:59
Tests completed in 713ms

For Invoke-MirrorDirectoryTree, in a WhatIf sceanrio, we should be able to create Directories; this saves other code from breaking. WhatIf should still apply to file creation.

Add Summary wide pairs

Some property values are too long to be displayed on the same line as other properties. Eg when processing directories, the fullpath is too often far too long a string, but its useful to see this displayed. It is not suitbale for it to be displayed in a key/value list. It is better for this to be displayed on their own line.

Add a new PassThru entry 'LOOPZ.SUMMARY-BLOCK.WIDE-PAIRS', which is an array of key/value pairs, where each pair is displayed on its own line.

De-cachify Rename-ForeachFsItem with [ProxyCommand]

The initial version of Rename-ForeachFsItem caches all pipeline items before piping them to Invoke-ForeachFsItem. This technique is undesirable if the pipeline is large. There is a way around this issue and as the solution is involved, it warrants a separate issue outside of the initial implementation of the command. We can use this as a blue print for the implementation of other commands.

See the answer posted to my stackoverflow question.

Also see this blog post Proxy Functions: Spice Up Your PowerShell Core Cmdlets

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.