Skip to content

Instantly share code, notes, and snippets.

@tsmarvin
Last active November 25, 2023 20:54
TM-DataManipulation

TM-DataManipulation Module

Introduction

TM-DataManipulation is a PowerShell module containing classes and functions designed to handle ranges of integers, manage semantic versioning, and perform string operations.

This module is part of a suite of tools designed to improve and streamline the PowerShell commandline and scripting experience.
Check out the rest of the modules by visiting my page on the PowerShell Gallery.

Features

  • Get-ContiguousRange: Generates a range of contiguous integers.
  • Test-MatchesSemVer: Tests if a string matches the Semantic Versioning pattern.
  • Write-ReverseString: Reverses the character order of a string.

Requirements

  • Windows PowerShell 5.1+, or PowerShell Core 7+.

Installation

Install TM-DataManipulation from the PowerShell Gallery:

Install-Module TM-DataManipulation -Scope CurrentUser -Repository PSGallery

For manual installation, download the module files and place them in a "TM-DataManipulation" folder in your PowerShell modules directory ($Env:PSModulePath).

MIT License
Copyright (c) 2023 Taylor Marvin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@{
# Script module or binary module file associated with this manifest.
RootModule = 'TM-DataManipulation.psm1'
# Version number of this module.
ModuleVersion = '0.0.4'
# Supported PSEditions
CompatiblePSEditions = @('Desktop','Core')
# ID used to uniquely identify this module
GUID = 'f95a7220-7a7b-4018-bd1d-5f756bd7b08f'
# Author of this module
Author = 'Taylor Marvin'
# Company or vendor of this module
CompanyName = 'N/A'
# Copyright statement for this module
Copyright = 'Taylor Marvin (2023)'
# Description of the functionality provided by this module
Description = 'This PowerShell module contains classes and functions designed to handle ranges of integers, manage semantic versioning, and perform string operations.'
# Minimum version of the PowerShell engine required by this module
PowerShellVersion = '5.1'
# Modules that must be imported into the global environment prior to importing this module
# RequiredModules = @()
# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
FunctionsToExport = @(
'Get-ContiguousRange',
'Test-MatchesSemVer',
'Write-ReverseString'
)
# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export.
AliasesToExport = @()
# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell.
PrivateData = @{
PSData = @{
# Tags applied to this module. These help with module discovery in online galleries.
Tags = @('Profile', 'Utility')
# A URL to the license for this module.
LicenseUri = 'https://gist.github.com/tsmarvin/f1da993cee28588113040e1a248de249#file-license'
# A URL to the main website for this project.
ProjectUri = 'https://gist.github.com/tsmarvin/f1da993cee28588113040e1a248de249'
# Prerelease string of this module
# Prerelease = ''
# Flag to indicate whether the module requires explicit user acceptance for install/update/save
RequireLicenseAcceptance = $false
}
}
}
using namespace System.Collections.Generic
class Range {
<#
.SYNOPSIS
A class to hold the start/end data for a given contiguous integer range.
#>
[long]$Start
[long]$End
# Default New Values
Range() {
$this.Start = ([int]::MinValue - 1)
$this.End = ([int]::MaxValue + 1)
}
# Set Values
Range([long]$Start, [long]$End) {
$this.Start = $Start
$this.End = $End
}
[string]ToString() {
return (
if ($this.Start -eq $this.End) {
$this.Start
} else {
"$($this.Start)-$($this.End)"
}
)
}
}
function Get-ContiguousRange {
<#
.SYNOPSIS
Finds contiguous ranges of integers within a given integer array.
.DESCRIPTION
This function takes a list of integers and identifies contiguous ranges within the list.
The output can be a list of Range objects or a string.
.PARAMETER IntRange
Array of integers to find contiguous ranges within
.PARAMETER CombineString
If selected, returns the contiguous ranges as a single comma separated string.
#>
[CmdletBinding()]
[OutputType([List[Range]], [string])]
param (
[Parameter(
Position = 0,
Mandatory,
HelpMessage = 'Enter one or more integers.'
)]
[int[]]$IntRange,
[Parameter(
Position = 1,
Mandatory = $false,
HelpMessage = 'Outputs contiguous ranges as a string.'
)]
[switch]$CombineString
)
begin {
$Return = [List[Range]]::new()
$RangeObj = [Range]::New()
[long]$Index = 0
[long[]]$LongRange = $IntRange | Sort-Object -Unique
}
process {
for ([long]$a = $LongRange[0]; [long]$a -le $LongRange[-1]; [long]$a++) {
# Set Start value
if ($RangeObj.Start -eq ([int]::MinValue - 1)) {
if ($LongRange[$Index] -ne $a) { $a = $LongRange[$Index] }
$RangeObj.Start = $a
}
# Set End Value
if (
($LongRange[$Index] -eq $a) -and
($LongRange[($Index + 1)] -ne ([long]$a + 1)) -and
($RangeObj.End -eq ([int]::MaxValue + 1))
) {
$RangeObj.End = $a
$Return.Add($RangeObj)
$RangeObj = [Range]::New()
}
$Index++
}
}
end {
if ($CombineString) {
$Strings = $Return | ForEach-Object { $_.ToString() }
return $Strings -join ', '
} else {
return $Return
}
}
}
class SemVer {
<#
.SYNOPSIS
Translates a version string into a formal semantic versioning pattern.
{Major}.{Minor}.{Patch}-{pre-releaseTag}+{buildNum}
.DESCRIPTION
Utilizes regular expressions to verify if a given string adheres to semantic versioning rules.
For more details: https://semver.org/
Examples of valid semantic versions:
ex0: 2.1.4
ex1: 5.12.96-pr1+000a
ex2: 10.3.2+002
ex3: 3.1.0-rc3
.PARAMETER Version
The version string to be parsed into a SemVer object.
.OUTPUTS
When the provided version does not follow semantic versioning format, the Valid field will be set to false.
The Major, Minor, and Patch properties will be initialized to 0 and the PreRelease and Build properties will
contain empty strings.
Conversely, if the version adheres to the semantic versioning format, the Valid field will be set to true.
The Major, Minor, and Patch properties will contain their respective values as [long]s.
The PreRelease and Build properties will either be empty if unused, or hold the string value of the relevant fields.
#>
[boolean]$Valid = $false
[long]$Major = 0
[long]$Minor = 0
[long]$Patch = 0
[string]$PreRelease = [string]::Empty
[string]$Build = [string]::Empty
SemVer( [string]$Version ) {
$SemVerRegex = '^' +
'(?<Major>0|[1-9]\d*)\.' +
'(?<Minor>0|[1-9]\d*)\.' +
'(?<Patch>0|[1-9]\d*)' +
'(?:-(?<PreRelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))' +
'?(?:\+(?<Build>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?' +
'$'
if ($Version -match $SemVerRegex) {
$this.Valid = $true
$this.Major = [long]::Parse($Matches['Major'])
$this.Minor = [long]::Parse($Matches['Minor'])
$this.Patch = [long]::Parse($Matches['Patch'])
$this.PreRelease = $Matches['PreRelease']
$this.Build = $Matches['Build']
}
}
[string] ToString() {
$result = [string]::Empty
if ($this.Valid) {
$result = "$($this.Major).$($this.Minor).$($this.Patch)"
if ([string]::IsNullOrEmpty($this.PreRelease) -eq $false) {
$result += "-$($this.PreRelease)"
}
if ([string]::IsNullOrEmpty($this.Build) -eq $false) {
$result += "+$($this.Build)"
}
}
return $result
}
}
function Test-MatchesSemVer {
<#
.SYNOPSIS
Evaluates if the provided version string adheres to semantic versioning rules.
.DESCRIPTION
This function checks whether the input string follows the semantic versioning format and returns the appropriate
boolean value or the [SemVer] object if PassThru is selected.
.PARAMETER Version
The version string to be evaluated against semantic versioning rules.
.PARAMETER PassThru
An optional switch parameter. When selected, returns the [SemVer] object instead of a boolean.
.OUTPUTS
If the version string does not adhere to semantic versioning format, the function returns false.
When the version string complies with semantic versioning rules, the function returns true, unless PassThru is
selected. If PassThru is selected and the version string is valid, it returns the [SemVer] object.
.EXAMPLE
if (Test-MatchesSemVer -Version $Version) {
Move-Item -Path $Csproj.FullName -Destination $PackagePath
}
.EXAMPLE
$SemVer = Test-MatchesSemVer -Version $Version -PassThru
if ($SemVer.Valid -and ($SemVer.PreRelease -eq [string]::Empty)) {
dotnet nuget push $Package.FullName --api-key $ApiKey --source $NugetSource
}
#>
[CmdletBinding()]
[OutputType([Boolean], [SemVer])]
param(
[Parameter(Mandatory)]
[string]$Version,
[Parameter(Mandatory = $false)]
[switch]$PassThru
)
[SemVer]$SemVer = [SemVer]::new($Version)
if ($SemVer.Valid) {
if ($PassThru) { return $SemVer } else { return $true }
}
return $false
}
function Write-ReverseString {
<#
.SYNOPSIS
Reverses a given string.
.DESCRIPTION
This function takes a string as input and reverses its characters. The output is the reversed string.
.PARAMETER String
The string to be reversed.
.PARAMETER Encoding
The encoding method to use when reversing the string.
Available options are 'Utf8' and 'Utf16'. The default is 'Utf8'.
#>
[CmdletBinding()]
[OutputType([string])]
param (
[Parameter(
Position = 0,
Mandatory,
ValueFromPipeline
)]
[ValidateNotNullOrEmpty()]
[string]$String,
[Parameter(
Position = 1,
Mandatory = $false
)]
[ValidateSet( 'Utf8', 'Utf16')]
[string]$Encoding = 'Utf8'
)
begin { $chars = [List[char]]::new() }
process {
$runes = @($String.EnumerateRunes())
for ($i = ($runes.Count - 1); $i -ge 0; $i--) {
$rune = $runes[$i]
switch ($Encoding) {
'Utf8' {
$points = [char[]]::new($rune.Utf8SequenceLength)
$rune.EncodeToUtf8($points) | Out-Null
}
'Utf16' {
$points = [char[]]::new($rune.Utf16SequenceLength)
$rune.EncodeToUtf16($points) | Out-Null
}
Default { throw "'$Encoding' is not a valid encoding option." }
}
$chars.AddRange($points)
}
}
end { return [string]::new($chars) }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment