From 37b7cc2ea0158b57e3c7f3d55d69bdee91341d6f Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Tue, 15 Apr 2025 14:00:20 -0300 Subject: [PATCH 01/53] progress on zlib --- module/PSCompression.psd1 | 4 + .../CommandFromCompressedStringBase.cs | 75 +++++++++++ .../CommandToCompressedStringBase.cs | 118 ++++++++++++++++++ .../Commands/ConvertFromGzipStringCommand.cs | 50 +------- .../Commands/ConvertFromZLibStringCommand.cs | 18 +++ .../Commands/ConvertToGzipStringCommand.cs | 107 +--------------- .../Commands/ConvertToZLibStringCommand.cs | 18 +++ ...ExceptionHelpers.cs => ExceptionHelper.cs} | 0 src/PSCompression/ZLibStream.cs | 116 +++++++++++++++++ 9 files changed, 357 insertions(+), 149 deletions(-) create mode 100644 src/PSCompression/CommandFromCompressedStringBase.cs create mode 100644 src/PSCompression/CommandToCompressedStringBase.cs create mode 100644 src/PSCompression/Commands/ConvertFromZLibStringCommand.cs create mode 100644 src/PSCompression/Commands/ConvertToZLibStringCommand.cs rename src/PSCompression/Exceptions/{ExceptionHelpers.cs => ExceptionHelper.cs} (100%) create mode 100644 src/PSCompression/ZLibStream.cs diff --git a/module/PSCompression.psd1 b/module/PSCompression.psd1 index 6c84776..b0bc8e7 100644 --- a/module/PSCompression.psd1 +++ b/module/PSCompression.psd1 @@ -87,6 +87,8 @@ 'Compress-GzipArchive' 'Compress-ZipArchive' 'Rename-ZipEntry' + 'ConvertFrom-ZLibString' + 'ConvertTo-ZLibString' ) # Variables to export from this module @@ -101,6 +103,8 @@ 'zip' 'ziparchive' 'gze' + 'zlibfromstring' + 'zlibtostring' ) # DSC resources to export from this module diff --git a/src/PSCompression/CommandFromCompressedStringBase.cs b/src/PSCompression/CommandFromCompressedStringBase.cs new file mode 100644 index 0000000..964e924 --- /dev/null +++ b/src/PSCompression/CommandFromCompressedStringBase.cs @@ -0,0 +1,75 @@ +using System; +using System.ComponentModel; +using System.IO; +using System.Management.Automation; +using System.Text; +using PSCompression.Exceptions; + +namespace PSCompression; + +[EditorBrowsable(EditorBrowsableState.Never)] +public abstract class CommandFromCompressedStringBase : PSCmdlet +{ + protected delegate Stream DecompressionStreamFactory(Stream inputStream); + + [Parameter(Mandatory = true, ValueFromPipeline = true, Position = 0)] + public string[] InputObject { get; set; } = null!; + + [Parameter] + [ArgumentCompleter(typeof(EncodingCompleter))] + [EncodingTransformation] + [ValidateNotNullOrEmpty] + public Encoding Encoding { get; set; } = new UTF8Encoding(); + + [Parameter] + public SwitchParameter Raw { get; set; } + + private void Decompress( + string base64string, + DecompressionStreamFactory decompressionStreamFactory) + { + using MemoryStream inStream = new(Convert.FromBase64String(base64string)); + using Stream decompressStream = decompressionStreamFactory(inStream); + using StreamReader reader = new(decompressStream, Encoding); + + if (Raw) + { + ReadToEnd(reader); + return; + } + + ReadLines(reader); + } + + private void ReadToEnd(StreamReader reader) => WriteObject(reader.ReadToEnd()); + + private void ReadLines(StreamReader reader) + { + string line; + while ((line = reader.ReadLine()) is not null) + { + WriteObject(line); + } + } + + protected abstract Stream CreateDecompressionStream(Stream inputStream); + + protected override void ProcessRecord() + { + foreach (string line in InputObject) + { + try + { + Decompress(line, CreateDecompressionStream); + } + catch (Exception _) when (_ is PipelineStoppedException or FlowControlException) + { + throw; + } + catch (Exception exception) + { + WriteError(exception.ToEnumerationError(line)); + } + } + } +} diff --git a/src/PSCompression/CommandToCompressedStringBase.cs b/src/PSCompression/CommandToCompressedStringBase.cs new file mode 100644 index 0000000..2e3e6b0 --- /dev/null +++ b/src/PSCompression/CommandToCompressedStringBase.cs @@ -0,0 +1,118 @@ +using System; +using System.ComponentModel; +using System.IO; +using System.IO.Compression; +using System.Management.Automation; +using System.Text; +using PSCompression.Exceptions; + +namespace PSCompression; + +[EditorBrowsable(EditorBrowsableState.Never)] +public abstract class CommandToCompressedStringBase : PSCmdlet, IDisposable +{ + private StreamWriter? _writer; + + private Stream? _compressStream; + + private readonly MemoryStream _outstream = new(); + + [Parameter(Mandatory = true, ValueFromPipeline = true, Position = 0)] + [AllowEmptyString] + public string[] InputObject { get; set; } = null!; + + [Parameter] + [ArgumentCompleter(typeof(EncodingCompleter))] + [EncodingTransformation] + [ValidateNotNullOrEmpty] + public Encoding Encoding { get; set; } = new UTF8Encoding(); + + [Parameter] + public CompressionLevel CompressionLevel { get; set; } = CompressionLevel.Optimal; + + [Parameter] + [Alias("Raw")] + public SwitchParameter AsByteStream { get; set; } + + [Parameter] + public SwitchParameter NoNewLine { get; set; } + + protected abstract Stream CreateCompressionStream( + Stream outputStream, + CompressionLevel compressionLevel); + + protected override void ProcessRecord() + { + try + { + _compressStream ??= CreateCompressionStream(_outstream, CompressionLevel); + _writer ??= new StreamWriter(_compressStream, Encoding); + + if (NoNewLine.IsPresent) + { + Write(_writer, InputObject); + return; + } + + WriteLines(_writer, InputObject); + } + catch (Exception exception) + { + WriteError(exception.ToWriteError(InputObject)); + } + } + + protected override void EndProcessing() + { + _writer?.Dispose(); + _compressStream?.Dispose(); + _outstream?.Dispose(); + + try + { + if (_writer is null || _compressStream is null || _outstream is null) + { + return; + } + + if (AsByteStream.IsPresent) + { + WriteObject(_outstream.ToArray(), enumerateCollection: false); + return; + } + + WriteObject(Convert.ToBase64String(_outstream.ToArray())); + } + catch (Exception _) when (_ is PipelineStoppedException or FlowControlException) + { + throw; + } + catch (Exception exception) + { + WriteError(exception.ToWriteError(_outstream)); + } + } + + private void WriteLines(StreamWriter writer, string[] lines) + { + foreach (string line in lines) + { + writer.WriteLine(line); + } + } + + private void Write(StreamWriter writer, string[] lines) + { + foreach (string line in lines) + { + writer.Write(line); + } + } + + public void Dispose() + { + _writer?.Dispose(); + _compressStream?.Dispose(); + _outstream?.Dispose(); + } +} diff --git a/src/PSCompression/Commands/ConvertFromGzipStringCommand.cs b/src/PSCompression/Commands/ConvertFromGzipStringCommand.cs index 2016913..b537a2d 100644 --- a/src/PSCompression/Commands/ConvertFromGzipStringCommand.cs +++ b/src/PSCompression/Commands/ConvertFromGzipStringCommand.cs @@ -1,58 +1,14 @@ -using System; using System.IO; using System.IO.Compression; using System.Management.Automation; -using System.Text; -using PSCompression.Exceptions; namespace PSCompression.Commands; [Cmdlet(VerbsData.ConvertFrom, "GzipString")] [OutputType(typeof(string))] [Alias("gzipfromstring")] -public sealed class ConvertFromGzipStringCommand : PSCmdlet +public sealed class ConvertFromGzipStringCommand : CommandFromCompressedStringBase { - [Parameter(Mandatory = true, ValueFromPipeline = true, Position = 0)] - public string[] InputObject { get; set; } = null!; - - [Parameter] - [ArgumentCompleter(typeof(EncodingCompleter))] - [EncodingTransformation] - [ValidateNotNullOrEmpty] - public Encoding Encoding { get; set; } = new UTF8Encoding(); - - [Parameter] - public SwitchParameter Raw { get; set; } - - protected override void ProcessRecord() - { - foreach (string line in InputObject) - { - try - { - using MemoryStream inStream = new(Convert.FromBase64String(line)); - using GZipStream gzip = new(inStream, CompressionMode.Decompress); - using StreamReader reader = new(gzip, Encoding); - - if (Raw.IsPresent) - { - WriteObject(reader.ReadToEnd()); - return; - } - - while (!reader.EndOfStream) - { - WriteObject(reader.ReadLine()); - } - } - catch (Exception _) when (_ is PipelineStoppedException or FlowControlException) - { - throw; - } - catch (Exception exception) - { - WriteError(exception.ToEnumerationError(line)); - } - } - } + protected override Stream CreateDecompressionStream(Stream inputStream) => + new GZipStream(inputStream, CompressionMode.Decompress); } diff --git a/src/PSCompression/Commands/ConvertFromZLibStringCommand.cs b/src/PSCompression/Commands/ConvertFromZLibStringCommand.cs new file mode 100644 index 0000000..92870bd --- /dev/null +++ b/src/PSCompression/Commands/ConvertFromZLibStringCommand.cs @@ -0,0 +1,18 @@ +using System.IO; +using System.IO.Compression; +using System.Management.Automation; + +namespace PSCompression.Commands; + +[Cmdlet(VerbsData.ConvertFrom, "ZLibString")] +[OutputType(typeof(string))] +[Alias("zlibfromstring")] +public sealed class ConvertFromZLibStringCommand : CommandFromCompressedStringBase +{ + protected override Stream CreateDecompressionStream(Stream inputStream) + { + inputStream.Seek(2, SeekOrigin.Begin); + DeflateStream deflate = new(inputStream, CompressionMode.Decompress); + return deflate; + } +} diff --git a/src/PSCompression/Commands/ConvertToGzipStringCommand.cs b/src/PSCompression/Commands/ConvertToGzipStringCommand.cs index 1d8d722..8c1fd9a 100644 --- a/src/PSCompression/Commands/ConvertToGzipStringCommand.cs +++ b/src/PSCompression/Commands/ConvertToGzipStringCommand.cs @@ -1,115 +1,18 @@ -using System; using System.IO; using System.IO.Compression; using System.Management.Automation; -using System.Text; -using PSCompression.Exceptions; namespace PSCompression.Commands; [Cmdlet(VerbsData.ConvertTo, "GzipString")] [OutputType(typeof(byte[]), typeof(string))] [Alias("gziptostring")] -public sealed class ConvertToGzipStringCommand : PSCmdlet, IDisposable +public sealed class ConvertToGzipStringCommand : CommandToCompressedStringBase { - private StreamWriter? _writer; - - private GZipStream? _gzip; - - private readonly MemoryStream _outstream = new(); - - [Parameter(Mandatory = true, ValueFromPipeline = true, Position = 0)] - [AllowEmptyString] - public string[] InputObject { get; set; } = null!; - - [Parameter] - [ArgumentCompleter(typeof(EncodingCompleter))] - [EncodingTransformation] - [ValidateNotNullOrEmpty] - public Encoding Encoding { get; set; } = new UTF8Encoding(); - - [Parameter] - public CompressionLevel CompressionLevel { get; set; } = CompressionLevel.Optimal; - - [Parameter] - [Alias("Raw")] - public SwitchParameter AsByteStream { get; set; } - - [Parameter] - public SwitchParameter NoNewLine { get; set; } - - protected override void ProcessRecord() - { - try - { - _gzip ??= new(_outstream, CompressionLevel); - _writer ??= new(_gzip, Encoding); - - if (NoNewLine.IsPresent) - { - Write(_writer, InputObject); - return; - } - - WriteLines(_writer, InputObject); - } - catch (Exception exception) - { - WriteError(exception.ToWriteError(InputObject)); - } - } - - protected override void EndProcessing() - { - _writer?.Dispose(); - _gzip?.Dispose(); - _outstream?.Dispose(); - - try - { - if (_writer is null || _gzip is null || _outstream is null) - { - return; - } - - if (AsByteStream.IsPresent) - { - WriteObject(_outstream.ToArray(), enumerateCollection: false); - return; - } - - WriteObject(Convert.ToBase64String(_outstream.ToArray())); - } - catch (Exception _) when (_ is PipelineStoppedException or FlowControlException) - { - throw; - } - catch (Exception exception) - { - WriteError(exception.ToWriteError(_outstream)); - } - } - - private void WriteLines(StreamWriter writer, string[] lines) - { - foreach (string line in lines) - { - writer.WriteLine(line); - } - } - - private void Write(StreamWriter writer, string[] lines) - { - foreach (string line in lines) - { - writer.Write(line); - } - } - - public void Dispose() + protected override Stream CreateCompressionStream( + Stream outputStream, + CompressionLevel compressionLevel) { - _writer?.Dispose(); - _gzip?.Dispose(); - _outstream?.Dispose(); + return new GZipStream(outputStream, compressionLevel); } } diff --git a/src/PSCompression/Commands/ConvertToZLibStringCommand.cs b/src/PSCompression/Commands/ConvertToZLibStringCommand.cs new file mode 100644 index 0000000..fc84b7a --- /dev/null +++ b/src/PSCompression/Commands/ConvertToZLibStringCommand.cs @@ -0,0 +1,18 @@ +using System.IO; +using System.IO.Compression; +using System.Management.Automation; + +namespace PSCompression.Commands; + +[Cmdlet(VerbsData.ConvertTo, "ZLibString")] +[OutputType(typeof(byte[]), typeof(string))] +[Alias("zlibtostring")] +public sealed class ConvertToZLibStringCommand : CommandToCompressedStringBase +{ + protected override Stream CreateCompressionStream( + Stream outputStream, + CompressionLevel compressionLevel) + { + return new ZlibStream(outputStream, compressionLevel); + } +} diff --git a/src/PSCompression/Exceptions/ExceptionHelpers.cs b/src/PSCompression/Exceptions/ExceptionHelper.cs similarity index 100% rename from src/PSCompression/Exceptions/ExceptionHelpers.cs rename to src/PSCompression/Exceptions/ExceptionHelper.cs diff --git a/src/PSCompression/ZLibStream.cs b/src/PSCompression/ZLibStream.cs new file mode 100644 index 0000000..79bbc27 --- /dev/null +++ b/src/PSCompression/ZLibStream.cs @@ -0,0 +1,116 @@ +using System; +using System.IO; +using System.IO.Compression; + +namespace PSCompression; + +internal sealed class ZlibStream : Stream +{ + private readonly Stream _outputStream; + + private readonly DeflateStream _deflateStream; + + private readonly MemoryStream _uncompressedBuffer; + + private bool _isDisposed; + + public ZlibStream(Stream outputStream, CompressionLevel compressionLevel) + { + _outputStream = outputStream ?? throw new ArgumentNullException(nameof(outputStream)); + _uncompressedBuffer = new MemoryStream(); + + // Write zlib header (0x78 0x9C for default compatibility) + _outputStream.WriteByte(0x78); + _outputStream.WriteByte(0x9C); + + // Initialize DeflateStream + _deflateStream = new DeflateStream(outputStream, compressionLevel, true); + } + + public override bool CanRead => false; + + public override bool CanSeek => false; + + public override bool CanWrite => true; + + public override long Length => throw new NotSupportedException(); + + public override long Position + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); + } + + public override void Flush() + { + _deflateStream.Flush(); + _outputStream.Flush(); + } + + public override int Read(byte[] buffer, int offset, int count) + { + throw new NotSupportedException("Reading is not supported."); + } + + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException("Seeking is not supported."); + } + + public override void SetLength(long value) + { + throw new NotSupportedException("Setting length is not supported."); + } + + public override void Write(byte[] buffer, int offset, int count) + { + // Buffer uncompressed data for Adler-32 + _uncompressedBuffer.Write(buffer, offset, count); + // Write to DeflateStream for compression + _deflateStream.Write(buffer, offset, count); + } + + private uint ComputeAdler32() + { + const uint MOD_ADLER = 65521; + uint a = 1, b = 0; + byte[] data = _uncompressedBuffer.ToArray(); + + foreach (byte byt in data) + { + a = (a + byt) % MOD_ADLER; + b = (b + a) % MOD_ADLER; + } + + return (b << 16) | a; + } + + protected override void Dispose(bool disposing) + { + if (_isDisposed) + { + return; + } + + if (disposing) + { + // Ensure DeflateStream is flushed and closed + _deflateStream.Dispose(); + + // Write Adler-32 checksum + uint adler32 = ComputeAdler32(); + byte[] checksum = BitConverter.GetBytes(adler32); + if (BitConverter.IsLittleEndian) + { + Array.Reverse(checksum); + } + _outputStream.Write(checksum, 0, checksum.Length); + + _uncompressedBuffer.Dispose(); + // Note: Don't dispose _outputStream, as it's owned by the caller + } + + _isDisposed = true; + base.Dispose(disposing); + } +} From 35b45b590599f7f7aa611b1fc69deb0c55c20a38 Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Tue, 15 Apr 2025 14:12:47 -0300 Subject: [PATCH 02/53] progress on deflate --- module/PSCompression.psd1 | 8 ++++++++ .../ConvertFromDeflateStringCommand.cs | 14 ++++++++++++++ .../Commands/ConvertToDeflateString.cs | 18 ++++++++++++++++++ src/PSCompression/ZLibStream.cs | 9 +-------- 4 files changed, 41 insertions(+), 8 deletions(-) create mode 100644 src/PSCompression/Commands/ConvertFromDeflateStringCommand.cs create mode 100644 src/PSCompression/Commands/ConvertToDeflateString.cs diff --git a/module/PSCompression.psd1 b/module/PSCompression.psd1 index b0bc8e7..35d4b4d 100644 --- a/module/PSCompression.psd1 +++ b/module/PSCompression.psd1 @@ -89,6 +89,8 @@ 'Rename-ZipEntry' 'ConvertFrom-ZLibString' 'ConvertTo-ZLibString' + 'ConvertFrom-DeflateString' + 'ConvertTo-DeflateString' ) # Variables to export from this module @@ -105,6 +107,8 @@ 'gze' 'zlibfromstring' 'zlibtostring' + 'deflatefromstring' + 'deflatetostring' ) # DSC resources to export from this module @@ -126,6 +130,10 @@ 'zip-compression' 'gzip' 'gzip-compression' + 'zlib' + 'zlib-compression' + 'deflate' + 'deflate-compression' 'compression' 'csharp' ) diff --git a/src/PSCompression/Commands/ConvertFromDeflateStringCommand.cs b/src/PSCompression/Commands/ConvertFromDeflateStringCommand.cs new file mode 100644 index 0000000..7a3764d --- /dev/null +++ b/src/PSCompression/Commands/ConvertFromDeflateStringCommand.cs @@ -0,0 +1,14 @@ +using System.IO; +using System.IO.Compression; +using System.Management.Automation; + +namespace PSCompression.Commands; + +[Cmdlet(VerbsData.ConvertFrom, "DeflateString")] +[OutputType(typeof(string))] +[Alias("deflatefromstring")] +public sealed class ConvertFromDeflateString : CommandFromCompressedStringBase +{ + protected override Stream CreateDecompressionStream(Stream inputStream) => + new DeflateStream(inputStream, CompressionMode.Decompress); +} diff --git a/src/PSCompression/Commands/ConvertToDeflateString.cs b/src/PSCompression/Commands/ConvertToDeflateString.cs new file mode 100644 index 0000000..c5fa339 --- /dev/null +++ b/src/PSCompression/Commands/ConvertToDeflateString.cs @@ -0,0 +1,18 @@ +using System.IO; +using System.IO.Compression; +using System.Management.Automation; + +namespace PSCompression.Commands; + +[Cmdlet(VerbsData.ConvertTo, "DeflateString")] +[OutputType(typeof(string))] +[Alias("deflatetostring")] +public sealed class ConvertToDeflateString : CommandToCompressedStringBase +{ + protected override Stream CreateCompressionStream( + Stream outputStream, + CompressionLevel compressionLevel) + { + return new DeflateStream(outputStream, compressionLevel); + } +} diff --git a/src/PSCompression/ZLibStream.cs b/src/PSCompression/ZLibStream.cs index 79bbc27..2d7c3a5 100644 --- a/src/PSCompression/ZLibStream.cs +++ b/src/PSCompression/ZLibStream.cs @@ -22,8 +22,6 @@ public ZlibStream(Stream outputStream, CompressionLevel compressionLevel) // Write zlib header (0x78 0x9C for default compatibility) _outputStream.WriteByte(0x78); _outputStream.WriteByte(0x9C); - - // Initialize DeflateStream _deflateStream = new DeflateStream(outputStream, compressionLevel, true); } @@ -64,9 +62,7 @@ public override void SetLength(long value) public override void Write(byte[] buffer, int offset, int count) { - // Buffer uncompressed data for Adler-32 _uncompressedBuffer.Write(buffer, offset, count); - // Write to DeflateStream for compression _deflateStream.Write(buffer, offset, count); } @@ -94,20 +90,17 @@ protected override void Dispose(bool disposing) if (disposing) { - // Ensure DeflateStream is flushed and closed _deflateStream.Dispose(); - // Write Adler-32 checksum uint adler32 = ComputeAdler32(); byte[] checksum = BitConverter.GetBytes(adler32); if (BitConverter.IsLittleEndian) { Array.Reverse(checksum); } - _outputStream.Write(checksum, 0, checksum.Length); + _outputStream.Write(checksum, 0, checksum.Length); _uncompressedBuffer.Dispose(); - // Note: Don't dispose _outputStream, as it's owned by the caller } _isDisposed = true; From 8759f03505991ff7c5238ab85ebd711c2f716d4e Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Tue, 15 Apr 2025 14:40:54 -0300 Subject: [PATCH 03/53] progress on brotli --- module/PSCompression.psd1 | 4 ++++ .../ConvertFromBrotliStringCommand.cs | 15 +++++++++++++++ .../Commands/ConvertToBrotliString.cs | 19 +++++++++++++++++++ src/PSCompression/PSCompression.csproj | 1 + 4 files changed, 39 insertions(+) create mode 100644 src/PSCompression/Commands/ConvertFromBrotliStringCommand.cs create mode 100644 src/PSCompression/Commands/ConvertToBrotliString.cs diff --git a/module/PSCompression.psd1 b/module/PSCompression.psd1 index 35d4b4d..99e4cb0 100644 --- a/module/PSCompression.psd1 +++ b/module/PSCompression.psd1 @@ -91,6 +91,8 @@ 'ConvertTo-ZLibString' 'ConvertFrom-DeflateString' 'ConvertTo-DeflateString' + 'ConvertFrom-BrotliString' + 'ConvertTo-BrotliString' ) # Variables to export from this module @@ -109,6 +111,8 @@ 'zlibtostring' 'deflatefromstring' 'deflatetostring' + 'brotlifromstring' + 'brotlitostring' ) # DSC resources to export from this module diff --git a/src/PSCompression/Commands/ConvertFromBrotliStringCommand.cs b/src/PSCompression/Commands/ConvertFromBrotliStringCommand.cs new file mode 100644 index 0000000..8c933bd --- /dev/null +++ b/src/PSCompression/Commands/ConvertFromBrotliStringCommand.cs @@ -0,0 +1,15 @@ +using System.IO; +using System.IO.Compression; +using System.Management.Automation; +using Brotli; + +namespace PSCompression.Commands; + +[Cmdlet(VerbsData.ConvertFrom, "BrotliString")] +[OutputType(typeof(string))] +[Alias("brotlifromstring")] +public sealed class ConvertFromBrotliStringCommand : CommandFromCompressedStringBase +{ + protected override Stream CreateDecompressionStream(Stream inputStream) => + new BrotliStream(inputStream, CompressionMode.Decompress); +} diff --git a/src/PSCompression/Commands/ConvertToBrotliString.cs b/src/PSCompression/Commands/ConvertToBrotliString.cs new file mode 100644 index 0000000..75342cf --- /dev/null +++ b/src/PSCompression/Commands/ConvertToBrotliString.cs @@ -0,0 +1,19 @@ +using System.IO; +using System.IO.Compression; +using System.Management.Automation; +using Brotli; + +namespace PSCompression.Commands; + +[Cmdlet(VerbsData.ConvertTo, "BrotliString")] +[OutputType(typeof(string))] +[Alias("brotlitostring")] +public sealed class ConvertToBrotliStringCommand : CommandToCompressedStringBase +{ + protected override Stream CreateCompressionStream( + Stream outputStream, + CompressionLevel compressionLevel) + { + return new BrotliStream(outputStream, CompressionMode.Compress); + } +} diff --git a/src/PSCompression/PSCompression.csproj b/src/PSCompression/PSCompression.csproj index e0fe9fd..df5c84c 100644 --- a/src/PSCompression/PSCompression.csproj +++ b/src/PSCompression/PSCompression.csproj @@ -9,6 +9,7 @@ + From 89d7ef85ee74aa49ba8a646c0ba2e9f029695766 Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Tue, 15 Apr 2025 18:05:31 -0300 Subject: [PATCH 04/53] replaces existing logic for Gzip adding package SharpZipLib --- .../CommandFromCompressedStringBase.cs | 16 ++------- .../CommandToCompressedStringBase.cs | 21 ++---------- .../ConvertFromDeflateStringCommand.cs | 2 +- ...ing.cs => ConvertToBrotliStringCommand.cs} | 0 ...ng.cs => ConvertToDeflateStringCommand.cs} | 2 +- .../Commands/ExpandGzipArchiveCommand.cs | 33 +++++++++++++------ ...Extensions.cs => CompressionExtensions.cs} | 31 ++++++++++++++++- src/PSCompression/PSCompression.csproj | 1 + 8 files changed, 62 insertions(+), 44 deletions(-) rename src/PSCompression/Commands/{ConvertToBrotliString.cs => ConvertToBrotliStringCommand.cs} (100%) rename src/PSCompression/Commands/{ConvertToDeflateString.cs => ConvertToDeflateStringCommand.cs} (83%) rename src/PSCompression/Extensions/{ZipEntryExtensions.cs => CompressionExtensions.cs} (79%) diff --git a/src/PSCompression/CommandFromCompressedStringBase.cs b/src/PSCompression/CommandFromCompressedStringBase.cs index 964e924..d68d2b7 100644 --- a/src/PSCompression/CommandFromCompressedStringBase.cs +++ b/src/PSCompression/CommandFromCompressedStringBase.cs @@ -4,6 +4,7 @@ using System.Management.Automation; using System.Text; using PSCompression.Exceptions; +using PSCompression.Extensions; namespace PSCompression; @@ -34,22 +35,11 @@ private void Decompress( if (Raw) { - ReadToEnd(reader); + reader.ReadToEnd(this); return; } - ReadLines(reader); - } - - private void ReadToEnd(StreamReader reader) => WriteObject(reader.ReadToEnd()); - - private void ReadLines(StreamReader reader) - { - string line; - while ((line = reader.ReadLine()) is not null) - { - WriteObject(line); - } + reader.ReadLines(this); } protected abstract Stream CreateDecompressionStream(Stream inputStream); diff --git a/src/PSCompression/CommandToCompressedStringBase.cs b/src/PSCompression/CommandToCompressedStringBase.cs index 2e3e6b0..1597a63 100644 --- a/src/PSCompression/CommandToCompressedStringBase.cs +++ b/src/PSCompression/CommandToCompressedStringBase.cs @@ -5,6 +5,7 @@ using System.Management.Automation; using System.Text; using PSCompression.Exceptions; +using PSCompression.Extensions; namespace PSCompression; @@ -50,11 +51,11 @@ protected override void ProcessRecord() if (NoNewLine.IsPresent) { - Write(_writer, InputObject); + _writer.WriteFrom(InputObject); return; } - WriteLines(_writer, InputObject); + _writer.WriteLinesFrom(InputObject); } catch (Exception exception) { @@ -93,22 +94,6 @@ protected override void EndProcessing() } } - private void WriteLines(StreamWriter writer, string[] lines) - { - foreach (string line in lines) - { - writer.WriteLine(line); - } - } - - private void Write(StreamWriter writer, string[] lines) - { - foreach (string line in lines) - { - writer.Write(line); - } - } - public void Dispose() { _writer?.Dispose(); diff --git a/src/PSCompression/Commands/ConvertFromDeflateStringCommand.cs b/src/PSCompression/Commands/ConvertFromDeflateStringCommand.cs index 7a3764d..fcb8708 100644 --- a/src/PSCompression/Commands/ConvertFromDeflateStringCommand.cs +++ b/src/PSCompression/Commands/ConvertFromDeflateStringCommand.cs @@ -7,7 +7,7 @@ namespace PSCompression.Commands; [Cmdlet(VerbsData.ConvertFrom, "DeflateString")] [OutputType(typeof(string))] [Alias("deflatefromstring")] -public sealed class ConvertFromDeflateString : CommandFromCompressedStringBase +public sealed class ConvertFromDeflateStringCommand : CommandFromCompressedStringBase { protected override Stream CreateDecompressionStream(Stream inputStream) => new DeflateStream(inputStream, CompressionMode.Decompress); diff --git a/src/PSCompression/Commands/ConvertToBrotliString.cs b/src/PSCompression/Commands/ConvertToBrotliStringCommand.cs similarity index 100% rename from src/PSCompression/Commands/ConvertToBrotliString.cs rename to src/PSCompression/Commands/ConvertToBrotliStringCommand.cs diff --git a/src/PSCompression/Commands/ConvertToDeflateString.cs b/src/PSCompression/Commands/ConvertToDeflateStringCommand.cs similarity index 83% rename from src/PSCompression/Commands/ConvertToDeflateString.cs rename to src/PSCompression/Commands/ConvertToDeflateStringCommand.cs index c5fa339..34a72d1 100644 --- a/src/PSCompression/Commands/ConvertToDeflateString.cs +++ b/src/PSCompression/Commands/ConvertToDeflateStringCommand.cs @@ -7,7 +7,7 @@ namespace PSCompression.Commands; [Cmdlet(VerbsData.ConvertTo, "DeflateString")] [OutputType(typeof(string))] [Alias("deflatetostring")] -public sealed class ConvertToDeflateString : CommandToCompressedStringBase +public sealed class ConvertToDeflateStringCommand : CommandToCompressedStringBase { protected override Stream CreateCompressionStream( Stream outputStream, diff --git a/src/PSCompression/Commands/ExpandGzipArchiveCommand.cs b/src/PSCompression/Commands/ExpandGzipArchiveCommand.cs index e454822..187d9e0 100644 --- a/src/PSCompression/Commands/ExpandGzipArchiveCommand.cs +++ b/src/PSCompression/Commands/ExpandGzipArchiveCommand.cs @@ -4,6 +4,7 @@ using System.Text; using PSCompression.Extensions; using PSCompression.Exceptions; +using ICSharpCode.SharpZipLib.GZip; namespace PSCompression.Commands; @@ -133,22 +134,34 @@ protected override void ProcessRecord() try { + using FileStream source = File.OpenRead(path); + using GZipInputStream gz = new(source); + if (_destination is not null) { - GzipReaderOps.CopyTo( - path: path, - isCoreCLR: PSVersionHelper.IsCoreCLR, - destination: _destination); + // GzipReaderOps.CopyTo( + // path: path, + // isCoreCLR: PSVersionHelper.IsCoreCLR, + // destination: _destination); + gz.CopyTo(_destination); + continue; + } + using StreamReader reader = new(gz, Encoding); + + if (Raw) + { + reader.ReadToEnd(this); continue; } - GzipReaderOps.GetContent( - path: path, - isCoreCLR: PSVersionHelper.IsCoreCLR, - raw: Raw.IsPresent, - encoding: Encoding, - cmdlet: this); + reader.ReadLines(this); + // GzipReaderOps.GetContent( + // path: path, + // isCoreCLR: PSVersionHelper.IsCoreCLR, + // raw: Raw.IsPresent, + // encoding: Encoding, + // cmdlet: this); } catch (Exception _) when (_ is PipelineStoppedException or FlowControlException) { diff --git a/src/PSCompression/Extensions/ZipEntryExtensions.cs b/src/PSCompression/Extensions/CompressionExtensions.cs similarity index 79% rename from src/PSCompression/Extensions/ZipEntryExtensions.cs rename to src/PSCompression/Extensions/CompressionExtensions.cs index 3cb1be5..f8a132f 100644 --- a/src/PSCompression/Extensions/ZipEntryExtensions.cs +++ b/src/PSCompression/Extensions/CompressionExtensions.cs @@ -1,11 +1,12 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using System.IO.Compression; +using System.Management.Automation; using System.Text.RegularExpressions; namespace PSCompression.Extensions; -internal static class ZipEntryExtensions +internal static class CompressionExtensions { private static readonly Regex s_reGetDirName = new( @"[^/]+(?=/$)", @@ -106,4 +107,32 @@ internal static string ChangePath( internal static string GetDirectoryName(this ZipArchiveEntry entry) => s_reGetDirName.Match(entry.FullName).Value; + + internal static void ReadToEnd(this StreamReader reader, PSCmdlet cmdlet) => + cmdlet.WriteObject(reader.ReadToEnd()); + + internal static void ReadLines(this StreamReader reader, PSCmdlet cmdlet) + { + string line; + while ((line = reader.ReadLine()) is not null) + { + cmdlet.WriteObject(line); + } + } + + internal static void WriteLinesFrom(this StreamWriter writer, string[] lines) + { + foreach (string line in lines) + { + writer.WriteLine(line); + } + } + + internal static void WriteFrom(this StreamWriter writer, string[] lines) + { + foreach (string line in lines) + { + writer.Write(line); + } + } } diff --git a/src/PSCompression/PSCompression.csproj b/src/PSCompression/PSCompression.csproj index df5c84c..c939771 100644 --- a/src/PSCompression/PSCompression.csproj +++ b/src/PSCompression/PSCompression.csproj @@ -11,6 +11,7 @@ + From 1c1542725cac419b600d884eb6c5ec43c331d070 Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Tue, 15 Apr 2025 18:58:52 -0300 Subject: [PATCH 05/53] bunch of alias renaming --- module/PSCompression.psd1 | 25 +-- .../Commands/CompressGzipArchiveCommand.cs | 2 +- .../Commands/CompressZipArchiveCommand.cs | 2 +- .../ConvertFromBrotliStringCommand.cs | 2 +- .../ConvertFromDeflateStringCommand.cs | 2 +- .../Commands/ConvertFromGzipStringCommand.cs | 2 +- .../Commands/ConvertFromZLibStringCommand.cs | 2 +- .../Commands/ConvertToBrotliStringCommand.cs | 2 +- .../Commands/ConvertToDeflateStringCommand.cs | 2 +- .../Commands/ConvertToGzipStringCommand.cs | 2 +- .../Commands/ConvertToZLibStringCommand.cs | 2 +- .../Commands/ExpandGzipArchiveCommand.cs | 23 ++- .../Commands/GetZipEntryCommand.cs | 2 +- .../Commands/GetZipEntryContentCommand.cs | 2 +- src/PSCompression/GzipReaderOps.cs | 150 ------------------ src/PSCompression/PSVersionHelper.cs | 23 --- tests/PSVersionHelper.tests.ps1 | 19 --- 17 files changed, 34 insertions(+), 230 deletions(-) delete mode 100644 src/PSCompression/GzipReaderOps.cs delete mode 100644 src/PSCompression/PSVersionHelper.cs delete mode 100644 tests/PSVersionHelper.tests.ps1 diff --git a/module/PSCompression.psd1 b/module/PSCompression.psd1 index 99e4cb0..9b32613 100644 --- a/module/PSCompression.psd1 +++ b/module/PSCompression.psd1 @@ -100,19 +100,20 @@ # 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 = @( - 'gziptofile' - 'gzipfromfile' - 'gziptostring' - 'gzipfromstring' + 'togzipfile' + 'fromgzipfile' + 'togzipstring' + 'fromgzipstring' 'zip' - 'ziparchive' - 'gze' - 'zlibfromstring' - 'zlibtostring' - 'deflatefromstring' - 'deflatetostring' - 'brotlifromstring' - 'brotlitostring' + 'zipcompress' + 'fromzlibstring' + 'tozlibstring' + 'fromdeflatestring' + 'todeflatestring' + 'frombrotlistring' + 'tobrotlistring' + 'zipge' + 'zipgc' ) # DSC resources to export from this module diff --git a/src/PSCompression/Commands/CompressGzipArchiveCommand.cs b/src/PSCompression/Commands/CompressGzipArchiveCommand.cs index ab42ddf..156f336 100644 --- a/src/PSCompression/Commands/CompressGzipArchiveCommand.cs +++ b/src/PSCompression/Commands/CompressGzipArchiveCommand.cs @@ -9,7 +9,7 @@ namespace PSCompression.Commands; [Cmdlet(VerbsData.Compress, "GzipArchive", DefaultParameterSetName = "Path")] [OutputType(typeof(FileInfo))] -[Alias("gziptofile")] +[Alias("togzipfile")] public sealed class CompressGzipArchive : CommandWithPathBase, IDisposable { private FileStream? _destination; diff --git a/src/PSCompression/Commands/CompressZipArchiveCommand.cs b/src/PSCompression/Commands/CompressZipArchiveCommand.cs index 0a17b1f..11a5041 100644 --- a/src/PSCompression/Commands/CompressZipArchiveCommand.cs +++ b/src/PSCompression/Commands/CompressZipArchiveCommand.cs @@ -11,7 +11,7 @@ namespace PSCompression.Commands; [Cmdlet(VerbsData.Compress, "ZipArchive")] [OutputType(typeof(FileInfo))] -[Alias("ziparchive")] +[Alias("zipcompress")] public sealed class CompressZipArchiveCommand : CommandWithPathBase, IDisposable { private ZipArchive? _zip; diff --git a/src/PSCompression/Commands/ConvertFromBrotliStringCommand.cs b/src/PSCompression/Commands/ConvertFromBrotliStringCommand.cs index 8c933bd..6f793c0 100644 --- a/src/PSCompression/Commands/ConvertFromBrotliStringCommand.cs +++ b/src/PSCompression/Commands/ConvertFromBrotliStringCommand.cs @@ -7,7 +7,7 @@ namespace PSCompression.Commands; [Cmdlet(VerbsData.ConvertFrom, "BrotliString")] [OutputType(typeof(string))] -[Alias("brotlifromstring")] +[Alias("frombrotlistring")] public sealed class ConvertFromBrotliStringCommand : CommandFromCompressedStringBase { protected override Stream CreateDecompressionStream(Stream inputStream) => diff --git a/src/PSCompression/Commands/ConvertFromDeflateStringCommand.cs b/src/PSCompression/Commands/ConvertFromDeflateStringCommand.cs index fcb8708..c02396f 100644 --- a/src/PSCompression/Commands/ConvertFromDeflateStringCommand.cs +++ b/src/PSCompression/Commands/ConvertFromDeflateStringCommand.cs @@ -6,7 +6,7 @@ namespace PSCompression.Commands; [Cmdlet(VerbsData.ConvertFrom, "DeflateString")] [OutputType(typeof(string))] -[Alias("deflatefromstring")] +[Alias("fromdeflatestring")] public sealed class ConvertFromDeflateStringCommand : CommandFromCompressedStringBase { protected override Stream CreateDecompressionStream(Stream inputStream) => diff --git a/src/PSCompression/Commands/ConvertFromGzipStringCommand.cs b/src/PSCompression/Commands/ConvertFromGzipStringCommand.cs index b537a2d..7b69b1b 100644 --- a/src/PSCompression/Commands/ConvertFromGzipStringCommand.cs +++ b/src/PSCompression/Commands/ConvertFromGzipStringCommand.cs @@ -6,7 +6,7 @@ namespace PSCompression.Commands; [Cmdlet(VerbsData.ConvertFrom, "GzipString")] [OutputType(typeof(string))] -[Alias("gzipfromstring")] +[Alias("fromgzipstring")] public sealed class ConvertFromGzipStringCommand : CommandFromCompressedStringBase { protected override Stream CreateDecompressionStream(Stream inputStream) => diff --git a/src/PSCompression/Commands/ConvertFromZLibStringCommand.cs b/src/PSCompression/Commands/ConvertFromZLibStringCommand.cs index 92870bd..dd29106 100644 --- a/src/PSCompression/Commands/ConvertFromZLibStringCommand.cs +++ b/src/PSCompression/Commands/ConvertFromZLibStringCommand.cs @@ -6,7 +6,7 @@ namespace PSCompression.Commands; [Cmdlet(VerbsData.ConvertFrom, "ZLibString")] [OutputType(typeof(string))] -[Alias("zlibfromstring")] +[Alias("fromzlibstring")] public sealed class ConvertFromZLibStringCommand : CommandFromCompressedStringBase { protected override Stream CreateDecompressionStream(Stream inputStream) diff --git a/src/PSCompression/Commands/ConvertToBrotliStringCommand.cs b/src/PSCompression/Commands/ConvertToBrotliStringCommand.cs index 75342cf..422c7b9 100644 --- a/src/PSCompression/Commands/ConvertToBrotliStringCommand.cs +++ b/src/PSCompression/Commands/ConvertToBrotliStringCommand.cs @@ -7,7 +7,7 @@ namespace PSCompression.Commands; [Cmdlet(VerbsData.ConvertTo, "BrotliString")] [OutputType(typeof(string))] -[Alias("brotlitostring")] +[Alias("tobrotlistring")] public sealed class ConvertToBrotliStringCommand : CommandToCompressedStringBase { protected override Stream CreateCompressionStream( diff --git a/src/PSCompression/Commands/ConvertToDeflateStringCommand.cs b/src/PSCompression/Commands/ConvertToDeflateStringCommand.cs index 34a72d1..a9cf75c 100644 --- a/src/PSCompression/Commands/ConvertToDeflateStringCommand.cs +++ b/src/PSCompression/Commands/ConvertToDeflateStringCommand.cs @@ -6,7 +6,7 @@ namespace PSCompression.Commands; [Cmdlet(VerbsData.ConvertTo, "DeflateString")] [OutputType(typeof(string))] -[Alias("deflatetostring")] +[Alias("todeflatestring")] public sealed class ConvertToDeflateStringCommand : CommandToCompressedStringBase { protected override Stream CreateCompressionStream( diff --git a/src/PSCompression/Commands/ConvertToGzipStringCommand.cs b/src/PSCompression/Commands/ConvertToGzipStringCommand.cs index 8c1fd9a..fcb4bc7 100644 --- a/src/PSCompression/Commands/ConvertToGzipStringCommand.cs +++ b/src/PSCompression/Commands/ConvertToGzipStringCommand.cs @@ -6,7 +6,7 @@ namespace PSCompression.Commands; [Cmdlet(VerbsData.ConvertTo, "GzipString")] [OutputType(typeof(byte[]), typeof(string))] -[Alias("gziptostring")] +[Alias("togzipstring")] public sealed class ConvertToGzipStringCommand : CommandToCompressedStringBase { protected override Stream CreateCompressionStream( diff --git a/src/PSCompression/Commands/ConvertToZLibStringCommand.cs b/src/PSCompression/Commands/ConvertToZLibStringCommand.cs index fc84b7a..44acb0e 100644 --- a/src/PSCompression/Commands/ConvertToZLibStringCommand.cs +++ b/src/PSCompression/Commands/ConvertToZLibStringCommand.cs @@ -6,7 +6,7 @@ namespace PSCompression.Commands; [Cmdlet(VerbsData.ConvertTo, "ZLibString")] [OutputType(typeof(byte[]), typeof(string))] -[Alias("zlibtostring")] +[Alias("tozlibstring")] public sealed class ConvertToZLibStringCommand : CommandToCompressedStringBase { protected override Stream CreateCompressionStream( diff --git a/src/PSCompression/Commands/ExpandGzipArchiveCommand.cs b/src/PSCompression/Commands/ExpandGzipArchiveCommand.cs index 187d9e0..4ee8876 100644 --- a/src/PSCompression/Commands/ExpandGzipArchiveCommand.cs +++ b/src/PSCompression/Commands/ExpandGzipArchiveCommand.cs @@ -15,7 +15,7 @@ namespace PSCompression.Commands; [OutputType( typeof(FileInfo), ParameterSetName = ["PathDestination", "LiteralPathDestination"])] -[Alias("gzipfromfile")] +[Alias("fromgzipfile")] public sealed class ExpandGzipArchiveCommand : CommandWithPathBase, IDisposable { private FileStream? _destination; @@ -98,7 +98,7 @@ public override string[] LiteralPath protected override void BeginProcessing() { - if (Destination is not null && _destination is null) + if (Destination is not null) { try { @@ -139,10 +139,6 @@ protected override void ProcessRecord() if (_destination is not null) { - // GzipReaderOps.CopyTo( - // path: path, - // isCoreCLR: PSVersionHelper.IsCoreCLR, - // destination: _destination); gz.CopyTo(_destination); continue; } @@ -156,12 +152,6 @@ protected override void ProcessRecord() } reader.ReadLines(this); - // GzipReaderOps.GetContent( - // path: path, - // isCoreCLR: PSVersionHelper.IsCoreCLR, - // raw: Raw.IsPresent, - // encoding: Encoding, - // cmdlet: this); } catch (Exception _) when (_ is PipelineStoppedException or FlowControlException) { @@ -176,9 +166,14 @@ protected override void ProcessRecord() protected override void EndProcessing() { - _destination?.Dispose(); + if (_destination is null) + { + return; + } + + _destination.Dispose(); - if (PassThru.IsPresent && _destination is not null) + if (PassThru.IsPresent) { WriteObject(new FileInfo(_destination.Name)); } diff --git a/src/PSCompression/Commands/GetZipEntryCommand.cs b/src/PSCompression/Commands/GetZipEntryCommand.cs index d4508be..674c3ad 100644 --- a/src/PSCompression/Commands/GetZipEntryCommand.cs +++ b/src/PSCompression/Commands/GetZipEntryCommand.cs @@ -11,7 +11,7 @@ namespace PSCompression.Commands; [Cmdlet(VerbsCommon.Get, "ZipEntry", DefaultParameterSetName = "Path")] [OutputType(typeof(ZipEntryDirectory), typeof(ZipEntryFile))] -[Alias("gezip")] +[Alias("zipge")] public sealed class GetZipEntryCommand : CommandWithPathBase { [Parameter( diff --git a/src/PSCompression/Commands/GetZipEntryContentCommand.cs b/src/PSCompression/Commands/GetZipEntryContentCommand.cs index a6030ff..11e954f 100644 --- a/src/PSCompression/Commands/GetZipEntryContentCommand.cs +++ b/src/PSCompression/Commands/GetZipEntryContentCommand.cs @@ -9,7 +9,7 @@ namespace PSCompression.Commands; [Cmdlet(VerbsCommon.Get, "ZipEntryContent", DefaultParameterSetName = "Stream")] [OutputType(typeof(string), ParameterSetName = ["Stream"])] [OutputType(typeof(byte), ParameterSetName = ["Bytes"])] -[Alias("gczip")] +[Alias("zipgc")] public sealed class GetZipEntryContentCommand : PSCmdlet, IDisposable { private readonly ZipArchiveCache _cache = new(); diff --git a/src/PSCompression/GzipReaderOps.cs b/src/PSCompression/GzipReaderOps.cs deleted file mode 100644 index 51a053b..0000000 --- a/src/PSCompression/GzipReaderOps.cs +++ /dev/null @@ -1,150 +0,0 @@ -using System.IO; -using System.IO.Compression; -using System.Management.Automation; -using System.Runtime.Serialization; -using System.Text; - -namespace PSCompression; - -internal static class GzipReaderOps -{ - private static readonly byte[] gzipPreamble = [0x1f, 0x8b, 0x08]; - - internal static void CopyTo( - string path, - bool isCoreCLR, - FileStream destination) - { - if (isCoreCLR) - { - using FileStream fs = File.OpenRead(path); - using GZipStream gzip = new(fs, CompressionMode.Decompress); - gzip.CopyTo(destination); - return; - } - - using MemoryStream mem = GetFrameworkStream(path); - mem.CopyTo(destination); - } - - internal static void GetContent( - string path, - bool isCoreCLR, - bool raw, - Encoding encoding, - PSCmdlet cmdlet) - { - if (isCoreCLR) - { - using FileStream fs = File.OpenRead(path); - using GZipStream gzip = new(fs, CompressionMode.Decompress); - - if (raw) - { - ReadToEnd(gzip, encoding, cmdlet); - return; - } - - ReadLines(gzip, encoding, cmdlet); - return; - } - - using MemoryStream stream = GetFrameworkStream(path); - - if (raw) - { - ReadToEnd(stream, encoding, cmdlet); - return; - } - - ReadLines(stream, encoding, cmdlet); - } - - private static void ReadLines( - Stream stream, - Encoding encoding, - PSCmdlet cmdlet) - { - using StreamReader reader = new(stream, encoding); - - while (!reader.EndOfStream) - { - cmdlet.WriteObject(reader.ReadLine()); - } - } - - private static void ReadToEnd( - Stream stream, - Encoding encoding, - PSCmdlet cmdlet) - { - using StreamReader reader = new(stream, encoding); - cmdlet.WriteObject(reader.ReadToEnd()); - } - - // this stuff is to make this work reading appended gzip streams in .net framework - // i hate it :( - private static MemoryStream GetFrameworkStream(string path) - { - int marker = 0; - int b; - using FileStream fs = File.OpenRead(path); - - byte[] preamble = new byte[3]; - fs.Read(preamble, 0, 3); - - for (int i = 0; i < 3; i++) - { - if(preamble[i] != gzipPreamble[i]) - { - throw new InvalidDataContractException( - "The archive entry was compressed using an unsupported compression method."); - } - } - - fs.Seek(0, SeekOrigin.Begin); - - MemoryStream outmem = new(); - - while ((b = fs.ReadByte()) != -1) - { - if (marker == 0 && (byte)b == gzipPreamble[marker]) - { - marker++; - continue; - } - - if (marker == 1) - { - if ((byte)b == gzipPreamble[marker]) - { - marker++; - continue; - } - - marker = 0; - } - - if (marker == 2) - { - if ((byte)b == gzipPreamble[marker]) - { - CopyTo(path, outmem, fs.Position - 3); - } - - marker = 0; - } - } - - outmem.Seek(0, SeekOrigin.Begin); - return outmem; - } - - private static void CopyTo(string path, MemoryStream outmem, long pos) - { - using FileStream substream = File.OpenRead(path); - substream.Seek(pos, SeekOrigin.Begin); - using GZipStream gzip = new(substream, CompressionMode.Decompress); - gzip.CopyTo(outmem); - } -} diff --git a/src/PSCompression/PSVersionHelper.cs b/src/PSCompression/PSVersionHelper.cs deleted file mode 100644 index d6ad5c0..0000000 --- a/src/PSCompression/PSVersionHelper.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Management.Automation; -using System.Reflection; - -namespace PSCompression; - -internal static class PSVersionHelper -{ - private static bool? _isCore; - - internal static bool IsCoreCLR => _isCore ??= IsCore(); - - private static bool IsCore() - { - PropertyInfo property = typeof(PowerShell) - .Assembly.GetType("System.Management.Automation.PSVersionInfo") - .GetProperty( - "PSVersion", - BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public); - - return (Version)property.GetValue(property) is not { Major: 5, Minor: 1 }; - } -} diff --git a/tests/PSVersionHelper.tests.ps1 b/tests/PSVersionHelper.tests.ps1 deleted file mode 100644 index 42807da..0000000 --- a/tests/PSVersionHelper.tests.ps1 +++ /dev/null @@ -1,19 +0,0 @@ -$ErrorActionPreference = 'Stop' - -$moduleName = (Get-Item ([IO.Path]::Combine($PSScriptRoot, '..', 'module', '*.psd1'))).BaseName -$manifestPath = [IO.Path]::Combine($PSScriptRoot, '..', 'output', $moduleName) - -Import-Module $manifestPath -Import-Module ([System.IO.Path]::Combine($PSScriptRoot, 'shared.psm1')) - -Describe 'PSVersionHelper Class' { - It 'Should have the same value as $IsCoreCLR' { - $property = [PSCompression.ZipEntryBase].Assembly. - GetType('PSCompression.PSVersionHelper'). - GetProperty( - 'IsCoreCLR', - [System.Reflection.BindingFlags] 'NonPublic, Static') - - $property.GetValue($property) | Should -BeExactly ([bool] $IsCoreCLR) - } -} From 73de2b5bb7eccdd207509dbe0412dce35acc3846 Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Tue, 15 Apr 2025 20:31:41 -0300 Subject: [PATCH 06/53] few improvements on existing classes --- .../CommandToCompressedStringBase.cs | 31 ++++----------- .../Commands/GetZipEntryContentCommand.cs | 21 ++++------ src/PSCompression/ZLibStream.cs | 5 ++- src/PSCompression/ZipContentOpsBase.cs | 10 ++--- src/PSCompression/ZipContentReader.cs | 39 ++++++++++++------- 5 files changed, 47 insertions(+), 59 deletions(-) diff --git a/src/PSCompression/CommandToCompressedStringBase.cs b/src/PSCompression/CommandToCompressedStringBase.cs index 1597a63..bb97784 100644 --- a/src/PSCompression/CommandToCompressedStringBase.cs +++ b/src/PSCompression/CommandToCompressedStringBase.cs @@ -67,37 +67,22 @@ protected override void EndProcessing() { _writer?.Dispose(); _compressStream?.Dispose(); - _outstream?.Dispose(); + _outstream.Dispose(); - try + if (AsByteStream.IsPresent) { - if (_writer is null || _compressStream is null || _outstream is null) - { - return; - } - - if (AsByteStream.IsPresent) - { - WriteObject(_outstream.ToArray(), enumerateCollection: false); - return; - } - - WriteObject(Convert.ToBase64String(_outstream.ToArray())); - } - catch (Exception _) when (_ is PipelineStoppedException or FlowControlException) - { - throw; - } - catch (Exception exception) - { - WriteError(exception.ToWriteError(_outstream)); + WriteObject(_outstream.ToArray(), enumerateCollection: false); + return; } + + WriteObject(Convert.ToBase64String(_outstream.ToArray())); } public void Dispose() { _writer?.Dispose(); _compressStream?.Dispose(); - _outstream?.Dispose(); + _outstream.Dispose(); + GC.SuppressFinalize(this); } } diff --git a/src/PSCompression/Commands/GetZipEntryContentCommand.cs b/src/PSCompression/Commands/GetZipEntryContentCommand.cs index 11e954f..97f0ee9 100644 --- a/src/PSCompression/Commands/GetZipEntryContentCommand.cs +++ b/src/PSCompression/Commands/GetZipEntryContentCommand.cs @@ -1,5 +1,4 @@ using System; -using System.IO.Compression; using System.Management.Automation; using System.Text; using PSCompression.Exceptions; @@ -39,7 +38,7 @@ protected override void ProcessRecord() { try { - ZipContentReader reader = new(GetOrAdd(entry)); + ZipContentReader reader = new(_cache.GetOrAdd(entry)); ReadEntry(entry, reader); } catch (Exception _) when (_ is PipelineStoppedException or FlowControlException) @@ -55,33 +54,27 @@ protected override void ProcessRecord() private void ReadEntry(ZipEntryFile entry, ZipContentReader reader) { - if (AsByteStream.IsPresent) + if (AsByteStream) { - if (Raw.IsPresent) + if (Raw) { - WriteObject(reader.ReadAllBytes(entry)); + reader.ReadAllBytes(entry, this); return; } - WriteObject( - reader.StreamBytes(entry, BufferSize), - enumerateCollection: true); + reader.StreamBytes(entry, BufferSize, this); return; } if (Raw.IsPresent) { - WriteObject(reader.ReadToEnd(entry, Encoding)); + reader.ReadToEnd(entry, Encoding, this); return; } - WriteObject( - reader.StreamLines(entry, Encoding), - enumerateCollection: true); + reader.StreamLines(entry, Encoding, this); } - private ZipArchive GetOrAdd(ZipEntryFile entry) => _cache.GetOrAdd(entry); - public void Dispose() { _cache?.Dispose(); diff --git a/src/PSCompression/ZLibStream.cs b/src/PSCompression/ZLibStream.cs index 2d7c3a5..46732aa 100644 --- a/src/PSCompression/ZLibStream.cs +++ b/src/PSCompression/ZLibStream.cs @@ -1,9 +1,12 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.IO.Compression; namespace PSCompression; +// Justification: ain't nobody got time for that! +[ExcludeFromCodeCoverage] internal sealed class ZlibStream : Stream { private readonly Stream _outputStream; @@ -14,7 +17,7 @@ internal sealed class ZlibStream : Stream private bool _isDisposed; - public ZlibStream(Stream outputStream, CompressionLevel compressionLevel) + internal ZlibStream(Stream outputStream, CompressionLevel compressionLevel) { _outputStream = outputStream ?? throw new ArgumentNullException(nameof(outputStream)); _uncompressedBuffer = new MemoryStream(); diff --git a/src/PSCompression/ZipContentOpsBase.cs b/src/PSCompression/ZipContentOpsBase.cs index 71b9e44..49c2a2e3 100644 --- a/src/PSCompression/ZipContentOpsBase.cs +++ b/src/PSCompression/ZipContentOpsBase.cs @@ -7,16 +7,14 @@ internal abstract class ZipContentOpsBase(ZipArchive zip) : IDisposable { protected ZipArchive _zip = zip; - protected byte[]? _buffer; - - public bool Disposed { get; internal set; } + protected bool _disposed; protected virtual void Dispose(bool disposing) { - if (disposing && !Disposed) + if (disposing && !_disposed) { - _zip?.Dispose(); - Disposed = true; + _zip.Dispose(); + _disposed = true; } } diff --git a/src/PSCompression/ZipContentReader.cs b/src/PSCompression/ZipContentReader.cs index 1dff6e2..7eecd9d 100644 --- a/src/PSCompression/ZipContentReader.cs +++ b/src/PSCompression/ZipContentReader.cs @@ -1,53 +1,62 @@ -using System.Collections.Generic; using System.IO; using System.IO.Compression; +using System.Management.Automation; using System.Text; +using PSCompression.Extensions; namespace PSCompression; internal sealed class ZipContentReader : ZipContentOpsBase { - internal ZipContentReader(ZipArchive zip) : base(zip) { } + private byte[]? _buffer; - internal IEnumerable StreamBytes(ZipEntryFile entry, int bufferSize) + internal ZipContentReader(ZipArchive zip) : base(zip) + { } + + internal void StreamBytes( + ZipEntryFile entry, + int bufferSize, + PSCmdlet cmdlet) { + int bytes; using Stream entryStream = entry.Open(_zip); _buffer ??= new byte[bufferSize]; - int bytes; while ((bytes = entryStream.Read(_buffer, 0, bufferSize)) > 0) { for (int i = 0; i < bytes; i++) { - yield return _buffer[i]; + cmdlet.WriteObject(_buffer[i]); } } } - internal byte[] ReadAllBytes(ZipEntryFile entry) + internal void ReadAllBytes(ZipEntryFile entry, PSCmdlet cmdlet) { using Stream entryStream = entry.Open(_zip); using MemoryStream mem = new(); entryStream.CopyTo(mem); - return mem.ToArray(); + cmdlet.WriteObject(mem.ToArray()); } - internal IEnumerable StreamLines(ZipEntryFile entry, Encoding encoding) + internal void StreamLines( + ZipEntryFile entry, + Encoding encoding, + PSCmdlet cmdlet) { using Stream entryStream = entry.Open(_zip); using StreamReader reader = new(entryStream, encoding); - - while (!reader.EndOfStream) - { - yield return reader.ReadLine(); - } + reader.ReadLines(cmdlet); } - internal string ReadToEnd(ZipEntryFile entry, Encoding encoding) + internal void ReadToEnd( + ZipEntryFile entry, + Encoding encoding, + PSCmdlet cmdlet) { using Stream entryStream = entry.Open(_zip); using StreamReader reader = new(entryStream, encoding); - return reader.ReadToEnd(); + reader.ReadToEnd(cmdlet); } } From 2c8e6b0dc510cbc35ac1c0302949a28ffe9aa812 Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Tue, 15 Apr 2025 20:39:00 -0300 Subject: [PATCH 07/53] few improvements on existing classes --- src/PSCompression/ZipContentOpsBase.cs | 2 ++ src/PSCompression/ZipContentReader.cs | 2 -- src/PSCompression/ZipContentWriter.cs | 3 --- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/PSCompression/ZipContentOpsBase.cs b/src/PSCompression/ZipContentOpsBase.cs index 49c2a2e3..1a726c3 100644 --- a/src/PSCompression/ZipContentOpsBase.cs +++ b/src/PSCompression/ZipContentOpsBase.cs @@ -5,6 +5,8 @@ namespace PSCompression; internal abstract class ZipContentOpsBase(ZipArchive zip) : IDisposable { + protected byte[]? _buffer; + protected ZipArchive _zip = zip; protected bool _disposed; diff --git a/src/PSCompression/ZipContentReader.cs b/src/PSCompression/ZipContentReader.cs index 7eecd9d..b56110c 100644 --- a/src/PSCompression/ZipContentReader.cs +++ b/src/PSCompression/ZipContentReader.cs @@ -8,8 +8,6 @@ namespace PSCompression; internal sealed class ZipContentReader : ZipContentOpsBase { - private byte[]? _buffer; - internal ZipContentReader(ZipArchive zip) : base(zip) { } diff --git a/src/PSCompression/ZipContentWriter.cs b/src/PSCompression/ZipContentWriter.cs index 6945e20..d0e8ca2 100644 --- a/src/PSCompression/ZipContentWriter.cs +++ b/src/PSCompression/ZipContentWriter.cs @@ -12,8 +12,6 @@ internal sealed class ZipContentWriter : ZipContentOpsBase private readonly Stream _stream; - private bool _disposed; - internal ZipContentWriter(ZipEntryFile entry, bool append, int bufferSize) : base(entry.OpenWrite()) { @@ -122,7 +120,6 @@ protected override void Dispose(bool disposing) { _writer?.Dispose(); _stream.Dispose(); - _disposed = true; base.Dispose(disposing); } } From 43ea1883ee88b15b1e0723a90d4557947d0f71ec Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Fri, 18 Apr 2025 11:30:31 -0300 Subject: [PATCH 08/53] hides CompressionLevel parameter for ConvertToBrotliStringCommand --- src/PSCompression/CommandToCompressedStringBase.cs | 2 +- src/PSCompression/Commands/ConvertToBrotliStringCommand.cs | 5 ++++- src/PSCompression/Commands/ConvertToDeflateStringCommand.cs | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/PSCompression/CommandToCompressedStringBase.cs b/src/PSCompression/CommandToCompressedStringBase.cs index bb97784..727246a 100644 --- a/src/PSCompression/CommandToCompressedStringBase.cs +++ b/src/PSCompression/CommandToCompressedStringBase.cs @@ -29,7 +29,7 @@ public abstract class CommandToCompressedStringBase : PSCmdlet, IDisposable public Encoding Encoding { get; set; } = new UTF8Encoding(); [Parameter] - public CompressionLevel CompressionLevel { get; set; } = CompressionLevel.Optimal; + public virtual CompressionLevel CompressionLevel { get; set; } = CompressionLevel.Optimal; [Parameter] [Alias("Raw")] diff --git a/src/PSCompression/Commands/ConvertToBrotliStringCommand.cs b/src/PSCompression/Commands/ConvertToBrotliStringCommand.cs index 422c7b9..bf15a12 100644 --- a/src/PSCompression/Commands/ConvertToBrotliStringCommand.cs +++ b/src/PSCompression/Commands/ConvertToBrotliStringCommand.cs @@ -6,10 +6,13 @@ namespace PSCompression.Commands; [Cmdlet(VerbsData.ConvertTo, "BrotliString")] -[OutputType(typeof(string))] +[OutputType(typeof(byte[]), typeof(string))] [Alias("tobrotlistring")] public sealed class ConvertToBrotliStringCommand : CommandToCompressedStringBase { + [Parameter(DontShow = true)] + public override CompressionLevel CompressionLevel { get; set; } + protected override Stream CreateCompressionStream( Stream outputStream, CompressionLevel compressionLevel) diff --git a/src/PSCompression/Commands/ConvertToDeflateStringCommand.cs b/src/PSCompression/Commands/ConvertToDeflateStringCommand.cs index a9cf75c..2f7808e 100644 --- a/src/PSCompression/Commands/ConvertToDeflateStringCommand.cs +++ b/src/PSCompression/Commands/ConvertToDeflateStringCommand.cs @@ -5,7 +5,7 @@ namespace PSCompression.Commands; [Cmdlet(VerbsData.ConvertTo, "DeflateString")] -[OutputType(typeof(string))] +[OutputType(typeof(byte[]), typeof(string))] [Alias("todeflatestring")] public sealed class ConvertToDeflateStringCommand : CommandToCompressedStringBase { From 48c929c53424db49e820ee4fe035e616dbfb3df9 Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Fri, 18 Apr 2025 12:47:48 -0300 Subject: [PATCH 09/53] adds base class CommandToCompressedFileBase to use in all compression to file derivates --- .../CommandToCompressedFileBase.cs | 137 ++++++++++++++++++ .../Commands/CompressGzipArchiveCommand.cs | 124 +--------------- 2 files changed, 143 insertions(+), 118 deletions(-) create mode 100644 src/PSCompression/CommandToCompressedFileBase.cs diff --git a/src/PSCompression/CommandToCompressedFileBase.cs b/src/PSCompression/CommandToCompressedFileBase.cs new file mode 100644 index 0000000..c23f74e --- /dev/null +++ b/src/PSCompression/CommandToCompressedFileBase.cs @@ -0,0 +1,137 @@ +using System; +using System.IO; +using System.IO.Compression; +using System.Management.Automation; +using PSCompression.Extensions; +using PSCompression.Exceptions; +using System.ComponentModel; + +namespace PSCompression; + +[EditorBrowsable(EditorBrowsableState.Never)] +public abstract class CommandToCompressedFileBase : CommandWithPathBase, IDisposable +{ + protected abstract string FileExtension { get; } + + private FileStream? _destination; + + private Stream? _compressStream; + + private FileMode Mode + { + get => (Update.IsPresent, Force.IsPresent) switch + { + (true, _) => FileMode.Append, + (_, true) => FileMode.Create, + _ => FileMode.CreateNew + }; + } + + [Parameter( + ParameterSetName = "InputBytes", + Mandatory = true, + ValueFromPipeline = true)] + public byte[]? InputBytes { get; set; } + + [Parameter(Mandatory = true, Position = 1)] + [Alias("DestinationPath")] + public string Destination { get; set; } = null!; + + [Parameter] + public virtual CompressionLevel CompressionLevel { get; set; } = CompressionLevel.Optimal; + + [Parameter] + public SwitchParameter Update { get; set; } + + [Parameter] + public SwitchParameter Force { get; set; } + + [Parameter] + public SwitchParameter PassThru { get; set; } + + protected abstract Stream CreateCompressionStream( + Stream destinationStream, + CompressionLevel compressionLevel); + + protected override void BeginProcessing() + { + Destination = ResolvePath(Destination).AddExtensionIfMissing(FileExtension); + + try + { + string parent = Destination.GetParent(); + + if (!Directory.Exists(parent)) + { + Directory.CreateDirectory(parent); + } + + _destination = File.Open(Destination, Mode); + } + catch (Exception exception) + { + ThrowTerminatingError(exception.ToStreamOpenError(Destination)); + } + } + + protected override void ProcessRecord() + { + Dbg.Assert(_destination is not null); + + if (InputBytes is not null) + { + try + { + _destination.Write(InputBytes, 0, InputBytes.Length); + } + catch (Exception exception) + { + WriteError(exception.ToWriteError(InputBytes)); + } + + return; + } + + _compressStream ??= CreateCompressionStream(_destination, CompressionLevel); + + foreach (string path in EnumerateResolvedPaths()) + { + if (!path.IsArchive()) + { + WriteError(ExceptionHelper.NotArchivePath( + path, + IsLiteral ? nameof(LiteralPath) : nameof(Path))); + + continue; + } + + try + { + using FileStream stream = File.OpenRead(path); + stream.CopyTo(_compressStream); + } + catch (Exception exception) + { + WriteError(exception.ToWriteError(path)); + } + } + } + + protected override void EndProcessing() + { + _compressStream?.Dispose(); + _destination?.Dispose(); + + if (PassThru.IsPresent && _destination is not null) + { + WriteObject(new FileInfo(_destination.Name)); + } + } + + public void Dispose() + { + _compressStream?.Dispose(); + _destination?.Dispose(); + GC.SuppressFinalize(this); + } +} diff --git a/src/PSCompression/Commands/CompressGzipArchiveCommand.cs b/src/PSCompression/Commands/CompressGzipArchiveCommand.cs index 156f336..8f48a01 100644 --- a/src/PSCompression/Commands/CompressGzipArchiveCommand.cs +++ b/src/PSCompression/Commands/CompressGzipArchiveCommand.cs @@ -1,132 +1,20 @@ -using System; using System.IO; using System.IO.Compression; using System.Management.Automation; -using PSCompression.Extensions; -using PSCompression.Exceptions; namespace PSCompression.Commands; [Cmdlet(VerbsData.Compress, "GzipArchive", DefaultParameterSetName = "Path")] [OutputType(typeof(FileInfo))] [Alias("togzipfile")] -public sealed class CompressGzipArchive : CommandWithPathBase, IDisposable +public sealed class CompressGzipArchive : CommandToCompressedFileBase { - private FileStream? _destination; + protected override string FileExtension => ".gz"; - private GZipStream? _gzip; - - private FileMode Mode - { - get => (Update.IsPresent, Force.IsPresent) switch - { - (true, _) => FileMode.Append, - (_, true) => FileMode.Create, - _ => FileMode.CreateNew - }; - } - - [Parameter( - ParameterSetName = "InputBytes", - Mandatory = true, - ValueFromPipeline = true)] - public byte[]? InputBytes { get; set; } - - [Parameter(Mandatory = true, Position = 1)] - [Alias("DestinationPath")] - public string Destination { get; set; } = null!; - - [Parameter] - public CompressionLevel CompressionLevel { get; set; } = CompressionLevel.Optimal; - - [Parameter] - public SwitchParameter Update { get; set; } - - [Parameter] - public SwitchParameter Force { get; set; } - - [Parameter] - public SwitchParameter PassThru { get; set; } - - protected override void BeginProcessing() - { - Destination = ResolvePath(Destination).AddExtensionIfMissing(".gz"); - - try - { - string parent = Destination.GetParent(); - - if (!Directory.Exists(parent)) - { - Directory.CreateDirectory(parent); - } - - _destination = File.Open(Destination, Mode); - } - catch (Exception exception) - { - ThrowTerminatingError(exception.ToStreamOpenError(Destination)); - } - } - - protected override void ProcessRecord() - { - Dbg.Assert(_destination is not null); - - if (InputBytes is not null) - { - try - { - _destination.Write(InputBytes, 0, InputBytes.Length); - } - catch (Exception exception) - { - WriteError(exception.ToWriteError(InputBytes)); - } - - return; - } - - _gzip ??= new GZipStream(_destination, CompressionLevel); - - foreach (string path in EnumerateResolvedPaths()) - { - if (!path.IsArchive()) - { - WriteError(ExceptionHelper.NotArchivePath( - path, - IsLiteral ? nameof(LiteralPath) : nameof(Path))); - - continue; - } - - try - { - using FileStream stream = File.OpenRead(path); - stream.CopyTo(_gzip); - } - catch (Exception exception) - { - WriteError(exception.ToWriteError(path)); - } - } - } - - protected override void EndProcessing() - { - _gzip?.Dispose(); - _destination?.Dispose(); - - if (PassThru.IsPresent && _destination is not null) - { - WriteObject(new FileInfo(_destination.Name)); - } - } - - public void Dispose() + protected override Stream CreateCompressionStream( + Stream destinationStream, + CompressionLevel compressionLevel) { - _gzip?.Dispose(); - _destination?.Dispose(); - GC.SuppressFinalize(this); + return new GZipStream(destinationStream, compressionLevel); } } From 60745eaec5801915ec3113bbe118615bd88d1fff Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Fri, 18 Apr 2025 12:49:29 -0300 Subject: [PATCH 10/53] adds base class CommandToCompressedFileBase to use in all compression to file derivatives --- src/PSCompression/CommandToCompressedFileBase.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PSCompression/CommandToCompressedFileBase.cs b/src/PSCompression/CommandToCompressedFileBase.cs index c23f74e..9cf2581 100644 --- a/src/PSCompression/CommandToCompressedFileBase.cs +++ b/src/PSCompression/CommandToCompressedFileBase.cs @@ -11,8 +11,6 @@ namespace PSCompression; [EditorBrowsable(EditorBrowsableState.Never)] public abstract class CommandToCompressedFileBase : CommandWithPathBase, IDisposable { - protected abstract string FileExtension { get; } - private FileStream? _destination; private Stream? _compressStream; @@ -27,6 +25,8 @@ private FileMode Mode }; } + protected abstract string FileExtension { get; } + [Parameter( ParameterSetName = "InputBytes", Mandatory = true, From 448840c5f0f54f5d8b3686089b6e0fe479f58868 Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Fri, 18 Apr 2025 13:04:59 -0300 Subject: [PATCH 11/53] adds warning if CompressionLevel on Brotli commands --- .../Commands/ConvertToBrotliStringCommand.cs | 11 +++++++++++ src/PSCompression/Exceptions/ExceptionHelper.cs | 13 +++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/PSCompression/Commands/ConvertToBrotliStringCommand.cs b/src/PSCompression/Commands/ConvertToBrotliStringCommand.cs index bf15a12..3b484e4 100644 --- a/src/PSCompression/Commands/ConvertToBrotliStringCommand.cs +++ b/src/PSCompression/Commands/ConvertToBrotliStringCommand.cs @@ -2,6 +2,7 @@ using System.IO.Compression; using System.Management.Automation; using Brotli; +using PSCompression.Exceptions; namespace PSCompression.Commands; @@ -13,6 +14,16 @@ public sealed class ConvertToBrotliStringCommand : CommandToCompressedStringBase [Parameter(DontShow = true)] public override CompressionLevel CompressionLevel { get; set; } + protected override void BeginProcessing() + { + this.WriteWarningForIgnoredParameter( + MyInvocation.BoundParameters.ContainsKey(nameof(CompressionLevel)), + nameof(CompressionLevel), + "Brotli.NET"); + + base.BeginProcessing(); + } + protected override Stream CreateCompressionStream( Stream outputStream, CompressionLevel compressionLevel) diff --git a/src/PSCompression/Exceptions/ExceptionHelper.cs b/src/PSCompression/Exceptions/ExceptionHelper.cs index 4e4a57a..3825f4a 100644 --- a/src/PSCompression/Exceptions/ExceptionHelper.cs +++ b/src/PSCompression/Exceptions/ExceptionHelper.cs @@ -122,4 +122,17 @@ internal static void ThrowIfFromStream(this ZipEntryBase entry) "The operation is not supported for entries created from input Stream."); } } + + internal static void WriteWarningForIgnoredParameter( + this PSCmdlet cmdlet, + bool condition, + string parameterName, + string context) + { + if (condition) + { + cmdlet.WriteWarning( + $"The {parameterName} parameter is not supported by {context} and has no effect on this cmdlet."); + } + } } From c90442845bc36ae253104071cd501e312add2fd5 Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Fri, 18 Apr 2025 13:25:41 -0300 Subject: [PATCH 12/53] Brotli has SetQuality method :facepalm: --- .../CommandToCompressedStringBase.cs | 2 +- .../Commands/ConvertToBrotliStringCommand.cs | 24 +++++++++---------- .../Exceptions/ExceptionHelper.cs | 13 ---------- 3 files changed, 12 insertions(+), 27 deletions(-) diff --git a/src/PSCompression/CommandToCompressedStringBase.cs b/src/PSCompression/CommandToCompressedStringBase.cs index 727246a..bb97784 100644 --- a/src/PSCompression/CommandToCompressedStringBase.cs +++ b/src/PSCompression/CommandToCompressedStringBase.cs @@ -29,7 +29,7 @@ public abstract class CommandToCompressedStringBase : PSCmdlet, IDisposable public Encoding Encoding { get; set; } = new UTF8Encoding(); [Parameter] - public virtual CompressionLevel CompressionLevel { get; set; } = CompressionLevel.Optimal; + public CompressionLevel CompressionLevel { get; set; } = CompressionLevel.Optimal; [Parameter] [Alias("Raw")] diff --git a/src/PSCompression/Commands/ConvertToBrotliStringCommand.cs b/src/PSCompression/Commands/ConvertToBrotliStringCommand.cs index 3b484e4..22f71d4 100644 --- a/src/PSCompression/Commands/ConvertToBrotliStringCommand.cs +++ b/src/PSCompression/Commands/ConvertToBrotliStringCommand.cs @@ -1,3 +1,4 @@ +using System; using System.IO; using System.IO.Compression; using System.Management.Automation; @@ -11,23 +12,20 @@ namespace PSCompression.Commands; [Alias("tobrotlistring")] public sealed class ConvertToBrotliStringCommand : CommandToCompressedStringBase { - [Parameter(DontShow = true)] - public override CompressionLevel CompressionLevel { get; set; } - - protected override void BeginProcessing() - { - this.WriteWarningForIgnoredParameter( - MyInvocation.BoundParameters.ContainsKey(nameof(CompressionLevel)), - nameof(CompressionLevel), - "Brotli.NET"); - - base.BeginProcessing(); - } + private uint MapCompressionLevelToBrotliQuality(CompressionLevel compressionLevel) => + compressionLevel switch + { + CompressionLevel.NoCompression => 0, + CompressionLevel.Fastest => 1, + _ => 11 + }; protected override Stream CreateCompressionStream( Stream outputStream, CompressionLevel compressionLevel) { - return new BrotliStream(outputStream, CompressionMode.Compress); + BrotliStream brotli = new(outputStream, CompressionMode.Compress); + brotli.SetQuality(MapCompressionLevelToBrotliQuality(compressionLevel)); + return brotli; } } diff --git a/src/PSCompression/Exceptions/ExceptionHelper.cs b/src/PSCompression/Exceptions/ExceptionHelper.cs index 3825f4a..4e4a57a 100644 --- a/src/PSCompression/Exceptions/ExceptionHelper.cs +++ b/src/PSCompression/Exceptions/ExceptionHelper.cs @@ -122,17 +122,4 @@ internal static void ThrowIfFromStream(this ZipEntryBase entry) "The operation is not supported for entries created from input Stream."); } } - - internal static void WriteWarningForIgnoredParameter( - this PSCmdlet cmdlet, - bool condition, - string parameterName, - string context) - { - if (condition) - { - cmdlet.WriteWarning( - $"The {parameterName} parameter is not supported by {context} and has no effect on this cmdlet."); - } - } } From 9e2371f1c6941c6c179a7ca8e3d41189f91608f3 Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Fri, 18 Apr 2025 13:25:58 -0300 Subject: [PATCH 13/53] Brotli has SetQuality method :facepalm: --- src/PSCompression/Commands/ConvertToBrotliStringCommand.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/PSCompression/Commands/ConvertToBrotliStringCommand.cs b/src/PSCompression/Commands/ConvertToBrotliStringCommand.cs index 22f71d4..b0ae5cf 100644 --- a/src/PSCompression/Commands/ConvertToBrotliStringCommand.cs +++ b/src/PSCompression/Commands/ConvertToBrotliStringCommand.cs @@ -1,9 +1,7 @@ -using System; using System.IO; using System.IO.Compression; using System.Management.Automation; using Brotli; -using PSCompression.Exceptions; namespace PSCompression.Commands; From 08d5f136c5acad9521d1f310436ab26294450b76 Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Fri, 18 Apr 2025 13:54:08 -0300 Subject: [PATCH 14/53] better naming for ext. methods --- .../CommandFromCompressedStringBase.cs | 4 ++-- .../CommandToCompressedStringBase.cs | 4 ++-- .../Commands/ConvertToBrotliStringCommand.cs | 11 ++--------- .../Commands/ExpandGzipArchiveCommand.cs | 4 ++-- .../Extensions/CompressionExtensions.cs | 16 ++++++++++++---- src/PSCompression/ZipContentReader.cs | 4 ++-- 6 files changed, 22 insertions(+), 21 deletions(-) diff --git a/src/PSCompression/CommandFromCompressedStringBase.cs b/src/PSCompression/CommandFromCompressedStringBase.cs index d68d2b7..3e02cad 100644 --- a/src/PSCompression/CommandFromCompressedStringBase.cs +++ b/src/PSCompression/CommandFromCompressedStringBase.cs @@ -35,11 +35,11 @@ private void Decompress( if (Raw) { - reader.ReadToEnd(this); + reader.WriteAllToPipeline(this); return; } - reader.ReadLines(this); + reader.WriteLinesToPipeline(this); } protected abstract Stream CreateDecompressionStream(Stream inputStream); diff --git a/src/PSCompression/CommandToCompressedStringBase.cs b/src/PSCompression/CommandToCompressedStringBase.cs index bb97784..e07bd0f 100644 --- a/src/PSCompression/CommandToCompressedStringBase.cs +++ b/src/PSCompression/CommandToCompressedStringBase.cs @@ -51,11 +51,11 @@ protected override void ProcessRecord() if (NoNewLine.IsPresent) { - _writer.WriteFrom(InputObject); + _writer.WriteContent(InputObject); return; } - _writer.WriteLinesFrom(InputObject); + _writer.WriteLines(InputObject); } catch (Exception exception) { diff --git a/src/PSCompression/Commands/ConvertToBrotliStringCommand.cs b/src/PSCompression/Commands/ConvertToBrotliStringCommand.cs index b0ae5cf..e4985f7 100644 --- a/src/PSCompression/Commands/ConvertToBrotliStringCommand.cs +++ b/src/PSCompression/Commands/ConvertToBrotliStringCommand.cs @@ -2,6 +2,7 @@ using System.IO.Compression; using System.Management.Automation; using Brotli; +using PSCompression.Extensions; namespace PSCompression.Commands; @@ -10,20 +11,12 @@ namespace PSCompression.Commands; [Alias("tobrotlistring")] public sealed class ConvertToBrotliStringCommand : CommandToCompressedStringBase { - private uint MapCompressionLevelToBrotliQuality(CompressionLevel compressionLevel) => - compressionLevel switch - { - CompressionLevel.NoCompression => 0, - CompressionLevel.Fastest => 1, - _ => 11 - }; - protected override Stream CreateCompressionStream( Stream outputStream, CompressionLevel compressionLevel) { BrotliStream brotli = new(outputStream, CompressionMode.Compress); - brotli.SetQuality(MapCompressionLevelToBrotliQuality(compressionLevel)); + brotli.SetQuality(compressionLevel.MapToBrotliQuality()); return brotli; } } diff --git a/src/PSCompression/Commands/ExpandGzipArchiveCommand.cs b/src/PSCompression/Commands/ExpandGzipArchiveCommand.cs index 4ee8876..1156254 100644 --- a/src/PSCompression/Commands/ExpandGzipArchiveCommand.cs +++ b/src/PSCompression/Commands/ExpandGzipArchiveCommand.cs @@ -147,11 +147,11 @@ protected override void ProcessRecord() if (Raw) { - reader.ReadToEnd(this); + reader.WriteAllToPipeline(this); continue; } - reader.ReadLines(this); + reader.WriteLinesToPipeline(this); } catch (Exception _) when (_ is PipelineStoppedException or FlowControlException) { diff --git a/src/PSCompression/Extensions/CompressionExtensions.cs b/src/PSCompression/Extensions/CompressionExtensions.cs index f8a132f..fa684b0 100644 --- a/src/PSCompression/Extensions/CompressionExtensions.cs +++ b/src/PSCompression/Extensions/CompressionExtensions.cs @@ -108,10 +108,10 @@ internal static string ChangePath( internal static string GetDirectoryName(this ZipArchiveEntry entry) => s_reGetDirName.Match(entry.FullName).Value; - internal static void ReadToEnd(this StreamReader reader, PSCmdlet cmdlet) => + internal static void WriteAllToPipeline(this StreamReader reader, PSCmdlet cmdlet) => cmdlet.WriteObject(reader.ReadToEnd()); - internal static void ReadLines(this StreamReader reader, PSCmdlet cmdlet) + internal static void WriteLinesToPipeline(this StreamReader reader, PSCmdlet cmdlet) { string line; while ((line = reader.ReadLine()) is not null) @@ -120,7 +120,7 @@ internal static void ReadLines(this StreamReader reader, PSCmdlet cmdlet) } } - internal static void WriteLinesFrom(this StreamWriter writer, string[] lines) + internal static void WriteLines(this StreamWriter writer, string[] lines) { foreach (string line in lines) { @@ -128,11 +128,19 @@ internal static void WriteLinesFrom(this StreamWriter writer, string[] lines) } } - internal static void WriteFrom(this StreamWriter writer, string[] lines) + internal static void WriteContent(this StreamWriter writer, string[] lines) { foreach (string line in lines) { writer.Write(line); } } + + internal static uint MapToBrotliQuality(this CompressionLevel compressionLevel) => + compressionLevel switch + { + CompressionLevel.NoCompression => 0, + CompressionLevel.Fastest => 1, + _ => 11 + }; } diff --git a/src/PSCompression/ZipContentReader.cs b/src/PSCompression/ZipContentReader.cs index b56110c..2f2a4ec 100644 --- a/src/PSCompression/ZipContentReader.cs +++ b/src/PSCompression/ZipContentReader.cs @@ -45,7 +45,7 @@ internal void StreamLines( { using Stream entryStream = entry.Open(_zip); using StreamReader reader = new(entryStream, encoding); - reader.ReadLines(cmdlet); + reader.WriteLinesToPipeline(cmdlet); } internal void ReadToEnd( @@ -55,6 +55,6 @@ internal void ReadToEnd( { using Stream entryStream = entry.Open(_zip); using StreamReader reader = new(entryStream, encoding); - reader.ReadToEnd(cmdlet); + reader.WriteAllToPipeline(cmdlet); } } From 64704921dc07cbce1fd859406e543735254ab577 Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Fri, 18 Apr 2025 14:20:01 -0300 Subject: [PATCH 15/53] extension method to create compressed brotli stream --- .../Commands/ConvertToBrotliStringCommand.cs | 4 +--- .../Extensions/CompressionExtensions.cs | 14 +++++++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/PSCompression/Commands/ConvertToBrotliStringCommand.cs b/src/PSCompression/Commands/ConvertToBrotliStringCommand.cs index e4985f7..6594762 100644 --- a/src/PSCompression/Commands/ConvertToBrotliStringCommand.cs +++ b/src/PSCompression/Commands/ConvertToBrotliStringCommand.cs @@ -15,8 +15,6 @@ protected override Stream CreateCompressionStream( Stream outputStream, CompressionLevel compressionLevel) { - BrotliStream brotli = new(outputStream, CompressionMode.Compress); - brotli.SetQuality(compressionLevel.MapToBrotliQuality()); - return brotli; + return outputStream.AsBrotliCompressedStream(compressionLevel); } } diff --git a/src/PSCompression/Extensions/CompressionExtensions.cs b/src/PSCompression/Extensions/CompressionExtensions.cs index fa684b0..c1c311e 100644 --- a/src/PSCompression/Extensions/CompressionExtensions.cs +++ b/src/PSCompression/Extensions/CompressionExtensions.cs @@ -3,6 +3,7 @@ using System.IO.Compression; using System.Management.Automation; using System.Text.RegularExpressions; +using Brotli; namespace PSCompression.Extensions; @@ -136,11 +137,18 @@ internal static void WriteContent(this StreamWriter writer, string[] lines) } } - internal static uint MapToBrotliQuality(this CompressionLevel compressionLevel) => - compressionLevel switch + internal static BrotliStream AsBrotliCompressedStream( + this Stream stream, + CompressionLevel compressionLevel) + { + BrotliStream brotli = new(stream, CompressionMode.Compress); + brotli.SetQuality(compressionLevel switch { CompressionLevel.NoCompression => 0, CompressionLevel.Fastest => 1, _ => 11 - }; + }); + + return brotli; + } } From 43616cfe30d46d55955ed654a118bdcd51ee2e19 Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Tue, 17 Jun 2025 12:18:52 -0300 Subject: [PATCH 16/53] huge update --- CHANGELOG.md | 19 ++ build.ps1 | 2 +- docs/en-US/Get-ZipEntryContent.md | 6 +- module/PSCompression.Format.ps1xml | 57 ++++- module/PSCompression.psd1 | 14 +- .../{ => Abstractions}/CommandWithPathBase.cs | 2 +- src/PSCompression/Abstractions/EntryBase.cs | 31 +++ .../Abstractions/ExpandEntryCommandBase.cs | 72 ++++++ .../FromCompressedStringCommandBase.cs} | 6 +- .../Abstractions/GetEntryCommandBase.cs | 168 +++++++++++++ .../GetEntryContentCommandBase.cs | 29 +++ .../Abstractions/TarEntryBase.cs | 47 ++++ .../ToCompressedFileCommandBase.cs | 232 ++++++++++++++++++ .../ToCompressedStringCommandBase.cs} | 4 +- .../{ => Abstractions}/ZipContentOpsBase.cs | 2 +- .../{ => Abstractions}/ZipEntryBase.cs | 67 ++--- src/PSCompression/Algorithm.cs | 11 + src/PSCompression/AlgorithmMappings.cs | 21 ++ src/PSCompression/ArchiveType.cs | 7 + .../CommandToCompressedFileBase.cs | 137 ----------- .../Commands/CompressGzipArchiveCommand.cs | 20 -- .../Commands/CompressTarArchiveCommand.cs | 74 ++++++ .../Commands/CompressZipArchiveCommand.cs | 217 ++-------------- .../ConvertFromBrotliStringCommand.cs | 3 +- .../ConvertFromDeflateStringCommand.cs | 3 +- .../Commands/ConvertFromGzipStringCommand.cs | 3 +- .../Commands/ConvertFromZLibStringCommand.cs | 3 +- .../Commands/ConvertToBrotliStringCommand.cs | 8 +- .../Commands/ConvertToDeflateStringCommand.cs | 7 +- .../Commands/ConvertToGzipStringCommand.cs | 7 +- .../Commands/ConvertToZLibStringCommand.cs | 7 +- .../Commands/ExpandGzipArchiveCommand.cs | 187 -------------- .../Commands/ExpandTarEntryCommand.cs | 13 + .../Commands/ExpandZipEntryCommand.cs | 74 +----- .../Commands/GetTarEntryCommand.cs | 80 ++++++ .../Commands/GetTarEntryContentCommand.cs | 82 +++++++ .../Commands/GetZipEntryCommand.cs | 204 +++------------ .../Commands/GetZipEntryContentCommand.cs | 25 +- .../Commands/NewZipEntryCommand.cs | 24 +- .../Commands/RemoveZipEntryCommand.cs | 1 + .../Commands/RenameZipEntryCommand.cs | 3 +- .../Exceptions/ExceptionHelper.cs | 66 +++-- .../Extensions/CompressionExtensions.cs | 141 ++++++++--- .../Extensions/PathExtensions.cs | 2 - src/PSCompression/OnModuleImportAndRemove.cs | 88 +++++++ src/PSCompression/PSCompression.csproj | 8 +- src/PSCompression/Records.cs | 4 +- src/PSCompression/SortingOps.cs | 14 +- src/PSCompression/TarEntryDirectory.cs | 25 ++ src/PSCompression/TarEntryFile.cs | 71 ++++++ src/PSCompression/ZLibStream.cs | 14 +- src/PSCompression/ZipArchiveCache.cs | 1 + src/PSCompression/ZipContentReader.cs | 3 +- src/PSCompression/ZipContentWriter.cs | 1 + src/PSCompression/ZipEntryCache.cs | 5 +- src/PSCompression/ZipEntryDirectory.cs | 15 +- src/PSCompression/ZipEntryFile.cs | 12 +- src/PSCompression/ZipEntryMoveCache.cs | 5 +- src/PSCompression/ZipEntryType.cs | 2 +- src/PSCompression/internal/_Format.cs | 3 +- tools/InvokeBuild.ps1 | 4 + tools/ProjectBuilder/Pester.cs | 8 +- tools/ProjectBuilder/Project.cs | 12 +- tools/ProjectBuilder/ProjectInfo.cs | 27 +- 64 files changed, 1506 insertions(+), 1004 deletions(-) rename src/PSCompression/{ => Abstractions}/CommandWithPathBase.cs (98%) create mode 100644 src/PSCompression/Abstractions/EntryBase.cs create mode 100644 src/PSCompression/Abstractions/ExpandEntryCommandBase.cs rename src/PSCompression/{CommandFromCompressedStringBase.cs => Abstractions/FromCompressedStringCommandBase.cs} (92%) create mode 100644 src/PSCompression/Abstractions/GetEntryCommandBase.cs create mode 100644 src/PSCompression/Abstractions/GetEntryContentCommandBase.cs create mode 100644 src/PSCompression/Abstractions/TarEntryBase.cs create mode 100644 src/PSCompression/Abstractions/ToCompressedFileCommandBase.cs rename src/PSCompression/{CommandToCompressedStringBase.cs => Abstractions/ToCompressedStringCommandBase.cs} (95%) rename src/PSCompression/{ => Abstractions}/ZipContentOpsBase.cs (93%) rename src/PSCompression/{ => Abstractions}/ZipEntryBase.cs (60%) create mode 100644 src/PSCompression/Algorithm.cs create mode 100644 src/PSCompression/AlgorithmMappings.cs create mode 100644 src/PSCompression/ArchiveType.cs delete mode 100644 src/PSCompression/CommandToCompressedFileBase.cs delete mode 100644 src/PSCompression/Commands/CompressGzipArchiveCommand.cs create mode 100644 src/PSCompression/Commands/CompressTarArchiveCommand.cs delete mode 100644 src/PSCompression/Commands/ExpandGzipArchiveCommand.cs create mode 100644 src/PSCompression/Commands/ExpandTarEntryCommand.cs create mode 100644 src/PSCompression/Commands/GetTarEntryCommand.cs create mode 100644 src/PSCompression/Commands/GetTarEntryContentCommand.cs create mode 100644 src/PSCompression/OnModuleImportAndRemove.cs create mode 100644 src/PSCompression/TarEntryDirectory.cs create mode 100644 src/PSCompression/TarEntryFile.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f42ee3..9decaf0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,24 @@ # CHANGELOG +## 06/17/2025 + +- Added commands supporting several algorithms to compress and decompress strings: + - `ConvertFrom-BrotliString` & `ConvertTo-BrotliString` (using to Brotli.NET) + - `ConvertFrom-DeflateString` & `ConvertTo-DeflateString` (from CLR) + - `ConvertFrom-ZlibString` & `ConvertTo-ZlibString` (custom implementation) +- Added commands for `.tar` entry management with a reduced set of operations compared to `zip` entry management: + - `Get-TarEntry`: Lists entries, serving as the main entry point for `TarEntry` cmdlets. + - `Get-TarEntryContent`: Retrieves the content of a tar entry. + - `Expand-TarEntry`: Extracts a tar entry to a file. +- Added commands to compress files and folders into `.tar` archives and extract `.tar` archives with various compression algorithms: + - `Compress-TarArchive` and `Expand-TarArchive`: Supported compression algorithms include `gz`, `br`, `bz2`, `zst`, `lz`, and `none` (no compression). + +This update was made possible by the following projects. If you find them helpful, please consider starring their repositories: + +- [Brotli.NET](https://github.com/XieJJ99/brotli.net) +- [SharpCompress](https://github.com/adamhathcock/sharpcompress) +- [SharpZipLib](https://github.com/icsharpcode/SharpZipLib) + ## 01/10/2025 - Code improvements. diff --git a/build.ps1 b/build.ps1 index 650b40b..0ecffa6 100644 --- a/build.ps1 +++ b/build.ps1 @@ -38,7 +38,7 @@ if (-not ('ProjectBuilder.ProjectInfo' -as [type])) { } } -$projectInfo = [ProjectBuilder.ProjectInfo]::Create($PSScriptRoot, $Configuration) +$projectInfo = [ProjectBuilder.ProjectInfo]::Create($PSScriptRoot, $Configuration, $PSVersionTable.PSVersion) $projectInfo.GetRequirements() | Import-Module -DisableNameChecking -Force $ErrorActionPreference = $prev diff --git a/docs/en-US/Get-ZipEntryContent.md b/docs/en-US/Get-ZipEntryContent.md index 53cb35c..5a33a51 100644 --- a/docs/en-US/Get-ZipEntryContent.md +++ b/docs/en-US/Get-ZipEntryContent.md @@ -17,7 +17,7 @@ Gets the content of a zip entry. ```powershell Get-ZipEntryContent - -ZipEntry + -Entry [-Encoding ] [-Raw] [] @@ -27,7 +27,7 @@ Get-ZipEntryContent ```powershell Get-ZipEntryContent - -ZipEntry + -Entry [-Raw] [-AsByteStream] [-BufferSize ] @@ -183,7 +183,7 @@ Accept pipeline input: False Accept wildcard characters: False ``` -### -ZipEntry +### -Entry The entry or entries to get the content from. This parameter can be and is meant to be bound from pipeline however can be also used as a named parameter. diff --git a/module/PSCompression.Format.ps1xml b/module/PSCompression.Format.ps1xml index cae0a22..6afc6f1 100644 --- a/module/PSCompression.Format.ps1xml +++ b/module/PSCompression.Format.ps1xml @@ -4,7 +4,7 @@ zipentryview - PSCompression.ZipEntryBase + PSCompression.Abstractions.ZipEntryBase [PSCompression.Internal._Format]::GetDirectoryPath($_) @@ -68,5 +68,60 @@ + + tarentryview + + PSCompression.Abstractions.TarEntryBase + + + [PSCompression.Internal._Format]::GetDirectoryPath($_) + + + + + + + 10 + Left + + + + 26 + Right + + + + 15 + Right + + + + Left + + + + + + + Type + + + [PSCompression.Internal._Format]::GetFormattedDate($_.LastWriteTime) + + + + if ($_ -is [PSCompression.TarEntryFile]) { + [PSCompression.Internal._Format]::GetFormattedLength($_.Length) + } + + + + Name + + + + + + diff --git a/module/PSCompression.psd1 b/module/PSCompression.psd1 index 9b32613..052647e 100644 --- a/module/PSCompression.psd1 +++ b/module/PSCompression.psd1 @@ -11,7 +11,7 @@ RootModule = 'bin/netstandard2.0/PSCompression.dll' # Version number of this module. - ModuleVersion = '2.1.0' + ModuleVersion = '3.0.0' # Supported PSEditions # CompatiblePSEditions = @() @@ -83,8 +83,6 @@ 'Expand-ZipEntry' 'ConvertTo-GzipString' 'ConvertFrom-GzipString' - 'Expand-GzipArchive' - 'Compress-GzipArchive' 'Compress-ZipArchive' 'Rename-ZipEntry' 'ConvertFrom-ZLibString' @@ -93,6 +91,10 @@ 'ConvertTo-DeflateString' 'ConvertFrom-BrotliString' 'ConvertTo-BrotliString' + 'Compress-TarArchive' + 'Get-TarEntry' + 'Get-TarEntryContent' + 'Expand-TarEntry' ) # Variables to export from this module @@ -100,11 +102,8 @@ # 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 = @( - 'togzipfile' - 'fromgzipfile' 'togzipstring' 'fromgzipstring' - 'zip' 'zipcompress' 'fromzlibstring' 'tozlibstring' @@ -114,6 +113,9 @@ 'tobrotlistring' 'zipge' 'zipgc' + 'tarcompress' + 'targe' + 'targc' ) # DSC resources to export from this module diff --git a/src/PSCompression/CommandWithPathBase.cs b/src/PSCompression/Abstractions/CommandWithPathBase.cs similarity index 98% rename from src/PSCompression/CommandWithPathBase.cs rename to src/PSCompression/Abstractions/CommandWithPathBase.cs index a5bc887..bb9e292 100644 --- a/src/PSCompression/CommandWithPathBase.cs +++ b/src/PSCompression/Abstractions/CommandWithPathBase.cs @@ -6,7 +6,7 @@ using PSCompression.Exceptions; using PSCompression.Extensions; -namespace PSCompression; +namespace PSCompression.Abstractions; [EditorBrowsable(EditorBrowsableState.Never)] public abstract class CommandWithPathBase : PSCmdlet diff --git a/src/PSCompression/Abstractions/EntryBase.cs b/src/PSCompression/Abstractions/EntryBase.cs new file mode 100644 index 0000000..78847fe --- /dev/null +++ b/src/PSCompression/Abstractions/EntryBase.cs @@ -0,0 +1,31 @@ +using System; +using System.IO; + +namespace PSCompression.Abstractions; + +public abstract class EntryBase(string source) +{ + protected Stream? _stream; + + protected string? _formatDirectoryPath; + + internal string? FormatDirectoryPath { get => _formatDirectoryPath ??= GetFormatDirectoryPath(); } + + internal bool FromStream { get => _stream is not null; } + + public string Source { get; } = source; + + public abstract string Name { get; protected set; } + + public abstract string RelativePath { get; } + + public abstract DateTime LastWriteTime { get; } + + public abstract long Length { get; internal set; } + + public abstract EntryType Type { get; } + + protected abstract string GetFormatDirectoryPath(); + + public override string ToString() => RelativePath; +} diff --git a/src/PSCompression/Abstractions/ExpandEntryCommandBase.cs b/src/PSCompression/Abstractions/ExpandEntryCommandBase.cs new file mode 100644 index 0000000..3399491 --- /dev/null +++ b/src/PSCompression/Abstractions/ExpandEntryCommandBase.cs @@ -0,0 +1,72 @@ +using System; +using System.IO; +using System.Management.Automation; +using PSCompression.Extensions; +using PSCompression.Exceptions; +using System.ComponentModel; + +namespace PSCompression.Abstractions; + +[EditorBrowsable(EditorBrowsableState.Never)] +public abstract class ExpandEntryCommandBase : PSCmdlet + where T : EntryBase +{ + [Parameter(Mandatory = true, ValueFromPipeline = true)] + public T[] InputObject { get; set; } = null!; + + [Parameter(Position = 0)] + [ValidateNotNullOrEmpty] + public string? Destination { get; set; } + + [Parameter] + public SwitchParameter Force { get; set; } + + [Parameter] + public SwitchParameter PassThru { get; set; } + + protected override void BeginProcessing() + { + Destination = Destination is null + ? SessionState.Path.CurrentFileSystemLocation.Path + : Destination.ResolvePath(this); + + if (File.Exists(Destination)) + { + ThrowTerminatingError(ExceptionHelper.NotDirectoryPath( + Destination, nameof(Destination))); + } + + if (!Directory.Exists(Destination)) + { + Directory.CreateDirectory(Destination); + } + } + + protected override void ProcessRecord() + { + Dbg.Assert(Destination is not null); + + foreach (T entry in InputObject) + { + try + { + FileSystemInfo info = Extract(entry); + + if (PassThru) + { + WriteObject(info); + } + } + catch (Exception _) when (_ is PipelineStoppedException or FlowControlException) + { + throw; + } + catch (Exception exception) + { + WriteError(exception.ToExtractEntryError(entry)); + } + } + } + + protected abstract FileSystemInfo Extract(T entry); +} diff --git a/src/PSCompression/CommandFromCompressedStringBase.cs b/src/PSCompression/Abstractions/FromCompressedStringCommandBase.cs similarity index 92% rename from src/PSCompression/CommandFromCompressedStringBase.cs rename to src/PSCompression/Abstractions/FromCompressedStringCommandBase.cs index 3e02cad..14fb148 100644 --- a/src/PSCompression/CommandFromCompressedStringBase.cs +++ b/src/PSCompression/Abstractions/FromCompressedStringCommandBase.cs @@ -6,10 +6,10 @@ using PSCompression.Exceptions; using PSCompression.Extensions; -namespace PSCompression; +namespace PSCompression.Abstractions; [EditorBrowsable(EditorBrowsableState.Never)] -public abstract class CommandFromCompressedStringBase : PSCmdlet +public abstract class FromCompressedStringCommandBase : PSCmdlet { protected delegate Stream DecompressionStreamFactory(Stream inputStream); @@ -35,7 +35,7 @@ private void Decompress( if (Raw) { - reader.WriteAllToPipeline(this); + reader.WriteAllTextToPipeline(this); return; } diff --git a/src/PSCompression/Abstractions/GetEntryCommandBase.cs b/src/PSCompression/Abstractions/GetEntryCommandBase.cs new file mode 100644 index 0000000..0fa484f --- /dev/null +++ b/src/PSCompression/Abstractions/GetEntryCommandBase.cs @@ -0,0 +1,168 @@ +using System.Linq; +using System.Management.Automation; +using System.IO; +using System.ComponentModel; +using System.Collections.Generic; +using System; +using PSCompression.Exceptions; +using ICSharpCode.SharpZipLib.Tar; +using ZstdSharp; +using Brotli; + +namespace PSCompression.Abstractions; + +[EditorBrowsable(EditorBrowsableState.Never)] +public abstract class GetEntryCommandBase : CommandWithPathBase +{ + internal abstract ArchiveType ArchiveType { get; } + + [Parameter( + ParameterSetName = "Stream", + Position = 0, + Mandatory = true, + ValueFromPipeline = true, + ValueFromPipelineByPropertyName = true)] + [Alias("RawContentStream")] + public Stream? InputStream { get; set; } + + private WildcardPattern[]? _includePatterns; + + private WildcardPattern[]? _excludePatterns; + + [Parameter] + public EntryType? Type { get; set; } + + [Parameter] + [SupportsWildcards] + public string[]? Include { get; set; } + + [Parameter] + [SupportsWildcards] + public string[]? Exclude { get; set; } + + protected override void BeginProcessing() + { + if (Exclude is null && Include is null) + { + return; + } + + const WildcardOptions options = WildcardOptions.Compiled + | WildcardOptions.CultureInvariant + | WildcardOptions.IgnoreCase; + + if (Exclude is not null) + { + _excludePatterns = [.. Exclude.Select(e => new WildcardPattern(e, options))]; + } + + if (Include is not null) + { + _includePatterns = [.. Include.Select(e => new WildcardPattern(e, options))]; + } + } + + protected override void ProcessRecord() + { + if (InputStream is not null) + { + try + { + if (InputStream.CanSeek) + { + InputStream.Seek(0, SeekOrigin.Begin); + } + + WriteObject( + GetEntriesFromStream(InputStream).ToEntrySort(), + enumerateCollection: true); + + return; + } + catch (Exception _) when (_ is PipelineStoppedException or FlowControlException) + { + throw; + } + catch (Exception exception) when (IsInvalidArchive(exception)) + { + ThrowTerminatingError(exception.ToInvalidArchive(ArchiveType, isStream: true)); + } + catch (Exception exception) + { + WriteError(exception.ToOpenError("InputStream")); + } + } + + foreach (string path in EnumerateResolvedPaths()) + { + if (path.WriteErrorIfNotArchive(IsLiteral ? nameof(LiteralPath) : nameof(Path), this)) + { + continue; + } + + try + { + WriteObject( + GetEntriesFromFile(path).ToEntrySort(), + enumerateCollection: true); + } + catch (Exception _) when (_ is PipelineStoppedException or FlowControlException) + { + throw; + } + catch (Exception exception) when (IsInvalidArchive(exception)) + { + ThrowTerminatingError(exception.ToInvalidArchive(ArchiveType)); + } + catch (Exception exception) + { + WriteError(exception.ToOpenError(path)); + } + } + } + + protected abstract IEnumerable GetEntriesFromFile(string path); + + protected abstract IEnumerable GetEntriesFromStream(Stream stream); + + private static bool MatchAny( + string name, + WildcardPattern[] patterns) + { + foreach (WildcardPattern pattern in patterns) + { + if (pattern.IsMatch(name)) + { + return true; + } + } + + return false; + } + + protected bool ShouldInclude(string name) + { + if (_includePatterns is null) + { + return true; + } + + return MatchAny(name, _includePatterns); + } + + protected bool ShouldExclude(string name) + { + if (_excludePatterns is null) + { + return false; + } + + return MatchAny(name, _excludePatterns); + } + + protected bool ShouldSkipEntry(bool isDirectory) => + isDirectory && Type is EntryType.Archive || !isDirectory && Type is EntryType.Directory; + + private bool IsInvalidArchive(Exception exception) => + exception is InvalidDataException or TarException or ZstdException or IOException or BrotliDecodeException; +} diff --git a/src/PSCompression/Abstractions/GetEntryContentCommandBase.cs b/src/PSCompression/Abstractions/GetEntryContentCommandBase.cs new file mode 100644 index 0000000..0427221 --- /dev/null +++ b/src/PSCompression/Abstractions/GetEntryContentCommandBase.cs @@ -0,0 +1,29 @@ +using System.ComponentModel; +using System.Management.Automation; +using System.Text; + +namespace PSCompression.Abstractions; + +[EditorBrowsable(EditorBrowsableState.Never)] +public abstract class GetEntryContentCommandBase : PSCmdlet + where T : EntryBase +{ + [Parameter(Mandatory = true, ValueFromPipeline = true)] + public T[] Entry { get; set; } = null!; + + [Parameter(ParameterSetName = "Stream")] + [ArgumentCompleter(typeof(EncodingCompleter))] + [EncodingTransformation] + [ValidateNotNullOrEmpty] + public Encoding Encoding { get; set; } = new UTF8Encoding(); + + [Parameter] + public SwitchParameter Raw { get; set; } + + [Parameter(ParameterSetName = "Bytes")] + public SwitchParameter AsByteStream { get; set; } + + [Parameter(ParameterSetName = "Bytes")] + [ValidateNotNullOrEmpty] + public int BufferSize { get; set; } = 128_000; +} diff --git a/src/PSCompression/Abstractions/TarEntryBase.cs b/src/PSCompression/Abstractions/TarEntryBase.cs new file mode 100644 index 0000000..628a8d8 --- /dev/null +++ b/src/PSCompression/Abstractions/TarEntryBase.cs @@ -0,0 +1,47 @@ +using System; +using System.IO; +using ICSharpCode.SharpZipLib.Tar; + +namespace PSCompression.Abstractions; + +public abstract class TarEntryBase(TarEntry entry, string source) : EntryBase(source) +{ + public override string Name { get; protected set; } = Path.GetFileName(entry.Name); + + public override string RelativePath { get; } = entry.Name; + + public override DateTime LastWriteTime { get; } = entry.ModTime; + + public override long Length { get; internal set; } = entry.Size; + + protected TarEntryBase(TarEntry entry, Stream? stream) + : this(entry, $"InputStream.{Guid.NewGuid()}") + { + _stream = stream; + } + + internal FileSystemInfo ExtractTo( + string destination, + bool overwrite) + { + destination = Path.GetFullPath(Path.Combine(destination, RelativePath)); + if (this is not TarEntryFile entryFile) + { + Directory.CreateDirectory(destination); + return new DirectoryInfo(destination); + } + + string parent = Path.GetDirectoryName(destination); + if (!Directory.Exists(parent)) + { + Directory.CreateDirectory(parent); + } + + using FileStream destStream = File.Open( + destination, + overwrite ? FileMode.Create : FileMode.CreateNew); + + entryFile.GetContentStream(destStream); + return new FileInfo(destination); + } +} diff --git a/src/PSCompression/Abstractions/ToCompressedFileCommandBase.cs b/src/PSCompression/Abstractions/ToCompressedFileCommandBase.cs new file mode 100644 index 0000000..99dbd83 --- /dev/null +++ b/src/PSCompression/Abstractions/ToCompressedFileCommandBase.cs @@ -0,0 +1,232 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Management.Automation; +using PSCompression.Extensions; +using PSCompression.Exceptions; +using System.ComponentModel; + +namespace PSCompression.Abstractions; + +[EditorBrowsable(EditorBrowsableState.Never)] +public abstract class ToCompressedFileCommandBase : CommandWithPathBase, IDisposable + where T : IDisposable +{ + private T? _archive; + + private FileStream? _destination; + + private WildcardPattern[]? _excludePatterns; + + private readonly Queue _queue = new(); + + private readonly HashSet _processed = []; + + private bool _disposed; + + private FileMode FileMode + { + get => (Update.IsPresent, Force.IsPresent) switch + { + (true, _) => FileMode.OpenOrCreate, + (_, true) => FileMode.Create, + _ => FileMode.CreateNew + }; + } + + protected abstract string FileExtension { get; } + + [Parameter(Mandatory = true, Position = 1)] + [Alias("DestinationPath")] + public string Destination { get; set; } = null!; + + [Parameter] + public CompressionLevel CompressionLevel { get; set; } = CompressionLevel.Optimal; + + [Parameter] + public virtual SwitchParameter Update { get; set; } + + [Parameter] + public SwitchParameter Force { get; set; } + + [Parameter] + public SwitchParameter PassThru { get; set; } + + [Parameter] + [SupportsWildcards] + [ValidateNotNullOrEmpty] + public string[]? Exclude { get; set; } + + protected abstract T CreateCompressionStream(Stream outputStream); + + protected override void BeginProcessing() + { + Destination = ResolvePath(Destination).AddExtensionIfMissing(FileExtension); + + try + { + string parent = Destination.GetParent(); + + if (!Directory.Exists(parent)) + { + Directory.CreateDirectory(parent); + } + + _destination = File.Open(Destination, FileMode); + _archive = CreateCompressionStream(_destination); + } + catch (Exception exception) + { + ThrowTerminatingError(exception.ToStreamOpenError(Destination)); + } + + if (Exclude is not null) + { + const WildcardOptions options = WildcardOptions.Compiled + | WildcardOptions.CultureInvariant + | WildcardOptions.IgnoreCase; + + _excludePatterns = [.. Exclude.Select(pattern => new WildcardPattern(pattern, options))]; + } + } + + protected override void ProcessRecord() + { + Dbg.Assert(_archive is not null); + _queue.Clear(); + + foreach (string path in EnumerateResolvedPaths()) + { + if (ShouldExclude(path) || ItemIsDestination(path, Destination)) + { + continue; + } + + if (Directory.Exists(path)) + { + Traverse(new DirectoryInfo(path), _archive); + continue; + } + + FileInfo file = new(path); + CreateOrUpdateFileEntry(_archive, file, file.Name); + } + } + + private void Traverse(DirectoryInfo dir, T archive) + { + _queue.Enqueue(dir); + IEnumerable enumerator; + int length = dir.Parent.FullName.Length + 1; + + while (_queue.Count > 0) + { + DirectoryInfo current = _queue.Dequeue(); + CreateDirectoryEntry(archive, current, current.RelativeTo(length)); + + try + { + enumerator = current.EnumerateFileSystemInfos(); + } + catch (Exception exception) + { + WriteError(exception.ToEnumerationError(current)); + continue; + } + + foreach (FileSystemInfo item in enumerator) + { + if (ShouldExclude(item.FullName)) + { + continue; + } + + if (item is DirectoryInfo directory) + { + _queue.Enqueue(directory); + continue; + } + + FileInfo file = (FileInfo)item; + if (ItemIsDestination(file.FullName, Destination)) + { + continue; + } + + CreateOrUpdateFileEntry(archive, file, file.RelativeTo(length)); + } + } + } + + protected abstract void CreateDirectoryEntry( + T archive, + DirectoryInfo directory, + string path); + + protected abstract void CreateOrUpdateFileEntry( + T archive, + FileInfo file, + string path); + + protected static FileStream OpenFileStream(FileInfo file) => + file.Open( + mode: FileMode.Open, + access: FileAccess.Read, + share: FileShare.ReadWrite | FileShare.Delete); + + private static bool ItemIsDestination(string source, string destination) => + source.Equals(destination, StringComparison.InvariantCultureIgnoreCase); + + private bool ShouldExclude(string path) + { + if (!_processed.Add(path)) + { + return true; + } + + if (_excludePatterns is null) + { + return false; + } + + foreach (WildcardPattern pattern in _excludePatterns) + { + if (pattern.IsMatch(path)) + { + return true; + } + } + + return false; + } + + protected override void EndProcessing() + { + _archive?.Dispose(); + _destination?.Dispose(); + + if (PassThru.IsPresent && _destination is not null) + { + WriteObject(new FileInfo(_destination.Name)); + } + } + + protected virtual void Dispose(bool disposing) + { + if (disposing && !_disposed) + { + _archive?.Dispose(); + _destination?.Dispose(); + } + + _disposed = true; + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } +} diff --git a/src/PSCompression/CommandToCompressedStringBase.cs b/src/PSCompression/Abstractions/ToCompressedStringCommandBase.cs similarity index 95% rename from src/PSCompression/CommandToCompressedStringBase.cs rename to src/PSCompression/Abstractions/ToCompressedStringCommandBase.cs index e07bd0f..be3d91e 100644 --- a/src/PSCompression/CommandToCompressedStringBase.cs +++ b/src/PSCompression/Abstractions/ToCompressedStringCommandBase.cs @@ -7,10 +7,10 @@ using PSCompression.Exceptions; using PSCompression.Extensions; -namespace PSCompression; +namespace PSCompression.Abstractions; [EditorBrowsable(EditorBrowsableState.Never)] -public abstract class CommandToCompressedStringBase : PSCmdlet, IDisposable +public abstract class ToCompressedStringCommandBase : PSCmdlet, IDisposable { private StreamWriter? _writer; diff --git a/src/PSCompression/ZipContentOpsBase.cs b/src/PSCompression/Abstractions/ZipContentOpsBase.cs similarity index 93% rename from src/PSCompression/ZipContentOpsBase.cs rename to src/PSCompression/Abstractions/ZipContentOpsBase.cs index 1a726c3..e8918a5 100644 --- a/src/PSCompression/ZipContentOpsBase.cs +++ b/src/PSCompression/Abstractions/ZipContentOpsBase.cs @@ -1,7 +1,7 @@ using System; using System.IO.Compression; -namespace PSCompression; +namespace PSCompression.Abstractions; internal abstract class ZipContentOpsBase(ZipArchive zip) : IDisposable { diff --git a/src/PSCompression/ZipEntryBase.cs b/src/PSCompression/Abstractions/ZipEntryBase.cs similarity index 60% rename from src/PSCompression/ZipEntryBase.cs rename to src/PSCompression/Abstractions/ZipEntryBase.cs index 06072b6..a637edd 100644 --- a/src/PSCompression/ZipEntryBase.cs +++ b/src/PSCompression/Abstractions/ZipEntryBase.cs @@ -1,42 +1,29 @@ using System; using System.IO; using System.IO.Compression; -using PSCompression.Extensions; using PSCompression.Exceptions; -namespace PSCompression; +namespace PSCompression.Abstractions; -public abstract class ZipEntryBase(ZipArchiveEntry entry, string source) +public abstract class ZipEntryBase(ZipArchiveEntry entry, string source) : EntryBase(source) { - protected string? _formatDirectoryPath; + public override string Name { get; protected set; } = entry.Name; - protected Stream? _stream; + public override string RelativePath { get; } = entry.FullName; - internal bool FromStream { get => _stream is not null; } + public override DateTime LastWriteTime { get; } = entry.LastWriteTime.LocalDateTime; - internal abstract string FormatDirectoryPath { get; } - - public string Source { get; } = source; - - public string Name { get; protected set; } = entry.Name; - - public string RelativePath { get; } = entry.FullName; - - public DateTime LastWriteTime { get; } = entry.LastWriteTime.LocalDateTime; - - public long Length { get; internal set; } = entry.Length; + public override long Length { get; internal set; } = entry.Length; public long CompressedLength { get; internal set; } = entry.CompressedLength; - public abstract ZipEntryType Type { get; } - protected ZipEntryBase(ZipArchiveEntry entry, Stream? stream) : this(entry, $"InputStream.{Guid.NewGuid()}") { _stream = stream; } - public ZipArchive OpenRead() => _stream is null + public ZipArchive OpenRead() => FromStream ? ZipFile.OpenRead(Source) : new ZipArchive(_stream); @@ -97,31 +84,45 @@ internal static string Move( { sourceStream.CopyTo(destinationStream); } - sourceEntry.Delete(); + sourceEntry.Delete(); return destination; } internal ZipArchive OpenZip(ZipArchiveMode mode) => - _stream is null - ? ZipFile.Open(Source, mode) - : new ZipArchive(_stream, mode, true); + FromStream + ? new ZipArchive(_stream, mode, true) + : ZipFile.Open(Source, mode); public FileSystemInfo ExtractTo(string destination, bool overwrite) { - using ZipArchive zip = _stream is null - ? ZipFile.OpenRead(Source) - : new ZipArchive(_stream); + using ZipArchive zip = _stream is not null + ? new ZipArchive(_stream, ZipArchiveMode.Read, leaveOpen: true) + : ZipFile.OpenRead(Source); - (string path, bool isArchive) = this.ExtractTo(zip, destination, overwrite); + return ExtractTo(destination, overwrite, zip); + } + + internal FileSystemInfo ExtractTo( + string destination, + bool overwrite, + ZipArchive zip) + { + destination = Path.GetFullPath(Path.Combine(destination, RelativePath)); + if (Type is EntryType.Directory) + { + Directory.CreateDirectory(destination); + return new DirectoryInfo(destination); + } - if (isArchive) + string parent = Path.GetDirectoryName(destination); + if (!Directory.Exists(parent)) { - return new FileInfo(path); + Directory.CreateDirectory(parent); } - return new DirectoryInfo(path); + ZipArchiveEntry entry = zip.GetEntry(RelativePath); + entry.ExtractToFile(destination, overwrite); + return new FileInfo(destination); } - - public override string ToString() => RelativePath; } diff --git a/src/PSCompression/Algorithm.cs b/src/PSCompression/Algorithm.cs new file mode 100644 index 0000000..3ada823 --- /dev/null +++ b/src/PSCompression/Algorithm.cs @@ -0,0 +1,11 @@ +namespace PSCompression; + +public enum Algorithm +{ + gz, + br, + bz2, + zst, + lz, + none +} diff --git a/src/PSCompression/AlgorithmMappings.cs b/src/PSCompression/AlgorithmMappings.cs new file mode 100644 index 0000000..d815a95 --- /dev/null +++ b/src/PSCompression/AlgorithmMappings.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.IO; + +namespace PSCompression; + +internal static class AlgorithmMappings +{ + private static readonly Dictionary _mappings = new( + StringComparer.InvariantCultureIgnoreCase) + { + [".gz"] = Algorithm.gz, + [".br"] = Algorithm.br, + [".bz2"] = Algorithm.bz2, + [".zst"] = Algorithm.zst, + [".lz"] = Algorithm.lz, + [".tar"] = Algorithm.none + }; + + internal static Algorithm Parse(string path) => _mappings[Path.GetExtension(path)]; +} diff --git a/src/PSCompression/ArchiveType.cs b/src/PSCompression/ArchiveType.cs new file mode 100644 index 0000000..1f0d5ba --- /dev/null +++ b/src/PSCompression/ArchiveType.cs @@ -0,0 +1,7 @@ +namespace PSCompression; + +internal enum ArchiveType +{ + zip, + tar +} diff --git a/src/PSCompression/CommandToCompressedFileBase.cs b/src/PSCompression/CommandToCompressedFileBase.cs deleted file mode 100644 index 9cf2581..0000000 --- a/src/PSCompression/CommandToCompressedFileBase.cs +++ /dev/null @@ -1,137 +0,0 @@ -using System; -using System.IO; -using System.IO.Compression; -using System.Management.Automation; -using PSCompression.Extensions; -using PSCompression.Exceptions; -using System.ComponentModel; - -namespace PSCompression; - -[EditorBrowsable(EditorBrowsableState.Never)] -public abstract class CommandToCompressedFileBase : CommandWithPathBase, IDisposable -{ - private FileStream? _destination; - - private Stream? _compressStream; - - private FileMode Mode - { - get => (Update.IsPresent, Force.IsPresent) switch - { - (true, _) => FileMode.Append, - (_, true) => FileMode.Create, - _ => FileMode.CreateNew - }; - } - - protected abstract string FileExtension { get; } - - [Parameter( - ParameterSetName = "InputBytes", - Mandatory = true, - ValueFromPipeline = true)] - public byte[]? InputBytes { get; set; } - - [Parameter(Mandatory = true, Position = 1)] - [Alias("DestinationPath")] - public string Destination { get; set; } = null!; - - [Parameter] - public virtual CompressionLevel CompressionLevel { get; set; } = CompressionLevel.Optimal; - - [Parameter] - public SwitchParameter Update { get; set; } - - [Parameter] - public SwitchParameter Force { get; set; } - - [Parameter] - public SwitchParameter PassThru { get; set; } - - protected abstract Stream CreateCompressionStream( - Stream destinationStream, - CompressionLevel compressionLevel); - - protected override void BeginProcessing() - { - Destination = ResolvePath(Destination).AddExtensionIfMissing(FileExtension); - - try - { - string parent = Destination.GetParent(); - - if (!Directory.Exists(parent)) - { - Directory.CreateDirectory(parent); - } - - _destination = File.Open(Destination, Mode); - } - catch (Exception exception) - { - ThrowTerminatingError(exception.ToStreamOpenError(Destination)); - } - } - - protected override void ProcessRecord() - { - Dbg.Assert(_destination is not null); - - if (InputBytes is not null) - { - try - { - _destination.Write(InputBytes, 0, InputBytes.Length); - } - catch (Exception exception) - { - WriteError(exception.ToWriteError(InputBytes)); - } - - return; - } - - _compressStream ??= CreateCompressionStream(_destination, CompressionLevel); - - foreach (string path in EnumerateResolvedPaths()) - { - if (!path.IsArchive()) - { - WriteError(ExceptionHelper.NotArchivePath( - path, - IsLiteral ? nameof(LiteralPath) : nameof(Path))); - - continue; - } - - try - { - using FileStream stream = File.OpenRead(path); - stream.CopyTo(_compressStream); - } - catch (Exception exception) - { - WriteError(exception.ToWriteError(path)); - } - } - } - - protected override void EndProcessing() - { - _compressStream?.Dispose(); - _destination?.Dispose(); - - if (PassThru.IsPresent && _destination is not null) - { - WriteObject(new FileInfo(_destination.Name)); - } - } - - public void Dispose() - { - _compressStream?.Dispose(); - _destination?.Dispose(); - GC.SuppressFinalize(this); - } -} diff --git a/src/PSCompression/Commands/CompressGzipArchiveCommand.cs b/src/PSCompression/Commands/CompressGzipArchiveCommand.cs deleted file mode 100644 index 8f48a01..0000000 --- a/src/PSCompression/Commands/CompressGzipArchiveCommand.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.IO; -using System.IO.Compression; -using System.Management.Automation; - -namespace PSCompression.Commands; - -[Cmdlet(VerbsData.Compress, "GzipArchive", DefaultParameterSetName = "Path")] -[OutputType(typeof(FileInfo))] -[Alias("togzipfile")] -public sealed class CompressGzipArchive : CommandToCompressedFileBase -{ - protected override string FileExtension => ".gz"; - - protected override Stream CreateCompressionStream( - Stream destinationStream, - CompressionLevel compressionLevel) - { - return new GZipStream(destinationStream, compressionLevel); - } -} diff --git a/src/PSCompression/Commands/CompressTarArchiveCommand.cs b/src/PSCompression/Commands/CompressTarArchiveCommand.cs new file mode 100644 index 0000000..7c2aafb --- /dev/null +++ b/src/PSCompression/Commands/CompressTarArchiveCommand.cs @@ -0,0 +1,74 @@ +using System; +using System.IO; +using System.Management.Automation; +using System.Text; +using ICSharpCode.SharpZipLib.Tar; +using PSCompression.Abstractions; +using PSCompression.Exceptions; +using PSCompression.Extensions; + +namespace PSCompression.Commands; + +[Cmdlet(VerbsData.Compress, "TarArchive")] +[OutputType(typeof(FileInfo))] +[Alias("tarcompress")] +public sealed class CompressTarArchiveCommand : ToCompressedFileCommandBase +{ + private Stream? _compressionStream; + + [Parameter] + [ValidateNotNull] + public Algorithm Algorithm { get; set; } = Algorithm.gz; + + protected override string FileExtension => Algorithm is Algorithm.none ? ".tar" : $".tar.{Algorithm}"; + + protected override TarOutputStream CreateCompressionStream(Stream outputStream) + { + _compressionStream = Algorithm.ToCompressedStream(outputStream, CompressionLevel); + return new TarOutputStream(_compressionStream, Encoding.UTF8); + } + + protected override void CreateDirectoryEntry( + TarOutputStream archive, + DirectoryInfo directory, + string path) + { + archive.CreateTarEntry( + entryName: path, + modTime: directory.LastWriteTimeUtc, + size: 0); + + archive.CloseEntry(); + } + + protected override void CreateOrUpdateFileEntry( + TarOutputStream archive, + FileInfo file, + string path) + { + archive.CreateTarEntry( + entryName: path, + modTime: file.LastWriteTimeUtc, + size: file.Length); + + try + { + using FileStream fs = OpenFileStream(file); + fs.CopyTo(archive); + } + catch (Exception exception) + { + WriteError(exception.ToStreamOpenError(file.FullName)); + } + finally + { + archive.CloseEntry(); + } + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + _compressionStream?.Dispose(); + } +} diff --git a/src/PSCompression/Commands/CompressZipArchiveCommand.cs b/src/PSCompression/Commands/CompressZipArchiveCommand.cs index 11a5041..9a335fe 100644 --- a/src/PSCompression/Commands/CompressZipArchiveCommand.cs +++ b/src/PSCompression/Commands/CompressZipArchiveCommand.cs @@ -1,27 +1,18 @@ using System; -using System.Collections.Generic; using System.IO; using System.IO.Compression; -using System.Linq; using System.Management.Automation; using PSCompression.Extensions; using PSCompression.Exceptions; +using PSCompression.Abstractions; namespace PSCompression.Commands; [Cmdlet(VerbsData.Compress, "ZipArchive")] [OutputType(typeof(FileInfo))] [Alias("zipcompress")] -public sealed class CompressZipArchiveCommand : CommandWithPathBase, IDisposable +public sealed class CompressZipArchiveCommand : ToCompressedFileCommandBase { - private ZipArchive? _zip; - - private FileStream? _destination; - - private WildcardPattern[]? _excludePatterns; - - private readonly Queue _queue = new(); - private ZipArchiveMode ZipArchiveMode { get => Force.IsPresent || Update.IsPresent @@ -29,156 +20,7 @@ private ZipArchiveMode ZipArchiveMode : ZipArchiveMode.Create; } - private FileMode FileMode - { - get => (Update.IsPresent, Force.IsPresent) switch - { - (true, _) => FileMode.OpenOrCreate, - (_, true) => FileMode.Create, - _ => FileMode.CreateNew - }; - } - - [Parameter(Mandatory = true, Position = 1)] - [Alias("DestinationPath")] - public string Destination { get; set; } = null!; - - [Parameter] - public CompressionLevel CompressionLevel { get; set; } = CompressionLevel.Optimal; - - [Parameter] - public SwitchParameter Update { get; set; } - - [Parameter] - public SwitchParameter Force { get; set; } - - [Parameter] - public SwitchParameter PassThru { get; set; } - - [Parameter] - [SupportsWildcards] - [ValidateNotNullOrEmpty] - public string[]? Exclude { get; set; } - - protected override void BeginProcessing() - { - Destination = ResolvePath(Destination).AddExtensionIfMissing(".zip"); - - try - { - string parent = Destination.GetParent(); - - if (!Directory.Exists(parent)) - { - Directory.CreateDirectory(parent); - } - - _destination = File.Open(Destination, FileMode); - _zip = new ZipArchive(_destination, ZipArchiveMode); - } - catch (Exception exception) - { - ThrowTerminatingError(exception.ToStreamOpenError(Destination)); - } - - if (Exclude is not null) - { - const WildcardOptions options = WildcardOptions.Compiled - | WildcardOptions.CultureInvariant - | WildcardOptions.IgnoreCase; - - _excludePatterns = [.. Exclude.Select(pattern => new WildcardPattern(pattern, options))]; - } - } - - protected override void ProcessRecord() - { - Dbg.Assert(_zip is not null); - _queue.Clear(); - - foreach (string path in EnumerateResolvedPaths()) - { - if (ShouldExclude(_excludePatterns, path)) - { - continue; - } - - if (!path.IsArchive()) - { - Traverse(new DirectoryInfo(path), _zip); - continue; - } - - FileInfo file = new(path); - if (Update.IsPresent && _zip.TryGetEntry(file.Name, out ZipArchiveEntry? entry)) - { - UpdateEntry(file, entry); - continue; - } - - CreateEntry(file, _zip, file.Name); - } - } - - private void Traverse(DirectoryInfo dir, ZipArchive zip) - { - _queue.Enqueue(dir); - IEnumerable enumerator; - int length = dir.Parent.FullName.Length + 1; - - while (_queue.Count > 0) - { - DirectoryInfo current = _queue.Dequeue(); - - string relative = current.RelativeTo(length); - - if (!Update.IsPresent || !zip.TryGetEntry(relative, out _)) - { - zip.CreateEntry(current.RelativeTo(length)); - } - - try - { - enumerator = current.EnumerateFileSystemInfos(); - } - catch (Exception exception) - { - WriteError(exception.ToEnumerationError(current)); - continue; - } - - foreach (FileSystemInfo item in enumerator) - { - if (ShouldExclude(_excludePatterns, item.FullName)) - { - continue; - } - - if (item is DirectoryInfo directory) - { - _queue.Enqueue(directory); - continue; - } - - FileInfo file = (FileInfo)item; - - if (ItemIsDestination(file.FullName, Destination)) - { - continue; - } - - relative = file.RelativeTo(length); - - if (Update.IsPresent && zip.TryGetEntry(relative, out ZipArchiveEntry? entry)) - { - UpdateEntry(file, entry); - continue; - } - - CreateEntry(file, zip, relative); - } - } - } + protected override string FileExtension => ".zip"; private void CreateEntry( FileInfo file, @@ -187,7 +29,7 @@ private void CreateEntry( { try { - using FileStream fileStream = Open(file); + using FileStream fileStream = OpenFileStream(file); using Stream stream = zip .CreateEntry(relativepath, CompressionLevel) .Open(); @@ -200,19 +42,13 @@ private void CreateEntry( } } - private static FileStream Open(FileInfo file) => - file.Open( - mode: FileMode.Open, - access: FileAccess.Read, - share: FileShare.ReadWrite | FileShare.Delete); - private void UpdateEntry( FileInfo file, ZipArchiveEntry entry) { try { - using FileStream fileStream = Open(file); + using FileStream fileStream = OpenFileStream(file); using Stream stream = entry.Open(); stream.SetLength(0); fileStream.CopyTo(stream); @@ -223,44 +59,31 @@ private void UpdateEntry( } } - private bool ItemIsDestination(string source, string destination) => - source.Equals(destination, StringComparison.InvariantCultureIgnoreCase); + protected override ZipArchive CreateCompressionStream(Stream outputStream) => + new(outputStream, ZipArchiveMode); - private static bool ShouldExclude( - WildcardPattern[]? patterns, + protected override void CreateDirectoryEntry( + ZipArchive archive, + DirectoryInfo directory, string path) { - if (patterns is null) - { - return false; - } - - foreach (WildcardPattern pattern in patterns) + if (!Update || !archive.TryGetEntry(path, out _)) { - if (pattern.IsMatch(path)) - { - return true; - } + archive.CreateEntry(path); } - - return false; } - protected override void EndProcessing() + protected override void CreateOrUpdateFileEntry( + ZipArchive archive, + FileInfo file, + string path) { - _zip?.Dispose(); - _destination?.Dispose(); - - if (PassThru.IsPresent && _destination is not null) + if (Update && archive.TryGetEntry(path, out ZipArchiveEntry? entry)) { - WriteObject(new FileInfo(_destination.Name)); + UpdateEntry(file, entry); + return; } - } - public void Dispose() - { - _zip?.Dispose(); - _destination?.Dispose(); - GC.SuppressFinalize(this); + CreateEntry(file, archive, path); } } diff --git a/src/PSCompression/Commands/ConvertFromBrotliStringCommand.cs b/src/PSCompression/Commands/ConvertFromBrotliStringCommand.cs index 6f793c0..2dec69f 100644 --- a/src/PSCompression/Commands/ConvertFromBrotliStringCommand.cs +++ b/src/PSCompression/Commands/ConvertFromBrotliStringCommand.cs @@ -2,13 +2,14 @@ using System.IO.Compression; using System.Management.Automation; using Brotli; +using PSCompression.Abstractions; namespace PSCompression.Commands; [Cmdlet(VerbsData.ConvertFrom, "BrotliString")] [OutputType(typeof(string))] [Alias("frombrotlistring")] -public sealed class ConvertFromBrotliStringCommand : CommandFromCompressedStringBase +public sealed class ConvertFromBrotliStringCommand : FromCompressedStringCommandBase { protected override Stream CreateDecompressionStream(Stream inputStream) => new BrotliStream(inputStream, CompressionMode.Decompress); diff --git a/src/PSCompression/Commands/ConvertFromDeflateStringCommand.cs b/src/PSCompression/Commands/ConvertFromDeflateStringCommand.cs index c02396f..8b4bf8a 100644 --- a/src/PSCompression/Commands/ConvertFromDeflateStringCommand.cs +++ b/src/PSCompression/Commands/ConvertFromDeflateStringCommand.cs @@ -1,13 +1,14 @@ using System.IO; using System.IO.Compression; using System.Management.Automation; +using PSCompression.Abstractions; namespace PSCompression.Commands; [Cmdlet(VerbsData.ConvertFrom, "DeflateString")] [OutputType(typeof(string))] [Alias("fromdeflatestring")] -public sealed class ConvertFromDeflateStringCommand : CommandFromCompressedStringBase +public sealed class ConvertFromDeflateStringCommand : FromCompressedStringCommandBase { protected override Stream CreateDecompressionStream(Stream inputStream) => new DeflateStream(inputStream, CompressionMode.Decompress); diff --git a/src/PSCompression/Commands/ConvertFromGzipStringCommand.cs b/src/PSCompression/Commands/ConvertFromGzipStringCommand.cs index 7b69b1b..6f4442a 100644 --- a/src/PSCompression/Commands/ConvertFromGzipStringCommand.cs +++ b/src/PSCompression/Commands/ConvertFromGzipStringCommand.cs @@ -1,13 +1,14 @@ using System.IO; using System.IO.Compression; using System.Management.Automation; +using PSCompression.Abstractions; namespace PSCompression.Commands; [Cmdlet(VerbsData.ConvertFrom, "GzipString")] [OutputType(typeof(string))] [Alias("fromgzipstring")] -public sealed class ConvertFromGzipStringCommand : CommandFromCompressedStringBase +public sealed class ConvertFromGzipStringCommand : FromCompressedStringCommandBase { protected override Stream CreateDecompressionStream(Stream inputStream) => new GZipStream(inputStream, CompressionMode.Decompress); diff --git a/src/PSCompression/Commands/ConvertFromZLibStringCommand.cs b/src/PSCompression/Commands/ConvertFromZLibStringCommand.cs index dd29106..3df4254 100644 --- a/src/PSCompression/Commands/ConvertFromZLibStringCommand.cs +++ b/src/PSCompression/Commands/ConvertFromZLibStringCommand.cs @@ -1,13 +1,14 @@ using System.IO; using System.IO.Compression; using System.Management.Automation; +using PSCompression.Abstractions; namespace PSCompression.Commands; [Cmdlet(VerbsData.ConvertFrom, "ZLibString")] [OutputType(typeof(string))] [Alias("fromzlibstring")] -public sealed class ConvertFromZLibStringCommand : CommandFromCompressedStringBase +public sealed class ConvertFromZLibStringCommand : FromCompressedStringCommandBase { protected override Stream CreateDecompressionStream(Stream inputStream) { diff --git a/src/PSCompression/Commands/ConvertToBrotliStringCommand.cs b/src/PSCompression/Commands/ConvertToBrotliStringCommand.cs index 6594762..82d16f7 100644 --- a/src/PSCompression/Commands/ConvertToBrotliStringCommand.cs +++ b/src/PSCompression/Commands/ConvertToBrotliStringCommand.cs @@ -1,7 +1,7 @@ using System.IO; using System.IO.Compression; using System.Management.Automation; -using Brotli; +using PSCompression.Abstractions; using PSCompression.Extensions; namespace PSCompression.Commands; @@ -9,12 +9,10 @@ namespace PSCompression.Commands; [Cmdlet(VerbsData.ConvertTo, "BrotliString")] [OutputType(typeof(byte[]), typeof(string))] [Alias("tobrotlistring")] -public sealed class ConvertToBrotliStringCommand : CommandToCompressedStringBase +public sealed class ConvertToBrotliStringCommand : ToCompressedStringCommandBase { protected override Stream CreateCompressionStream( Stream outputStream, CompressionLevel compressionLevel) - { - return outputStream.AsBrotliCompressedStream(compressionLevel); - } + => outputStream.AsBrotliCompressedStream(compressionLevel); } diff --git a/src/PSCompression/Commands/ConvertToDeflateStringCommand.cs b/src/PSCompression/Commands/ConvertToDeflateStringCommand.cs index 2f7808e..f43f8f0 100644 --- a/src/PSCompression/Commands/ConvertToDeflateStringCommand.cs +++ b/src/PSCompression/Commands/ConvertToDeflateStringCommand.cs @@ -1,18 +1,17 @@ using System.IO; using System.IO.Compression; using System.Management.Automation; +using PSCompression.Abstractions; namespace PSCompression.Commands; [Cmdlet(VerbsData.ConvertTo, "DeflateString")] [OutputType(typeof(byte[]), typeof(string))] [Alias("todeflatestring")] -public sealed class ConvertToDeflateStringCommand : CommandToCompressedStringBase +public sealed class ConvertToDeflateStringCommand : ToCompressedStringCommandBase { protected override Stream CreateCompressionStream( Stream outputStream, CompressionLevel compressionLevel) - { - return new DeflateStream(outputStream, compressionLevel); - } + => new DeflateStream(outputStream, compressionLevel); } diff --git a/src/PSCompression/Commands/ConvertToGzipStringCommand.cs b/src/PSCompression/Commands/ConvertToGzipStringCommand.cs index fcb4bc7..f1b2366 100644 --- a/src/PSCompression/Commands/ConvertToGzipStringCommand.cs +++ b/src/PSCompression/Commands/ConvertToGzipStringCommand.cs @@ -1,18 +1,17 @@ using System.IO; using System.IO.Compression; using System.Management.Automation; +using PSCompression.Abstractions; namespace PSCompression.Commands; [Cmdlet(VerbsData.ConvertTo, "GzipString")] [OutputType(typeof(byte[]), typeof(string))] [Alias("togzipstring")] -public sealed class ConvertToGzipStringCommand : CommandToCompressedStringBase +public sealed class ConvertToGzipStringCommand : ToCompressedStringCommandBase { protected override Stream CreateCompressionStream( Stream outputStream, CompressionLevel compressionLevel) - { - return new GZipStream(outputStream, compressionLevel); - } + => new GZipStream(outputStream, compressionLevel); } diff --git a/src/PSCompression/Commands/ConvertToZLibStringCommand.cs b/src/PSCompression/Commands/ConvertToZLibStringCommand.cs index 44acb0e..f15e547 100644 --- a/src/PSCompression/Commands/ConvertToZLibStringCommand.cs +++ b/src/PSCompression/Commands/ConvertToZLibStringCommand.cs @@ -1,18 +1,17 @@ using System.IO; using System.IO.Compression; using System.Management.Automation; +using PSCompression.Abstractions; namespace PSCompression.Commands; [Cmdlet(VerbsData.ConvertTo, "ZLibString")] [OutputType(typeof(byte[]), typeof(string))] [Alias("tozlibstring")] -public sealed class ConvertToZLibStringCommand : CommandToCompressedStringBase +public sealed class ConvertToZLibStringCommand : ToCompressedStringCommandBase { protected override Stream CreateCompressionStream( Stream outputStream, CompressionLevel compressionLevel) - { - return new ZlibStream(outputStream, compressionLevel); - } + => new ZlibStream(outputStream, compressionLevel); } diff --git a/src/PSCompression/Commands/ExpandGzipArchiveCommand.cs b/src/PSCompression/Commands/ExpandGzipArchiveCommand.cs deleted file mode 100644 index 1156254..0000000 --- a/src/PSCompression/Commands/ExpandGzipArchiveCommand.cs +++ /dev/null @@ -1,187 +0,0 @@ -using System; -using System.IO; -using System.Management.Automation; -using System.Text; -using PSCompression.Extensions; -using PSCompression.Exceptions; -using ICSharpCode.SharpZipLib.GZip; - -namespace PSCompression.Commands; - -[Cmdlet(VerbsData.Expand, "GzipArchive")] -[OutputType( - typeof(string), - ParameterSetName = ["Path", "LiteralPath"])] -[OutputType( - typeof(FileInfo), - ParameterSetName = ["PathDestination", "LiteralPathDestination"])] -[Alias("fromgzipfile")] -public sealed class ExpandGzipArchiveCommand : CommandWithPathBase, IDisposable -{ - private FileStream? _destination; - - private FileMode FileMode - { - get => (Update.IsPresent, Force.IsPresent) switch - { - (true, _) => FileMode.Append, - (_, true) => FileMode.Create, - _ => FileMode.CreateNew - }; - } - - [Parameter( - ParameterSetName = "Path", - Position = 0, - Mandatory = true, - ValueFromPipeline = true)] - [Parameter( - ParameterSetName = "PathDestination", - Position = 0, - Mandatory = true, - ValueFromPipeline = true)] - [SupportsWildcards] - public override string[] Path - { - get => _paths; - set => _paths = value; - } - - [Parameter( - ParameterSetName = "LiteralPath", - Mandatory = true, - ValueFromPipelineByPropertyName = true)] - [Parameter( - ParameterSetName = "LiteralPathDestination", - Mandatory = true, - ValueFromPipelineByPropertyName = true)] - [Alias("PSPath")] - public override string[] LiteralPath - { - get => _paths; - set => _paths = value; - } - - [Parameter( - Mandatory = true, - Position = 1, - ParameterSetName = "PathDestination")] - [Parameter( - Mandatory = true, - Position = 1, - ParameterSetName = "LiteralPathDestination")] - [Alias("DestinationPath")] - public string Destination { get; set; } = null!; - - [Parameter(ParameterSetName = "PathDestination")] - [Parameter(ParameterSetName = "LiteralPathDestination")] - [ArgumentCompleter(typeof(EncodingCompleter))] - [EncodingTransformation] - [ValidateNotNullOrEmpty] - public Encoding Encoding { get; set; } = new UTF8Encoding(); - - [Parameter(ParameterSetName = "Path")] - [Parameter(ParameterSetName = "LiteralPath")] - public SwitchParameter Raw { get; set; } - - [Parameter(ParameterSetName = "PathDestination")] - [Parameter(ParameterSetName = "LiteralPathDestination")] - public SwitchParameter PassThru { get; set; } - - [Parameter(ParameterSetName = "PathDestination")] - [Parameter(ParameterSetName = "LiteralPathDestination")] - public SwitchParameter Force { get; set; } - - [Parameter(ParameterSetName = "PathDestination")] - [Parameter(ParameterSetName = "LiteralPathDestination")] - public SwitchParameter Update { get; set; } - - protected override void BeginProcessing() - { - if (Destination is not null) - { - try - { - Destination = ResolvePath(Destination); - string parent = Destination.GetParent(); - - if (!Directory.Exists(parent)) - { - Directory.CreateDirectory(parent); - } - - _destination = File.Open(Destination, FileMode); - } - catch (Exception exception) - { - ThrowTerminatingError(exception.ToStreamOpenError(Destination)); - } - } - } - - protected override void ProcessRecord() - { - foreach (string path in EnumerateResolvedPaths()) - { - if (!path.IsArchive()) - { - WriteError(ExceptionHelper.NotArchivePath( - path, - IsLiteral ? nameof(LiteralPath) : nameof(Path))); - - continue; - } - - try - { - using FileStream source = File.OpenRead(path); - using GZipInputStream gz = new(source); - - if (_destination is not null) - { - gz.CopyTo(_destination); - continue; - } - - using StreamReader reader = new(gz, Encoding); - - if (Raw) - { - reader.WriteAllToPipeline(this); - continue; - } - - reader.WriteLinesToPipeline(this); - } - catch (Exception _) when (_ is PipelineStoppedException or FlowControlException) - { - throw; - } - catch (Exception exception) - { - WriteError(exception.ToOpenError(path)); - } - } - } - - protected override void EndProcessing() - { - if (_destination is null) - { - return; - } - - _destination.Dispose(); - - if (PassThru.IsPresent) - { - WriteObject(new FileInfo(_destination.Name)); - } - } - - public void Dispose() - { - _destination?.Dispose(); - GC.SuppressFinalize(this); - } -} diff --git a/src/PSCompression/Commands/ExpandTarEntryCommand.cs b/src/PSCompression/Commands/ExpandTarEntryCommand.cs new file mode 100644 index 0000000..ff22321 --- /dev/null +++ b/src/PSCompression/Commands/ExpandTarEntryCommand.cs @@ -0,0 +1,13 @@ +using System.IO; +using System.Management.Automation; +using PSCompression.Abstractions; + +namespace PSCompression.Commands; + +[Cmdlet(VerbsData.Expand, "TarEntry")] +[OutputType(typeof(FileSystemInfo))] +public sealed class ExpandTarEntryCommand : ExpandEntryCommandBase +{ + protected override FileSystemInfo Extract(TarEntryBase entry) => + entry.ExtractTo(Destination!, Force); +} diff --git a/src/PSCompression/Commands/ExpandZipEntryCommand.cs b/src/PSCompression/Commands/ExpandZipEntryCommand.cs index e13e849..f1c02b3 100644 --- a/src/PSCompression/Commands/ExpandZipEntryCommand.cs +++ b/src/PSCompression/Commands/ExpandZipEntryCommand.cs @@ -1,84 +1,18 @@ using System; using System.IO; using System.Management.Automation; -using PSCompression.Extensions; -using PSCompression.Exceptions; +using PSCompression.Abstractions; namespace PSCompression.Commands; [Cmdlet(VerbsData.Expand, "ZipEntry")] [OutputType(typeof(FileSystemInfo))] -public sealed class ExpandZipEntryCommand : PSCmdlet, IDisposable +public sealed class ExpandZipEntryCommand : ExpandEntryCommandBase, IDisposable { private readonly ZipArchiveCache _cache = new(); - [Parameter(Mandatory = true, ValueFromPipeline = true)] - public ZipEntryBase[] InputObject { get; set; } = null!; - - [Parameter(Position = 0)] - [ValidateNotNullOrEmpty] - public string? Destination { get; set; } - - [Parameter] - public SwitchParameter Force { get; set; } - - [Parameter] - public SwitchParameter PassThru { get; set; } - - protected override void BeginProcessing() - { - Destination = Destination is null - ? SessionState.Path.CurrentFileSystemLocation.Path - : Destination.ResolvePath(this); - - if (Destination.IsArchive()) - { - ThrowTerminatingError( - ExceptionHelper.NotDirectoryPath( - Destination, - nameof(Destination))); - } - - if (!Directory.Exists(Destination)) - { - Directory.CreateDirectory(Destination); - } - } - - protected override void ProcessRecord() - { - Dbg.Assert(Destination is not null); - - foreach (ZipEntryBase entry in InputObject) - { - try - { - (string path, bool isfile) = entry.ExtractTo( - _cache.GetOrAdd(entry), - Destination, - Force.IsPresent); - - if (PassThru.IsPresent) - { - if (isfile) - { - WriteObject(new FileInfo(path)); - continue; - } - - WriteObject(new DirectoryInfo(path)); - } - } - catch (Exception _) when (_ is PipelineStoppedException or FlowControlException) - { - throw; - } - catch (Exception exception) - { - WriteError(exception.ToExtractEntryError(entry)); - } - } - } + protected override FileSystemInfo Extract(ZipEntryBase entry) => + entry.ExtractTo(Destination!, Force, _cache.GetOrAdd(entry)); public void Dispose() { diff --git a/src/PSCompression/Commands/GetTarEntryCommand.cs b/src/PSCompression/Commands/GetTarEntryCommand.cs new file mode 100644 index 0000000..4e3b7cf --- /dev/null +++ b/src/PSCompression/Commands/GetTarEntryCommand.cs @@ -0,0 +1,80 @@ +using System.Collections.Generic; +using System.IO; +using System.Management.Automation; +using System.Text; +using ICSharpCode.SharpZipLib.Tar; +using PSCompression.Abstractions; +using PSCompression.Extensions; + +namespace PSCompression.Commands; + +[Cmdlet(VerbsCommon.Get, "TarEntry", DefaultParameterSetName = "Path")] +[OutputType(typeof(TarEntryDirectory), typeof(TarEntryFile))] +[Alias("targe")] +public sealed class GetTarEntryCommand : GetEntryCommandBase +{ + [Parameter] + public Algorithm Algorithm { get; set; } + + internal override ArchiveType ArchiveType => ArchiveType.tar; + + protected override IEnumerable GetEntriesFromFile(string path) + { + List entries = []; + + if (!MyInvocation.BoundParameters.ContainsKey(nameof(Algorithm))) + { + Algorithm = AlgorithmMappings.Parse(path); + } + + using (FileStream fs = File.OpenRead(path)) + using (Stream stream = Algorithm.FromCompressedStream(fs)) + using (TarInputStream tar = new(stream, Encoding.UTF8)) + { + foreach (TarEntry entry in tar.EnumerateEntries()) + { + if (ShouldSkipEntry(entry.IsDirectory)) + { + continue; + } + + if (!ShouldInclude(entry.Name) || ShouldExclude(entry.Name)) + { + continue; + } + + entries.Add(entry.IsDirectory + ? new TarEntryDirectory(entry, path) + : new TarEntryFile(entry, path, Algorithm)); + } + } + + return [.. entries]; + } + + protected override IEnumerable GetEntriesFromStream(Stream stream) + { + List entries = []; + Stream decompressStream = Algorithm.FromCompressedStream(stream); + TarInputStream tar = new(decompressStream, Encoding.UTF8); + + foreach (TarEntry entry in tar.EnumerateEntries()) + { + if (ShouldSkipEntry(entry.IsDirectory)) + { + continue; + } + + if (!ShouldInclude(entry.Name) || ShouldExclude(entry.Name)) + { + continue; + } + + entries.Add(entry.IsDirectory + ? new TarEntryDirectory(entry, stream) + : new TarEntryFile(entry, stream, Algorithm)); + } + + return [.. entries]; + } +} diff --git a/src/PSCompression/Commands/GetTarEntryContentCommand.cs b/src/PSCompression/Commands/GetTarEntryContentCommand.cs new file mode 100644 index 0000000..82bcded --- /dev/null +++ b/src/PSCompression/Commands/GetTarEntryContentCommand.cs @@ -0,0 +1,82 @@ +using System; +using System.IO; +using System.Management.Automation; +using PSCompression.Abstractions; +using PSCompression.Exceptions; +using PSCompression.Extensions; + +namespace PSCompression.Commands; + +[Cmdlet(VerbsCommon.Get, "TarEntryContent", DefaultParameterSetName = "Stream")] +[OutputType(typeof(string), ParameterSetName = ["Stream"])] +[OutputType(typeof(byte), ParameterSetName = ["Bytes"])] +[Alias("targc")] +public sealed class GetTarEntryContentCommand : GetEntryContentCommandBase +{ + private byte[]? _buffer; + + protected override void ProcessRecord() + { + foreach (TarEntryFile entry in Entry) + { + try + { + ReadEntry(entry); + } + catch (Exception _) when (_ is PipelineStoppedException or FlowControlException) + { + throw; + } + catch (Exception exception) + { + WriteError(exception.ToOpenError(entry.Source)); + } + } + } + + private void ReadEntry(TarEntryFile entry) + { + using MemoryStream mem = new(); + + if (!entry.GetContentStream(mem)) + { + return; + } + + if (AsByteStream) + { + if (Raw) + { + WriteObject(mem.ToArray()); + return; + } + + StreamBytes(mem); + return; + } + + using StreamReader reader = new(mem, Encoding); + + if (Raw.IsPresent) + { + reader.WriteAllTextToPipeline(this); + return; + } + + reader.WriteLinesToPipeline(this); + } + + private void StreamBytes(Stream stream) + { + int bytesRead; + _buffer ??= new byte[BufferSize]; + + while ((bytesRead = stream.Read(_buffer, 0, BufferSize)) > 0) + { + for (int i = 0; i < bytesRead; i++) + { + WriteObject(_buffer[i]); + } + } + } +} diff --git a/src/PSCompression/Commands/GetZipEntryCommand.cs b/src/PSCompression/Commands/GetZipEntryCommand.cs index 674c3ad..4f2a7aa 100644 --- a/src/PSCompression/Commands/GetZipEntryCommand.cs +++ b/src/PSCompression/Commands/GetZipEntryCommand.cs @@ -1,203 +1,71 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.IO.Compression; -using System.Linq; using System.Management.Automation; -using PSCompression.Extensions; -using PSCompression.Exceptions; using System.IO; +using PSCompression.Abstractions; namespace PSCompression.Commands; [Cmdlet(VerbsCommon.Get, "ZipEntry", DefaultParameterSetName = "Path")] [OutputType(typeof(ZipEntryDirectory), typeof(ZipEntryFile))] [Alias("zipge")] -public sealed class GetZipEntryCommand : CommandWithPathBase +public sealed class GetZipEntryCommand : GetEntryCommandBase { - [Parameter( - ParameterSetName = "Stream", - Position = 0, - Mandatory = true, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true)] - [Alias("RawContentStream")] - public Stream? InputStream { get; set; } + internal override ArchiveType ArchiveType => ArchiveType.zip; - private readonly List _output = []; - - private WildcardPattern[]? _includePatterns; - - private WildcardPattern[]? _excludePatterns; - - [Parameter] - public ZipEntryType? Type { get; set; } - - [Parameter] - [SupportsWildcards] - public string[]? Include { get; set; } - - [Parameter] - [SupportsWildcards] - public string[]? Exclude { get; set; } - - protected override void BeginProcessing() - { - if (Exclude is null && Include is null) - { - return; - } - - const WildcardOptions options = WildcardOptions.Compiled - | WildcardOptions.CultureInvariant - | WildcardOptions.IgnoreCase; - - if (Exclude is not null) - { - _excludePatterns = [.. Exclude.Select(e => new WildcardPattern(e, options))]; - } - - if (Include is not null) - { - _includePatterns = [.. Include.Select(e => new WildcardPattern(e, options))]; - } - } - - protected override void ProcessRecord() + protected override IEnumerable GetEntriesFromFile(string path) { - IEnumerable entries; - if (InputStream is not null) + List entries = []; + using (ZipArchive zip = ZipFile.OpenRead(path)) { - ZipEntryBase CreateFromStream(ZipArchiveEntry entry, bool isDirectory) => - isDirectory - ? new ZipEntryDirectory(entry, InputStream) - : new ZipEntryFile(entry, InputStream); - - try + foreach (ZipArchiveEntry entry in zip.Entries) { - using (ZipArchive zip = new(InputStream, ZipArchiveMode.Read, true)) + bool isDirectory = string.IsNullOrEmpty(entry.Name); + + if (ShouldSkipEntry(isDirectory)) { - entries = GetEntries(zip, CreateFromStream); + continue; } - WriteObject(entries, enumerateCollection: true); - return; - } - catch (Exception _) when (_ is PipelineStoppedException or FlowControlException) - { - throw; - } - catch (InvalidDataException exception) - { - ThrowTerminatingError(exception.ToInvalidZipArchive()); - } - catch (Exception exception) - { - WriteError(exception.ToOpenError("InputStream")); - } - } - - foreach (string path in EnumerateResolvedPaths()) - { - ZipEntryBase CreateFromFile(ZipArchiveEntry entry, bool isDirectory) => - isDirectory - ? new ZipEntryDirectory(entry, path) - : new ZipEntryFile(entry, path); - - if (!path.IsArchive()) - { - WriteError( - ExceptionHelper.NotArchivePath( - path, - IsLiteral ? nameof(LiteralPath) : nameof(Path))); - - continue; - } - - try - { - using (ZipArchive zip = ZipFile.OpenRead(path)) + if (!ShouldInclude(entry.Name) || ShouldExclude(entry.Name)) { - entries = GetEntries(zip, CreateFromFile); + continue; } - WriteObject(entries, enumerateCollection: true); - } - catch (Exception _) when (_ is PipelineStoppedException or FlowControlException) - { - throw; - } - catch (InvalidDataException exception) - { - ThrowTerminatingError(exception.ToInvalidZipArchive()); - } - catch (Exception exception) - { - WriteError(exception.ToOpenError(path)); - } - } - } - - private IEnumerable GetEntries( - ZipArchive zip, - Func createMethod) - { - _output.Clear(); - foreach (ZipArchiveEntry entry in zip.Entries) - { - bool isDirectory = string.IsNullOrEmpty(entry.Name); - - if (ShouldSkipEntry(isDirectory)) - { - continue; - } - - if (!ShouldInclude(entry) || ShouldExclude(entry)) - { - continue; + entries.Add(isDirectory + ? new ZipEntryDirectory(entry, path) + : new ZipEntryFile(entry, path)); } - - _output.Add(createMethod(entry, isDirectory)); } - return _output.ZipEntrySort(); + return [.. entries]; } - private static bool MatchAny( - ZipArchiveEntry entry, - WildcardPattern[] patterns) + protected override IEnumerable GetEntriesFromStream(Stream stream) { - foreach (WildcardPattern pattern in patterns) + List entries = []; + using (ZipArchive zip = new(stream, ZipArchiveMode.Read, true)) { - if (pattern.IsMatch(entry.FullName)) + foreach (ZipArchiveEntry entry in zip.Entries) { - return true; - } - } + bool isDirectory = string.IsNullOrEmpty(entry.Name); - return false; - } - - private bool ShouldInclude(ZipArchiveEntry entry) - { - if (_includePatterns is null) - { - return true; - } + if (ShouldSkipEntry(isDirectory)) + { + continue; + } - return MatchAny(entry, _includePatterns); - } + if (!ShouldInclude(entry.Name) || ShouldExclude(entry.Name)) + { + continue; + } - private bool ShouldExclude(ZipArchiveEntry entry) - { - if (_excludePatterns is null) - { - return false; + entries.Add(isDirectory + ? new ZipEntryDirectory(entry, stream) + : new ZipEntryFile(entry, stream)); + } } - return MatchAny(entry, _excludePatterns); + return [.. entries]; } - - private bool ShouldSkipEntry(bool isDirectory) => - isDirectory && Type is ZipEntryType.Archive - || !isDirectory && Type is ZipEntryType.Directory; } diff --git a/src/PSCompression/Commands/GetZipEntryContentCommand.cs b/src/PSCompression/Commands/GetZipEntryContentCommand.cs index 97f0ee9..c69ef7d 100644 --- a/src/PSCompression/Commands/GetZipEntryContentCommand.cs +++ b/src/PSCompression/Commands/GetZipEntryContentCommand.cs @@ -1,6 +1,6 @@ using System; using System.Management.Automation; -using System.Text; +using PSCompression.Abstractions; using PSCompression.Exceptions; namespace PSCompression.Commands; @@ -9,32 +9,13 @@ namespace PSCompression.Commands; [OutputType(typeof(string), ParameterSetName = ["Stream"])] [OutputType(typeof(byte), ParameterSetName = ["Bytes"])] [Alias("zipgc")] -public sealed class GetZipEntryContentCommand : PSCmdlet, IDisposable +public sealed class GetZipEntryContentCommand : GetEntryContentCommandBase, IDisposable { private readonly ZipArchiveCache _cache = new(); - [Parameter(Mandatory = true, ValueFromPipeline = true)] - public ZipEntryFile[] ZipEntry { get; set; } = null!; - - [Parameter(ParameterSetName = "Stream")] - [ArgumentCompleter(typeof(EncodingCompleter))] - [EncodingTransformation] - [ValidateNotNullOrEmpty] - public Encoding Encoding { get; set; } = new UTF8Encoding(); - - [Parameter] - public SwitchParameter Raw { get; set; } - - [Parameter(ParameterSetName = "Bytes")] - public SwitchParameter AsByteStream { get; set; } - - [Parameter(ParameterSetName = "Bytes")] - [ValidateNotNullOrEmpty] - public int BufferSize { get; set; } = 128_000; - protected override void ProcessRecord() { - foreach (ZipEntryFile entry in ZipEntry) + foreach (ZipEntryFile entry in Entry) { try { diff --git a/src/PSCompression/Commands/NewZipEntryCommand.cs b/src/PSCompression/Commands/NewZipEntryCommand.cs index 68b16e0..ac34d40 100644 --- a/src/PSCompression/Commands/NewZipEntryCommand.cs +++ b/src/PSCompression/Commands/NewZipEntryCommand.cs @@ -7,6 +7,7 @@ using System.Text; using PSCompression.Extensions; using PSCompression.Exceptions; +using PSCompression.Abstractions; namespace PSCompression.Commands; @@ -55,13 +56,7 @@ public string[]? EntryPath protected override void BeginProcessing() { Destination = Destination.ResolvePath(this); - if (!Destination.IsArchive()) - { - ThrowTerminatingError( - ExceptionHelper.NotArchivePath( - Destination, - nameof(Destination))); - } + Destination.WriteErrorIfNotArchive(nameof(Destination), this, isTerminating: true); try { @@ -99,14 +94,7 @@ protected override void BeginProcessing() Dbg.Assert(SourcePath is not null); // Create Entries from file here SourcePath = SourcePath.ResolvePath(this); - - if (!SourcePath.IsArchive()) - { - ThrowTerminatingError( - ExceptionHelper.NotArchivePath( - SourcePath, - nameof(SourcePath))); - } + SourcePath.WriteErrorIfNotArchive(nameof(SourcePath), this, isTerminating: true); using FileStream fileStream = File.Open( path: SourcePath, @@ -200,10 +188,10 @@ protected override void EndProcessing() } } - private IEnumerable GetResult() + private IEnumerable GetResult() { using ZipArchive zip = ZipFile.OpenRead(Destination); - List _result = new(_entries.Count); + List _result = new(_entries.Count); foreach (ZipArchiveEntry entry in _entries) { @@ -220,7 +208,7 @@ private IEnumerable GetResult() Destination)); } - return _result.ZipEntrySort(); + return _result.ToEntrySort(); } public void Dispose() diff --git a/src/PSCompression/Commands/RemoveZipEntryCommand.cs b/src/PSCompression/Commands/RemoveZipEntryCommand.cs index f952b1d..981f67a 100644 --- a/src/PSCompression/Commands/RemoveZipEntryCommand.cs +++ b/src/PSCompression/Commands/RemoveZipEntryCommand.cs @@ -1,6 +1,7 @@ using System; using System.IO.Compression; using System.Management.Automation; +using PSCompression.Abstractions; using PSCompression.Exceptions; namespace PSCompression.Commands; diff --git a/src/PSCompression/Commands/RenameZipEntryCommand.cs b/src/PSCompression/Commands/RenameZipEntryCommand.cs index 38683da..f59c620 100644 --- a/src/PSCompression/Commands/RenameZipEntryCommand.cs +++ b/src/PSCompression/Commands/RenameZipEntryCommand.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO.Compression; using System.Management.Automation; +using PSCompression.Abstractions; using PSCompression.Exceptions; using PSCompression.Extensions; @@ -85,7 +86,7 @@ protected override void EndProcessing() _zipEntryCache .AddRange(_moveCache.GetPassThruMappings()) .GetEntries() - .ZipEntrySort(), + .ToEntrySort(), enumerateCollection: true); } diff --git a/src/PSCompression/Exceptions/ExceptionHelper.cs b/src/PSCompression/Exceptions/ExceptionHelper.cs index 4e4a57a..d24006d 100644 --- a/src/PSCompression/Exceptions/ExceptionHelper.cs +++ b/src/PSCompression/Exceptions/ExceptionHelper.cs @@ -3,6 +3,7 @@ using System.IO; using System.IO.Compression; using System.Management.Automation; +using PSCompression.Abstractions; using PSCompression.Extensions; namespace PSCompression.Exceptions; @@ -13,12 +14,32 @@ internal static class ExceptionHelper private static readonly char[] s_InvalidPathChar = Path.GetInvalidPathChars(); - internal static ErrorRecord NotArchivePath(string path, string paramname) => - new( - new ArgumentException( - $"The specified path '{path}' does not exist or is a Directory.", - paramname), - "NotArchivePath", ErrorCategory.InvalidArgument, path); + internal static bool WriteErrorIfNotArchive( + this string path, + string paramname, + PSCmdlet cmdlet, + bool isTerminating = false) + { + if (File.Exists(path)) + { + return false; + } + + ArgumentException exception = new( + $"The specified path '{path}' does not exist or is a Directory.", + paramname); + + ErrorRecord error = new(exception, "NotArchivePath", ErrorCategory.InvalidArgument, path); + + if (isTerminating) + { + cmdlet.ThrowTerminatingError(error); + } + + cmdlet.WriteError(error); + return true; + } + internal static ErrorRecord NotDirectoryPath(string path, string paramname) => new( @@ -33,12 +54,12 @@ internal static ErrorRecord ToInvalidProviderError(this ProviderInfo provider, s "NotFileSystemPath", ErrorCategory.InvalidArgument, path); internal static ErrorRecord ToOpenError(this Exception exception, string path) => - new(exception, "ZipOpen", ErrorCategory.OpenError, path); + new(exception, "EntryOpen", ErrorCategory.OpenError, path); internal static ErrorRecord ToResolvePathError(this Exception exception, string path) => new(exception, "ResolvePath", ErrorCategory.NotSpecified, path); - internal static ErrorRecord ToExtractEntryError(this Exception exception, ZipEntryBase entry) => + internal static ErrorRecord ToExtractEntryError(this Exception exception, EntryBase entry) => new(exception, "ExtractEntry", ErrorCategory.NotSpecified, entry); internal static ErrorRecord ToStreamOpenError(this Exception exception, ZipEntryBase entry) => @@ -62,16 +83,25 @@ internal static ErrorRecord ToEntryNotFoundError(this EntryNotFoundException exc internal static ErrorRecord ToEnumerationError(this Exception exception, object item) => new(exception, "EnumerationError", ErrorCategory.ReadError, item); - internal static ErrorRecord ToInvalidZipArchive(this InvalidDataException exception) => - new( - new InvalidDataException( - "Specified path or stream is not a valid zip archive, " + - "might be compressed using an unsupported method, " + - "or could be corrupted.", - exception), - "InvalidZipArchive", - ErrorCategory.InvalidData, - null); + internal static ErrorRecord ToInvalidArchive( + this Exception exception, + ArchiveType type, + bool isStream = false) + { + string basemsg = $"Specified path or stream is not a valid {type} archive, " + + "might be compressed using an unsupported method, " + + "or could be corrupted."; + + if (type is ArchiveType.tar && isStream) + { + basemsg += " When reading a tar archive from a stream, " + + "use the -Algorithm parameter to specify the compression type."; + } + + return new ErrorRecord( + new InvalidDataException(basemsg, exception), + "InvalidArchive", ErrorCategory.InvalidData, null); + } internal static void ThrowIfNotFound( diff --git a/src/PSCompression/Extensions/CompressionExtensions.cs b/src/PSCompression/Extensions/CompressionExtensions.cs index c1c311e..4e6ceb9 100644 --- a/src/PSCompression/Extensions/CompressionExtensions.cs +++ b/src/PSCompression/Extensions/CompressionExtensions.cs @@ -1,9 +1,17 @@ +using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.IO.Compression; using System.Management.Automation; using System.Text.RegularExpressions; using Brotli; +using ICSharpCode.SharpZipLib.BZip2; +using ICSharpCode.SharpZipLib.Tar; +using SharpCompress.Compressors.BZip2; +using SharpCompress.Compressors.LZMA; +using ZstdSharp; +using SharpCompressors = SharpCompress.Compressors; namespace PSCompression.Extensions; @@ -18,8 +26,8 @@ internal static class CompressionExtensions internal static string RelativeTo(this DirectoryInfo directory, int length) => (directory.FullName.Substring(length) + _directorySeparator).NormalizeEntryPath(); - internal static string RelativeTo(this FileInfo directory, int length) => - directory.FullName.Substring(length).NormalizeFileEntryPath(); + internal static string RelativeTo(this FileInfo file, int length) => + file.FullName.Substring(length).NormalizeFileEntryPath(); internal static ZipArchiveEntry CreateEntryFromFile( this ZipArchive zip, @@ -46,35 +54,8 @@ internal static ZipArchiveEntry CreateEntryFromFile( internal static bool TryGetEntry( this ZipArchive zip, string path, - [NotNullWhen(true)] out ZipArchiveEntry? entry) => - (entry = zip.GetEntry(path)) is not null; - - internal static (string, bool) ExtractTo( - this ZipEntryBase entryBase, - ZipArchive zip, - string destination, - bool overwrite) - { - destination = Path.GetFullPath( - Path.Combine(destination, entryBase.RelativePath)); - - if (entryBase.Type is ZipEntryType.Directory) - { - Directory.CreateDirectory(destination); - return (destination, false); - } - - string parent = Path.GetDirectoryName(destination); - - if (!Directory.Exists(parent)) - { - Directory.CreateDirectory(parent); - } - - ZipArchiveEntry entry = zip.GetEntry(entryBase.RelativePath); - entry.ExtractToFile(destination, overwrite); - return (destination, true); - } + [NotNullWhen(true)] out ZipArchiveEntry? entry) + => (entry = zip.GetEntry(path)) is not null; internal static string ChangeName( this ZipEntryFile file, @@ -95,22 +76,25 @@ internal static string ChangeName( internal static string ChangeName( this ZipEntryDirectory directory, - string newname) => - s_reGetDirName.Replace( + string newname) + => s_reGetDirName.Replace( directory.RelativePath.NormalizePath(), newname); internal static string ChangePath( this ZipArchiveEntry entry, string oldPath, - string newPath) => - string.Concat(newPath, entry.FullName.Remove(0, oldPath.Length)); + string newPath) + => string.Concat(newPath, entry.FullName.Remove(0, oldPath.Length)); + + internal static string GetDirectoryName(this ZipArchiveEntry entry) + => s_reGetDirName.Match(entry.FullName).Value; - internal static string GetDirectoryName(this ZipArchiveEntry entry) => - s_reGetDirName.Match(entry.FullName).Value; + internal static string GetDirectoryName(this TarEntry entry) + => s_reGetDirName.Match(entry.Name).Value; - internal static void WriteAllToPipeline(this StreamReader reader, PSCmdlet cmdlet) => - cmdlet.WriteObject(reader.ReadToEnd()); + internal static void WriteAllTextToPipeline(this StreamReader reader, PSCmdlet cmdlet) + => cmdlet.WriteObject(reader.ReadToEnd()); internal static void WriteLinesToPipeline(this StreamReader reader, PSCmdlet cmdlet) { @@ -151,4 +135,83 @@ internal static BrotliStream AsBrotliCompressedStream( return brotli; } + + internal static BZip2OutputStream AsBZip2CompressedStream( + this Stream stream, + CompressionLevel compressionLevel) + { + int blockSize = compressionLevel switch + { + CompressionLevel.NoCompression => 1, + CompressionLevel.Fastest => 2, + _ => 9 + }; + + return new BZip2OutputStream(stream, blockSize); + } + + internal static CompressionStream AsZstCompressedStream( + this Stream stream, + CompressionLevel compressionLevel) + { + int level = compressionLevel switch + { + CompressionLevel.NoCompression => 1, + CompressionLevel.Fastest => 3, + _ => 19 + }; + + return new CompressionStream(stream, level); + } + + internal static LZipStream AsLzCompressedStream(this Stream outputStream) => + new(outputStream, SharpCompressors.CompressionMode.Compress); + + internal static Stream ToCompressedStream( + this Algorithm algorithm, + Stream stream, + CompressionLevel compressionLevel) + => algorithm switch + { + Algorithm.gz => new GZipStream(stream, compressionLevel), + Algorithm.br => stream.AsBrotliCompressedStream(compressionLevel), + Algorithm.zst => stream.AsZstCompressedStream(compressionLevel), + Algorithm.lz => stream.AsLzCompressedStream(), + Algorithm.bz2 => stream.AsBZip2CompressedStream(compressionLevel), + _ => stream + }; + + internal static Stream FromCompressedStream( + this Algorithm algorithm, + Stream stream) + => algorithm switch + { + Algorithm.gz => new GZipStream(stream, CompressionMode.Decompress), + Algorithm.br => new BrotliStream(stream, CompressionMode.Decompress), + Algorithm.zst => new DecompressionStream(stream), + Algorithm.lz => new LZipStream(stream, SharpCompressors.CompressionMode.Decompress), + Algorithm.bz2 => new BZip2Stream(stream, SharpCompressors.CompressionMode.Decompress, true), + _ => stream + }; + + internal static void CreateTarEntry( + this TarOutputStream stream, + string entryName, + DateTime modTime, + long size) + { + TarEntry entry = TarEntry.CreateTarEntry(entryName); + entry.TarHeader.Size = size; + entry.TarHeader.ModTime = modTime; + stream.PutNextEntry(entry); + } + + internal static IEnumerable EnumerateEntries(this TarInputStream tar) + { + TarEntry? entry; + while ((entry = tar.GetNextEntry()) is not null) + { + yield return entry; + } + } } diff --git a/src/PSCompression/Extensions/PathExtensions.cs b/src/PSCompression/Extensions/PathExtensions.cs index 1809627..cafd02a 100644 --- a/src/PSCompression/Extensions/PathExtensions.cs +++ b/src/PSCompression/Extensions/PathExtensions.cs @@ -52,8 +52,6 @@ internal static bool Validate( return false; } - internal static bool IsArchive(this string path) => File.Exists(path); - internal static string GetParent(this string path) => Path.GetDirectoryName(path); internal static string AddExtensionIfMissing(this string path, string extension) diff --git a/src/PSCompression/OnModuleImportAndRemove.cs b/src/PSCompression/OnModuleImportAndRemove.cs new file mode 100644 index 0000000..bc5b8b7 --- /dev/null +++ b/src/PSCompression/OnModuleImportAndRemove.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Management.Automation; +using System.Reflection; +using System.Runtime.InteropServices; + +/// +/// OnModuleImportAndRemove is a class that implements the IModuleAssemblyInitializer and IModuleAssemblyCleanup interfaces. +/// This class is used to handle the assembly resolve event when the module is imported and removed. +/// +public class OnModuleImportAndRemove : IModuleAssemblyInitializer, IModuleAssemblyCleanup +{ + /// + /// OnImport is called when the module is imported. + /// + public void OnImport() + { + if (IsNetFramework()) + { + AppDomain.CurrentDomain.AssemblyResolve += MyResolveEventHandler; + } + } + + /// + /// OnRemove is called when the module is removed. + /// + /// + public void OnRemove(PSModuleInfo module) + { + if (IsNetFramework()) + { + AppDomain.CurrentDomain.AssemblyResolve -= MyResolveEventHandler; + } + } + + /// + /// MyResolveEventHandler is a method that handles the AssemblyResolve event. + /// + /// + /// + /// + private static Assembly? MyResolveEventHandler(object? sender, ResolveEventArgs args) + { + string libDirectory = Path.GetDirectoryName(typeof(OnModuleImportAndRemove).Assembly.Location); + List directoriesToSearch = []; + + if (!string.IsNullOrEmpty(libDirectory)) + { + directoriesToSearch.Add(libDirectory); + if (Directory.Exists(libDirectory)) + { + IEnumerable dirs = Directory.EnumerateDirectories( + libDirectory, "*", SearchOption.AllDirectories); + + directoriesToSearch.AddRange(dirs); + } + } + + string requestedAssemblyName = new AssemblyName(args.Name).Name + ".dll"; + + foreach (string directory in directoriesToSearch) + { + string assemblyPath = Path.Combine(directory, requestedAssemblyName); + + if (File.Exists(assemblyPath)) + { + try + { + return Assembly.LoadFrom(assemblyPath); + } + catch (Exception ex) + { + Console.WriteLine($"Failed to load assembly from {assemblyPath}: {ex.Message}"); + } + } + } + + return null; + } + + /// + /// Determine if the current runtime is .NET Framework + /// + /// + private bool IsNetFramework() => RuntimeInformation.FrameworkDescription + .StartsWith(".NET Framework", StringComparison.OrdinalIgnoreCase); +} diff --git a/src/PSCompression/PSCompression.csproj b/src/PSCompression/PSCompression.csproj index c939771..2adb154 100644 --- a/src/PSCompression/PSCompression.csproj +++ b/src/PSCompression/PSCompression.csproj @@ -8,10 +8,16 @@ latest + + + + + - + + diff --git a/src/PSCompression/Records.cs b/src/PSCompression/Records.cs index 722c402..c2abbf6 100644 --- a/src/PSCompression/Records.cs +++ b/src/PSCompression/Records.cs @@ -1,5 +1,7 @@ +using PSCompression.Abstractions; + namespace PSCompression; internal record struct EntryWithPath(ZipEntryBase ZipEntry, string Path); -internal record struct PathWithType(string Path, ZipEntryType EntryType); +internal record struct PathWithType(string Path, EntryType EntryType); diff --git a/src/PSCompression/SortingOps.cs b/src/PSCompression/SortingOps.cs index 127839a..23891f7 100644 --- a/src/PSCompression/SortingOps.cs +++ b/src/PSCompression/SortingOps.cs @@ -1,27 +1,29 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using PSCompression.Abstractions; using PSCompression.Extensions; namespace PSCompression; internal static class SortingOps { - private static string SortByParent(ZipEntryBase entry) => + private static string SortByParent(EntryBase entry) => Path.GetDirectoryName(entry.RelativePath) .NormalizeEntryPath(); - private static int SortByLength(ZipEntryBase entry) => + private static int SortByLength(EntryBase entry) => entry.RelativePath.Count(e => e == '/'); - private static string SortByName(ZipEntryBase entry) => + private static string SortByName(EntryBase entry) => entry.Name; - private static ZipEntryType SortByType(ZipEntryBase entry) => + private static EntryType SortByType(EntryBase entry) => entry.Type; - internal static IEnumerable ZipEntrySort( - this IEnumerable zip) => zip + internal static IEnumerable ToEntrySort( + this IEnumerable entryCollection) + => entryCollection .OrderBy(SortByParent) .ThenBy(SortByType) .ThenBy(SortByLength) diff --git a/src/PSCompression/TarEntryDirectory.cs b/src/PSCompression/TarEntryDirectory.cs new file mode 100644 index 0000000..986a86a --- /dev/null +++ b/src/PSCompression/TarEntryDirectory.cs @@ -0,0 +1,25 @@ +using System.IO; +using ICSharpCode.SharpZipLib.Tar; +using PSCompression.Abstractions; +using PSCompression.Extensions; + +namespace PSCompression; + +public sealed class TarEntryDirectory : TarEntryBase +{ + internal TarEntryDirectory(TarEntry entry, string source) + : base(entry, source) + { + Name = entry.GetDirectoryName(); + } + + internal TarEntryDirectory(TarEntry entry, Stream? stream) + : base(entry, stream) + { + Name = entry.GetDirectoryName(); + } + + public override EntryType Type => EntryType.Directory; + + protected override string GetFormatDirectoryPath() => $"/{RelativePath.NormalizeEntryPath()}"; +} diff --git a/src/PSCompression/TarEntryFile.cs b/src/PSCompression/TarEntryFile.cs new file mode 100644 index 0000000..cf894ae --- /dev/null +++ b/src/PSCompression/TarEntryFile.cs @@ -0,0 +1,71 @@ +using System.IO; +using System.Linq; +using System.Text; +using ICSharpCode.SharpZipLib.Tar; +using PSCompression.Abstractions; +using PSCompression.Extensions; + +namespace PSCompression; + +public sealed class TarEntryFile : TarEntryBase +{ + private readonly Algorithm _algorithm; + + public string BaseName => Path.GetFileNameWithoutExtension(Name); + + public string Extension => Path.GetExtension(RelativePath); + + public override EntryType Type => EntryType.Archive; + + internal TarEntryFile(TarEntry entry, string source, Algorithm algorithm) + : base(entry, source) + { + _algorithm = algorithm; + } + + internal TarEntryFile(TarEntry entry, Stream? stream, Algorithm algorithm) + : base(entry, stream) + { + _algorithm = algorithm; + } + + protected override string GetFormatDirectoryPath() => + $"/{Path.GetDirectoryName(RelativePath).NormalizeEntryPath()}"; + + internal bool GetContentStream(Stream stream) + { + Stream? sourceStream = null; + Stream? decompressedStream = null; + TarInputStream? tar = null; + + try + { + sourceStream = _stream ?? File.OpenRead(Source); + sourceStream.Seek(0, SeekOrigin.Begin); + decompressedStream = _algorithm.FromCompressedStream(sourceStream); + tar = new(decompressedStream, Encoding.UTF8); + + TarEntry? entry = tar + .EnumerateEntries() + .FirstOrDefault(e => e.Name == RelativePath); + + if (entry is null or { Size: 0 }) + { + return false; + } + + tar.CopyTo(stream, (int)entry.Size); + stream.Seek(0, SeekOrigin.Begin); + return true; + } + finally + { + if (!FromStream) + { + tar?.Dispose(); + decompressedStream?.Dispose(); + sourceStream?.Dispose(); + } + } + } +} diff --git a/src/PSCompression/ZLibStream.cs b/src/PSCompression/ZLibStream.cs index 46732aa..968e8d4 100644 --- a/src/PSCompression/ZLibStream.cs +++ b/src/PSCompression/ZLibStream.cs @@ -5,7 +5,7 @@ namespace PSCompression; -// Justification: ain't nobody got time for that! +// ain't nobody got time for that [ExcludeFromCodeCoverage] internal sealed class ZlibStream : Stream { @@ -48,20 +48,14 @@ public override void Flush() _outputStream.Flush(); } - public override int Read(byte[] buffer, int offset, int count) - { + public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException("Reading is not supported."); - } - public override long Seek(long offset, SeekOrigin origin) - { + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException("Seeking is not supported."); - } - public override void SetLength(long value) - { + public override void SetLength(long value) => throw new NotSupportedException("Setting length is not supported."); - } public override void Write(byte[] buffer, int offset, int count) { diff --git a/src/PSCompression/ZipArchiveCache.cs b/src/PSCompression/ZipArchiveCache.cs index 11ab3cf..4cfd7bb 100644 --- a/src/PSCompression/ZipArchiveCache.cs +++ b/src/PSCompression/ZipArchiveCache.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO.Compression; +using PSCompression.Abstractions; namespace PSCompression; diff --git a/src/PSCompression/ZipContentReader.cs b/src/PSCompression/ZipContentReader.cs index 2f2a4ec..500594a 100644 --- a/src/PSCompression/ZipContentReader.cs +++ b/src/PSCompression/ZipContentReader.cs @@ -2,6 +2,7 @@ using System.IO.Compression; using System.Management.Automation; using System.Text; +using PSCompression.Abstractions; using PSCompression.Extensions; namespace PSCompression; @@ -55,6 +56,6 @@ internal void ReadToEnd( { using Stream entryStream = entry.Open(_zip); using StreamReader reader = new(entryStream, encoding); - reader.WriteAllToPipeline(cmdlet); + reader.WriteAllTextToPipeline(cmdlet); } } diff --git a/src/PSCompression/ZipContentWriter.cs b/src/PSCompression/ZipContentWriter.cs index d0e8ca2..9a418f7 100644 --- a/src/PSCompression/ZipContentWriter.cs +++ b/src/PSCompression/ZipContentWriter.cs @@ -1,6 +1,7 @@ using System.IO; using System.IO.Compression; using System.Text; +using PSCompression.Abstractions; namespace PSCompression; diff --git a/src/PSCompression/ZipEntryCache.cs b/src/PSCompression/ZipEntryCache.cs index e8c3a67..c167cf3 100644 --- a/src/PSCompression/ZipEntryCache.cs +++ b/src/PSCompression/ZipEntryCache.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO.Compression; +using PSCompression.Abstractions; namespace PSCompression; @@ -38,9 +39,9 @@ internal IEnumerable GetEntries() foreach (var entry in _cache) { using ZipArchive zip = ZipFile.OpenRead(entry.Key); - foreach ((string path, ZipEntryType type) in entry.Value) + foreach ((string path, EntryType type) in entry.Value) { - if (type is ZipEntryType.Archive) + if (type is EntryType.Archive) { yield return new ZipEntryFile(zip.GetEntry(path), entry.Key); continue; diff --git a/src/PSCompression/ZipEntryDirectory.cs b/src/PSCompression/ZipEntryDirectory.cs index 86ca69f..d70b21d 100644 --- a/src/PSCompression/ZipEntryDirectory.cs +++ b/src/PSCompression/ZipEntryDirectory.cs @@ -3,6 +3,7 @@ using System.IO; using System.IO.Compression; using System.Linq; +using PSCompression.Abstractions; using PSCompression.Extensions; namespace PSCompression; @@ -11,12 +12,7 @@ public sealed class ZipEntryDirectory : ZipEntryBase { private const StringComparison _comparer = StringComparison.InvariantCultureIgnoreCase; - internal override string FormatDirectoryPath - { - get => _formatDirectoryPath ??= $"/{RelativePath.NormalizeEntryPath()}"; - } - - public override ZipEntryType Type => ZipEntryType.Directory; + public override EntryType Type => EntryType.Directory; internal ZipEntryDirectory(ZipArchiveEntry entry, string source) : base(entry, source) @@ -26,10 +22,15 @@ internal ZipEntryDirectory(ZipArchiveEntry entry, string source) internal ZipEntryDirectory(ZipArchiveEntry entry, Stream? stream) : base(entry, stream) - { } + { + Name = entry.GetDirectoryName(); + } internal IEnumerable GetChilds(ZipArchive zip) => zip.Entries.Where(e => !string.Equals(e.FullName, RelativePath, _comparer) && e.FullName.StartsWith(RelativePath, _comparer)); + + protected override string GetFormatDirectoryPath() => + $"/{RelativePath.NormalizeEntryPath()}"; } diff --git a/src/PSCompression/ZipEntryFile.cs b/src/PSCompression/ZipEntryFile.cs index 3c76543..7d7a7b5 100644 --- a/src/PSCompression/ZipEntryFile.cs +++ b/src/PSCompression/ZipEntryFile.cs @@ -1,5 +1,6 @@ using System.IO; using System.IO.Compression; +using PSCompression.Abstractions; using PSCompression.Exceptions; using PSCompression.Extensions; @@ -7,15 +8,9 @@ namespace PSCompression; public sealed class ZipEntryFile : ZipEntryBase { - internal override string FormatDirectoryPath - { - get => _formatDirectoryPath ??= - $"/{Path.GetDirectoryName(RelativePath).NormalizeEntryPath()}"; - } - public string CompressionRatio => GetRatio(Length, CompressedLength); - public override ZipEntryType Type => ZipEntryType.Archive; + public override EntryType Type => EntryType.Archive; public string BaseName => Path.GetFileNameWithoutExtension(Name); @@ -64,4 +59,7 @@ internal void Refresh(ZipArchive zip) Length = entry.Length; CompressedLength = entry.CompressedLength; } + + protected override string GetFormatDirectoryPath() => + $"/{Path.GetDirectoryName(RelativePath).NormalizeEntryPath()}"; } diff --git a/src/PSCompression/ZipEntryMoveCache.cs b/src/PSCompression/ZipEntryMoveCache.cs index cd92190..158800b 100644 --- a/src/PSCompression/ZipEntryMoveCache.cs +++ b/src/PSCompression/ZipEntryMoveCache.cs @@ -3,6 +3,7 @@ using System.IO.Compression; using System.Linq; using System.Text.RegularExpressions; +using PSCompression.Abstractions; using PSCompression.Extensions; namespace PSCompression; @@ -31,7 +32,7 @@ private Dictionary WithSource(ZipEntryBase entry) internal bool IsDirectoryEntry(string source, string path) => _cache[source].TryGetValue(path, out EntryWithPath entryWithPath) - && entryWithPath.ZipEntry.Type is ZipEntryType.Directory; + && entryWithPath.ZipEntry.Type is EntryType.Directory; internal void AddEntry(ZipEntryBase entry, string newname) => WithSource(entry).Add(entry.RelativePath, new(entry, newname)); @@ -72,7 +73,7 @@ private Dictionary GetChildMappings( foreach (var pair in pathChanges.OrderByDescending(e => e.Key)) { (ZipEntryBase entry, string newname) = pair.Value; - if (entry.Type is ZipEntryType.Archive) + if (entry.Type is EntryType.Archive) { newpath = ((ZipEntryFile)entry).ChangeName(newname); result[pair.Key] = newpath; diff --git a/src/PSCompression/ZipEntryType.cs b/src/PSCompression/ZipEntryType.cs index 0b875b6..112af46 100644 --- a/src/PSCompression/ZipEntryType.cs +++ b/src/PSCompression/ZipEntryType.cs @@ -1,6 +1,6 @@ namespace PSCompression; -public enum ZipEntryType +public enum EntryType { Directory = 0, Archive = 1 diff --git a/src/PSCompression/internal/_Format.cs b/src/PSCompression/internal/_Format.cs index d06d7c5..9a35d01 100644 --- a/src/PSCompression/internal/_Format.cs +++ b/src/PSCompression/internal/_Format.cs @@ -2,6 +2,7 @@ using System.ComponentModel; using System.Globalization; using System.Management.Automation; +using PSCompression.Abstractions; namespace PSCompression.Internal; @@ -26,7 +27,7 @@ public static class _Format ]; [Hidden, EditorBrowsable(EditorBrowsableState.Never)] - public static string GetDirectoryPath(ZipEntryBase entry) => entry.FormatDirectoryPath; + public static string? GetDirectoryPath(EntryBase entry) => entry.FormatDirectoryPath; [Hidden, EditorBrowsable(EditorBrowsableState.Never)] public static string GetFormattedDate(DateTime dateTime) => diff --git a/tools/InvokeBuild.ps1 b/tools/InvokeBuild.ps1 index 4ece54d..8a2e74f 100644 --- a/tools/InvokeBuild.ps1 +++ b/tools/InvokeBuild.ps1 @@ -19,6 +19,10 @@ task BuildManaged { try { foreach ($framework in $ProjectInfo.Project.TargetFrameworks) { + if ($framework -match '^net4' -and -not $IsWindows) { + continue + } + Write-Host "Compiling for $framework" dotnet @arguments --framework $framework diff --git a/tools/ProjectBuilder/Pester.cs b/tools/ProjectBuilder/Pester.cs index fccaac1..901ddeb 100644 --- a/tools/ProjectBuilder/Pester.cs +++ b/tools/ProjectBuilder/Pester.cs @@ -66,7 +66,7 @@ public string[] GetTestArgs(Version version) if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { - arguments.AddRange([ "-ExecutionPolicy", "Bypass" ]); + arguments.AddRange(["-ExecutionPolicy", "Bypass"]); } arguments.AddRange([ @@ -99,12 +99,12 @@ public string[] GetTestArgs(Version version) if (File.Exists(unitCoveragePath)) { - arguments.AddRange([ "--merge-with", unitCoveragePath ]); + arguments.AddRange(["--merge-with", unitCoveragePath]); } - if (Environment.GetEnvironmentVariable("GITHUB_ACTIONS") is "true") + if (Environment.GetEnvironmentVariable("GITHUB_ACTIONS") == "true") { - arguments.AddRange([ "--source-mapping-file", sourceMappingFile ]); + arguments.AddRange(["--source-mapping-file", sourceMappingFile]); File.WriteAllText( sourceMappingFile, $"|{_info.Root.FullName}{Path.DirectorySeparatorChar}=/_/"); diff --git a/tools/ProjectBuilder/Project.cs b/tools/ProjectBuilder/Project.cs index 831c292..9afcd3a 100644 --- a/tools/ProjectBuilder/Project.cs +++ b/tools/ProjectBuilder/Project.cs @@ -2,6 +2,7 @@ using System.Collections; using System.IO; using System.Linq; +using System.Text.RegularExpressions; namespace ProjectBuilder; @@ -15,7 +16,16 @@ public sealed class Project public string[]? TargetFrameworks { get; internal set; } - public string? TestFramework { get => TargetFrameworks.FirstOrDefault(); } + public string? TestFramework + { + get => _info.PowerShellVersion is { Major: 5, Minor: 1 } + ? TargetFrameworks + .Where(e => Regex.Match(e, "^net(?:4|standard)").Success) + .FirstOrDefault() + : TargetFrameworks + .Where(e => !e.StartsWith("net4")) + .FirstOrDefault(); + } private Configuration Configuration { get => _info.Configuration; } diff --git a/tools/ProjectBuilder/ProjectInfo.cs b/tools/ProjectBuilder/ProjectInfo.cs index b1f7b2c..044cce9 100644 --- a/tools/ProjectBuilder/ProjectInfo.cs +++ b/tools/ProjectBuilder/ProjectInfo.cs @@ -22,6 +22,8 @@ public sealed class ProjectInfo public Pester Pester { get; } + public Version PowerShellVersion { get; } + public string? AnalyzerPath { get @@ -41,8 +43,9 @@ public string? AnalyzerPath private string? _analyzerPath; - private ProjectInfo(string path) + private ProjectInfo(string path, Version psVersion) { + PowerShellVersion = psVersion; Root = AssertDirectory(path); Module = new Module( @@ -60,12 +63,14 @@ private ProjectInfo(string path) public static ProjectInfo Create( string path, - Configuration configuration) + Configuration configuration, + Version psVersion) { - ProjectInfo builder = new(path) + ProjectInfo builder = new(path, psVersion) { Configuration = configuration }; + builder.Module.Manifest = GetManifest(builder); builder.Module.Version = GetManifestVersion(builder); builder.Project.Release = GetReleasePath( @@ -167,14 +172,18 @@ private static string GetProjectFile(ProjectInfo builder) => private static Version? GetManifestVersion(ProjectInfo builder) { using PowerShell powershell = PowerShell.Create(RunspaceMode.CurrentRunspace); - Hashtable? moduleInfo = powershell - .AddCommand("Import-PowerShellDataFile") - .AddArgument(builder.Module.Manifest?.FullName) - .Invoke() + PSModuleInfo? moduleInfo = powershell + .AddCommand("Test-ModuleManifest") + .AddParameters(new Dictionary() + { + ["Path"] = builder.Module.Manifest?.FullName, + ["ErrorAction"] = "Ignore" + }) + .Invoke() .FirstOrDefault(); - return powershell.HadErrors + return powershell.Streams.Error is { Count: > 0 } ? throw powershell.Streams.Error.First().Exception - : LanguagePrimitives.ConvertTo(moduleInfo?["ModuleVersion"]); + : moduleInfo.Version; } } From 72a45f1a36712fe67ffae9486db504573f78657e Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Tue, 17 Jun 2025 13:05:27 -0300 Subject: [PATCH 17/53] fixing tests --- CHANGELOG.md | 2 + .../Abstractions/GetEntryCommandBase.cs | 7 +- .../Abstractions/ZipEntryBase.cs | 4 +- .../Commands/GetZipEntryCommand.cs | 4 +- tests/GzipCmdlets.tests.ps1 | 216 ------------------ tests/ToFromStringCompression.ps1 | 58 +++++ tests/ZipEntryCmdlets.tests.ps1 | 6 +- tests/ZipEntryDirectory.tests.ps1 | 2 +- tests/ZipEntryFile.tests.ps1 | 2 +- 9 files changed, 75 insertions(+), 226 deletions(-) delete mode 100644 tests/GzipCmdlets.tests.ps1 create mode 100644 tests/ToFromStringCompression.ps1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 9decaf0..45ee629 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ - `Expand-TarEntry`: Extracts a tar entry to a file. - Added commands to compress files and folders into `.tar` archives and extract `.tar` archives with various compression algorithms: - `Compress-TarArchive` and `Expand-TarArchive`: Supported compression algorithms include `gz`, `br`, `bz2`, `zst`, `lz`, and `none` (no compression). +- Removed commands: + - `Compress-GzipArchive` and `Expand-GzipArchive`: These were deprecated as they only supported single-file compression, which is now better handled by the module’s `.tar` archive functionality. This update was made possible by the following projects. If you find them helpful, please consider starring their repositories: diff --git a/src/PSCompression/Abstractions/GetEntryCommandBase.cs b/src/PSCompression/Abstractions/GetEntryCommandBase.cs index 0fa484f..bc4290b 100644 --- a/src/PSCompression/Abstractions/GetEntryCommandBase.cs +++ b/src/PSCompression/Abstractions/GetEntryCommandBase.cs @@ -164,5 +164,10 @@ protected bool ShouldSkipEntry(bool isDirectory) => isDirectory && Type is EntryType.Archive || !isDirectory && Type is EntryType.Directory; private bool IsInvalidArchive(Exception exception) => - exception is InvalidDataException or TarException or ZstdException or IOException or BrotliDecodeException; + exception is + InvalidDataException + or TarException + or ZstdException + or IOException + or BrotliDecodeException; } diff --git a/src/PSCompression/Abstractions/ZipEntryBase.cs b/src/PSCompression/Abstractions/ZipEntryBase.cs index a637edd..66c332d 100644 --- a/src/PSCompression/Abstractions/ZipEntryBase.cs +++ b/src/PSCompression/Abstractions/ZipEntryBase.cs @@ -24,8 +24,8 @@ protected ZipEntryBase(ZipArchiveEntry entry, Stream? stream) } public ZipArchive OpenRead() => FromStream - ? ZipFile.OpenRead(Source) - : new ZipArchive(_stream); + ? new ZipArchive(_stream) + : ZipFile.OpenRead(Source); public ZipArchive OpenWrite() { diff --git a/src/PSCompression/Commands/GetZipEntryCommand.cs b/src/PSCompression/Commands/GetZipEntryCommand.cs index 4f2a7aa..a00b9aa 100644 --- a/src/PSCompression/Commands/GetZipEntryCommand.cs +++ b/src/PSCompression/Commands/GetZipEntryCommand.cs @@ -27,7 +27,7 @@ protected override IEnumerable GetEntriesFromFile(string path) continue; } - if (!ShouldInclude(entry.Name) || ShouldExclude(entry.Name)) + if (!ShouldInclude(entry.FullName) || ShouldExclude(entry.FullName)) { continue; } @@ -55,7 +55,7 @@ protected override IEnumerable GetEntriesFromStream(Stream stream) continue; } - if (!ShouldInclude(entry.Name) || ShouldExclude(entry.Name)) + if (!ShouldInclude(entry.FullName) || ShouldExclude(entry.FullName)) { continue; } diff --git a/tests/GzipCmdlets.tests.ps1 b/tests/GzipCmdlets.tests.ps1 deleted file mode 100644 index a965ce4..0000000 --- a/tests/GzipCmdlets.tests.ps1 +++ /dev/null @@ -1,216 +0,0 @@ -$ErrorActionPreference = 'Stop' - -$moduleName = (Get-Item ([IO.Path]::Combine($PSScriptRoot, '..', 'module', '*.psd1'))).BaseName -$manifestPath = [IO.Path]::Combine($PSScriptRoot, '..', 'output', $moduleName) - -Import-Module $manifestPath -Import-Module ([System.IO.Path]::Combine($PSScriptRoot, 'shared.psm1')) - -Describe 'Gzip Cmdlets' { - Context 'ConvertFrom-GzipString' -Tag 'ConvertFrom-GzipString' { - It 'Should throw on a non b64 encoded input' { - { 'foo' | ConvertFrom-GzipString } | - Should -Throw - } - } - - Context 'ConvertTo & ConvertFrom GzipString' -Tag 'ConvertTo & ConvertFrom GzipString' { - BeforeAll { - $content = 'hello', 'world', '!' - $content | Out-Null - } - - It 'Can compress strings to gzip b64 strings from pipeline' { - $encoded = { $content | ConvertTo-GzipString } | - Should -Not -Throw -PassThru - - $encoded | ConvertFrom-GzipString | - Should -BeExactly $contet - } - - It 'Can compress strings to gzip b64 strings positionally' { - $encoded = { ConvertTo-GzipString -InputObject $content } | - Should -Not -Throw -PassThru - - $encoded | ConvertFrom-GzipString | - Should -BeExactly $contet - } - - It 'Can compress strings to gzip and output raw bytes' { - [byte[]] $bytes = $content | ConvertTo-GzipString -AsByteStream - $result = Decode $bytes - $result.TrimEnd() | Should -BeExactly ($content -join [System.Environment]::NewLine) - } - - It 'Can convert gzip b64 compressed string and output multi-line string' { - $content | ConvertTo-GzipString | - ConvertFrom-GzipString -Raw | - ForEach-Object TrimEnd | - Should -BeExactly ($content -join [System.Environment]::NewLine) - } - - It 'Concatenates strings when the -NoNewLine switch is used' { - $content | ConvertTo-GzipString -NoNewLine | - ConvertFrom-GzipString | - Should -BeExactly (-join $content) - } - } - - Context 'Compress & Expand GzipArchive' -Tag 'Compress & Expand GzipArchive' { - BeforeAll { - $testString = 'hello world!' - $content = $testString | New-Item (Join-Path $TestDrive content.txt) - $appendedContent = 'this is appended content...' | New-Item (Join-Path $TestDrive appendedContent.txt) - $destination = Join-Path $TestDrive -ChildPath test.gz - $testString, $content, $appendedContent, $destination | Out-Null - } - - It 'Can create a Gzip compressed file from a specified path' { - $content | - Compress-GzipArchive -DestinationPath $destination -PassThru | - Expand-GzipArchive | - Should -BeExactly ($content | Get-Content) - } - - It 'Outputs a single multiline string when using the -Raw switch' { - Expand-GzipArchive $destination -Raw | - Should -BeExactly ($content | Get-Content -Raw) - } - - It 'Can append content to a Gzip compressed file from a specified path' { - $appendedContent | - Compress-GzipArchive -DestinationPath $destination -PassThru -Update | - Expand-GzipArchive | - Should -BeExactly (-join @($content, $appendedContent | Get-Content)) - } - - It 'Can expand Gzip files with appended content to a destination file' { - $expandGzipArchiveSplat = @{ - LiteralPath = $destination - DestinationPath = (Join-Path $TestDrive extractappended.txt) - PassThru = $true - } - - Get-Content -LiteralPath (Expand-GzipArchive @expandGzipArchiveSplat).FullName | - Should -BeExactly (-join @($content, $appendedContent | Get-Content)) - } - - It 'Should not overwrite an existing Gzip file without -Force' { - { $content | Compress-GzipArchive -DestinationPath $destination } | - Should -Throw - } - - It 'Can overwrite an existing Gzip file with -Force' { - { $content | Compress-GzipArchive -DestinationPath $destination -Force } | - Should -Not -Throw - - Expand-GzipArchive -LiteralPath $destination | - Should -BeExactly ($content | Get-Content) - } - - It 'Can expand Gzip files to a destination file' { - $expandGzipArchiveSplat = @{ - LiteralPath = $destination - DestinationPath = (Join-Path $TestDrive extract.txt) - PassThru = $true - } - - Get-Content -LiteralPath (Expand-GzipArchive @expandGzipArchiveSplat).FullName | - Should -BeExactly ($content | Get-Content) - } - - It 'Should throw if expanding to an existing file' { - $expandGzipArchiveSplat = @{ - LiteralPath = $destination - DestinationPath = (Join-Path $TestDrive extract.txt) - } - - { Expand-GzipArchive @expandGzipArchiveSplat } | - Should -Throw - } - - It 'Can append content with -Update' { - $expandGzipArchiveSplat = @{ - LiteralPath = $destination - DestinationPath = (Join-Path $TestDrive extract.txt) - PassThru = $true - Update = $true - } - - $appendedContent = $testString + $testString - Get-Content -LiteralPath (Expand-GzipArchive @expandGzipArchiveSplat).FullName | - Should -BeExactly $appendedContent - } - - It 'Can overwrite the destination file with -Force' { - $expandGzipArchiveSplat = @{ - LiteralPath = $destination - DestinationPath = (Join-Path $TestDrive extract.txt) - PassThru = $true - Force = $true - } - - Get-Content -LiteralPath (Expand-GzipArchive @expandGzipArchiveSplat).FullName | - Should -BeExactly $testString - } - - It 'Should throw if the Path is not an Archive' { - { Expand-GzipArchive $pwd } | Should -Throw - } - - It 'Should throw if the Path is not a Gzip Archive' { - { Expand-GzipArchive -LiteralPath (Join-Path $TestDrive content.txt) } | - Should -Throw - } - - It 'Can create folders when the destination directory does not exist' { - $expandGzipArchiveSplat = @{ - LiteralPath = $destination - DestinationPath = [IO.Path]::Combine($TestDrive, 'does', 'not', 'exist', 'extract.txt') - PassThru = $true - Force = $true - } - - { Expand-GzipArchive @expandGzipArchiveSplat } | - Should -Not -Throw - } - - It 'Should throw if trying to compress a directory' { - $folders = 0..5 | ForEach-Object { - New-Item (Join-Path $TestDrive "folder $_") -ItemType Directory - } - - { Compress-GzipArchive $folders.FullName -Destination $destination -Force } | - Should -Throw - } - - Context 'Compress-GzipArchive with InputBytes' { - BeforeAll { - $path = [IO.Path]::Combine($TestDrive, 'this', 'is', 'atest', 'file') - $testStrings = 'hello', 'world', '!' - $path, $testStrings | Out-Null - } - - It 'Can create compressed files from input bytes' { - $testStrings | ConvertTo-GzipString -AsByteStream | - Compress-GzipArchive -DestinationPath $path -PassThru | - Expand-GzipArchive | - Should -BeExactly $testStrings - } - - It 'Can append to compressed files from input bytes' { - $testStrings | ConvertTo-GzipString -AsByteStream | - Compress-GzipArchive -DestinationPath $path -PassThru -Update | - Expand-GzipArchive | - Should -BeExactly ($testStrings + $testStrings) - } - - It 'Can overwrite a compressed file from input bytes' { - $testStrings | ConvertTo-GzipString -AsByteStream | - Compress-GzipArchive -DestinationPath $path -PassThru -Force | - Expand-GzipArchive | - Should -BeExactly $testStrings - } - } - } -} diff --git a/tests/ToFromStringCompression.ps1 b/tests/ToFromStringCompression.ps1 new file mode 100644 index 0000000..aeb03c2 --- /dev/null +++ b/tests/ToFromStringCompression.ps1 @@ -0,0 +1,58 @@ +$ErrorActionPreference = 'Stop' + +$moduleName = (Get-Item ([IO.Path]::Combine($PSScriptRoot, '..', 'module', '*.psd1'))).BaseName +$manifestPath = [IO.Path]::Combine($PSScriptRoot, '..', 'output', $moduleName) + +Import-Module $manifestPath +Import-Module ([System.IO.Path]::Combine($PSScriptRoot, 'shared.psm1')) + +Describe 'ToFromStringCompression' { + Context 'ConvertFrom-GzipString' -Tag 'ConvertFrom-GzipString' { + It 'Should throw on a non b64 encoded input' { + { 'foo' | ConvertFrom-GzipString } | + Should -Throw + } + } + + Context 'ConvertTo & ConvertFrom GzipString' -Tag 'ConvertTo & ConvertFrom GzipString' { + BeforeAll { + $content = 'hello', 'world', '!' + $content | Out-Null + } + + It 'Can compress strings to gzip b64 strings from pipeline' { + $encoded = { $content | ConvertTo-GzipString } | + Should -Not -Throw -PassThru + + $encoded | ConvertFrom-GzipString | + Should -BeExactly $contet + } + + It 'Can compress strings to gzip b64 strings positionally' { + $encoded = { ConvertTo-GzipString -InputObject $content } | + Should -Not -Throw -PassThru + + $encoded | ConvertFrom-GzipString | + Should -BeExactly $contet + } + + It 'Can compress strings to gzip and output raw bytes' { + [byte[]] $bytes = $content | ConvertTo-GzipString -AsByteStream + $result = Decode $bytes + $result.TrimEnd() | Should -BeExactly ($content -join [System.Environment]::NewLine) + } + + It 'Can convert gzip b64 compressed string and output multi-line string' { + $content | ConvertTo-GzipString | + ConvertFrom-GzipString -Raw | + ForEach-Object TrimEnd | + Should -BeExactly ($content -join [System.Environment]::NewLine) + } + + It 'Concatenates strings when the -NoNewLine switch is used' { + $content | ConvertTo-GzipString -NoNewLine | + ConvertFrom-GzipString | + Should -BeExactly (-join $content) + } + } +} diff --git a/tests/ZipEntryCmdlets.tests.ps1 b/tests/ZipEntryCmdlets.tests.ps1 index e414608..45527b9 100644 --- a/tests/ZipEntryCmdlets.tests.ps1 +++ b/tests/ZipEntryCmdlets.tests.ps1 @@ -115,16 +115,16 @@ Describe 'ZipEntry Cmdlets' { Context 'Get-ZipEntry' -Tag 'Get-ZipEntry' { It 'Can list entries in a zip archive' { $zip | Get-ZipEntry | - Should -BeOfType ([PSCompression.ZipEntryBase]) + Should -BeOfType ([PSCompression.Abstractions.ZipEntryBase]) } It 'Can list entries from a Stream' { Invoke-WebRequest $uri | Get-ZipEntry | - Should -BeOfType ([PSCompression.ZipEntryBase]) + Should -BeOfType ([PSCompression.Abstractions.ZipEntryBase]) Use-Object ($stream = $zip.OpenRead()) { $stream | Get-ZipEntry | - Should -BeOfType ([PSCompression.ZipEntryBase]) + Should -BeOfType ([PSCompression.Abstractions.ZipEntryBase]) } } diff --git a/tests/ZipEntryDirectory.tests.ps1 b/tests/ZipEntryDirectory.tests.ps1 index 85414ef..a37d744 100644 --- a/tests/ZipEntryDirectory.tests.ps1 +++ b/tests/ZipEntryDirectory.tests.ps1 @@ -11,6 +11,6 @@ Describe 'ZipEntryDirectory Class' { } It 'Should be of type Directory' { - ($zip | Get-ZipEntry).Type | Should -BeExactly ([PSCompression.ZipEntryType]::Directory) + ($zip | Get-ZipEntry).Type | Should -BeExactly ([PSCompression.EntryType]::Directory) } } diff --git a/tests/ZipEntryFile.tests.ps1 b/tests/ZipEntryFile.tests.ps1 index ff5ad4f..b944bbe 100644 --- a/tests/ZipEntryFile.tests.ps1 +++ b/tests/ZipEntryFile.tests.ps1 @@ -13,7 +13,7 @@ Describe 'ZipEntryFile Class' { } It 'Should be of type Archive' { - ($zip | Get-ZipEntry).Type | Should -BeExactly ([PSCompression.ZipEntryType]::Archive) + ($zip | Get-ZipEntry).Type | Should -BeExactly ([PSCompression.EntryType]::Archive) } It 'Should Have a BaseName Property' { From 9fb937569ee0323e33f4a806945d14af4cbfa08e Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Tue, 17 Jun 2025 14:03:03 -0300 Subject: [PATCH 18/53] adding to and from compression tests --- tests/EncodingCompleter.tests.ps1 | 10 ++-- tests/ToFromStringCompression.ps1 | 80 ++++++++++++++++++------------- tests/shared.psm1 | 49 ++++--------------- 3 files changed, 60 insertions(+), 79 deletions(-) diff --git a/tests/EncodingCompleter.tests.ps1 b/tests/EncodingCompleter.tests.ps1 index 25e413d..b3b5544 100644 --- a/tests/EncodingCompleter.tests.ps1 +++ b/tests/EncodingCompleter.tests.ps1 @@ -1,10 +1,12 @@ -$ErrorActionPreference = 'Stop' +using namespace System.IO -$moduleName = (Get-Item ([IO.Path]::Combine($PSScriptRoot, '..', 'module', '*.psd1'))).BaseName -$manifestPath = [IO.Path]::Combine($PSScriptRoot, '..', 'output', $moduleName) +$ErrorActionPreference = 'Stop' + +$moduleName = (Get-Item ([Path]::Combine($PSScriptRoot, '..', 'module', '*.psd1'))).BaseName +$manifestPath = [Path]::Combine($PSScriptRoot, '..', 'output', $moduleName) Import-Module $manifestPath -Import-Module ([System.IO.Path]::Combine($PSScriptRoot, 'shared.psm1')) +Import-Module ([Path]::Combine($PSScriptRoot, 'shared.psm1')) BeforeAll { $encodingSet = @( diff --git a/tests/ToFromStringCompression.ps1 b/tests/ToFromStringCompression.ps1 index aeb03c2..5f1ac11 100644 --- a/tests/ToFromStringCompression.ps1 +++ b/tests/ToFromStringCompression.ps1 @@ -1,58 +1,70 @@ -$ErrorActionPreference = 'Stop' +using namespace System.IO -$moduleName = (Get-Item ([IO.Path]::Combine($PSScriptRoot, '..', 'module', '*.psd1'))).BaseName -$manifestPath = [IO.Path]::Combine($PSScriptRoot, '..', 'output', $moduleName) +$ErrorActionPreference = 'Stop' + +$moduleName = (Get-Item ([Path]::Combine($PSScriptRoot, '..', 'module', '*.psd1'))).BaseName +$manifestPath = [Path]::Combine($PSScriptRoot, '..', 'output', $moduleName) Import-Module $manifestPath -Import-Module ([System.IO.Path]::Combine($PSScriptRoot, 'shared.psm1')) +Import-Module ([Path]::Combine($PSScriptRoot, 'shared.psm1')) + +Describe 'To & From String Compression' { + BeforeAll { + $conversionCommands = @{ + 'ConvertFrom-BrotliString' = 'ConvertTo-BrotliString' + 'ConvertFrom-DeflateString' = 'ConvertTo-DeflateString' + 'ConvertFrom-GzipString' = 'ConvertTo-GzipString' + 'ConvertFrom-ZlibString' = 'ConvertTo-ZlibString' + } + + $conversionCommands | Out-Null + } -Describe 'ToFromStringCompression' { - Context 'ConvertFrom-GzipString' -Tag 'ConvertFrom-GzipString' { + Context 'ConvertFrom Commands' -Tag 'ConvertFrom Commands' { It 'Should throw on a non b64 encoded input' { - { 'foo' | ConvertFrom-GzipString } | - Should -Throw + $conversionCommands.Keys | ForEach-Object { + { 'foo' | & $_ } | Should -Throw -ExceptionType ([FormatException]) + } } } - Context 'ConvertTo & ConvertFrom GzipString' -Tag 'ConvertTo & ConvertFrom GzipString' { + Context 'ConvertTo & ConvertFrom General Usage' -Tag 'ConvertTo & ConvertFrom General Usage' { BeforeAll { $content = 'hello', 'world', '!' $content | Out-Null } - It 'Can compress strings to gzip b64 strings from pipeline' { - $encoded = { $content | ConvertTo-GzipString } | - Should -Not -Throw -PassThru - - $encoded | ConvertFrom-GzipString | - Should -BeExactly $contet - } - - It 'Can compress strings to gzip b64 strings positionally' { - $encoded = { ConvertTo-GzipString -InputObject $content } | - Should -Not -Throw -PassThru + It 'Can compress strings and expand strings' { + $conversionCommands.Keys | ForEach-Object { + $encoded = { $content | & $conversionCommands[$_] } | + Should -Not -Throw -PassThru - $encoded | ConvertFrom-GzipString | - Should -BeExactly $contet + $encoded | & $_ | Should -BeExactly $contet + } } - It 'Can compress strings to gzip and output raw bytes' { - [byte[]] $bytes = $content | ConvertTo-GzipString -AsByteStream - $result = Decode $bytes - $result.TrimEnd() | Should -BeExactly ($content -join [System.Environment]::NewLine) + It 'Can compress strings outputting raw bytes' { + $conversionCommands.Keys | ForEach-Object { + [byte[]] $bytes = $content | & $conversionCommands[$_] -AsByteStream + $result = [Convert]::ToBase64String($bytes) | & $_ + $result | Should -BeExactly $content + } } - It 'Can convert gzip b64 compressed string and output multi-line string' { - $content | ConvertTo-GzipString | - ConvertFrom-GzipString -Raw | - ForEach-Object TrimEnd | - Should -BeExactly ($content -join [System.Environment]::NewLine) + It 'Can expand b64 compressed strings and output a multi-line string' { + $contentAsString = $content -join [Environment]::NewLine + $conversionCommands.Keys | ForEach-Object { + $content | & $conversionCommands[$_] | & $_ -Raw | + ForEach-Object TrimEnd | Should -BeExactly $contentAsString + } } It 'Concatenates strings when the -NoNewLine switch is used' { - $content | ConvertTo-GzipString -NoNewLine | - ConvertFrom-GzipString | - Should -BeExactly (-join $content) + $contentAsString = -join $content + $conversionCommands.Keys | ForEach-Object { + $content | & $conversionCommands[$_] -NoNewLine | & $_ | + Should -BeExactly $contentAsString + } } } } diff --git a/tests/shared.psm1 b/tests/shared.psm1 index 1d1d53e..85b4e06 100644 --- a/tests/shared.psm1 +++ b/tests/shared.psm1 @@ -1,47 +1,16 @@ -function Complete { - [OutputType([System.Management.Automation.CompletionResult])] - param([string] $Expression) - - end { - [System.Management.Automation.CommandCompletion]::CompleteInput( - $Expression, - $Expression.Length, - $null).CompletionMatches - } -} - -function Decode { - param([byte[]] $bytes) - - end { - try { - $gzip = [System.IO.Compression.GZipStream]::new( - ($mem = [System.IO.MemoryStream]::new($bytes)), - [System.IO.Compression.CompressionMode]::Decompress) +using namespace System.Management.Automation +using namespace System.Runtime.InteropServices +using namespace PSCompression - $out = [System.IO.MemoryStream]::new() - $gzip.CopyTo($out) - } - finally { - if ($gzip -is [System.IDisposable]) { - $gzip.Dispose() - } - - if ($mem -is [System.IDisposable]) { - $mem.Dispose() - } +function Complete { + param([string] $Expression) - if ($out -is [System.IDisposable]) { - $out.Dispose() - [System.Text.UTF8Encoding]::new().GetString($out.ToArray()) - } - } - } + [CommandCompletion]::CompleteInput($Expression, $Expression.Length, $null).CompletionMatches } function Test-Completer { param( - [ArgumentCompleter([PSCompression.EncodingCompleter])] + [ArgumentCompleter([EncodingCompleter])] [string] $Test ) } @@ -56,9 +25,7 @@ function Get-Structure { } } -$osIsWindows = [System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform( - [System.Runtime.InteropServices.OSPlatform]::Windows) - +$osIsWindows = [RuntimeInformation]::IsOSPlatform([OSPlatform]::Windows) $osIsWindows | Out-Null $exportModuleMemberSplat = @{ From be1707dccf1f8e72c7d1998abb630673b9ca79e8 Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Tue, 17 Jun 2025 16:53:26 -0300 Subject: [PATCH 19/53] adding some compresstararchive tests --- .../ToCompressedFileCommandBase.cs | 3 +- src/PSCompression/AlgorithmMappings.cs | 24 +++++++- .../Commands/CompressTarArchiveCommand.cs | 11 ++++ .../Extensions/PathExtensions.cs | 3 +- ....ps1 => CompressArchiveCommands.tests.ps1} | 61 ++++++++++++------- 5 files changed, 78 insertions(+), 24 deletions(-) rename tests/{CompressZipArchive.tests.ps1 => CompressArchiveCommands.tests.ps1} (65%) diff --git a/src/PSCompression/Abstractions/ToCompressedFileCommandBase.cs b/src/PSCompression/Abstractions/ToCompressedFileCommandBase.cs index 99dbd83..ac2925c 100644 --- a/src/PSCompression/Abstractions/ToCompressedFileCommandBase.cs +++ b/src/PSCompression/Abstractions/ToCompressedFileCommandBase.cs @@ -63,7 +63,8 @@ private FileMode FileMode protected override void BeginProcessing() { - Destination = ResolvePath(Destination).AddExtensionIfMissing(FileExtension); + Destination = ResolvePath(Destination) + .AddExtensionIfMissing(FileExtension); try { diff --git a/src/PSCompression/AlgorithmMappings.cs b/src/PSCompression/AlgorithmMappings.cs index d815a95..2cd6eda 100644 --- a/src/PSCompression/AlgorithmMappings.cs +++ b/src/PSCompression/AlgorithmMappings.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; namespace PSCompression; @@ -9,13 +10,34 @@ internal static class AlgorithmMappings private static readonly Dictionary _mappings = new( StringComparer.InvariantCultureIgnoreCase) { + // Gzip [".gz"] = Algorithm.gz, + [".gzip"] = Algorithm.gz, + [".tgz"] = Algorithm.gz, + + // Brotli [".br"] = Algorithm.br, + + // Bzip2 [".bz2"] = Algorithm.bz2, + [".bzip2"] = Algorithm.bz2, + [".tbz2"] = Algorithm.bz2, + [".tbz"] = Algorithm.bz2, + + // Zstandard [".zst"] = Algorithm.zst, + + // Lzip [".lz"] = Algorithm.lz, + + // No compression [".tar"] = Algorithm.none }; - internal static Algorithm Parse(string path) => _mappings[Path.GetExtension(path)]; + internal static Algorithm Parse(string path) => + _mappings.TryGetValue(Path.GetExtension(path), out Algorithm value) + ? value : Algorithm.none; + + internal static bool HasExtension(string path) => + _mappings.Keys.Any(e => path.EndsWith(e, StringComparison.InvariantCultureIgnoreCase)); } diff --git a/src/PSCompression/Commands/CompressTarArchiveCommand.cs b/src/PSCompression/Commands/CompressTarArchiveCommand.cs index 7c2aafb..656688a 100644 --- a/src/PSCompression/Commands/CompressTarArchiveCommand.cs +++ b/src/PSCompression/Commands/CompressTarArchiveCommand.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Management.Automation; using System.Text; @@ -16,6 +17,10 @@ public sealed class CompressTarArchiveCommand : ToCompressedFileCommandBase Date: Tue, 17 Jun 2025 17:04:50 -0300 Subject: [PATCH 20/53] adding some compresstararchive tests --- src/PSCompression/OnModuleImportAndRemove.cs | 2 ++ tests/EncodingCompleter.tests.ps1 | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/PSCompression/OnModuleImportAndRemove.cs b/src/PSCompression/OnModuleImportAndRemove.cs index bc5b8b7..e100d8b 100644 --- a/src/PSCompression/OnModuleImportAndRemove.cs +++ b/src/PSCompression/OnModuleImportAndRemove.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Management.Automation; using System.Reflection; @@ -9,6 +10,7 @@ /// OnModuleImportAndRemove is a class that implements the IModuleAssemblyInitializer and IModuleAssemblyCleanup interfaces. /// This class is used to handle the assembly resolve event when the module is imported and removed. /// +[ExcludeFromCodeCoverage] public class OnModuleImportAndRemove : IModuleAssemblyInitializer, IModuleAssemblyCleanup { /// diff --git a/tests/EncodingCompleter.tests.ps1 b/tests/EncodingCompleter.tests.ps1 index b3b5544..a069285 100644 --- a/tests/EncodingCompleter.tests.ps1 +++ b/tests/EncodingCompleter.tests.ps1 @@ -41,10 +41,10 @@ Describe 'EncodingCompleter Class' { It 'Should not offer ansi as a completion result if the OS is not Windows' { if ($osIsWindows) { + (Complete 'Test-Completer ansi').CompletionText | Should -Not -BeNullOrEmpty return } - (Complete 'Test-Completer ansi').CompletionText | - Should -BeNullOrEmpty + (Complete 'Test-Completer ansi').CompletionText | Should -BeNullOrEmpty } } From a6e001e6731fab85218c60210a0fbdc04a89991b Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Tue, 17 Jun 2025 17:16:35 -0300 Subject: [PATCH 21/53] missing .test.ps1 file name on test file --- ...romStringCompression.ps1 => ToFromStringCompression.tests.ps1} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/{ToFromStringCompression.ps1 => ToFromStringCompression.tests.ps1} (100%) diff --git a/tests/ToFromStringCompression.ps1 b/tests/ToFromStringCompression.tests.ps1 similarity index 100% rename from tests/ToFromStringCompression.ps1 rename to tests/ToFromStringCompression.tests.ps1 From 6cbe59c325280c13b255f27ad92932e1dbee7e9d Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Tue, 17 Jun 2025 17:42:49 -0300 Subject: [PATCH 22/53] chagning Brotli.NET to BrotliSharpLib :pray: --- src/PSCompression/Abstractions/GetEntryCommandBase.cs | 4 +--- src/PSCompression/Commands/ConvertFromBrotliStringCommand.cs | 2 +- src/PSCompression/Extensions/CompressionExtensions.cs | 2 +- src/PSCompression/PSCompression.csproj | 2 +- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/PSCompression/Abstractions/GetEntryCommandBase.cs b/src/PSCompression/Abstractions/GetEntryCommandBase.cs index bc4290b..1844aca 100644 --- a/src/PSCompression/Abstractions/GetEntryCommandBase.cs +++ b/src/PSCompression/Abstractions/GetEntryCommandBase.cs @@ -7,7 +7,6 @@ using PSCompression.Exceptions; using ICSharpCode.SharpZipLib.Tar; using ZstdSharp; -using Brotli; namespace PSCompression.Abstractions; @@ -168,6 +167,5 @@ exception is InvalidDataException or TarException or ZstdException - or IOException - or BrotliDecodeException; + or IOException; } diff --git a/src/PSCompression/Commands/ConvertFromBrotliStringCommand.cs b/src/PSCompression/Commands/ConvertFromBrotliStringCommand.cs index 2dec69f..dfe29a7 100644 --- a/src/PSCompression/Commands/ConvertFromBrotliStringCommand.cs +++ b/src/PSCompression/Commands/ConvertFromBrotliStringCommand.cs @@ -1,7 +1,7 @@ using System.IO; using System.IO.Compression; using System.Management.Automation; -using Brotli; +using BrotliSharpLib; using PSCompression.Abstractions; namespace PSCompression.Commands; diff --git a/src/PSCompression/Extensions/CompressionExtensions.cs b/src/PSCompression/Extensions/CompressionExtensions.cs index 4e6ceb9..57838c2 100644 --- a/src/PSCompression/Extensions/CompressionExtensions.cs +++ b/src/PSCompression/Extensions/CompressionExtensions.cs @@ -5,7 +5,7 @@ using System.IO.Compression; using System.Management.Automation; using System.Text.RegularExpressions; -using Brotli; +using BrotliSharpLib; using ICSharpCode.SharpZipLib.BZip2; using ICSharpCode.SharpZipLib.Tar; using SharpCompress.Compressors.BZip2; diff --git a/src/PSCompression/PSCompression.csproj b/src/PSCompression/PSCompression.csproj index 2adb154..4a12f39 100644 --- a/src/PSCompression/PSCompression.csproj +++ b/src/PSCompression/PSCompression.csproj @@ -14,7 +14,7 @@ - + From 51f14caf797ad815e7a6b849c4315d063617aa3c Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Tue, 17 Jun 2025 18:02:47 -0300 Subject: [PATCH 23/53] updating changelog --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 45ee629..74bff12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,9 +17,9 @@ This update was made possible by the following projects. If you find them helpful, please consider starring their repositories: -- [Brotli.NET](https://github.com/XieJJ99/brotli.net) -- [SharpCompress](https://github.com/adamhathcock/sharpcompress) - [SharpZipLib](https://github.com/icsharpcode/SharpZipLib) +- [SharpCompress](https://github.com/adamhathcock/sharpcompress) +- [BrotliSharpLib](https://github.com/master131/BrotliSharpLib) ## 01/10/2025 From 02f935037cc0b3c480bfda3208473e2490e8a99c Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Wed, 18 Jun 2025 20:00:32 -0300 Subject: [PATCH 24/53] starting with expandtararchivecommand --- CHANGELOG.md | 2 +- .../Commands/ExpandTarArchiveCommand.cs | 36 +++++++++++++++++++ .../Commands/NewZipEntryCommand.cs | 14 ++++---- .../Extensions/CompressionExtensions.cs | 2 +- .../Extensions/PathExtensions.cs | 9 +---- tests/ZipEntryCmdlets.tests.ps1 | 33 ++++++++++++----- 6 files changed, 69 insertions(+), 27 deletions(-) create mode 100644 src/PSCompression/Commands/ExpandTarArchiveCommand.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 74bff12..b7cb655 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ## 06/17/2025 - Added commands supporting several algorithms to compress and decompress strings: - - `ConvertFrom-BrotliString` & `ConvertTo-BrotliString` (using to Brotli.NET) + - `ConvertFrom-BrotliString` & `ConvertTo-BrotliString` (using to BrotliSharpLib) - `ConvertFrom-DeflateString` & `ConvertTo-DeflateString` (from CLR) - `ConvertFrom-ZlibString` & `ConvertTo-ZlibString` (custom implementation) - Added commands for `.tar` entry management with a reduced set of operations compared to `zip` entry management: diff --git a/src/PSCompression/Commands/ExpandTarArchiveCommand.cs b/src/PSCompression/Commands/ExpandTarArchiveCommand.cs new file mode 100644 index 0000000..9f6555f --- /dev/null +++ b/src/PSCompression/Commands/ExpandTarArchiveCommand.cs @@ -0,0 +1,36 @@ +using System.Management.Automation; +using PSCompression.Abstractions; + +namespace PSCompression.Commands; + +[Cmdlet(VerbsData.Expand, "TarArchive")] +public sealed class ExpandTarArchiveCommand : CommandWithPathBase +{ + private bool _shouldInferAlgo; + + [Parameter(Mandatory = true, Position = 1)] + public string Destination { get; set; } = null!; + + [Parameter] + public Algorithm Algorithm { get; set; } + + [Parameter] + public SwitchParameter Force { get; set; } + + [Parameter] + public SwitchParameter PassThru { get; set; } + + protected override void BeginProcessing() => + _shouldInferAlgo = !MyInvocation.BoundParameters.ContainsKey(nameof(Algorithm)); + + protected override void ProcessRecord() + { + foreach (string path in EnumerateResolvedPaths()) + { + if (_shouldInferAlgo) + { + Algorithm = AlgorithmMappings.Parse(path); + } + } + } +} diff --git a/src/PSCompression/Commands/NewZipEntryCommand.cs b/src/PSCompression/Commands/NewZipEntryCommand.cs index ac34d40..44b9b9c 100644 --- a/src/PSCompression/Commands/NewZipEntryCommand.cs +++ b/src/PSCompression/Commands/NewZipEntryCommand.cs @@ -73,10 +73,9 @@ protected override void BeginProcessing() { if (!Force.IsPresent) { - WriteError( - DuplicatedEntryException - .Create(entry, Destination) - .ToDuplicatedEntryError()); + WriteError(DuplicatedEntryException + .Create(entry, Destination) + .ToDuplicatedEntryError()); continue; } @@ -109,10 +108,9 @@ protected override void BeginProcessing() { if (!Force.IsPresent) { - WriteError( - DuplicatedEntryException - .Create(entry, Destination) - .ToDuplicatedEntryError()); + WriteError(DuplicatedEntryException + .Create(entry, Destination) + .ToDuplicatedEntryError()); continue; } diff --git a/src/PSCompression/Extensions/CompressionExtensions.cs b/src/PSCompression/Extensions/CompressionExtensions.cs index 57838c2..236b986 100644 --- a/src/PSCompression/Extensions/CompressionExtensions.cs +++ b/src/PSCompression/Extensions/CompressionExtensions.cs @@ -35,7 +35,7 @@ internal static ZipArchiveEntry CreateEntryFromFile( FileStream fileStream, CompressionLevel compressionLevel) { - if (entry.IsDirectoryPath()) + if (entry.EndsWith("/") || entry.EndsWith("\\")) { return zip.CreateEntry(entry); } diff --git a/src/PSCompression/Extensions/PathExtensions.cs b/src/PSCompression/Extensions/PathExtensions.cs index 3fc59ea..5c5a22a 100644 --- a/src/PSCompression/Extensions/PathExtensions.cs +++ b/src/PSCompression/Extensions/PathExtensions.cs @@ -13,10 +13,6 @@ public static class PathExtensions @"(?:^[a-z]:)?[\\/]+|(? internal static string NormalizeFileEntryPath(this string path) => NormalizeEntryPath(path).TrimEnd('/'); - internal static bool IsDirectoryPath(this string path) => - s_reEntryDir.IsMatch(path); - public static string NormalizePath(this string path) => - s_reEntryDir.IsMatch(path) + path.EndsWith("/") || path.EndsWith("\\") ? NormalizeEntryPath(path) : NormalizeFileEntryPath(path); } diff --git a/tests/ZipEntryCmdlets.tests.ps1 b/tests/ZipEntryCmdlets.tests.ps1 index 45527b9..c62cf5a 100644 --- a/tests/ZipEntryCmdlets.tests.ps1 +++ b/tests/ZipEntryCmdlets.tests.ps1 @@ -1,15 +1,19 @@ -$ErrorActionPreference = 'Stop' +using namespace System.IO +using namespace PSCompression +using namespace PSCompression.Extensions -$moduleName = (Get-Item ([IO.Path]::Combine($PSScriptRoot, '..', 'module', '*.psd1'))).BaseName -$manifestPath = [IO.Path]::Combine($PSScriptRoot, '..', 'output', $moduleName) +$ErrorActionPreference = 'Stop' + +$moduleName = (Get-Item ([Path]::Combine($PSScriptRoot, '..', 'module', '*.psd1'))).BaseName +$manifestPath = [Path]::Combine($PSScriptRoot, '..', 'output', $moduleName) Import-Module $manifestPath -Import-Module ([System.IO.Path]::Combine($PSScriptRoot, 'shared.psm1')) +Import-Module ([Path]::Combine($PSScriptRoot, 'shared.psm1')) -Describe 'ZipEntry Cmdlets' { +Describe 'Entry Cmdlets' { BeforeAll { $zip = New-Item (Join-Path $TestDrive test.zip) -ItemType File -Force - $file = New-Item ([System.IO.Path]::Combine($TestDrive, 'someFile.txt')) -ItemType File -Value 'foo' + $file = New-Item ([Path]::Combine($TestDrive, 'someFile.txt')) -ItemType File -Value 'foo' $uri = 'https://www.powershellgallery.com/api/v2/package/PSCompression' $zip, $file, $uri | Out-Null } @@ -42,12 +46,12 @@ Describe 'ZipEntry Cmdlets' { It 'Can create new zip file entries' { New-ZipEntry $zip.FullName -EntryPath test\newentry.txt | - Should -BeOfType ([PSCompression.ZipEntryFile]) + Should -BeOfType ([ZipEntryFile]) } It 'Can create new zip directory entries' { New-ZipEntry $zip.FullName -EntryPath test\ | - Should -BeOfType ([PSCompression.ZipEntryDirectory]) + Should -BeOfType ([ZipEntryDirectory]) } It 'Can create multiple entries' { @@ -108,7 +112,18 @@ Describe 'ZipEntry Cmdlets' { $entry = New-ZipEntry @newZipEntrySplat $entry | Get-ZipEntryContent | Should -Be 'hello world!' $entry.RelativePath | - Should -Be ([PSCompression.Extensions.PathExtensions]::NormalizePath($item.FullName)) + Should -Be ([PathExtensions]::NormalizePath($item.FullName)) + } + + It 'Ignores copying content to a ZipEntryDirectory from source file' { + $newZipEntrySplat = @{ + SourcePath = (Join-Path $TestDrive helloworld.txt) + Destination = $zip.FullName + EntryPath = 'directoryEntry/', 'directoryEntry2\' + } + + $entry = New-ZipEntry @newZipEntrySplat + $entry | ForEach-Object { $_.Length | Should -BeExactly 0 } } } From 53734f45fd54ec9b633bd2a8c62303d67bc99f53 Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Thu, 19 Jun 2025 14:52:50 -0300 Subject: [PATCH 25/53] done with expandtararchive --- CHANGELOG.md | 2 +- module/PSCompression.psd1 | 1 + .../Abstractions/TarEntryBase.cs | 10 +- .../Abstractions/ZipEntryBase.cs | 3 +- src/PSCompression/Algorithm.cs | 1 - src/PSCompression/AlgorithmMappings.cs | 7 -- .../Commands/CompressTarArchiveCommand.cs | 5 +- .../Commands/ExpandTarArchiveCommand.cs | 114 +++++++++++++++++- .../Exceptions/ExceptionHelper.cs | 9 +- .../Extensions/CompressionExtensions.cs | 2 - .../Extensions/PathExtensions.cs | 31 ++++- src/PSCompression/SortingOps.cs | 3 +- src/PSCompression/TarEntryFile.cs | 8 +- 13 files changed, 158 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b7cb655..27908fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ - `Get-TarEntryContent`: Retrieves the content of a tar entry. - `Expand-TarEntry`: Extracts a tar entry to a file. - Added commands to compress files and folders into `.tar` archives and extract `.tar` archives with various compression algorithms: - - `Compress-TarArchive` and `Expand-TarArchive`: Supported compression algorithms include `gz`, `br`, `bz2`, `zst`, `lz`, and `none` (no compression). + - `Compress-TarArchive` and `Expand-TarArchive`: Supported compression algorithms include `gz`, `bz2`, `zst`, `lz`, and `none` (no compression). - Removed commands: - `Compress-GzipArchive` and `Expand-GzipArchive`: These were deprecated as they only supported single-file compression, which is now better handled by the module’s `.tar` archive functionality. diff --git a/module/PSCompression.psd1 b/module/PSCompression.psd1 index 052647e..cd86614 100644 --- a/module/PSCompression.psd1 +++ b/module/PSCompression.psd1 @@ -95,6 +95,7 @@ 'Get-TarEntry' 'Get-TarEntryContent' 'Expand-TarEntry' + 'Expand-TarArchive' ) # Variables to export from this module diff --git a/src/PSCompression/Abstractions/TarEntryBase.cs b/src/PSCompression/Abstractions/TarEntryBase.cs index 628a8d8..11f41cf 100644 --- a/src/PSCompression/Abstractions/TarEntryBase.cs +++ b/src/PSCompression/Abstractions/TarEntryBase.cs @@ -1,6 +1,7 @@ using System; using System.IO; using ICSharpCode.SharpZipLib.Tar; +using PSCompression.Extensions; namespace PSCompression.Abstractions; @@ -24,14 +25,16 @@ internal FileSystemInfo ExtractTo( string destination, bool overwrite) { - destination = Path.GetFullPath(Path.Combine(destination, RelativePath)); + destination = Path.GetFullPath( + Path.Combine(destination, RelativePath)); + if (this is not TarEntryFile entryFile) { Directory.CreateDirectory(destination); return new DirectoryInfo(destination); } - string parent = Path.GetDirectoryName(destination); + string parent = destination.GetParent(); if (!Directory.Exists(parent)) { Directory.CreateDirectory(parent); @@ -39,7 +42,8 @@ internal FileSystemInfo ExtractTo( using FileStream destStream = File.Open( destination, - overwrite ? FileMode.Create : FileMode.CreateNew); + overwrite ? FileMode.Create : FileMode.CreateNew, + FileAccess.Write); entryFile.GetContentStream(destStream); return new FileInfo(destination); diff --git a/src/PSCompression/Abstractions/ZipEntryBase.cs b/src/PSCompression/Abstractions/ZipEntryBase.cs index 66c332d..d808e1a 100644 --- a/src/PSCompression/Abstractions/ZipEntryBase.cs +++ b/src/PSCompression/Abstractions/ZipEntryBase.cs @@ -2,6 +2,7 @@ using System.IO; using System.IO.Compression; using PSCompression.Exceptions; +using PSCompression.Extensions; namespace PSCompression.Abstractions; @@ -115,7 +116,7 @@ internal FileSystemInfo ExtractTo( return new DirectoryInfo(destination); } - string parent = Path.GetDirectoryName(destination); + string parent = destination.GetParent(); if (!Directory.Exists(parent)) { Directory.CreateDirectory(parent); diff --git a/src/PSCompression/Algorithm.cs b/src/PSCompression/Algorithm.cs index 3ada823..b3967fb 100644 --- a/src/PSCompression/Algorithm.cs +++ b/src/PSCompression/Algorithm.cs @@ -3,7 +3,6 @@ namespace PSCompression; public enum Algorithm { gz, - br, bz2, zst, lz, diff --git a/src/PSCompression/AlgorithmMappings.cs b/src/PSCompression/AlgorithmMappings.cs index 2cd6eda..f9fc456 100644 --- a/src/PSCompression/AlgorithmMappings.cs +++ b/src/PSCompression/AlgorithmMappings.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; namespace PSCompression; @@ -15,9 +14,6 @@ internal static class AlgorithmMappings [".gzip"] = Algorithm.gz, [".tgz"] = Algorithm.gz, - // Brotli - [".br"] = Algorithm.br, - // Bzip2 [".bz2"] = Algorithm.bz2, [".bzip2"] = Algorithm.bz2, @@ -37,7 +33,4 @@ internal static class AlgorithmMappings internal static Algorithm Parse(string path) => _mappings.TryGetValue(Path.GetExtension(path), out Algorithm value) ? value : Algorithm.none; - - internal static bool HasExtension(string path) => - _mappings.Keys.Any(e => path.EndsWith(e, StringComparison.InvariantCultureIgnoreCase)); } diff --git a/src/PSCompression/Commands/CompressTarArchiveCommand.cs b/src/PSCompression/Commands/CompressTarArchiveCommand.cs index 656688a..56f28fd 100644 --- a/src/PSCompression/Commands/CompressTarArchiveCommand.cs +++ b/src/PSCompression/Commands/CompressTarArchiveCommand.cs @@ -25,7 +25,10 @@ public sealed class CompressTarArchiveCommand : ToCompressedFileCommandBase Algorithm is Algorithm.none ? ".tar" : $".tar.{Algorithm}"; + protected override string FileExtension + { + get => Algorithm == Algorithm.none ? ".tar" : $".tar.{Algorithm}"; + } protected override TarOutputStream CreateCompressionStream(Stream outputStream) { diff --git a/src/PSCompression/Commands/ExpandTarArchiveCommand.cs b/src/PSCompression/Commands/ExpandTarArchiveCommand.cs index 9f6555f..af9d5cc 100644 --- a/src/PSCompression/Commands/ExpandTarArchiveCommand.cs +++ b/src/PSCompression/Commands/ExpandTarArchiveCommand.cs @@ -1,15 +1,23 @@ +using System; +using System.IO; using System.Management.Automation; +using System.Text; +using ICSharpCode.SharpZipLib.Tar; using PSCompression.Abstractions; +using PSCompression.Exceptions; +using PSCompression.Extensions; +using IO = System.IO; namespace PSCompression.Commands; [Cmdlet(VerbsData.Expand, "TarArchive")] +[OutputType(typeof(FileInfo), typeof(DirectoryInfo))] public sealed class ExpandTarArchiveCommand : CommandWithPathBase { private bool _shouldInferAlgo; - [Parameter(Mandatory = true, Position = 1)] - public string Destination { get; set; } = null!; + [Parameter(Position = 1)] + public string? Destination { get; set; } [Parameter] public Algorithm Algorithm { get; set; } @@ -20,17 +28,115 @@ public sealed class ExpandTarArchiveCommand : CommandWithPathBase [Parameter] public SwitchParameter PassThru { get; set; } - protected override void BeginProcessing() => - _shouldInferAlgo = !MyInvocation.BoundParameters.ContainsKey(nameof(Algorithm)); + protected override void BeginProcessing() + { + Destination = Destination is null + ? SessionState.Path.CurrentFileSystemLocation.Path + : Destination.ResolvePath(this); + + if (File.Exists(Destination)) + { + ThrowTerminatingError(ExceptionHelper.NotDirectoryPath( + Destination, nameof(Destination))); + } + + if (!Directory.Exists(Destination)) + { + Directory.CreateDirectory(Destination); + } + + _shouldInferAlgo = !MyInvocation + .BoundParameters + .ContainsKey(nameof(Algorithm)); + } protected override void ProcessRecord() { + Dbg.Assert(Destination is not null); + foreach (string path in EnumerateResolvedPaths()) { if (_shouldInferAlgo) { Algorithm = AlgorithmMappings.Parse(path); } + + try + { + ExtractArchive(path); + } + catch (Exception _) when (_ is PipelineStoppedException or FlowControlException) + { + throw; + } + catch (Exception exception) + { + WriteError(exception.ToWriteError(path)); + } + } + } + + private void ExtractArchive(string path) + { + using FileStream fs = File.OpenRead(path); + using Stream decompress = Algorithm.FromCompressedStream(fs); + using TarInputStream tar = new(decompress, Encoding.UTF8); + + foreach (TarEntry entry in tar.EnumerateEntries()) + { + try + { + ExtractEntry(entry, tar); + } + catch (Exception _) when (_ is PipelineStoppedException or FlowControlException) + { + throw; + } + catch (Exception exception) + { + WriteError(exception.ToExtractEntryError(entry)); + } + } + } + + private void ExtractEntry(TarEntry entry, TarInputStream tar) + { + string destination = IO.Path.GetFullPath( + IO.Path.Combine(Destination, entry.Name)); + + if (entry.IsDirectory) + { + DirectoryInfo dir = new(destination); + dir.Create(Force); + + if (PassThru) + { + WriteObject(dir.AppendPSProperties(dir.Parent.FullName)); + } + + return; + } + + string parent = destination.GetParent(); + if (!Directory.Exists(parent)) + { + Directory.CreateDirectory(parent); + } + + using (FileStream destStream = File.Open( + destination, + Force ? FileMode.Create : FileMode.CreateNew, + FileAccess.Write)) + { + if (entry.Size > 0) + { + tar.CopyTo(destStream, (int)entry.Size); + } + } + + if (PassThru) + { + WriteObject(new FileInfo(destination).AppendPSProperties(parent)); } } } diff --git a/src/PSCompression/Exceptions/ExceptionHelper.cs b/src/PSCompression/Exceptions/ExceptionHelper.cs index d24006d..8ab9059 100644 --- a/src/PSCompression/Exceptions/ExceptionHelper.cs +++ b/src/PSCompression/Exceptions/ExceptionHelper.cs @@ -59,14 +59,11 @@ internal static ErrorRecord ToOpenError(this Exception exception, string path) = internal static ErrorRecord ToResolvePathError(this Exception exception, string path) => new(exception, "ResolvePath", ErrorCategory.NotSpecified, path); - internal static ErrorRecord ToExtractEntryError(this Exception exception, EntryBase entry) => + internal static ErrorRecord ToExtractEntryError(this Exception exception, object entry) => new(exception, "ExtractEntry", ErrorCategory.NotSpecified, entry); - internal static ErrorRecord ToStreamOpenError(this Exception exception, ZipEntryBase entry) => - new(exception, "StreamOpen", ErrorCategory.NotSpecified, entry); - - internal static ErrorRecord ToStreamOpenError(this Exception exception, string path) => - new(exception, "StreamOpen", ErrorCategory.NotSpecified, path); + internal static ErrorRecord ToStreamOpenError(this Exception exception, object item) => + new(exception, "StreamOpen", ErrorCategory.NotSpecified, item); internal static ErrorRecord ToWriteError(this Exception exception, object? item) => new(exception, "WriteError", ErrorCategory.WriteError, item); diff --git a/src/PSCompression/Extensions/CompressionExtensions.cs b/src/PSCompression/Extensions/CompressionExtensions.cs index 236b986..1046511 100644 --- a/src/PSCompression/Extensions/CompressionExtensions.cs +++ b/src/PSCompression/Extensions/CompressionExtensions.cs @@ -174,7 +174,6 @@ internal static Stream ToCompressedStream( => algorithm switch { Algorithm.gz => new GZipStream(stream, compressionLevel), - Algorithm.br => stream.AsBrotliCompressedStream(compressionLevel), Algorithm.zst => stream.AsZstCompressedStream(compressionLevel), Algorithm.lz => stream.AsLzCompressedStream(), Algorithm.bz2 => stream.AsBZip2CompressedStream(compressionLevel), @@ -187,7 +186,6 @@ internal static Stream FromCompressedStream( => algorithm switch { Algorithm.gz => new GZipStream(stream, CompressionMode.Decompress), - Algorithm.br => new BrotliStream(stream, CompressionMode.Decompress), Algorithm.zst => new DecompressionStream(stream), Algorithm.lz => new LZipStream(stream, SharpCompressors.CompressionMode.Decompress), Algorithm.bz2 => new BZip2Stream(stream, SharpCompressors.CompressionMode.Decompress, true), diff --git a/src/PSCompression/Extensions/PathExtensions.cs b/src/PSCompression/Extensions/PathExtensions.cs index 5c5a22a..6cf79f6 100644 --- a/src/PSCompression/Extensions/PathExtensions.cs +++ b/src/PSCompression/Extensions/PathExtensions.cs @@ -15,6 +15,11 @@ public static class PathExtensions private const string _directorySeparator = "/"; + public static string NormalizePath(this string path) => + path.EndsWith("/") || path.EndsWith("\\") + ? NormalizeEntryPath(path) + : NormalizeFileEntryPath(path); + internal static string ResolvePath(this string path, PSCmdlet cmdlet) { string resolved = cmdlet.SessionState.Path.GetUnresolvedProviderPathFromPSPath( @@ -52,8 +57,7 @@ internal static bool Validate( internal static string AddExtensionIfMissing(this string path, string extension) { - if (!path.EndsWith(extension, StringComparison.InvariantCultureIgnoreCase) - && !AlgorithmMappings.HasExtension(path)) + if (!path.EndsWith(extension, StringComparison.InvariantCultureIgnoreCase)) { path += extension; } @@ -67,8 +71,23 @@ internal static string NormalizeEntryPath(this string path) => internal static string NormalizeFileEntryPath(this string path) => NormalizeEntryPath(path).TrimEnd('/'); - public static string NormalizePath(this string path) => - path.EndsWith("/") || path.EndsWith("\\") - ? NormalizeEntryPath(path) - : NormalizeFileEntryPath(path); + internal static void Create(this DirectoryInfo dir, bool force) + { + if (force || !dir.Exists) + { + dir.Create(); + return; + } + + throw new IOException($"The directory '{dir.FullName}' already exists."); + } + + internal static PSObject AppendPSProperties(this FileSystemInfo info, string parent) + { + const string provider = @"Microsoft.PowerShell.Core\FileSystem::"; + PSObject pso = PSObject.AsPSObject(info); + pso.Properties.Add(new PSNoteProperty("PSPath", $"{provider}{info.FullName}")); + pso.Properties.Add(new PSNoteProperty("PSParentPath", $"{provider}{parent}")); + return pso; + } } diff --git a/src/PSCompression/SortingOps.cs b/src/PSCompression/SortingOps.cs index 23891f7..189ce0f 100644 --- a/src/PSCompression/SortingOps.cs +++ b/src/PSCompression/SortingOps.cs @@ -9,8 +9,7 @@ namespace PSCompression; internal static class SortingOps { private static string SortByParent(EntryBase entry) => - Path.GetDirectoryName(entry.RelativePath) - .NormalizeEntryPath(); + entry.RelativePath.GetParent().NormalizeEntryPath(); private static int SortByLength(EntryBase entry) => entry.RelativePath.Count(e => e == '/'); diff --git a/src/PSCompression/TarEntryFile.cs b/src/PSCompression/TarEntryFile.cs index cf894ae..6ee3676 100644 --- a/src/PSCompression/TarEntryFile.cs +++ b/src/PSCompression/TarEntryFile.cs @@ -30,9 +30,9 @@ internal TarEntryFile(TarEntry entry, Stream? stream, Algorithm algorithm) } protected override string GetFormatDirectoryPath() => - $"/{Path.GetDirectoryName(RelativePath).NormalizeEntryPath()}"; + $"/{RelativePath.GetParent().NormalizeEntryPath()}"; - internal bool GetContentStream(Stream stream) + internal bool GetContentStream(Stream destination) { Stream? sourceStream = null; Stream? decompressedStream = null; @@ -54,8 +54,8 @@ internal bool GetContentStream(Stream stream) return false; } - tar.CopyTo(stream, (int)entry.Size); - stream.Seek(0, SeekOrigin.Begin); + tar.CopyTo(destination, (int)entry.Size); + destination.Seek(0, SeekOrigin.Begin); return true; } finally From 727e159cb861fafc3870ae9c5fbb1b246b753fac Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Thu, 19 Jun 2025 15:59:58 -0300 Subject: [PATCH 26/53] some code improvements. simplifications... --- .../Abstractions/ExpandEntryCommandBase.cs | 12 ++++++----- .../Abstractions/TarEntryBase.cs | 14 ++++++------- .../ToCompressedFileCommandBase.cs | 8 ++------ .../Abstractions/ZipEntryBase.cs | 20 +++++++++---------- .../Commands/ExpandTarArchiveCommand.cs | 14 ++++--------- .../Commands/ExpandTarEntryCommand.cs | 2 +- .../Commands/ExpandZipEntryCommand.cs | 2 +- .../Extensions/PathExtensions.cs | 2 -- src/PSCompression/SortingOps.cs | 2 +- src/PSCompression/TarEntryFile.cs | 2 +- 10 files changed, 33 insertions(+), 45 deletions(-) diff --git a/src/PSCompression/Abstractions/ExpandEntryCommandBase.cs b/src/PSCompression/Abstractions/ExpandEntryCommandBase.cs index 3399491..45f4d3c 100644 --- a/src/PSCompression/Abstractions/ExpandEntryCommandBase.cs +++ b/src/PSCompression/Abstractions/ExpandEntryCommandBase.cs @@ -4,6 +4,7 @@ using PSCompression.Extensions; using PSCompression.Exceptions; using System.ComponentModel; +using System.Runtime.CompilerServices; namespace PSCompression.Abstractions; @@ -36,10 +37,7 @@ protected override void BeginProcessing() Destination, nameof(Destination))); } - if (!Directory.Exists(Destination)) - { - Directory.CreateDirectory(Destination); - } + Directory.CreateDirectory(Destination); } protected override void ProcessRecord() @@ -54,7 +52,11 @@ protected override void ProcessRecord() if (PassThru) { - WriteObject(info); + string parent = info is DirectoryInfo dir + ? dir.Parent.FullName + : Unsafe.As(info).DirectoryName; + + WriteObject(info.AppendPSProperties(parent)); } } catch (Exception _) when (_ is PipelineStoppedException or FlowControlException) diff --git a/src/PSCompression/Abstractions/TarEntryBase.cs b/src/PSCompression/Abstractions/TarEntryBase.cs index 11f41cf..b83a4b4 100644 --- a/src/PSCompression/Abstractions/TarEntryBase.cs +++ b/src/PSCompression/Abstractions/TarEntryBase.cs @@ -30,15 +30,13 @@ internal FileSystemInfo ExtractTo( if (this is not TarEntryFile entryFile) { - Directory.CreateDirectory(destination); - return new DirectoryInfo(destination); + DirectoryInfo dir = new(destination); + dir.Create(overwrite); + return dir; } - string parent = destination.GetParent(); - if (!Directory.Exists(parent)) - { - Directory.CreateDirectory(parent); - } + FileInfo file = new(destination); + file.Directory.Create(); using FileStream destStream = File.Open( destination, @@ -46,6 +44,6 @@ internal FileSystemInfo ExtractTo( FileAccess.Write); entryFile.GetContentStream(destStream); - return new FileInfo(destination); + return file; } } diff --git a/src/PSCompression/Abstractions/ToCompressedFileCommandBase.cs b/src/PSCompression/Abstractions/ToCompressedFileCommandBase.cs index ac2925c..4121fd4 100644 --- a/src/PSCompression/Abstractions/ToCompressedFileCommandBase.cs +++ b/src/PSCompression/Abstractions/ToCompressedFileCommandBase.cs @@ -7,6 +7,7 @@ using PSCompression.Extensions; using PSCompression.Exceptions; using System.ComponentModel; +using IOPath = System.IO.Path; namespace PSCompression.Abstractions; @@ -68,12 +69,7 @@ protected override void BeginProcessing() try { - string parent = Destination.GetParent(); - - if (!Directory.Exists(parent)) - { - Directory.CreateDirectory(parent); - } + Directory.CreateDirectory(IOPath.GetDirectoryName(Destination)); _destination = File.Open(Destination, FileMode); _archive = CreateCompressionStream(_destination); diff --git a/src/PSCompression/Abstractions/ZipEntryBase.cs b/src/PSCompression/Abstractions/ZipEntryBase.cs index d808e1a..d4a4344 100644 --- a/src/PSCompression/Abstractions/ZipEntryBase.cs +++ b/src/PSCompression/Abstractions/ZipEntryBase.cs @@ -109,21 +109,21 @@ internal FileSystemInfo ExtractTo( bool overwrite, ZipArchive zip) { - destination = Path.GetFullPath(Path.Combine(destination, RelativePath)); - if (Type is EntryType.Directory) - { - Directory.CreateDirectory(destination); - return new DirectoryInfo(destination); - } + destination = Path.GetFullPath( + Path.Combine(destination, RelativePath)); - string parent = destination.GetParent(); - if (!Directory.Exists(parent)) + if (Type == EntryType.Directory) { - Directory.CreateDirectory(parent); + DirectoryInfo dir = new(destination); + dir.Create(overwrite); + return dir; } + FileInfo file = new(destination); + file.Directory.Create(); + ZipArchiveEntry entry = zip.GetEntry(RelativePath); entry.ExtractToFile(destination, overwrite); - return new FileInfo(destination); + return file; } } diff --git a/src/PSCompression/Commands/ExpandTarArchiveCommand.cs b/src/PSCompression/Commands/ExpandTarArchiveCommand.cs index af9d5cc..a0bbbef 100644 --- a/src/PSCompression/Commands/ExpandTarArchiveCommand.cs +++ b/src/PSCompression/Commands/ExpandTarArchiveCommand.cs @@ -40,10 +40,7 @@ protected override void BeginProcessing() Destination, nameof(Destination))); } - if (!Directory.Exists(Destination)) - { - Directory.CreateDirectory(Destination); - } + Directory.CreateDirectory(Destination); _shouldInferAlgo = !MyInvocation .BoundParameters @@ -117,11 +114,8 @@ private void ExtractEntry(TarEntry entry, TarInputStream tar) return; } - string parent = destination.GetParent(); - if (!Directory.Exists(parent)) - { - Directory.CreateDirectory(parent); - } + FileInfo file = new(destination); + file.Directory.Create(); using (FileStream destStream = File.Open( destination, @@ -136,7 +130,7 @@ private void ExtractEntry(TarEntry entry, TarInputStream tar) if (PassThru) { - WriteObject(new FileInfo(destination).AppendPSProperties(parent)); + WriteObject(file.AppendPSProperties(file.DirectoryName)); } } } diff --git a/src/PSCompression/Commands/ExpandTarEntryCommand.cs b/src/PSCompression/Commands/ExpandTarEntryCommand.cs index ff22321..ecbc42c 100644 --- a/src/PSCompression/Commands/ExpandTarEntryCommand.cs +++ b/src/PSCompression/Commands/ExpandTarEntryCommand.cs @@ -5,7 +5,7 @@ namespace PSCompression.Commands; [Cmdlet(VerbsData.Expand, "TarEntry")] -[OutputType(typeof(FileSystemInfo))] +[OutputType(typeof(FileInfo), typeof(DirectoryInfo))] public sealed class ExpandTarEntryCommand : ExpandEntryCommandBase { protected override FileSystemInfo Extract(TarEntryBase entry) => diff --git a/src/PSCompression/Commands/ExpandZipEntryCommand.cs b/src/PSCompression/Commands/ExpandZipEntryCommand.cs index f1c02b3..dd60197 100644 --- a/src/PSCompression/Commands/ExpandZipEntryCommand.cs +++ b/src/PSCompression/Commands/ExpandZipEntryCommand.cs @@ -6,7 +6,7 @@ namespace PSCompression.Commands; [Cmdlet(VerbsData.Expand, "ZipEntry")] -[OutputType(typeof(FileSystemInfo))] +[OutputType(typeof(FileInfo), typeof(DirectoryInfo))] public sealed class ExpandZipEntryCommand : ExpandEntryCommandBase, IDisposable { private readonly ZipArchiveCache _cache = new(); diff --git a/src/PSCompression/Extensions/PathExtensions.cs b/src/PSCompression/Extensions/PathExtensions.cs index 6cf79f6..c64aae4 100644 --- a/src/PSCompression/Extensions/PathExtensions.cs +++ b/src/PSCompression/Extensions/PathExtensions.cs @@ -53,8 +53,6 @@ internal static bool Validate( return false; } - internal static string GetParent(this string path) => Path.GetDirectoryName(path); - internal static string AddExtensionIfMissing(this string path, string extension) { if (!path.EndsWith(extension, StringComparison.InvariantCultureIgnoreCase)) diff --git a/src/PSCompression/SortingOps.cs b/src/PSCompression/SortingOps.cs index 189ce0f..b275207 100644 --- a/src/PSCompression/SortingOps.cs +++ b/src/PSCompression/SortingOps.cs @@ -9,7 +9,7 @@ namespace PSCompression; internal static class SortingOps { private static string SortByParent(EntryBase entry) => - entry.RelativePath.GetParent().NormalizeEntryPath(); + Path.GetDirectoryName(entry.RelativePath).NormalizeEntryPath(); private static int SortByLength(EntryBase entry) => entry.RelativePath.Count(e => e == '/'); diff --git a/src/PSCompression/TarEntryFile.cs b/src/PSCompression/TarEntryFile.cs index 6ee3676..10773f7 100644 --- a/src/PSCompression/TarEntryFile.cs +++ b/src/PSCompression/TarEntryFile.cs @@ -30,7 +30,7 @@ internal TarEntryFile(TarEntry entry, Stream? stream, Algorithm algorithm) } protected override string GetFormatDirectoryPath() => - $"/{RelativePath.GetParent().NormalizeEntryPath()}"; + $"/{Path.GetDirectoryName(RelativePath).NormalizeEntryPath()}"; internal bool GetContentStream(Stream destination) { From 45484e93ba1b939917dae5f4650b71fce3a35715 Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Thu, 19 Jun 2025 20:32:42 -0300 Subject: [PATCH 27/53] adding tests --- ...> CompressExpandArchiveCommands.tests.ps1} | 88 ++++++++++++------- 1 file changed, 58 insertions(+), 30 deletions(-) rename tests/{CompressArchiveCommands.tests.ps1 => CompressExpandArchiveCommands.tests.ps1} (60%) diff --git a/tests/CompressArchiveCommands.tests.ps1 b/tests/CompressExpandArchiveCommands.tests.ps1 similarity index 60% rename from tests/CompressArchiveCommands.tests.ps1 rename to tests/CompressExpandArchiveCommands.tests.ps1 index 3e399dc..e439dad 100644 --- a/tests/CompressArchiveCommands.tests.ps1 +++ b/tests/CompressExpandArchiveCommands.tests.ps1 @@ -1,4 +1,5 @@ using namespace System.IO +using namespace System.Collections.Generic $ErrorActionPreference = 'Stop' @@ -8,25 +9,37 @@ $manifestPath = [Path]::Combine($PSScriptRoot, '..', 'output', $moduleName) Import-Module $manifestPath Import-Module ([Path]::Combine($PSScriptRoot, 'shared.psm1')) -Describe 'CompressArchive Commands' -Tag 'CompressArchive Commands' { +Describe 'Compress & Expand Archive Commands' -Tag 'Compress & Expand Archive Commands' { BeforeAll { $algos = [PSCompression.Algorithm].GetEnumValues() + $sourceName = 'CompressArchiveTests' $destName = 'CompressArchiveExtract' + $testpath = Join-Path $TestDrive $sourceName $extractpath = Join-Path $TestDrive $destName + + $fileCount = $dirCount = 0 $structure = Get-Structure | ForEach-Object { $newItemSplat = @{ ItemType = ('Directory', 'File')[$_.EndsWith('.txt')] - Value = (Get-Random) + Value = Get-Random Force = $true Path = Join-Path $testpath $_ } New-Item @newItemSplat + + if ($newItemSplat['ItemType'] -eq 'Directory') { + $dirCount++ + } + else { + $fileCount++ + } } - $structure, $extractpath, $algos | Out-Null + $dirCount++ # Includes the folder itself + $extractpath, $structure, $algos | Out-Null } It 'Can compress a folder and all its child items' { @@ -60,29 +73,24 @@ Describe 'CompressArchive Commands' -Tag 'CompressArchive Commands' { } It 'Extracted files should be exactly the same with the same structure' { - BeforeAll { - $map = @{} - Get-ChildItem $testpath -Recurse | ForEach-Object { - $relative = $_.FullName.Substring($testpath.Length) - if ($_ -is [FileInfo]) { - $map[$relative] = ($_ | Get-FileHash -Algorithm MD5).Hash - return - } - $map[$relative] = $null - } + Get-ChildItem $TestDrive -File | ForEach-Object { + $destination = [Path]::Combine($TestDrive, "ExpandTest_$($_.Extension.TrimStart('.'))") - Expand-Archive "$extractpath.zip" $extractpath + if ($_.Extension -eq '.zip') { + $_ | Expand-Archive -DestinationPath $destination + } + elseif ($_.Extension -match '\.tar.*') { + $_ | Expand-TarArchive -Destination $destination + } + else { + return + } - $extractpath = Join-Path $extractpath $sourceName - Get-ChildItem $extractpath -Recurse | ForEach-Object { - $relative = $_.FullName.Substring($extractpath.Length) - $map.ContainsKey($relative) | Should -BeTrue + $expanded = Get-ChildItem $destination -Recurse + $files, $dirs = $expanded.Where({ $_ -is [FileInfo] }, 'Split') - if ($_ -is [FileInfo]) { - $thishash = ($_ | Get-FileHash -Algorithm MD5).Hash - $map[$relative] | Should -BeExactly $thishash - } - } + $files | Should -HaveCount $fileCount + $dirs | Should -HaveCount $dirCount } } @@ -107,21 +115,41 @@ Describe 'CompressArchive Commands' -Tag 'CompressArchive Commands' { It 'Should skip the entry if the source and destination are the same' { Push-Location $TestDrive - $zipname = 'testskipitself.zip' + $name = 'testskipitself' - { Compress-ZipArchive $pwd.Path $zipname } | + { Compress-ZipArchive $pwd.Path $name } | Should -Not -Throw - { Compress-ZipArchive $pwd.Path $zipname -Force } | + { Compress-ZipArchive $pwd.Path $name -Force } | Should -Not -Throw - { Compress-ZipArchive $pwd.Path $zipname -Update } | + { Compress-ZipArchive $pwd.Path $name -Update } | Should -Not -Throw - Expand-Archive $zipname -DestinationPath skipitself + $destination = [guid]::NewGuid() + Expand-Archive "${name}.zip" -DestinationPath $destination + + Get-ChildItem $destination -Recurse | ForEach-Object Name | + Should -Not -Contain "${name}.zip" - Get-ChildItem skipitself -Recurse | ForEach-Object Name | - Should -Not -Contain $zipname + $archive = [Queue[FileInfo]]::new() + $algos | ForEach-Object { + $currentName = "${name}_${_}" + { Compress-TarArchive $pwd.Path $currentName -Algorithm $_ } | + Should -Not -Throw + + + { + $result = Compress-TarArchive $pwd.Path $currentName -Algorithm $_ -Force -PassThru + $archive.Enqueue($result) + } | Should -Not -Throw + + $info = $archive.Dequeue() + $info | Should -BeOfType ([FileInfo]) + Expand-TarArchive $info.FullName -Destination $currentName + Get-ChildItem $currentName -Recurse | ForEach-Object Name | + Should -Not -Contain $info.Name + } } It 'Should skip items that match the exclusion patterns' { From 93a994b04d40f125ae4c75db852aa931f32935a7 Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Sat, 21 Jun 2025 15:28:11 -0300 Subject: [PATCH 28/53] adding tests --- .../ToCompressedFileCommandBase.cs | 3 +- tests/CompressExpandArchiveCommands.tests.ps1 | 52 ++++++++++++++++--- 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/src/PSCompression/Abstractions/ToCompressedFileCommandBase.cs b/src/PSCompression/Abstractions/ToCompressedFileCommandBase.cs index 4121fd4..2338d13 100644 --- a/src/PSCompression/Abstractions/ToCompressedFileCommandBase.cs +++ b/src/PSCompression/Abstractions/ToCompressedFileCommandBase.cs @@ -206,7 +206,8 @@ protected override void EndProcessing() if (PassThru.IsPresent && _destination is not null) { - WriteObject(new FileInfo(_destination.Name)); + FileInfo passthru = new(_destination.Name); + WriteObject(passthru.AppendPSProperties(passthru.DirectoryName)); } } diff --git a/tests/CompressExpandArchiveCommands.tests.ps1 b/tests/CompressExpandArchiveCommands.tests.ps1 index e439dad..0c2196a 100644 --- a/tests/CompressExpandArchiveCommands.tests.ps1 +++ b/tests/CompressExpandArchiveCommands.tests.ps1 @@ -1,5 +1,6 @@ using namespace System.IO using namespace System.Collections.Generic +using namespace System.Management.Automation $ErrorActionPreference = 'Stop' @@ -20,7 +21,7 @@ Describe 'Compress & Expand Archive Commands' -Tag 'Compress & Expand Archive Co $extractpath = Join-Path $TestDrive $destName $fileCount = $dirCount = 0 - $structure = Get-Structure | ForEach-Object { + Get-Structure | ForEach-Object { $newItemSplat = @{ ItemType = ('Directory', 'File')[$_.EndsWith('.txt')] Value = Get-Random @@ -28,7 +29,7 @@ Describe 'Compress & Expand Archive Commands' -Tag 'Compress & Expand Archive Co Path = Join-Path $testpath $_ } - New-Item @newItemSplat + $null = New-Item @newItemSplat if ($newItemSplat['ItemType'] -eq 'Directory') { $dirCount++ @@ -39,7 +40,7 @@ Describe 'Compress & Expand Archive Commands' -Tag 'Compress & Expand Archive Co } $dirCount++ # Includes the folder itself - $extractpath, $structure, $algos | Out-Null + $extractpath, $algos | Out-Null } It 'Can compress a folder and all its child items' { @@ -153,12 +154,51 @@ Describe 'Compress & Expand Archive Commands' -Tag 'Compress & Expand Archive Co } It 'Should skip items that match the exclusion patterns' { - Remove-Item "$extractpath.zip" -Force - Compress-ZipArchive $testpath $extractpath -Exclude *testfile00*, *testfolder05* - Expand-Archive "$extractpath.zip" $extractpath + Get-ChildItem $TestDrive | + Where-Object Name -NE $sourceName | + Remove-Item -Recurse + + $compressZipArchiveSplat = @{ + Exclude = '*testfile00*', '*testfolder05*' + Path = $testpath + Destination = $extractpath + PassThru = $true + } + + Compress-ZipArchive @compressZipArchiveSplat | + Expand-Archive -DestinationPath $extractpath + Get-ChildItem $extractpath -Recurse | ForEach-Object { $_.FullName | Should -Not -BeLike *testfile00* $_.FullName | Should -Not -BeLike *testfolder05* } + + Remove-Item $extractpath -Recurse + + $compressTarArchiveSplat = @{ + LiteralPath = $testpath + PassThru = $true + Exclude = '*testfile00*', '*testfolder05*' + Destination = $extractpath + } + + $expanded = Compress-TarArchive @compressTarArchiveSplat | + Expand-TarArchive -Destination $extractpath -PassThru + + $expanded | ForEach-Object { + $_.FullName | Should -Not -BeLike *testfile00* + $_.FullName | Should -Not -BeLike *testfolder05* + } + } + + It 'CompressTarArchive outputs a warning when algorithm is lzip and CompressionLevel is used' { + $compressTarArchiveSplat = @{ + CompressionLevel = 'Optimal' + Algorithm = 'lz' + Destination = 'shouldWarn' + } + + Compress-TarArchive @compressTarArchiveSplat $testpath 3>&1 | + Should -BeOfType ([WarningRecord]) } } From 5787df47052b17b11275517897af5926136b378c Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Sat, 21 Jun 2025 15:55:00 -0300 Subject: [PATCH 29/53] adding tests --- .../Commands/CompressTarArchiveCommand.cs | 4 +++- .../Extensions/CompressionExtensions.cs | 6 ----- tests/CompressExpandArchiveCommands.tests.ps1 | 22 ++++++++++++++----- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/PSCompression/Commands/CompressTarArchiveCommand.cs b/src/PSCompression/Commands/CompressTarArchiveCommand.cs index 56f28fd..2bfecb7 100644 --- a/src/PSCompression/Commands/CompressTarArchiveCommand.cs +++ b/src/PSCompression/Commands/CompressTarArchiveCommand.cs @@ -35,7 +35,9 @@ protected override TarOutputStream CreateCompressionStream(Stream outputStream) if (Algorithm == Algorithm.lz && MyInvocation.BoundParameters.ContainsKey(nameof(CompressionLevel))) { - WriteWarning("The lzip algorithm does not support the CompressionLevel parameter; it will be ignored."); + WriteWarning( + "The lzip algorithm does not support custom CompressionLevel settings. " + + "The default compression level will be used."); } _compressionStream = Algorithm.ToCompressedStream(outputStream, CompressionLevel); diff --git a/src/PSCompression/Extensions/CompressionExtensions.cs b/src/PSCompression/Extensions/CompressionExtensions.cs index 1046511..5b4904d 100644 --- a/src/PSCompression/Extensions/CompressionExtensions.cs +++ b/src/PSCompression/Extensions/CompressionExtensions.cs @@ -81,12 +81,6 @@ internal static string ChangeName( directory.RelativePath.NormalizePath(), newname); - internal static string ChangePath( - this ZipArchiveEntry entry, - string oldPath, - string newPath) - => string.Concat(newPath, entry.FullName.Remove(0, oldPath.Length)); - internal static string GetDirectoryName(this ZipArchiveEntry entry) => s_reGetDirName.Match(entry.FullName).Value; diff --git a/tests/CompressExpandArchiveCommands.tests.ps1 b/tests/CompressExpandArchiveCommands.tests.ps1 index 0c2196a..603ef24 100644 --- a/tests/CompressExpandArchiveCommands.tests.ps1 +++ b/tests/CompressExpandArchiveCommands.tests.ps1 @@ -1,5 +1,6 @@ -using namespace System.IO -using namespace System.Collections.Generic +using namespace System.Collections.Generic +using namespace System.IO +using namespace System.IO.Compression using namespace System.Management.Automation $ErrorActionPreference = 'Stop' @@ -67,9 +68,20 @@ Describe 'Compress & Expand Archive Commands' -Tag 'Compress & Expand Archive Co { Compress-ZipArchive $testpath $extractpath -Force } | Should -Not -Throw - $algos | ForEach-Object { - { Compress-TarArchive $testpath $extractpath -Algorithm $_ -Force } | - Should -Not -Throw + foreach ($level in [CompressionLevel].GetEnumNames()) { + foreach ($algo in $algos) { + $compressTarArchiveSplat = @{ + PassThru = $true + Algorithm = $algo + Destination = $extractpath + CompressionLevel = $level + Force = $true + WarningAction = 'Ignore' + } + + { $testpath | Compress-TarArchive @compressTarArchiveSplat } | + Should -Not -Throw + } } } From 0097c3772252cac51e97e7b8d18b8b8a5f3e04c9 Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Sat, 21 Jun 2025 16:19:22 -0300 Subject: [PATCH 30/53] adding tests --- tests/ToFromStringCompression.tests.ps1 | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/ToFromStringCompression.tests.ps1 b/tests/ToFromStringCompression.tests.ps1 index 5f1ac11..558ad4b 100644 --- a/tests/ToFromStringCompression.tests.ps1 +++ b/tests/ToFromStringCompression.tests.ps1 @@ -1,4 +1,5 @@ using namespace System.IO +using namespace System.IO.Compression $ErrorActionPreference = 'Stop' @@ -35,11 +36,11 @@ Describe 'To & From String Compression' { } It 'Can compress strings and expand strings' { - $conversionCommands.Keys | ForEach-Object { - $encoded = { $content | & $conversionCommands[$_] } | - Should -Not -Throw -PassThru - - $encoded | & $_ | Should -BeExactly $contet + foreach ($level in [CompressionLevel].GetEnumNames()) { + $conversionCommands.Keys | ForEach-Object { + $encoded = $content | & $conversionCommands[$_] -CompressionLevel $level + $encoded | & $_ | Should -BeExactly $content + } } } From 06ec32cff76626a9155ff725b778c6ee02467b24 Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Sat, 21 Jun 2025 18:03:25 -0300 Subject: [PATCH 31/53] adds gettarentrycommand tests --- ...ests.ps1 => ArchiveEntryCmdlets.tests.ps1} | 176 ++++++++++++++---- tests/CompressExpandArchiveCommands.tests.ps1 | 13 ++ 2 files changed, 157 insertions(+), 32 deletions(-) rename tests/{ZipEntryCmdlets.tests.ps1 => ArchiveEntryCmdlets.tests.ps1} (74%) diff --git a/tests/ZipEntryCmdlets.tests.ps1 b/tests/ArchiveEntryCmdlets.tests.ps1 similarity index 74% rename from tests/ZipEntryCmdlets.tests.ps1 rename to tests/ArchiveEntryCmdlets.tests.ps1 index c62cf5a..a9120d9 100644 --- a/tests/ZipEntryCmdlets.tests.ps1 +++ b/tests/ArchiveEntryCmdlets.tests.ps1 @@ -1,6 +1,7 @@ -using namespace System.IO -using namespace PSCompression -using namespace PSCompression.Extensions +using namespace System +using namespace System.Collections +using namespace System.IO +using namespace System.Text $ErrorActionPreference = 'Stop' @@ -10,11 +11,30 @@ $manifestPath = [Path]::Combine($PSScriptRoot, '..', 'output', $moduleName) Import-Module $manifestPath Import-Module ([Path]::Combine($PSScriptRoot, 'shared.psm1')) -Describe 'Entry Cmdlets' { +Describe 'Archive Entry Cmdlets' { BeforeAll { $zip = New-Item (Join-Path $TestDrive test.zip) -ItemType File -Force $file = New-Item ([Path]::Combine($TestDrive, 'someFile.txt')) -ItemType File -Value 'foo' $uri = 'https://www.powershellgallery.com/api/v2/package/PSCompression' + $testTarName = 'TarEntryTests' + $testTarpath = Join-Path $TestDrive $testTarName + + Get-Structure | ForEach-Object { + $newItemSplat = @{ + ItemType = ('Directory', 'File')[$_.EndsWith('.txt')] + Value = Get-Random + Force = $true + Path = Join-Path $testTarpath $_ + } + + $null = New-Item @newItemSplat + } + + $algos = [PSCompression.Algorithm].GetEnumValues() + $algos | ForEach-Object { + Compress-TarArchive $testTarpath -Destination $testTarpath -Algorithm $_ + } + $zip, $file, $uri | Out-Null } @@ -46,12 +66,12 @@ Describe 'Entry Cmdlets' { It 'Can create new zip file entries' { New-ZipEntry $zip.FullName -EntryPath test\newentry.txt | - Should -BeOfType ([ZipEntryFile]) + Should -BeOfType ([PSCompression.ZipEntryFile]) } It 'Can create new zip directory entries' { New-ZipEntry $zip.FullName -EntryPath test\ | - Should -BeOfType ([ZipEntryDirectory]) + Should -BeOfType ([PSCompression.ZipEntryDirectory]) } It 'Can create multiple entries' { @@ -112,7 +132,7 @@ Describe 'Entry Cmdlets' { $entry = New-ZipEntry @newZipEntrySplat $entry | Get-ZipEntryContent | Should -Be 'hello world!' $entry.RelativePath | - Should -Be ([PathExtensions]::NormalizePath($item.FullName)) + Should -Be ([PSCompression.Extensions.PathExtensions]::NormalizePath($item.FullName)) } It 'Ignores copying content to a ZipEntryDirectory from source file' { @@ -145,34 +165,34 @@ Describe 'Entry Cmdlets' { It 'Should throw when not targetting a FileSystem Provider Path' { { Get-ZipEntry function:\* } | - Should -Throw -ExceptionType ([System.NotSupportedException]) + Should -Throw -ExceptionType ([NotSupportedException]) { Get-ZipEntry -LiteralPath function:\ } | - Should -Throw -ExceptionType ([System.NotSupportedException]) + Should -Throw -ExceptionType ([NotSupportedException]) } - It 'Should throw when the path is not a Zip' { + It 'Should throw when the path is not a zip archive' { { Get-ZipEntry $file.FullName } | - Should -Throw -ExceptionType ([System.IO.InvalidDataException]) + Should -Throw -ExceptionType ([InvalidDataException]) } - It 'Should throw when a Stream is not a Zip' { + It 'Should throw when a Stream is not a zip archive' { { Use-Object ($stream = $file.OpenRead()) { Get-ZipEntry $stream } - } | Should -Throw -ExceptionType ([System.IO.InvalidDataException]) + } | Should -Throw -ExceptionType ([InvalidDataException]) } - It 'Should throw when a Stream is Disposed' { + It 'Should throw when a Stream is disposed' { { (Use-Object ($stream = (Invoke-WebRequest $uri).RawContentStream) { $stream }) | Get-ZipEntry - } | Should -Throw -ExceptionType ([System.ObjectDisposedException]) + } | Should -Throw -ExceptionType ([ObjectDisposedException]) } It 'Should throw if the path is not a file' { { Get-ZipEntry $TestDrive } | - Should -Throw -ExceptionType ([System.ArgumentException]) + Should -Throw -ExceptionType ([ArgumentException]) } It 'Can list zip file entries' { @@ -197,6 +217,100 @@ Describe 'Entry Cmdlets' { } } + Context 'Get-TarEntry' -Tag 'Get-TarEntry' { + BeforeAll { + $tarArchives = Get-ChildItem $TestDrive -File | + Where-Object { $_.BaseName.StartsWith($testTarName) } + + $tarArchives | Out-Null + } + + It 'Can list entries in a tar archive' { + $tarArchives | Get-TarEntry | + Should -BeOfType ([PSCompression.Abstractions.TarEntryBase]) + } + + It 'Can list entries from a Stream' { + foreach ($archive in $tarArchives) { + $algo = $archive.Extension + if ($archive.Extension -eq '.tar') { + $algo = 'none' + } + + Use-Object ($stream = $archive.OpenRead()) { + $stream | Get-TarEntry -Algorithm $algo.TrimStart('.') | + Should -BeOfType ([PSCompression.Abstractions.TarEntryBase]) + } + } + } + + It 'Should throw when not targetting a FileSystem Provider Path' { + { Get-TarEntry function:\* } | + Should -Throw -ExceptionType ([NotSupportedException]) + + { Get-TarEntry -LiteralPath function:\ } | + Should -Throw -ExceptionType ([NotSupportedException]) + } + + It 'Should throw when the path is not a tar archive' { + { Get-TarEntry $file.FullName } | + Should -Throw -ExceptionType ([InvalidDataException]) + } + + It 'Should throw when a Stream is not a tar archive' { + { + Use-Object ($stream = $file.OpenRead()) { + Get-TarEntry $stream + } + } | Should -Throw -ExceptionType ([InvalidDataException]) + } + + It 'Should throw when a Stream is a tar archive with wrong algorithm' { + $testArchive = $tarArchives | + Where-Object { $_.Extension -ne '.tar' -and $_.Extension -notmatch $algos[0] } | + Select-Object -First 1 + + { + Use-Object ($stream = $testArchive.OpenRead()) { + $stream | Get-TarEntry -Algorithm $algos[0] + } + } | Should -Throw -ExceptionType ([InvalidDataException]) + } + + It 'Should throw when a Stream is disposed' { + { + $gz = $tarArchives | Where-Object Extension -EQ .gz + (Use-Object ($stream = $gz.OpenRead()) { $stream }) | Get-TarEntry + } | Should -Throw + } + + It 'Should throw if the path is not a file' { + { Get-TarEntry $TestDrive } | + Should -Throw -ExceptionType ([ArgumentException]) + } + + It 'Can list tar file entries' { + $tarArchives | Get-TarEntry -Type Archive | + Should -BeOfType ([PSCompression.TarEntryFile]) + } + + It 'Can list tar directory entries' { + $tarArchives | Get-TarEntry -Type Directory | + Should -BeOfType ([PSCompression.TarEntryDirectory]) + } + + It 'Can list a specific entry with the -Include parameter' { + $tarArchives | Get-TarEntry -Include "${testTarName}/testfolder05/testfile00.txt" | + Should -BeOfType ([PSCompression.TarEntryFile]) + } + + It 'Can exclude entries using the -Exclude parameter' { + $tarArchives | Get-TarEntry -Exclude *.txt | + ForEach-Object Extension | + Should -Not -Be '.txt' + } + } + Context 'Get-ZipEntryContent' -Tag 'Get-ZipEntryContent' { It 'Can read content from zip file entries' { $zip | Get-ZipEntry -Type Archive | @@ -208,7 +322,7 @@ Describe 'Entry Cmdlets' { Invoke-WebRequest $uri | Get-ZipEntry -Type Archive -Include *.psd1 | Get-ZipEntryContent -Raw | Invoke-Expression | - Should -BeOfType ([System.Collections.IDictionary]) + Should -BeOfType ([IDictionary]) } It 'Should throw when a Stream is Diposed' { @@ -217,7 +331,7 @@ Describe 'Entry Cmdlets' { $stream | Get-ZipEntry -Type Archive -Include *.psd1 } $entry | Get-ZipEntryContent -Raw - } | Should -Throw -ExceptionType ([System.ObjectDisposedException]) + } | Should -Throw -ExceptionType ([ObjectDisposedException]) } It 'Should not throw when an instance wrapped in PSObject is passed as Encoding argument' { @@ -225,11 +339,11 @@ Describe 'Entry Cmdlets' { { $zip | Get-ZipEntry -Type Archive | Get-ZipEntryContent -Encoding $enc } | Should -Not -Throw - $enc = [System.Text.Encoding]::UTF8 | Write-Output + $enc = [Encoding]::UTF8 | Write-Output { $zip | Get-ZipEntry -Type Archive | Get-ZipEntryContent -Encoding $enc } | Should -Not -Throw - $enc = [System.Text.Encoding]::UTF8.CodePage | Write-Output + $enc = [Encoding]::UTF8.CodePage | Write-Output { $zip | Get-ZipEntry -Type Archive | Get-ZipEntryContent -Encoding $enc } | Should -Not -Throw } @@ -267,7 +381,7 @@ Describe 'Entry Cmdlets' { It 'Should throw if trying to remove entries created from input Stream' { { Invoke-WebRequest $uri | Get-ZipEntry | Remove-ZipEntry } | - Should -Throw -ExceptionType ([System.NotSupportedException]) + Should -Throw -ExceptionType ([NotSupportedException]) } It 'Can remove directory entries' { @@ -289,9 +403,7 @@ Describe 'Entry Cmdlets' { Context 'Set-ZipEntryContent' -Tag 'Set-ZipEntryContent' { BeforeAll { $content = 'hello', 'world', '!' - [byte[]] $bytes = $content | ForEach-Object { - [System.Text.Encoding]::UTF8.GetBytes($_) - } + [byte[]] $bytes = $content | ForEach-Object { [Encoding]::UTF8.GetBytes($_) } $entry = New-ZipEntry $zip.FullName -EntryPath test\helloworld.txt $entry, $content, $bytes | Out-Null } @@ -301,13 +413,13 @@ Describe 'Entry Cmdlets' { $entry | Get-ZipEntryContent | Should -BeExactly $content $entry | Get-ZipEntryContent -Raw | ForEach-Object TrimEnd | - Should -BeExactly ($content -join [System.Environment]::NewLine) + Should -BeExactly ($content -join [Environment]::NewLine) } It 'Should throw if trying to write content to entries created from input Stream' { $entry = Invoke-WebRequest $uri | Get-ZipEntry -Include *.psd1 { 'test' | Set-ZipEntryContent $entry } | - Should -Throw -ExceptionType ([System.NotSupportedException]) + Should -Throw -ExceptionType ([NotSupportedException]) } It 'Can append content to a zip file entry' { @@ -316,7 +428,7 @@ Describe 'Entry Cmdlets' { $entry | Get-ZipEntryContent | Should -BeExactly $newContent $entry | Get-ZipEntryContent -Raw | ForEach-Object TrimEnd | - Should -BeExactly ($newContent -join [System.Environment]::NewLine) + Should -BeExactly ($newContent -join [Environment]::NewLine) } It 'Can write raw bytes to a zip file entry' { @@ -369,7 +481,7 @@ Describe 'Entry Cmdlets' { It 'Should throw if trying to rename entries created from input Stream' { { Invoke-WebRequest $uri | Get-ZipEntry | Rename-ZipEntry -NewName { 'test' + $_.Name } } | - Should -Throw -ExceptionType ([System.NotSupportedException]) + Should -Throw -ExceptionType ([NotSupportedException]) } It 'Produces output with -PassThru' { @@ -406,7 +518,7 @@ Describe 'Entry Cmdlets' { } It 'Should throw if using invalid FileName chars' { - $invalidChars = [System.IO.Path]::GetInvalidFileNameChars() + $invalidChars = [Path]::GetInvalidFileNameChars() { $zip | Get-ZipEntry -Type Archive | Rename-ZipEntry -NewName { $_.Name + $invalidChars[0] } } | @@ -444,10 +556,10 @@ Describe 'Entry Cmdlets' { It 'Can extract zip file entries created from input Stream' { Invoke-WebRequest $uri | Get-ZipEntry -Type Archive -Include *.psd1 | Expand-ZipEntry -Destination $destination -PassThru -OutVariable psd1 | - Should -BeOfType ([System.IO.FileInfo]) + Should -BeOfType ([FileInfo]) Get-Content $psd1.FullName -Raw | Invoke-Expression | - Should -BeOfType ([System.Collections.IDictionary]) + Should -BeOfType ([IDictionary]) $psd1.Delete() } @@ -458,7 +570,7 @@ Describe 'Entry Cmdlets' { $stream | Get-ZipEntry -Type Archive -Include *.psd1 } $entry | Expand-ZipEntry -Destination $destination -Force - } | Should -Throw -ExceptionType ([System.ObjectDisposedException]) + } | Should -Throw -ExceptionType ([ObjectDisposedException]) } It 'Should throw when -Destination is an invalid path' { diff --git a/tests/CompressExpandArchiveCommands.tests.ps1 b/tests/CompressExpandArchiveCommands.tests.ps1 index 603ef24..c46c63b 100644 --- a/tests/CompressExpandArchiveCommands.tests.ps1 +++ b/tests/CompressExpandArchiveCommands.tests.ps1 @@ -213,4 +213,17 @@ Describe 'Compress & Expand Archive Commands' -Tag 'Compress & Expand Archive Co Compress-TarArchive @compressTarArchiveSplat $testpath 3>&1 | Should -BeOfType ([WarningRecord]) } + + Context 'ExpandTarArchive Command' -Tag 'ExpandTarArchive Command' { + BeforeAll { + $destination = 'shouldThrowIfExists' + $compressed = Compress-TarArchive $testpath $destination -PassThru + $compressed | Expand-TarArchive -Destination $destination + } + + It 'Should throw if destination already exists' { + { $compressed | Expand-TarArchive -Destination $destination } | + Should -Throw -ExceptionType ([IOException]) + } + } } From f1abe3659a72e96b68fdb32d764864cdb355709d Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Sat, 21 Jun 2025 19:45:41 -0300 Subject: [PATCH 32/53] corrects a few tests --- .../Commands/ExpandTarArchiveCommand.cs | 43 +++++++++++-------- tests/CompressExpandArchiveCommands.tests.ps1 | 20 +++++++-- 2 files changed, 42 insertions(+), 21 deletions(-) diff --git a/src/PSCompression/Commands/ExpandTarArchiveCommand.cs b/src/PSCompression/Commands/ExpandTarArchiveCommand.cs index a0bbbef..8cd30f4 100644 --- a/src/PSCompression/Commands/ExpandTarArchiveCommand.cs +++ b/src/PSCompression/Commands/ExpandTarArchiveCommand.cs @@ -1,6 +1,9 @@ using System; +using System.Collections.Generic; using System.IO; +using System.Linq; using System.Management.Automation; +using System.Runtime.CompilerServices; using System.Text; using ICSharpCode.SharpZipLib.Tar; using PSCompression.Abstractions; @@ -42,8 +45,7 @@ protected override void BeginProcessing() Directory.CreateDirectory(Destination); - _shouldInferAlgo = !MyInvocation - .BoundParameters + _shouldInferAlgo = !MyInvocation.BoundParameters .ContainsKey(nameof(Algorithm)); } @@ -60,7 +62,18 @@ protected override void ProcessRecord() try { - ExtractArchive(path); + IOrderedEnumerable output = ExtractArchive(path) + .Select(info => + { + string parent = info is DirectoryInfo dir + ? dir.Parent.FullName + : Unsafe.As(info).DirectoryName; + + return info.AppendPSProperties(parent); + }) + .OrderBy(pso => pso.Properties["PSParentPath"].Value); + + WriteObject(output, enumerateCollection: true); } catch (Exception _) when (_ is PipelineStoppedException or FlowControlException) { @@ -73,8 +86,10 @@ protected override void ProcessRecord() } } - private void ExtractArchive(string path) + private IEnumerable ExtractArchive(string path) { + FileSystemInfo? info = null; + using FileStream fs = File.OpenRead(path); using Stream decompress = Algorithm.FromCompressedStream(fs); using TarInputStream tar = new(decompress, Encoding.UTF8); @@ -83,7 +98,7 @@ private void ExtractArchive(string path) { try { - ExtractEntry(entry, tar); + info = ExtractEntry(entry, tar); } catch (Exception _) when (_ is PipelineStoppedException or FlowControlException) { @@ -92,11 +107,14 @@ private void ExtractArchive(string path) catch (Exception exception) { WriteError(exception.ToExtractEntryError(entry)); + continue; } + + yield return info; } } - private void ExtractEntry(TarEntry entry, TarInputStream tar) + private FileSystemInfo ExtractEntry(TarEntry entry, TarInputStream tar) { string destination = IO.Path.GetFullPath( IO.Path.Combine(Destination, entry.Name)); @@ -105,13 +123,7 @@ private void ExtractEntry(TarEntry entry, TarInputStream tar) { DirectoryInfo dir = new(destination); dir.Create(Force); - - if (PassThru) - { - WriteObject(dir.AppendPSProperties(dir.Parent.FullName)); - } - - return; + return dir; } FileInfo file = new(destination); @@ -128,9 +140,6 @@ private void ExtractEntry(TarEntry entry, TarInputStream tar) } } - if (PassThru) - { - WriteObject(file.AppendPSProperties(file.DirectoryName)); - } + return file; } } diff --git a/tests/CompressExpandArchiveCommands.tests.ps1 b/tests/CompressExpandArchiveCommands.tests.ps1 index c46c63b..83c4b36 100644 --- a/tests/CompressExpandArchiveCommands.tests.ps1 +++ b/tests/CompressExpandArchiveCommands.tests.ps1 @@ -217,13 +217,25 @@ Describe 'Compress & Expand Archive Commands' -Tag 'Compress & Expand Archive Co Context 'ExpandTarArchive Command' -Tag 'ExpandTarArchive Command' { BeforeAll { $destination = 'shouldThrowIfExists' + New-Item $destination -ItemType Directory | Out-Null $compressed = Compress-TarArchive $testpath $destination -PassThru - $compressed | Expand-TarArchive -Destination $destination + $compressed | Out-Null } - It 'Should throw if destination already exists' { - { $compressed | Expand-TarArchive -Destination $destination } | - Should -Throw -ExceptionType ([IOException]) + It 'Should throw if destination is an existing file' { + { $compressed | Expand-TarArchive -Destination "${destination}.tar.gz" } | + Should -Throw -ExceptionType ([ArgumentException]) + } + + It 'Can expand the archive using the current directory as Destination when not specified' { + try { + Push-Location $destination + { Expand-TarArchive $compressed } | Should -Not -Throw + Get-ChildItem | Should -Not -BeNullOrEmpty + } + finally { + Pop-Location + } } } } From 822adaec63c30fe5b33e3eeb357c555bd2bd8d23 Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Sat, 21 Jun 2025 19:52:31 -0300 Subject: [PATCH 33/53] corrects a few tests --- tests/CompressExpandArchiveCommands.tests.ps1 | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/CompressExpandArchiveCommands.tests.ps1 b/tests/CompressExpandArchiveCommands.tests.ps1 index 83c4b36..739a3e0 100644 --- a/tests/CompressExpandArchiveCommands.tests.ps1 +++ b/tests/CompressExpandArchiveCommands.tests.ps1 @@ -237,5 +237,15 @@ Describe 'Compress & Expand Archive Commands' -Tag 'Compress & Expand Archive Co Pop-Location } } + + It 'Should overwrite files if already exist' { + $compressed | Expand-TarArchive -Destination testOverwrite + + { $compressed | Expand-TarArchive -Destination testOverwrite } | + Should -Throw -ExceptionType ([IOException]) + + { $compressed | Expand-TarArchive -Destination testOverwrite -Force } | + Should -Not -Throw + } } } From 985bf603115f4bca1be442e4e6e7d4242aa44a28 Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Sat, 21 Jun 2025 20:27:05 -0300 Subject: [PATCH 34/53] bug... --- .../Commands/ExpandTarArchiveCommand.cs | 41 ++++++++++--------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/src/PSCompression/Commands/ExpandTarArchiveCommand.cs b/src/PSCompression/Commands/ExpandTarArchiveCommand.cs index 8cd30f4..74bff20 100644 --- a/src/PSCompression/Commands/ExpandTarArchiveCommand.cs +++ b/src/PSCompression/Commands/ExpandTarArchiveCommand.cs @@ -62,18 +62,23 @@ protected override void ProcessRecord() try { - IOrderedEnumerable output = ExtractArchive(path) - .Select(info => - { - string parent = info is DirectoryInfo dir - ? dir.Parent.FullName - : Unsafe.As(info).DirectoryName; - - return info.AppendPSProperties(parent); - }) - .OrderBy(pso => pso.Properties["PSParentPath"].Value); - - WriteObject(output, enumerateCollection: true); + FileSystemInfo[] output = ExtractArchive(path); + + if (PassThru) + { + IOrderedEnumerable result = output + .Select(info => + { + string parent = info is DirectoryInfo dir + ? dir.Parent.FullName + : Unsafe.As(info).DirectoryName; + + return info.AppendPSProperties(parent); + }) + .OrderBy(pso => pso.Properties["PSParentPath"].Value); + + WriteObject(result, enumerateCollection: true); + } } catch (Exception _) when (_ is PipelineStoppedException or FlowControlException) { @@ -86,19 +91,18 @@ protected override void ProcessRecord() } } - private IEnumerable ExtractArchive(string path) + private FileSystemInfo[] ExtractArchive(string path) { - FileSystemInfo? info = null; - using FileStream fs = File.OpenRead(path); using Stream decompress = Algorithm.FromCompressedStream(fs); using TarInputStream tar = new(decompress, Encoding.UTF8); + List result = []; foreach (TarEntry entry in tar.EnumerateEntries()) { try { - info = ExtractEntry(entry, tar); + result.Add(ExtractEntry(entry, tar)); } catch (Exception _) when (_ is PipelineStoppedException or FlowControlException) { @@ -107,11 +111,10 @@ private IEnumerable ExtractArchive(string path) catch (Exception exception) { WriteError(exception.ToExtractEntryError(entry)); - continue; } - - yield return info; } + + return [.. result]; } private FileSystemInfo ExtractEntry(TarEntry entry, TarInputStream tar) From 68e1af6125cfada26086bb49fd4f0bbd9c525ef5 Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Sat, 21 Jun 2025 20:35:46 -0300 Subject: [PATCH 35/53] bug... --- .../Commands/ExpandTarArchiveCommand.cs | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/PSCompression/Commands/ExpandTarArchiveCommand.cs b/src/PSCompression/Commands/ExpandTarArchiveCommand.cs index 74bff20..5d5cd26 100644 --- a/src/PSCompression/Commands/ExpandTarArchiveCommand.cs +++ b/src/PSCompression/Commands/ExpandTarArchiveCommand.cs @@ -34,7 +34,8 @@ public sealed class ExpandTarArchiveCommand : CommandWithPathBase protected override void BeginProcessing() { Destination = Destination is null - ? SessionState.Path.CurrentFileSystemLocation.Path + // PowerShell is retarded and decided to mix up ProviderPath & Path + ? SessionState.Path.CurrentFileSystemLocation.ProviderPath : Destination.ResolvePath(this); if (File.Exists(Destination)) @@ -67,14 +68,7 @@ protected override void ProcessRecord() if (PassThru) { IOrderedEnumerable result = output - .Select(info => - { - string parent = info is DirectoryInfo dir - ? dir.Parent.FullName - : Unsafe.As(info).DirectoryName; - - return info.AppendPSProperties(parent); - }) + .Select(AppendPSProperties) .OrderBy(pso => pso.Properties["PSParentPath"].Value); WriteObject(result, enumerateCollection: true); @@ -145,4 +139,13 @@ private FileSystemInfo ExtractEntry(TarEntry entry, TarInputStream tar) return file; } + + private static PSObject AppendPSProperties(FileSystemInfo info) + { + string parent = info is DirectoryInfo dir + ? dir.Parent.FullName + : Unsafe.As(info).DirectoryName; + + return info.AppendPSProperties(parent); + } } From 63d30c7754bb5da907ac29fc55af9cd7233f07d8 Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Sun, 22 Jun 2025 10:28:35 -0300 Subject: [PATCH 36/53] adding tests for GetTarEntryContent command --- .../Abstractions/ExpandEntryCommandBase.cs | 10 +- .../Abstractions/GetEntryCommandBase.cs | 63 ++++++------- .../Commands/ExpandTarArchiveCommand.cs | 12 +-- .../Commands/GetTarEntryCommand.cs | 42 ++++----- .../Extensions/CompressionExtensions.cs | 3 +- .../Extensions/PathExtensions.cs | 10 ++ tests/ArchiveEntryCmdlets.tests.ps1 | 92 ++++++++++++++++--- 7 files changed, 144 insertions(+), 88 deletions(-) diff --git a/src/PSCompression/Abstractions/ExpandEntryCommandBase.cs b/src/PSCompression/Abstractions/ExpandEntryCommandBase.cs index 45f4d3c..0bed864 100644 --- a/src/PSCompression/Abstractions/ExpandEntryCommandBase.cs +++ b/src/PSCompression/Abstractions/ExpandEntryCommandBase.cs @@ -4,7 +4,6 @@ using PSCompression.Extensions; using PSCompression.Exceptions; using System.ComponentModel; -using System.Runtime.CompilerServices; namespace PSCompression.Abstractions; @@ -28,7 +27,8 @@ public abstract class ExpandEntryCommandBase : PSCmdlet protected override void BeginProcessing() { Destination = Destination is null - ? SessionState.Path.CurrentFileSystemLocation.Path + // PowerShell is retarded and decided to mix up ProviderPath & Path + ? SessionState.Path.CurrentFileSystemLocation.ProviderPath : Destination.ResolvePath(this); if (File.Exists(Destination)) @@ -52,11 +52,7 @@ protected override void ProcessRecord() if (PassThru) { - string parent = info is DirectoryInfo dir - ? dir.Parent.FullName - : Unsafe.As(info).DirectoryName; - - WriteObject(info.AppendPSProperties(parent)); + WriteObject(info.AppendPSProperties()); } } catch (Exception _) when (_ is PipelineStoppedException or FlowControlException) diff --git a/src/PSCompression/Abstractions/GetEntryCommandBase.cs b/src/PSCompression/Abstractions/GetEntryCommandBase.cs index 1844aca..3e93b7a 100644 --- a/src/PSCompression/Abstractions/GetEntryCommandBase.cs +++ b/src/PSCompression/Abstractions/GetEntryCommandBase.cs @@ -65,36 +65,14 @@ protected override void ProcessRecord() { if (InputStream is not null) { - try - { - if (InputStream.CanSeek) - { - InputStream.Seek(0, SeekOrigin.Begin); - } - - WriteObject( - GetEntriesFromStream(InputStream).ToEntrySort(), - enumerateCollection: true); - - return; - } - catch (Exception _) when (_ is PipelineStoppedException or FlowControlException) - { - throw; - } - catch (Exception exception) when (IsInvalidArchive(exception)) - { - ThrowTerminatingError(exception.ToInvalidArchive(ArchiveType, isStream: true)); - } - catch (Exception exception) - { - WriteError(exception.ToOpenError("InputStream")); - } + HandleFromStream(InputStream); + return; } foreach (string path in EnumerateResolvedPaths()) { - if (path.WriteErrorIfNotArchive(IsLiteral ? nameof(LiteralPath) : nameof(Path), this)) + if (path.WriteErrorIfNotArchive( + IsLiteral ? nameof(LiteralPath) : nameof(Path), this)) { continue; } @@ -120,6 +98,33 @@ protected override void ProcessRecord() } } + private void HandleFromStream(Stream stream) + { + try + { + if (stream.CanSeek) + { + stream.Seek(0, SeekOrigin.Begin); + } + + WriteObject( + GetEntriesFromStream(stream).ToEntrySort(), + enumerateCollection: true); + } + catch (Exception _) when (_ is PipelineStoppedException or FlowControlException) + { + throw; + } + catch (Exception exception) when (IsInvalidArchive(exception)) + { + ThrowTerminatingError(exception.ToInvalidArchive(ArchiveType, isStream: true)); + } + catch (Exception exception) + { + WriteError(exception.ToOpenError("InputStream")); + } + } + protected abstract IEnumerable GetEntriesFromFile(string path); protected abstract IEnumerable GetEntriesFromStream(Stream stream); @@ -163,9 +168,5 @@ protected bool ShouldSkipEntry(bool isDirectory) => isDirectory && Type is EntryType.Archive || !isDirectory && Type is EntryType.Directory; private bool IsInvalidArchive(Exception exception) => - exception is - InvalidDataException - or TarException - or ZstdException - or IOException; + exception is InvalidDataException or TarException or ZstdException or IOException; } diff --git a/src/PSCompression/Commands/ExpandTarArchiveCommand.cs b/src/PSCompression/Commands/ExpandTarArchiveCommand.cs index 5d5cd26..615fb5b 100644 --- a/src/PSCompression/Commands/ExpandTarArchiveCommand.cs +++ b/src/PSCompression/Commands/ExpandTarArchiveCommand.cs @@ -3,7 +3,6 @@ using System.IO; using System.Linq; using System.Management.Automation; -using System.Runtime.CompilerServices; using System.Text; using ICSharpCode.SharpZipLib.Tar; using PSCompression.Abstractions; @@ -68,7 +67,7 @@ protected override void ProcessRecord() if (PassThru) { IOrderedEnumerable result = output - .Select(AppendPSProperties) + .Select(PathExtensions.AppendPSProperties) .OrderBy(pso => pso.Properties["PSParentPath"].Value); WriteObject(result, enumerateCollection: true); @@ -139,13 +138,4 @@ private FileSystemInfo ExtractEntry(TarEntry entry, TarInputStream tar) return file; } - - private static PSObject AppendPSProperties(FileSystemInfo info) - { - string parent = info is DirectoryInfo dir - ? dir.Parent.FullName - : Unsafe.As(info).DirectoryName; - - return info.AppendPSProperties(parent); - } } diff --git a/src/PSCompression/Commands/GetTarEntryCommand.cs b/src/PSCompression/Commands/GetTarEntryCommand.cs index 4e3b7cf..994e4d7 100644 --- a/src/PSCompression/Commands/GetTarEntryCommand.cs +++ b/src/PSCompression/Commands/GetTarEntryCommand.cs @@ -20,41 +20,35 @@ public sealed class GetTarEntryCommand : GetEntryCommandBase protected override IEnumerable GetEntriesFromFile(string path) { - List entries = []; - if (!MyInvocation.BoundParameters.ContainsKey(nameof(Algorithm))) { Algorithm = AlgorithmMappings.Parse(path); } - using (FileStream fs = File.OpenRead(path)) - using (Stream stream = Algorithm.FromCompressedStream(fs)) - using (TarInputStream tar = new(stream, Encoding.UTF8)) + using FileStream fs = File.OpenRead(path); + using Stream stream = Algorithm.FromCompressedStream(fs); + using TarInputStream tar = new(stream, Encoding.UTF8); + + foreach (TarEntry entry in tar.EnumerateEntries()) { - foreach (TarEntry entry in tar.EnumerateEntries()) + if (ShouldSkipEntry(entry.IsDirectory)) { - if (ShouldSkipEntry(entry.IsDirectory)) - { - continue; - } - - if (!ShouldInclude(entry.Name) || ShouldExclude(entry.Name)) - { - continue; - } + continue; + } - entries.Add(entry.IsDirectory - ? new TarEntryDirectory(entry, path) - : new TarEntryFile(entry, path, Algorithm)); + if (!ShouldInclude(entry.Name) || ShouldExclude(entry.Name)) + { + continue; } - } - return [.. entries]; + yield return entry.IsDirectory + ? new TarEntryDirectory(entry, path) + : new TarEntryFile(entry, path, Algorithm); + } } protected override IEnumerable GetEntriesFromStream(Stream stream) { - List entries = []; Stream decompressStream = Algorithm.FromCompressedStream(stream); TarInputStream tar = new(decompressStream, Encoding.UTF8); @@ -70,11 +64,9 @@ protected override IEnumerable GetEntriesFromStream(Stream stream) continue; } - entries.Add(entry.IsDirectory + yield return entry.IsDirectory ? new TarEntryDirectory(entry, stream) - : new TarEntryFile(entry, stream, Algorithm)); + : new TarEntryFile(entry, stream, Algorithm); } - - return [.. entries]; } } diff --git a/src/PSCompression/Extensions/CompressionExtensions.cs b/src/PSCompression/Extensions/CompressionExtensions.cs index 5b4904d..a1fe7ca 100644 --- a/src/PSCompression/Extensions/CompressionExtensions.cs +++ b/src/PSCompression/Extensions/CompressionExtensions.cs @@ -8,7 +8,6 @@ using BrotliSharpLib; using ICSharpCode.SharpZipLib.BZip2; using ICSharpCode.SharpZipLib.Tar; -using SharpCompress.Compressors.BZip2; using SharpCompress.Compressors.LZMA; using ZstdSharp; using SharpCompressors = SharpCompress.Compressors; @@ -182,7 +181,7 @@ internal static Stream FromCompressedStream( Algorithm.gz => new GZipStream(stream, CompressionMode.Decompress), Algorithm.zst => new DecompressionStream(stream), Algorithm.lz => new LZipStream(stream, SharpCompressors.CompressionMode.Decompress), - Algorithm.bz2 => new BZip2Stream(stream, SharpCompressors.CompressionMode.Decompress, true), + Algorithm.bz2 => new BZip2InputStream(stream), _ => stream }; diff --git a/src/PSCompression/Extensions/PathExtensions.cs b/src/PSCompression/Extensions/PathExtensions.cs index c64aae4..0a2e5d6 100644 --- a/src/PSCompression/Extensions/PathExtensions.cs +++ b/src/PSCompression/Extensions/PathExtensions.cs @@ -1,6 +1,7 @@ using System; using System.IO; using System.Management.Automation; +using System.Runtime.CompilerServices; using System.Text.RegularExpressions; using Microsoft.PowerShell.Commands; using PSCompression.Exceptions; @@ -80,6 +81,15 @@ internal static void Create(this DirectoryInfo dir, bool force) throw new IOException($"The directory '{dir.FullName}' already exists."); } + internal static PSObject AppendPSProperties(this FileSystemInfo info) + { + string parent = info is DirectoryInfo dir + ? dir.Parent.FullName + : Unsafe.As(info).DirectoryName; + + return info.AppendPSProperties(parent); + } + internal static PSObject AppendPSProperties(this FileSystemInfo info, string parent) { const string provider = @"Microsoft.PowerShell.Core\FileSystem::"; diff --git a/tests/ArchiveEntryCmdlets.tests.ps1 b/tests/ArchiveEntryCmdlets.tests.ps1 index a9120d9..4d335b5 100644 --- a/tests/ArchiveEntryCmdlets.tests.ps1 +++ b/tests/ArchiveEntryCmdlets.tests.ps1 @@ -31,11 +31,11 @@ Describe 'Archive Entry Cmdlets' { } $algos = [PSCompression.Algorithm].GetEnumValues() - $algos | ForEach-Object { - Compress-TarArchive $testTarpath -Destination $testTarpath -Algorithm $_ + $tarArchives = $algos | ForEach-Object { + Compress-TarArchive $testTarpath $testTarpath -Algorithm $_ -PassThru } - $zip, $file, $uri | Out-Null + $zip, $file, $uri, $tarArchives | Out-Null } Context 'New-ZipEntry' -Tag 'New-ZipEntry' { @@ -218,13 +218,6 @@ Describe 'Archive Entry Cmdlets' { } Context 'Get-TarEntry' -Tag 'Get-TarEntry' { - BeforeAll { - $tarArchives = Get-ChildItem $TestDrive -File | - Where-Object { $_.BaseName.StartsWith($testTarName) } - - $tarArchives | Out-Null - } - It 'Can list entries in a tar archive' { $tarArchives | Get-TarEntry | Should -BeOfType ([PSCompression.Abstractions.TarEntryBase]) @@ -232,13 +225,15 @@ Describe 'Archive Entry Cmdlets' { It 'Can list entries from a Stream' { foreach ($archive in $tarArchives) { - $algo = $archive.Extension if ($archive.Extension -eq '.tar') { $algo = 'none' } + else { + $algo = $archive.Extension.TrimStart('.') + } Use-Object ($stream = $archive.OpenRead()) { - $stream | Get-TarEntry -Algorithm $algo.TrimStart('.') | + $stream | Get-TarEntry -Algorithm $algo | Should -BeOfType ([PSCompression.Abstractions.TarEntryBase]) } } @@ -366,6 +361,79 @@ Describe 'Archive Entry Cmdlets' { } } + Context 'Get-TarEntryContent' -Tag 'Get-TarEntryContent' { + It 'Can read content from tar file entries' { + $tarArchives | Get-TarEntry -Type Archive | + Get-TarEntryContent | + Should -Match '^\d+$' + } + + It 'Can read content from tar file entries created from input Stream' { + foreach ($archive in $tarArchives) { + if ($archive.Extension -eq '.tar') { + $algo = 'none' + } + else { + $algo = $archive.Extension.TrimStart('.') + } + + Use-Object ($stream = $archive.OpenRead()) { + $stream | Get-TarEntry -Algorithm $algo -Type Archive | + Get-TarEntryContent | + Should -Match '^\d+$' + } + } + } + + It 'Should throw when a Stream is Diposed' { + { + foreach ($archive in $tarArchives) { + if ($archive.Extension -eq '.tar') { + $algo = 'none' + } + else { + $algo = $archive.Extension.TrimStart('.') + } + + $entry = Use-Object ($stream = $archive.OpenRead()) { + $stream | Get-TarEntry -Algorithm $algo -Type Archive + } + + { $entry | Get-ZipEntryContent } | Should -Throw + } + } + } + + It 'Should not throw when an instance wrapped in PSObject is passed as Encoding argument' { + $enc = Write-Output utf8 + { $tarArchives | Get-TarEntry -Type Archive | Get-TarEntryContent -Encoding $enc } | + Should -Not -Throw + + $enc = [Encoding]::UTF8 | Write-Output + { $tarArchives | Get-TarEntry -Type Archive | Get-TarEntryContent -Encoding $enc } | + Should -Not -Throw + + $enc = [Encoding]::UTF8.CodePage | Write-Output + { $tarArchives | Get-TarEntry -Type Archive | Get-TarEntryContent -Encoding $enc } | + Should -Not -Throw + } + + It 'Can read bytes from tar file entries' { + $tarArchives | Get-TarEntry -Type Archive | Get-TarEntryContent -AsByteStream | + Should -BeOfType ([byte]) + } + + It 'Can output a byte array when using the -Raw switch' { + $tarArchives | Get-TarEntry -Type Archive | Get-TarEntryContent -AsByteStream -Raw | + Should -BeOfType ([byte[]]) + } + + It 'Should not attempt to read a directory entry' { + { $tarArchives | Get-TarEntry -Type Directory | Get-TarEntryContent } | + Should -Throw + } + } + Context 'Remove-ZipEntry' -Tag 'Remove-ZipEntry' { It 'No entry is removed with -WhatIf' { $zip | Get-ZipEntry | Remove-ZipEntry -WhatIf From e7c15632dc60a39534c93340afb1946ffde2f3ce Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Sun, 22 Jun 2025 10:35:19 -0300 Subject: [PATCH 37/53] adding tests for GetTarEntryContent command --- tests/ArchiveEntryCmdlets.tests.ps1 | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/ArchiveEntryCmdlets.tests.ps1 b/tests/ArchiveEntryCmdlets.tests.ps1 index 4d335b5..bde928c 100644 --- a/tests/ArchiveEntryCmdlets.tests.ps1 +++ b/tests/ArchiveEntryCmdlets.tests.ps1 @@ -366,6 +366,10 @@ Describe 'Archive Entry Cmdlets' { $tarArchives | Get-TarEntry -Type Archive | Get-TarEntryContent | Should -Match '^\d+$' + + $tarArchives | Get-TarEntry -Type Archive | + Get-TarEntryContent -Raw | + Should -Match '^\d+$' } It 'Can read content from tar file entries created from input Stream' { From 41b36ddc2ef11daf421903fc27075920563f998e Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Sun, 22 Jun 2025 10:48:30 -0300 Subject: [PATCH 38/53] typo.. --- tests/ArchiveEntryCmdlets.tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ArchiveEntryCmdlets.tests.ps1 b/tests/ArchiveEntryCmdlets.tests.ps1 index bde928c..3f20034 100644 --- a/tests/ArchiveEntryCmdlets.tests.ps1 +++ b/tests/ArchiveEntryCmdlets.tests.ps1 @@ -403,7 +403,7 @@ Describe 'Archive Entry Cmdlets' { $stream | Get-TarEntry -Algorithm $algo -Type Archive } - { $entry | Get-ZipEntryContent } | Should -Throw + { $entry | Get-TarEntryContent } | Should -Throw } } } From 148d655b86a5f32117898f2df4a402c9cde42d8b Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Sun, 22 Jun 2025 10:53:12 -0300 Subject: [PATCH 39/53] typo.. --- tests/ArchiveEntryCmdlets.tests.ps1 | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/tests/ArchiveEntryCmdlets.tests.ps1 b/tests/ArchiveEntryCmdlets.tests.ps1 index 3f20034..ca09935 100644 --- a/tests/ArchiveEntryCmdlets.tests.ps1 +++ b/tests/ArchiveEntryCmdlets.tests.ps1 @@ -390,21 +390,19 @@ Describe 'Archive Entry Cmdlets' { } It 'Should throw when a Stream is Diposed' { - { - foreach ($archive in $tarArchives) { - if ($archive.Extension -eq '.tar') { - $algo = 'none' - } - else { - $algo = $archive.Extension.TrimStart('.') - } - - $entry = Use-Object ($stream = $archive.OpenRead()) { - $stream | Get-TarEntry -Algorithm $algo -Type Archive - } - - { $entry | Get-TarEntryContent } | Should -Throw + foreach ($archive in $tarArchives) { + if ($archive.Extension -eq '.tar') { + $algo = 'none' + } + else { + $algo = $archive.Extension.TrimStart('.') } + + $entry = Use-Object ($stream = $archive.OpenRead()) { + $stream | Get-TarEntry -Algorithm $algo -Type Archive + } + + { $entry | Get-TarEntryContent } | Should -Throw } } From 655c24a527a2571f698e086cf24b8dbc48d459b2 Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Sun, 22 Jun 2025 16:07:08 -0300 Subject: [PATCH 40/53] adds expandtarentry command tests --- ...iveCompressionExpansionCommands.tests.ps1} | 34 +---- ... ArchiveEntryManagementCommands.tests.ps1} | 136 ++++++++++++++++-- tests/EncodingCompleter.tests.ps1 | 40 +++--- tests/EncodingTransformation.tests.ps1 | 36 ++--- tests/FormattingInternals.tests.ps1 | 15 +- ...ingCompressionExpansionCommands.tests.ps1} | 2 +- tests/ZipEntryBase.tests.ps1 | 33 ++--- tests/ZipEntryDirectory.tests.ps1 | 8 +- tests/ZipEntryFile.tests.ps1 | 13 +- tests/shared.psm1 | 42 +++++- 10 files changed, 249 insertions(+), 110 deletions(-) rename tests/{CompressExpandArchiveCommands.tests.ps1 => ArchiveCompressionExpansionCommands.tests.ps1} (89%) rename tests/{ArchiveEntryCmdlets.tests.ps1 => ArchiveEntryManagementCommands.tests.ps1} (84%) rename tests/{ToFromStringCompression.tests.ps1 => StringCompressionExpansionCommands.tests.ps1} (98%) diff --git a/tests/CompressExpandArchiveCommands.tests.ps1 b/tests/ArchiveCompressionExpansionCommands.tests.ps1 similarity index 89% rename from tests/CompressExpandArchiveCommands.tests.ps1 rename to tests/ArchiveCompressionExpansionCommands.tests.ps1 index 739a3e0..a79455e 100644 --- a/tests/CompressExpandArchiveCommands.tests.ps1 +++ b/tests/ArchiveCompressionExpansionCommands.tests.ps1 @@ -11,37 +11,15 @@ $manifestPath = [Path]::Combine($PSScriptRoot, '..', 'output', $moduleName) Import-Module $manifestPath Import-Module ([Path]::Combine($PSScriptRoot, 'shared.psm1')) -Describe 'Compress & Expand Archive Commands' -Tag 'Compress & Expand Archive Commands' { +Describe 'Archive Compression & Expansion Commands' -Tag 'Archive Compression & Expansion Commands' { BeforeAll { $algos = [PSCompression.Algorithm].GetEnumValues() - $sourceName = 'CompressArchiveTests' $destName = 'CompressArchiveExtract' - $testpath = Join-Path $TestDrive $sourceName $extractpath = Join-Path $TestDrive $destName - - $fileCount = $dirCount = 0 - Get-Structure | ForEach-Object { - $newItemSplat = @{ - ItemType = ('Directory', 'File')[$_.EndsWith('.txt')] - Value = Get-Random - Force = $true - Path = Join-Path $testpath $_ - } - - $null = New-Item @newItemSplat - - if ($newItemSplat['ItemType'] -eq 'Directory') { - $dirCount++ - } - else { - $fileCount++ - } - } - - $dirCount++ # Includes the folder itself - $extractpath, $algos | Out-Null + $itemCounts = Get-Structure | Build-Structure $testpath + $extractpath, $algos, $itemCounts | Out-Null } It 'Can compress a folder and all its child items' { @@ -102,8 +80,8 @@ Describe 'Compress & Expand Archive Commands' -Tag 'Compress & Expand Archive Co $expanded = Get-ChildItem $destination -Recurse $files, $dirs = $expanded.Where({ $_ -is [FileInfo] }, 'Split') - $files | Should -HaveCount $fileCount - $dirs | Should -HaveCount $dirCount + $files | Should -HaveCount $itemCounts.File + $dirs | Should -HaveCount $itemCounts.Directory } } @@ -214,7 +192,7 @@ Describe 'Compress & Expand Archive Commands' -Tag 'Compress & Expand Archive Co Should -BeOfType ([WarningRecord]) } - Context 'ExpandTarArchive Command' -Tag 'ExpandTarArchive Command' { + Context 'Expand-TarArchive' -Tag 'Expand-TarArchive' { BeforeAll { $destination = 'shouldThrowIfExists' New-Item $destination -ItemType Directory | Out-Null diff --git a/tests/ArchiveEntryCmdlets.tests.ps1 b/tests/ArchiveEntryManagementCommands.tests.ps1 similarity index 84% rename from tests/ArchiveEntryCmdlets.tests.ps1 rename to tests/ArchiveEntryManagementCommands.tests.ps1 index ca09935..dbda73b 100644 --- a/tests/ArchiveEntryCmdlets.tests.ps1 +++ b/tests/ArchiveEntryManagementCommands.tests.ps1 @@ -11,31 +11,30 @@ $manifestPath = [Path]::Combine($PSScriptRoot, '..', 'output', $moduleName) Import-Module $manifestPath Import-Module ([Path]::Combine($PSScriptRoot, 'shared.psm1')) -Describe 'Archive Entry Cmdlets' { +Describe 'Archive Entry Management Commands' { BeforeAll { $zip = New-Item (Join-Path $TestDrive test.zip) -ItemType File -Force $file = New-Item ([Path]::Combine($TestDrive, 'someFile.txt')) -ItemType File -Value 'foo' $uri = 'https://www.powershellgallery.com/api/v2/package/PSCompression' $testTarName = 'TarEntryTests' $testTarpath = Join-Path $TestDrive $testTarName + $itemCounts = Get-Structure | Build-Structure $testTarpath + $totalCount = $itemCounts.File + $itemCounts.Directory + $algos = [PSCompression.Algorithm].GetEnumValues() - Get-Structure | ForEach-Object { - $newItemSplat = @{ - ItemType = ('Directory', 'File')[$_.EndsWith('.txt')] - Value = Get-Random - Force = $true - Path = Join-Path $testTarpath $_ + $tarArchives = foreach ($algo in $algos) { + $compressTarArchiveSplat = @{ + Algorithm = $algo + PassThru = $true + LiteralPath = $testTarpath + Destination = $testTarpath } - - $null = New-Item @newItemSplat + Compress-TarArchive @compressTarArchiveSplat } - $algos = [PSCompression.Algorithm].GetEnumValues() - $tarArchives = $algos | ForEach-Object { - Compress-TarArchive $testTarpath $testTarpath -Algorithm $_ -PassThru - } - $zip, $file, $uri, $tarArchives | Out-Null + + $zip, $file, $uri, $tarArchives, $itemCounts, $totalCount | Out-Null } Context 'New-ZipEntry' -Tag 'New-ZipEntry' { @@ -643,6 +642,17 @@ Describe 'Archive Entry Cmdlets' { } | Should -Throw -ExceptionType ([ObjectDisposedException]) } + It 'Should extract entries to the current directory when -Destination is not specified' { + try { + New-Item expandToCurrent -ItemType Directory | Push-Location + { $zip | Get-ZipEntry | Expand-ZipEntry } | Should -Not -Throw + Get-ChildItem | Should -Not -HaveCount 0 + } + finally { + Pop-Location + } + } + It 'Should throw when -Destination is an invalid path' { { $zip | Get-ZipEntry | Expand-ZipEntry -Destination function: } | Should -Throw @@ -673,4 +683,102 @@ Describe 'Archive Entry Cmdlets' { ForEach-Object { $_ | Get-Content | Should -BeExactly $content } } } + + Context 'Expand-TarEntry' -Tag 'Expand-TarEntry' { + It 'Can extract entries to a destination directory' { + $tarArchives | ForEach-Object { + $destination = "extract_$($_.Extension)" + + { $_ | Get-TarEntry | Expand-TarEntry -Destination $destination } | + Should -Not -Throw + + Get-ChildItem $destination -Recurse | + Should -HaveCount $totalCount + } + } + + It 'Can extract tar entries created from input Stream' { + foreach ($archive in $tarArchives) { + if ($archive.Extension -eq '.tar') { + $algo = 'none' + } + else { + $algo = $archive.Extension.TrimStart('.') + } + + $destination = "extract_${algo}_fromStream" + + $result = Use-Object ($stream = $archive.OpenRead()) { + $stream | Get-TarEntry -Algorithm $algo | + Expand-TarEntry -Destination $destination -PassThru + } + + $result | Should -BeOfType ([FileSystemInfo]) + $result | Should -HaveCount $totalCount + } + } + + It 'Should throw when a Stream is Diposed' { + foreach ($archive in $tarArchives) { + if ($archive.Extension -eq '.tar') { + $algo = 'none' + } + else { + $algo = $archive.Extension.TrimStart('.') + } + + $destination = "extract_$($_.Extension)_fromStream" + + $entries = Use-Object ($stream = $archive.OpenRead()) { + $stream | Get-TarEntry -Algorithm $algo + } + + { $entries | Expand-TarEntry -Destination $destination } | + Should -Throw + } + } + + It 'Should throw when -Destination is an invalid path' { + { $tarArchives | Get-TarEntry | Expand-TarEntry -Destination function: } | + Should -Throw + } + + It 'Should throw if the destination path argument belongs to a file' { + $tarArchive = $tarArchives[0] + { $tarArchive | Get-TarEntry | Expand-TarEntry -Destination $tarArchive.FullName } | + Should -Throw + } + + It 'Should not overwrite files without -Force' { + $tarArchives | ForEach-Object { + $destination = "extract_$($_.Extension)" + + { $_ | Get-TarEntry | Expand-TarEntry -Destination $destination } | + Should -Throw -ExceptionType ([IOException]) + } + } + + It 'Can overwrite files if using -Force' { + $tarArchives | ForEach-Object { + $destination = "extract_$($_.Extension)" + + { $_ | Get-TarEntry | Expand-TarEntry -Destination $destination -Force } | + Should -Not -Throw + } + } + + It 'Should extract entries to the current directory when -Destination is not specified' { + foreach ($archive in $tarArchives) { + try { + New-Item "expandToCurrent_$($archive.Extension)" -ItemType Directory | Push-Location + { $archive | Get-TarEntry | Expand-TarEntry } | Should -Not -Throw + Get-ChildItem | Should -Not -HaveCount 0 + } + finally { + Pop-Location + } + + } + } + } } diff --git a/tests/EncodingCompleter.tests.ps1 b/tests/EncodingCompleter.tests.ps1 index a069285..cfcb621 100644 --- a/tests/EncodingCompleter.tests.ps1 +++ b/tests/EncodingCompleter.tests.ps1 @@ -8,27 +8,27 @@ $manifestPath = [Path]::Combine($PSScriptRoot, '..', 'output', $moduleName) Import-Module $manifestPath Import-Module ([Path]::Combine($PSScriptRoot, 'shared.psm1')) -BeforeAll { - $encodingSet = @( - 'ascii' - 'bigendianUtf32' - 'unicode' - 'utf8' - 'utf8NoBOM' - 'bigendianUnicode' - 'oem' - 'utf8BOM' - 'utf32' - - if ($osIsWindows) { - 'ansi' - } - ) - - $encodingSet | Out-Null -} - Describe 'EncodingCompleter Class' { + BeforeAll { + $encodingSet = @( + 'ascii' + 'bigendianUtf32' + 'unicode' + 'utf8' + 'utf8NoBOM' + 'bigendianUnicode' + 'oem' + 'utf8BOM' + 'utf32' + + if ($osIsWindows) { + 'ansi' + } + ) + + $encodingSet | Out-Null + } + It 'Completes results from a completion set' { (Complete 'Test-Completer ').CompletionText | Should -BeExactly $encodingSet diff --git a/tests/EncodingTransformation.tests.ps1 b/tests/EncodingTransformation.tests.ps1 index ec9e4fe..b144c89 100644 --- a/tests/EncodingTransformation.tests.ps1 +++ b/tests/EncodingTransformation.tests.ps1 @@ -1,10 +1,13 @@ -$ErrorActionPreference = 'Stop' +using namespace System.IO +using namespace System.Text -$moduleName = (Get-Item ([IO.Path]::Combine($PSScriptRoot, '..', 'module', '*.psd1'))).BaseName -$manifestPath = [IO.Path]::Combine($PSScriptRoot, '..', 'output', $moduleName) +$ErrorActionPreference = 'Stop' + +$moduleName = (Get-Item ([Path]::Combine($PSScriptRoot, '..', 'module', '*.psd1'))).BaseName +$manifestPath = [Path]::Combine($PSScriptRoot, '..', 'output', $moduleName) Import-Module $manifestPath -Import-Module ([System.IO.Path]::Combine($PSScriptRoot, 'shared.psm1')) +Import-Module ([Path]::Combine($PSScriptRoot, 'shared.psm1')) Describe 'EncodingTransformation Class' { BeforeAll { @@ -13,23 +16,22 @@ Describe 'EncodingTransformation Class' { { [System.Runtime.InteropServices.DllImport("Kernel32.dll")] public static extern int GetACP(); - } - ' + }' $encodings = @{ - 'ascii' = [System.Text.ASCIIEncoding]::new() - 'bigendianunicode' = [System.Text.UnicodeEncoding]::new($true, $true) - 'bigendianutf32' = [System.Text.UTF32Encoding]::new($true, $true) + 'ascii' = [ASCIIEncoding]::new() + 'bigendianunicode' = [UnicodeEncoding]::new($true, $true) + 'bigendianutf32' = [UTF32Encoding]::new($true, $true) 'oem' = [Console]::OutputEncoding - 'unicode' = [System.Text.UnicodeEncoding]::new() - 'utf8' = [System.Text.UTF8Encoding]::new($false) - 'utf8bom' = [System.Text.UTF8Encoding]::new($true) - 'utf8nobom' = [System.Text.UTF8Encoding]::new($false) - 'utf32' = [System.Text.UTF32Encoding]::new() + 'unicode' = [UnicodeEncoding]::new() + 'utf8' = [UTF8Encoding]::new($false) + 'utf8bom' = [UTF8Encoding]::new($true) + 'utf8nobom' = [UTF8Encoding]::new($false) + 'utf32' = [UTF32Encoding]::new() } if ($osIsWindows) { - $encodings['ansi'] = [System.Text.Encoding]::GetEncoding([Acp]::GetACP()) + $encodings['ansi'] = [Encoding]::GetEncoding([Acp]::GetACP()) } $transform = [PSCompression.EncodingTransformation]::new() @@ -37,8 +39,8 @@ Describe 'EncodingTransformation Class' { } It 'Transforms Encoding to Encoding' { - $transform.Transform($ExecutionContext, [System.Text.Encoding]::UTF8) | - Should -BeExactly ([System.Text.Encoding]::UTF8) + $transform.Transform($ExecutionContext, [Encoding]::UTF8) | + Should -BeExactly ([Encoding]::UTF8) } It 'Transforms a completion set to their Encoding Representations' { diff --git a/tests/FormattingInternals.tests.ps1 b/tests/FormattingInternals.tests.ps1 index 24ebe04..2a02570 100644 --- a/tests/FormattingInternals.tests.ps1 +++ b/tests/FormattingInternals.tests.ps1 @@ -1,10 +1,12 @@ -$ErrorActionPreference = 'Stop' +using namespace System.IO -$moduleName = (Get-Item ([IO.Path]::Combine($PSScriptRoot, '..', 'module', '*.psd1'))).BaseName -$manifestPath = [IO.Path]::Combine($PSScriptRoot, '..', 'output', $moduleName) +$ErrorActionPreference = 'Stop' + +$moduleName = (Get-Item ([Path]::Combine($PSScriptRoot, '..', 'module', '*.psd1'))).BaseName +$manifestPath = [Path]::Combine($PSScriptRoot, '..', 'output', $moduleName) Import-Module $manifestPath -Import-Module ([System.IO.Path]::Combine($PSScriptRoot, 'shared.psm1')) +Import-Module ([Path]::Combine($PSScriptRoot, 'shared.psm1')) Describe 'Formatting internals' { BeforeAll { @@ -26,6 +28,9 @@ Describe 'Formatting internals' { It 'Formats datetime instances' { [PSCompression.Internal._Format]::GetFormattedDate([datetime]::Now) | - Should -BeExactly ([string]::Format([CultureInfo]::CurrentCulture,'{0,10:d} {0,8:t}', [datetime]::Now)) + Should -BeExactly ([string]::Format( + [CultureInfo]::CurrentCulture, + '{0,10:d} {0,8:t}', + [datetime]::Now)) } } diff --git a/tests/ToFromStringCompression.tests.ps1 b/tests/StringCompressionExpansionCommands.tests.ps1 similarity index 98% rename from tests/ToFromStringCompression.tests.ps1 rename to tests/StringCompressionExpansionCommands.tests.ps1 index 558ad4b..acec54b 100644 --- a/tests/ToFromStringCompression.tests.ps1 +++ b/tests/StringCompressionExpansionCommands.tests.ps1 @@ -9,7 +9,7 @@ $manifestPath = [Path]::Combine($PSScriptRoot, '..', 'output', $moduleName) Import-Module $manifestPath Import-Module ([Path]::Combine($PSScriptRoot, 'shared.psm1')) -Describe 'To & From String Compression' { +Describe 'String Compression & Expansion Commands' { BeforeAll { $conversionCommands = @{ 'ConvertFrom-BrotliString' = 'ConvertTo-BrotliString' diff --git a/tests/ZipEntryBase.tests.ps1 b/tests/ZipEntryBase.tests.ps1 index 43afcad..c23aac3 100644 --- a/tests/ZipEntryBase.tests.ps1 +++ b/tests/ZipEntryBase.tests.ps1 @@ -1,10 +1,13 @@ -$ErrorActionPreference = 'Stop' +using namespace System.IO +using namespace System.IO.Compression -$moduleName = (Get-Item ([IO.Path]::Combine($PSScriptRoot, '..', 'module', '*.psd1'))).BaseName -$manifestPath = [IO.Path]::Combine($PSScriptRoot, '..', 'output', $moduleName) +$ErrorActionPreference = 'Stop' + +$moduleName = (Get-Item ([Path]::Combine($PSScriptRoot, '..', 'module', '*.psd1'))).BaseName +$manifestPath = [Path]::Combine($PSScriptRoot, '..', 'output', $moduleName) Import-Module $manifestPath -Import-Module ([System.IO.Path]::Combine($PSScriptRoot, 'shared.psm1')) +Import-Module ([Path]::Combine($PSScriptRoot, 'shared.psm1')) Describe 'ZipEntryBase Class' { BeforeAll { @@ -15,37 +18,35 @@ Describe 'ZipEntryBase Class' { It 'Can extract an entry' { ($zip | Get-ZipEntry -Type Archive).ExtractTo($TestDrive, $false) | - Should -BeOfType ([System.IO.FileInfo]) + Should -BeOfType ([FileInfo]) } It 'Can overwrite a file when extracting' { ($zip | Get-ZipEntry -Type Archive).ExtractTo($TestDrive, $true) | - Should -BeOfType ([System.IO.FileInfo]) + Should -BeOfType ([FileInfo]) } It 'Can extract a file from entries created from input Stream' { Use-Object ($stream = $zip.OpenRead()) { ($stream | Get-ZipEntry -Type Archive).ExtractTo($TestDrive, $true) - } | Should -BeOfType ([System.IO.FileInfo]) + } | Should -BeOfType ([FileInfo]) } It 'Can create a new folder in the destination path when extracting' { $entry = $zip | Get-ZipEntry -Type Archive - $file = $entry.ExtractTo( - [System.IO.Path]::Combine($TestDrive, 'myTestFolder'), - $false) + $file = $entry.ExtractTo([Path]::Combine($TestDrive, 'myTestFolder'), $false) - $file.FullName | Should -BeExactly ([System.IO.Path]::Combine($TestDrive, 'myTestFolder', $entry.Name)) + $file.FullName | Should -BeExactly ([Path]::Combine($TestDrive, 'myTestFolder', $entry.Name)) } It 'Can extract folders' { ($zip | Get-ZipEntry -Type Directory).ExtractTo($TestDrive, $false) | - Should -BeOfType ([System.IO.DirectoryInfo]) + Should -BeOfType ([DirectoryInfo]) } It 'Can overwrite folders when extracting' { ($zip | Get-ZipEntry -Type Directory).ExtractTo($TestDrive, $true) | - Should -BeOfType ([System.IO.DirectoryInfo]) + Should -BeOfType ([DirectoryInfo]) } It 'Has a LastWriteTime Property' { @@ -76,17 +77,17 @@ Describe 'ZipEntryBase Class' { It 'Opens a ZipArchive on OpenRead() and OpenWrite()' { Use-Object ($archive = ($zip | Get-ZipEntry).OpenRead()) { - $archive | Should -BeOfType ([System.IO.Compression.ZipArchive]) + $archive | Should -BeOfType ([ZipArchive]) } Use-Object ($stream = $zip.OpenRead()) { Use-Object ($archive = ($stream | Get-ZipEntry).OpenRead()) { - $archive | Should -BeOfType ([System.IO.Compression.ZipArchive]) + $archive | Should -BeOfType ([ZipArchive]) } } Use-Object ($archive = ($zip | Get-ZipEntry).OpenWrite()) { - $archive | Should -BeOfType ([System.IO.Compression.ZipArchive]) + $archive | Should -BeOfType ([ZipArchive]) } } diff --git a/tests/ZipEntryDirectory.tests.ps1 b/tests/ZipEntryDirectory.tests.ps1 index a37d744..a731a6a 100644 --- a/tests/ZipEntryDirectory.tests.ps1 +++ b/tests/ZipEntryDirectory.tests.ps1 @@ -1,8 +1,10 @@ -$moduleName = (Get-Item ([IO.Path]::Combine($PSScriptRoot, '..', 'module', '*.psd1'))).BaseName -$manifestPath = [IO.Path]::Combine($PSScriptRoot, '..', 'output', $moduleName) +using namespace System.IO + +$moduleName = (Get-Item ([Path]::Combine($PSScriptRoot, '..', 'module', '*.psd1'))).BaseName +$manifestPath = [Path]::Combine($PSScriptRoot, '..', 'output', $moduleName) Import-Module $manifestPath -Import-Module ([System.IO.Path]::Combine($PSScriptRoot, 'shared.psm1')) +Import-Module ([Path]::Combine($PSScriptRoot, 'shared.psm1')) Describe 'ZipEntryDirectory Class' { BeforeAll { diff --git a/tests/ZipEntryFile.tests.ps1 b/tests/ZipEntryFile.tests.ps1 index b944bbe..672950a 100644 --- a/tests/ZipEntryFile.tests.ps1 +++ b/tests/ZipEntryFile.tests.ps1 @@ -1,10 +1,13 @@ -$ErrorActionPreference = 'Stop' +using namespace System.IO +using namespace System.IO.Compression -$moduleName = (Get-Item ([IO.Path]::Combine($PSScriptRoot, '..', 'module', '*.psd1'))).BaseName -$manifestPath = [IO.Path]::Combine($PSScriptRoot, '..', 'output', $moduleName) +$ErrorActionPreference = 'Stop' + +$moduleName = (Get-Item ([Path]::Combine($PSScriptRoot, '..', 'module', '*.psd1'))).BaseName +$manifestPath = [Path]::Combine($PSScriptRoot, '..', 'output', $moduleName) Import-Module $manifestPath -Import-Module ([System.IO.Path]::Combine($PSScriptRoot, 'shared.psm1')) +Import-Module ([Path]::Combine($PSScriptRoot, 'shared.psm1')) Describe 'ZipEntryFile Class' { BeforeAll { @@ -28,7 +31,7 @@ Describe 'ZipEntryFile Class' { It 'Should Open the source zip' { Use-Object ($stream = ($zip | Get-ZipEntry).OpenRead()) { - $stream | Should -BeOfType ([System.IO.Compression.ZipArchive]) + $stream | Should -BeOfType ([ZipArchive]) } } } diff --git a/tests/shared.psm1 b/tests/shared.psm1 index 85b4e06..d2fd5b3 100644 --- a/tests/shared.psm1 +++ b/tests/shared.psm1 @@ -25,12 +25,52 @@ function Get-Structure { } } +function Build-Structure { + param( + [Parameter(ValueFromPipeline, Mandatory)] + [string] $Item, + + [Parameter(Mandatory, Position = 0)] + [string] $Path) + + begin { + $fileCount = $dirCount = 0 + } + process { + $isFile = $Item.EndsWith('.txt') + + $newItemSplat = @{ + ItemType = ('Directory', 'File')[$isFile] + Value = Get-Random + Force = $true + Path = Join-Path $Path $Item + } + + $null = New-Item @newItemSplat + + if ($isFile) { + $fileCount++ + return + } + + $dirCount++ + } + end { + $dirCount++ # Includes the folder itself + + [pscustomobject]@{ + File = $fileCount + Directory = $dirCount + } + } +} + $osIsWindows = [RuntimeInformation]::IsOSPlatform([OSPlatform]::Windows) $osIsWindows | Out-Null $exportModuleMemberSplat = @{ Variable = 'moduleName', 'manifestPath', 'osIsWindows' - Function = 'Decode', 'Complete', 'Test-Completer', 'Get-Structure' + Function = 'Decode', 'Complete', 'Test-Completer', 'Get-Structure', 'Build-Structure' } Export-ModuleMember @exportModuleMemberSplat From d5dbc54ba79515b1891765b587b751c59da0a009 Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Sun, 22 Jun 2025 16:35:10 -0300 Subject: [PATCH 41/53] i tink done with tests --- tests/ArchiveEntryManagementCommands.tests.ps1 | 2 -- ...ry.tests.ps1 => DirectoryEntryTypes.tests.ps1} | 7 ++++++- ...tryFile.tests.ps1 => FileEntryTypes.tests.ps1} | 15 ++++++++++++++- tests/FormattingInternals.tests.ps1 | 9 +++++++++ 4 files changed, 29 insertions(+), 4 deletions(-) rename tests/{ZipEntryDirectory.tests.ps1 => DirectoryEntryTypes.tests.ps1} (64%) rename tests/{ZipEntryFile.tests.ps1 => FileEntryTypes.tests.ps1} (66%) diff --git a/tests/ArchiveEntryManagementCommands.tests.ps1 b/tests/ArchiveEntryManagementCommands.tests.ps1 index dbda73b..614ce13 100644 --- a/tests/ArchiveEntryManagementCommands.tests.ps1 +++ b/tests/ArchiveEntryManagementCommands.tests.ps1 @@ -32,8 +32,6 @@ Describe 'Archive Entry Management Commands' { Compress-TarArchive @compressTarArchiveSplat } - - $zip, $file, $uri, $tarArchives, $itemCounts, $totalCount | Out-Null } diff --git a/tests/ZipEntryDirectory.tests.ps1 b/tests/DirectoryEntryTypes.tests.ps1 similarity index 64% rename from tests/ZipEntryDirectory.tests.ps1 rename to tests/DirectoryEntryTypes.tests.ps1 index a731a6a..115e0da 100644 --- a/tests/ZipEntryDirectory.tests.ps1 +++ b/tests/DirectoryEntryTypes.tests.ps1 @@ -6,13 +6,18 @@ $manifestPath = [Path]::Combine($PSScriptRoot, '..', 'output', $moduleName) Import-Module $manifestPath Import-Module ([Path]::Combine($PSScriptRoot, 'shared.psm1')) -Describe 'ZipEntryDirectory Class' { +Describe 'Directory Entry Types' { BeforeAll { $zip = New-Item (Join-Path $TestDrive test.zip) -ItemType File -Force New-ZipEntry $zip.FullName -EntryPath afolder/ + $tarArchive = New-Item (Join-Path $TestDrive afolder) -ItemType Directory -Force | + Compress-TarArchive -Destination 'testTarFile' -PassThru + + $tarArchive | Out-Null } It 'Should be of type Directory' { ($zip | Get-ZipEntry).Type | Should -BeExactly ([PSCompression.EntryType]::Directory) + ($tarArchive | Get-TarEntry).Type | Should -BeExactly ([PSCompression.EntryType]::Directory) } } diff --git a/tests/ZipEntryFile.tests.ps1 b/tests/FileEntryTypes.tests.ps1 similarity index 66% rename from tests/ZipEntryFile.tests.ps1 rename to tests/FileEntryTypes.tests.ps1 index 672950a..6dd11a5 100644 --- a/tests/ZipEntryFile.tests.ps1 +++ b/tests/FileEntryTypes.tests.ps1 @@ -9,24 +9,37 @@ $manifestPath = [Path]::Combine($PSScriptRoot, '..', 'output', $moduleName) Import-Module $manifestPath Import-Module ([Path]::Combine($PSScriptRoot, 'shared.psm1')) -Describe 'ZipEntryFile Class' { +Describe 'File Entry Types' { BeforeAll { $zip = New-Item (Join-Path $TestDrive test.zip) -ItemType File -Force 'hello world!' | New-ZipEntry $zip.FullName -EntryPath helloworld.txt + + $tarArchive = New-Item (Join-Path $TestDrive helloworld.txt) -ItemType File -Force | + Compress-TarArchive -Destination 'testTarFile' -PassThru + + $tarArchive | Out-Null } It 'Should be of type Archive' { ($zip | Get-ZipEntry).Type | Should -BeExactly ([PSCompression.EntryType]::Archive) + + ($tarArchive | Get-TarEntry).Type | Should -BeExactly ([PSCompression.EntryType]::Archive) } It 'Should Have a BaseName Property' { ($zip | Get-ZipEntry).BaseName | Should -BeOfType ([string]) ($zip | Get-ZipEntry).BaseName | Should -BeExactly helloworld + + ($tarArchive | Get-TarEntry).BaseName | Should -BeOfType ([string]) + ($tarArchive | Get-TarEntry).BaseName | Should -BeExactly helloworld } It 'Should Have an Extension Property' { ($zip | Get-ZipEntry).Extension | Should -BeOfType ([string]) ($zip | Get-ZipEntry).Extension | Should -BeExactly .txt + + ($tarArchive | Get-TarEntry).Extension | Should -BeOfType ([string]) + ($tarArchive | Get-TarEntry).Extension | Should -BeExactly .txt } It 'Should Open the source zip' { diff --git a/tests/FormattingInternals.tests.ps1 b/tests/FormattingInternals.tests.ps1 index 2a02570..f9c54c9 100644 --- a/tests/FormattingInternals.tests.ps1 +++ b/tests/FormattingInternals.tests.ps1 @@ -13,6 +13,11 @@ Describe 'Formatting internals' { $zip = New-Item (Join-Path $TestDrive test.zip) -ItemType File -Force 'hello world!' | New-ZipEntry $zip.FullName -EntryPath helloworld.txt New-ZipEntry $zip.FullName -EntryPath afolder/ + $testTarName = 'formattingTarTest' + $testTarpath = Join-Path $TestDrive $testTarName + Get-Structure | Build-Structure $testTarpath + $tarArchive = Compress-TarArchive $testTarpath -Destination $testTarName -PassThru + $tarArchive | Out-Null } It 'Converts Length to their friendly representation' { @@ -24,6 +29,10 @@ Describe 'Formatting internals' { $zip | Get-ZipEntry | ForEach-Object { [PSCompression.Internal._Format]::GetDirectoryPath($_) } | Should -BeOfType ([string]) + + $tarArchive | Get-TarEntry | ForEach-Object { + [PSCompression.Internal._Format]::GetDirectoryPath($_) + } | Should -BeOfType ([string]) } It 'Formats datetime instances' { From 438ca5102d9d651b06572bd9abcfdd77cba3fa12 Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Sun, 22 Jun 2025 16:45:01 -0300 Subject: [PATCH 42/53] i tink done with tests --- tests/FileEntryTypes.tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/FileEntryTypes.tests.ps1 b/tests/FileEntryTypes.tests.ps1 index 6dd11a5..e28eca7 100644 --- a/tests/FileEntryTypes.tests.ps1 +++ b/tests/FileEntryTypes.tests.ps1 @@ -15,7 +15,7 @@ Describe 'File Entry Types' { 'hello world!' | New-ZipEntry $zip.FullName -EntryPath helloworld.txt $tarArchive = New-Item (Join-Path $TestDrive helloworld.txt) -ItemType File -Force | - Compress-TarArchive -Destination 'testTarFile' -PassThru + Compress-TarArchive -Destination 'testTarDirectory' -PassThru $tarArchive | Out-Null } From 903bcbc4cddd87aa42ec9125f446ea53eed76691 Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Mon, 23 Jun 2025 20:36:49 -0300 Subject: [PATCH 43/53] done with docs fucking finally --- CHANGELOG.md | 1 + docs/en-US/Compress-GzipArchive.md | 290 ---------------- docs/en-US/Compress-TarArchive.md | 274 +++++++++++++++ docs/en-US/Compress-ZipArchive.md | 42 +-- docs/en-US/ConvertFrom-BrotliString.md | 139 ++++++++ docs/en-US/ConvertFrom-DeflateString.md | 139 ++++++++ docs/en-US/ConvertFrom-GzipString.md | 18 +- docs/en-US/ConvertFrom-ZLibString.md | 141 ++++++++ docs/en-US/ConvertTo-BrotliString.md | 208 ++++++++++++ docs/en-US/ConvertTo-DeflateString.md | 208 ++++++++++++ docs/en-US/ConvertTo-GzipString.md | 46 ++- docs/en-US/ConvertTo-ZLibString.md | 210 ++++++++++++ docs/en-US/Expand-GzipArchive.md | 277 --------------- docs/en-US/Expand-TarArchive.md | 256 ++++++++++++++ docs/en-US/Expand-TarEntry.md | 197 +++++++++++ docs/en-US/Expand-ZipEntry.md | 6 +- docs/en-US/Get-TarEntry.md | 318 ++++++++++++++++++ docs/en-US/Get-TarEntryContent.md | 243 +++++++++++++ docs/en-US/Get-ZipEntry.md | 26 +- docs/en-US/Get-ZipEntryContent.md | 16 +- docs/en-US/New-ZipEntry.md | 6 +- docs/en-US/Remove-ZipEntry.md | 2 +- docs/en-US/Rename-ZipEntry.md | 6 +- docs/en-US/Set-ZipEntryContent.md | 4 +- .../ToCompressedStringCommandBase.cs | 3 +- 25 files changed, 2437 insertions(+), 639 deletions(-) delete mode 100644 docs/en-US/Compress-GzipArchive.md create mode 100644 docs/en-US/Compress-TarArchive.md create mode 100644 docs/en-US/ConvertFrom-BrotliString.md create mode 100644 docs/en-US/ConvertFrom-DeflateString.md create mode 100644 docs/en-US/ConvertFrom-ZLibString.md create mode 100644 docs/en-US/ConvertTo-BrotliString.md create mode 100644 docs/en-US/ConvertTo-DeflateString.md create mode 100644 docs/en-US/ConvertTo-ZLibString.md delete mode 100644 docs/en-US/Expand-GzipArchive.md create mode 100644 docs/en-US/Expand-TarArchive.md create mode 100644 docs/en-US/Expand-TarEntry.md create mode 100644 docs/en-US/Get-TarEntry.md create mode 100644 docs/en-US/Get-TarEntryContent.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 27908fb..19ab710 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ This update was made possible by the following projects. If you find them helpfu - [SharpZipLib](https://github.com/icsharpcode/SharpZipLib) - [SharpCompress](https://github.com/adamhathcock/sharpcompress) - [BrotliSharpLib](https://github.com/master131/BrotliSharpLib) +- [ZstdSharp](https://github.com/oleg-st/ZstdSharp) ## 01/10/2025 diff --git a/docs/en-US/Compress-GzipArchive.md b/docs/en-US/Compress-GzipArchive.md deleted file mode 100644 index 876e09a..0000000 --- a/docs/en-US/Compress-GzipArchive.md +++ /dev/null @@ -1,290 +0,0 @@ ---- -external help file: PSCompression-help.xml -Module Name: PSCompression -online version: https://github.com/santisq/PSCompression -schema: 2.0.0 ---- - -# Compress-GzipArchive - -## SYNOPSIS - -Creates a Gzip compressed file from specified paths or input bytes. - -## SYNTAX - -### Path (Default) - -```powershell -Compress-GzipArchive - -Path - -Destination - [-CompressionLevel ] - [-Update] - [-Force] - [-PassThru] - [] -``` - -### LiteralPath - -```powershell -Compress-GzipArchive - -LiteralPath - -Destination - [-CompressionLevel ] - [-Update] - [-Force] - [-PassThru] - [] -``` - -### InputBytes - -```powershell -Compress-GzipArchive - -InputBytes - -Destination - [-CompressionLevel ] - [-Update] - [-Force] - [-PassThru] - [] -``` - -## DESCRIPTION - -The `Compress-GzipArchive` cmdlet can compress one or more specified file paths into a single Gzip archive using the [`GzipStream` Class](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression.gzipstream). - -> [!TIP] -> For expansion see [`Expand-GzipArchive`](Expand-ZipEntry.md). - -## EXAMPLES - -### Example 1: Create a Gzip compressed file from a File Path - -```powershell -PS ..\pwsh> Compress-GzipArchive path\to\myFile.ext -Destination myFile.gz -``` - -If the destination does not end with `.gz` the extension is automatically added. - -### Example 2: Create a Gzip compressed file from a string - -```powershell -PS ..\pwsh> 'hello world!' | ConvertTo-GzipString -AsByteStream | - Compress-GzipArchive -Destination .\files\file.gz -``` - -Demonstrates how `-AsByteStream` works on `ConvertTo-GzipString`. -Sends the compressed bytes to `Compress-GzipArchive`. - -### Example 3: Append content to a Gzip archive - -```powershell -PS ..\pwsh> 'this is new content...' | ConvertTo-GzipString -AsByteStream | - Compress-GzipArchive -Destination .\files\file.gz -Update -``` - -Demonstrates how `-Update` works. - -### Example 4: Replace a Gzip archive with new content - -```powershell -PS ..\pwsh> $lorem = Invoke-RestMethod loripsum.net/api/10/long/plaintext -PS ..\pwsh> $lorem | ConvertTo-GzipString -AsByteStream | - Compress-GzipArchive -Destination .\files\file.gz -Force -``` - -Demonstrates how `-Force` works. - -### Example 5: Compressing multiple files into one Gzip archive - -```powershell -PS ..\pwsh> 0..10 | ForEach-Object { - Invoke-RestMethod loripsum.net/api/10/long/plaintext -OutFile .\files\lorem$_.txt -} - -# Check the total Length of the downloaded files -PS ..\pwsh> (Get-Content .\files\lorem*.txt | Measure-Object Length -Sum).Sum / 1kb -86.787109375 - -# Check the total Length after compression -PS ..\pwsh> (Compress-GzipArchive .\files\lorem*.txt -Destination .\files\mergedLorem.gz -PassThru).Length / 1kb -27.6982421875 -``` - -> [!NOTE] -> Due to the nature of Gzip without Tar, all file contents are merged into a single file. - -## PARAMETERS - -### -CompressionLevel - -Define the compression level that should be used. -See [`CompressionLevel` Enum](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression.compressionlevel) for details. - -```yaml -Type: CompressionLevel -Parameter Sets: (All) -Aliases: -Accepted values: Optimal, Fastest, NoCompression, SmallestSize - -Required: False -Position: Named -Default value: Optimal -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Destination - -The path where to store the Gzip compressed file. - -> [!NOTE] -> -> - The parent directory is created if it does not exist. -> - If the path does not have an extension, the cmdlet appends the `.gz` file name extension. - -```yaml -Type: String -Parameter Sets: (All) -Aliases: DestinationPath - -Required: True -Position: 1 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Force - -Overwrites the Gzip archive if exists, otherwise it creates it. - -> [!NOTE] -> If `-Force` and `-Update` are used together this cmdlet will append content to the destination file. - -```yaml -Type: SwitchParameter -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: False -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -InputBytes - -This cmdlet can take input bytes from pipeline to create the output `.gz` archive file. - -> [!NOTE] -> This parameter is meant to be used exclusively in combination with [`ConvertTo-GzipString -AsByteStream`](./ConvertTo-GzipString.md#example-2-create-a-gzip-compressed-file-from-a-string). - -```yaml -Type: Byte[] -Parameter Sets: InputBytes -Aliases: - -Required: True -Position: Named -Default value: None -Accept pipeline input: True (ByValue) -Accept wildcard characters: False -``` - -### -LiteralPath - -Specifies the path or paths to the files that you want to add to the Gzip archive file. -Unlike the `-Path` Parameter, the value of `-LiteralPath` is used exactly as it's typed. -No characters are interpreted as wildcards - -```yaml -Type: String[] -Parameter Sets: LiteralPath -Aliases: PSPath - -Required: True -Position: Named -Default value: None -Accept pipeline input: True (ByPropertyName) -Accept wildcard characters: False -``` - -### -PassThru - -Outputs the object representing the compressed file. -The cmdlet produces no output by default. - -```yaml -Type: SwitchParameter -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: False -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Path - -Specifies the path or paths to the files that you want to add to the Gzip archive file. -To specify multiple paths, and include files in multiple locations, use commas to separate the paths. -This Parameter accepts wildcard characters. -Wildcard characters allow you to add all files in a directory to your archive file. - -```yaml -Type: String[] -Parameter Sets: Path -Aliases: - -Required: True -Position: 0 -Default value: None -Accept pipeline input: True (ByValue) -Accept wildcard characters: True -``` - -### -Update - -Appends content to the existing Gzip file if exists, otherwise it creates it. - -> [!NOTE] -> If `-Force` and `-Update` are used together this cmdlet will append content to the destination file. - -```yaml -Type: SwitchParameter -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: False -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters - -This cmdlet supports the common parameters. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -### String - -You can pipe paths to this cmdlet. Output from `Get-ChildItem` or `Get-Item` can be piped to this cmdlet. - -## OUTPUTS - -### None - -By default, this cmdlet produces no output. - -### FileInfo - -When the `-PassThru` switch is used this cmdlet outputs the `FileInfo` instance representing the compressed file. diff --git a/docs/en-US/Compress-TarArchive.md b/docs/en-US/Compress-TarArchive.md new file mode 100644 index 0000000..893a01e --- /dev/null +++ b/docs/en-US/Compress-TarArchive.md @@ -0,0 +1,274 @@ +--- +external help file: PSCompression.dll-Help.xml +Module Name: PSCompression +online version: https://github.com/santisq/PSCompression +schema: 2.0.0 +--- + +# Compress-TarArchive + +## SYNOPSIS + +The `Compress-TarArchive` cmdlet creates a compressed tar archive file from one or more specified files or directories. It supports multiple compression algorithms and provides flexible file inclusion and exclusion options, similar to the `Compress-ZipArchive` cmdlet. + +## SYNTAX + +### Path + +```powershell +Compress-TarArchive + [-Path] + [-Destination] + [-Algorithm ] + [-CompressionLevel ] + [-Force] + [-PassThru] + [-Exclude ] + [] +``` + +### LiteralPath + +```powershell +Compress-TarArchive + -LiteralPath + [-Destination] + [-Algorithm ] + [-CompressionLevel ] + [-Force] + [-PassThru] + [-Exclude ] + [] +``` + +## DESCRIPTION + +The `Compress-TarArchive` cmdlet creates a tar archive, optionally compressed with algorithms like gzip, bzip2, zstd, or lz4, in a PowerShell-native environment. It simplifies file and directory archiving by integrating seamlessly with PowerShell’s object-oriented pipeline, allowing flexible file selection through cmdlets like `Get-ChildItem` or `Get-Item`. With support for selective inclusion via `-Exclude`, customizable compression levels, and the ability to overwrite existing archives, it provides a convenient alternative to traditional tar utilities for PowerShell users, while preserving directory structures and metadata. + +## EXAMPLES + +### Example 1: Compress all `.log` files in a directory + +```powershell +Get-ChildItem C:\Logs -Recurse -Filter *.log | + Compress-TarArchive -Destination C:\Archives\logs.tar.gz +``` + +This example demonstrates how to compress all `.log` files in the `C:\Logs` directory into a gzip-compressed tar archive named `logs.tar.gz` in the `C:\Archives` directory. + +> [!NOTE] +> If not specified, the cmdlet will use the gzip algorithm as default. + +### Example 2: Compress a folder using `Fastest` Compression Level + +```powershell +Compress-TarArchive -Path .\path -Destination myPath.tar.gz -CompressionLevel Fastest +``` + +This example shows how to compress the entire path directory into a gzip-compressed tar archive named `myPath.tar.gz` using the `Fastest` compression level for quicker processing. + +### Example 3: Replacing an existing Tar Archive + +```powershell +Compress-TarArchive -Path .\path -Destination dest.tar.gz -Force +``` + +This example illustrates how to create a new tar archive named `dest.tar.gz` from the path directory, overwriting any existing archive with the same name using the `-Force` parameter. + +### Example 4: Exclude files and folders from source + +```powershell +Compress-TarArchive -Path .\path -Destination myPath.tar.gz -Exclude *.xyz, *\test\* +``` + +This example shows how to compress all items in `path` excluding all files having a `.xyz` extension and excluding +a folder with name `test` and all its child items. + +> [!TIP] +> +> The `-Exclude` parameter supports [wildcard patterns](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_wildcards?view=powershell-7.4&viewFallbackFrom=powershell-7.3), +exclusion patterns are tested against the items `.FullName` property. + +### Example 5: Compress a directory using bzip2 algorithm + +```powershell +Compress-TarArchive -Path .\data -Destination C:\Backups\data.tar.bz2 -Algorithm bz2 +``` + +This example demonstrates how to compress the `data` directory into a bzip2-compressed tar archive named `data.tar.bz2` in the `C:\Backups` directory using the `bz2` algorithm. + +## PARAMETERS + +### -Algorithm + +Specifies the compression algorithm to use when creating the tar archive. Supported algorithms include `gz`, `bz2`, `zst`, `lz`, and `none` (no compression). + +> [!NOTE] +> If not specified, the archive is created using the gzip algorithm (`gz`). + +```yaml +Type: Algorithm +Parameter Sets: (All) +Aliases: +Accepted values: gz, bz2, zst, lz, none +Required: False +Position: Named +Default value: none +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -CompressionLevel + +Specifies the compression level for the selected algorithm, balancing speed and file size. See [`CompressionLevel` Enum](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression.compressionlevel) for details. The default is algorithm-dependent but typically `Optimal`. + +```yaml +Type: CompressionLevel +Parameter Sets: (All) +Aliases: +Accepted values: Optimal, Fastest, NoCompression, SmallestSize +Required: False +Position: Named +Default value: Optimal +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Destination + +Specifies the path to the tar archive output file. The destination must include the file name and either an absolute or relative path. + +> [!NOTE] +> If the file name lacks an extension, the `-Algorithm` parameter determines the extension is appended (e.g., `.tar.gz` for `gz`, `.tar` for `none`). + +```yaml +Type: String +Parameter Sets: (All) +Aliases: DestinationPath +Required: True +Position: 1 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Exclude + +Specifies an array of string patterns to exclude files or directories from the archive. Matching items are excluded based on their `.FullName` property. Wildcard characters are supported. + +> [!NOTE] +> Patterns are tested against the object's `.FullName` property. + +```yaml +Type: String[] +Parameter Sets: (All) +Aliases: +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: True +``` + +### -Force + +Overwrites the destination tar archive if it exists, creating a new one. All existing entries are lost. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -LiteralPath + +Specifies the exact path or paths to the files or directories to include in the archive file. Unlike the `-Path` parameter, the value of `-LiteralPath` is used exactly as typed, with no wildcard character interpretation. + +```yaml +Type: String[] +Parameter Sets: LiteralPath +Aliases: PSPath +Required: True +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -PassThru + +Outputs a `FileInfo` object representing the created tar archive. By default, the cmdlet produces no output. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Path + +Specifies the path or paths to the files or directories to include in the archive file. To specify multiple paths and include files from multiple locations, use commas to separate the paths. This parameter accepts wildcard characters, allowing you to include all files in a directory or match specific patterns. + +> [!TIP] +> Using wildcards with a root directory affects the archive's contents: +> +> - To create an archive that includes the root directory and all its files and subdirectories, specify the root directory without wildcards. For example: `-Path C:\Reference` +> - To create an archive that excludes the root directory but includes all its files and subdirectories, use the asterisk (`*`) wildcard. For example: `-Path C:\Reference\*` +> - To create an archive that only includes files in the root directory (excluding subdirectories), use the star-dot-star (`*.*`) wildcard. For example: `-Path C:\Reference\*.*` + +```yaml +Type: String[] +Parameter Sets: Path +Aliases: +Required: True +Position: 0 +Default value: None +Accept pipeline input: True (ByValue) +Accept wildcard characters: True +``` + +### CommonParameters + +This cmdlet supports the common parameters. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +### System.String[] + +You can pipe strings containing paths to files or directories. Output from `Get-ChildItem` or `Get-Item` can be piped to this cmdlet. + +## OUTPUTS + +### None + +By default, this cmdlet produces no output. + +### System.IO.FileInfo + +When the `-PassThru` switch is used, this cmdlet outputs a `FileInfo` object representing the created tar archive. + +## NOTES + +This cmdlet is designed to provide a PowerShell-native way to create tar archives with flexible compression options. It integrates seamlessly with other PowerShell cmdlets for file manipulation and filtering. + +## RELATED LINKS + +[__Compress-ZipArchive__](https://github.com/santisq/PSCompression) + +[__SharpZipLib__](https://github.com/icsharpcode/SharpZipLib) + +[__SharpCompress__](https://github.com/adamhathcock/sharpcompress) + +[__ZstdSharp__](https://github.com/oleg-st/ZstdSharp) + +[__System.IO.Compression__](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression) diff --git a/docs/en-US/Compress-ZipArchive.md b/docs/en-US/Compress-ZipArchive.md index 02dcc62..85dcd91 100644 --- a/docs/en-US/Compress-ZipArchive.md +++ b/docs/en-US/Compress-ZipArchive.md @@ -17,8 +17,8 @@ The `Compress-ZipArchive` cmdlet creates a compressed, or zipped, archive file f ```powershell Compress-ZipArchive - -Path - -Destination + [-Path] + [-Destination] [-CompressionLevel ] [-Update] [-Force] @@ -32,7 +32,7 @@ Compress-ZipArchive ```powershell Compress-ZipArchive -LiteralPath - -Destination + [-Destination] [-CompressionLevel ] [-Update] [-Force] @@ -126,16 +126,14 @@ exclusion patterns are tested against the items `.FullName` property. ### -Path -Specifies the path or paths to the files that you want to add to the archive zipped file. To specify multiple paths, and include files in multiple locations, use commas to separate the paths. - -This parameter accepts wildcard characters. Wildcard characters allow you to add all files in a directory to your archive file. +Specifies the path or paths to the files or directories to include in the archive file. To specify multiple paths and include files from multiple locations, use commas to separate the paths. This parameter accepts wildcard characters, allowing you to include all files in a directory or match specific patterns. > [!TIP] > Using wildcards with a root directory affects the archive's contents: > -> - To create an archive that includes the root directory, and all its files and subdirectories, specify the root directory in the Path without wildcards. For example: `-Path C:\Reference` -> - To create an archive that excludes the root directory, but zips all its files and subdirectories, use the asterisk (`*`) wildcard. For example: `-Path C:\Reference\*` -> - To create an archive that only zips the files in the root directory, use the star-dot-star (`*.*`) wildcard. Subdirectories of the root aren't included in the archive. For example: `-Path C:\Reference\*.*` +> - To create an archive that includes the root directory and all its files and subdirectories, specify the root directory without wildcards. For example: `-Path C:\Reference` +> - To create an archive that excludes the root directory but includes all its files and subdirectories, use the asterisk (`*`) wildcard. For example: `-Path C:\Reference\*` +> - To create an archive that only includes files in the root directory (excluding subdirectories), use the star-dot-star (`*.*`) wildcard. For example: `-Path C:\Reference\*.*` ```yaml Type: String[] @@ -151,9 +149,7 @@ Accept wildcard characters: True ### -LiteralPath -Specifies the path or paths to the files that you want to add to the archive zipped file. -Unlike the Path `-Parameter`, the value of `-LiteralPath` is used exactly as it's typed. -No characters are interpreted as wildcards +Specifies the exact path or paths to the files or directories to include in the archive file. Unlike the `-Path` parameter, the value of `-LiteralPath` is used exactly as typed, with no wildcard character interpretation. ```yaml Type: String[] @@ -172,7 +168,7 @@ Accept wildcard characters: False This parameter is required and specifies the path to the archive output file. The destination should include the name of the zipped file, and either the absolute or relative path to the zipped file. > [!NOTE] -> If the file name does not have an extension, the cmdlet appends the `.zip` file name extension. +> If the file name lacks an extension, the cmdlet appends the `.zip` file name extension. ```yaml Type: String @@ -205,9 +201,7 @@ Accept wildcard characters: False ### -Exclude -Specifies an array of one or more string patterns to be matched as the cmdlet gets child items. -Any matching item is excluded from the created zip archive. -Wildcard characters are accepted. +Specifies an array of string patterns to exclude files or directories from the archive. Matching items are excluded based on their `.FullName` property. Wildcard characters are supported. > [!NOTE] > Patterns are tested against the object's `.FullName` property. @@ -245,7 +239,7 @@ Accept wildcard characters: False ### -Force -Overwrites the destination archive if exists otherwise it creates a new one. All Zip entries are lost. +Overwrites the destination archive if exists otherwise it creates a new one. All existing entries are lost. > [!NOTE] > If `-Force` and `-Update` are used together this cmdlet will add or update entries. @@ -284,9 +278,9 @@ This cmdlet supports the common parameters. For more information, see [about_Com ## INPUTS -### String +### System.String[] -You can pipe a string that contains a path to one or more files. Output from `Get-ChildItem` or `Get-Item` can be piped to this cmdlet. +You can pipe strings containing paths to files or directories. Output from `Get-ChildItem` or `Get-Item` can be piped to this cmdlet. ## OUTPUTS @@ -294,10 +288,18 @@ You can pipe a string that contains a path to one or more files. Output from `Ge By default, this cmdlet produces no output. -### FileInfo +### System.IO.FileInfo When the `-PassThru` switch is used this cmdlet outputs the `FileInfo` instance representing the compressed file. ## NOTES This cmdlet was initially posted to address [this Stack Overflow question](https://stackoverflow.com/a/72611161/15339544). [Another question](https://stackoverflow.com/q/74129754/15339544) in the same site pointed out another limitation with the native cmdlet, it can't compress if another process has a handle on a file. To overcome this issue, and also to emulate explorer's behavior when compressing files used by another process, the cmdlet defaults to __[`FileShare 'ReadWrite, Delete'`](https://learn.microsoft.com/en-us/dotnet/api/system.io.fileshare?view=net-6.0)__ when opening a [`FileStream`](https://learn.microsoft.com/en-us/dotnet/api/system.io.file.open?view=net-7.0). + +## RELATED LINKS + +[__System.IO.Compression__](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression) + +[__ZipArchive Class__](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression.ziparchive) + +[__ZipArchiveEntry Class__](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression.ziparchiveentry) diff --git a/docs/en-US/ConvertFrom-BrotliString.md b/docs/en-US/ConvertFrom-BrotliString.md new file mode 100644 index 0000000..189034b --- /dev/null +++ b/docs/en-US/ConvertFrom-BrotliString.md @@ -0,0 +1,139 @@ +--- +external help file: PSCompression.dll-Help.xml +Module Name: PSCompression +online version: https://github.com/santisq/PSCompression +schema: 2.0.0 +--- + +# ConvertFrom-BrotliString + +## SYNOPSIS + +Expands Brotli Base64 compressed input strings. + +## SYNTAX + +```powershell +ConvertFrom-BrotliString + [-InputObject] + [-Encoding ] + [-Raw] + [] +``` + +## DESCRIPTION + +The `ConvertFrom-BrotliString` cmdlet expands Base64 encoded Brotli compressed strings using the `BrotliStream` class from the `BrotliSharpLib` library. This cmdlet is the counterpart of [`ConvertTo-BrotliString`](./ConvertTo-BrotliString.md). + +## EXAMPLES + +### Example 1: Expanding a Brotli compressed string + +```powershell +PS ..\pwsh> ConvertFrom-BrotliString CwiAaGVsbG8NCndvcmxkDQohDQoD + +hello +world +! +``` + +This example expands a Brotli Base64 encoded string back to its original strings. + +### Example 2: Demonstrates how `-Raw` works + +```powershell +PS ..\pwsh> $strings = 'hello', 'world', '!' + +# New lines are preserved when the cmdlet receives an array of strings. +PS ..\pwsh> $strings | ConvertTo-BrotliString | ConvertFrom-BrotliString + +hello +world +! + +# When using the `-Raw` switch, all strings are returned as a single string +PS ..\pwsh> $strings | ConvertTo-BrotliString -NoNewLine | ConvertFrom-BrotliString -Raw + +helloworld! +``` + +This example shows how the `-Raw` switch concatenates the expanded strings into a single string with newlines preserved. + +## PARAMETERS + +### -Encoding + +Determines the character encoding used when expanding the input strings. + +> [!NOTE] +> The default encoding is `utf8NoBOM`. + +```yaml +Type: Encoding +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: Utf8 +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -InputObject + +Specifies the input string or strings to expand. + +```yaml +Type: String[] +Parameter Sets: (All) +Aliases: + +Required: True +Position: 0 +Default value: None +Accept pipeline input: True (ByValue) +Accept wildcard characters: False +``` + +### -Raw + +Outputs the expanded string as a single string with newlines preserved. By default, newline characters in the expanded string are used as delimiters to separate the input into an array of strings. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters + +This cmdlet supports the common parameters. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +### System.String[] + +You can pipe Brotli Base64 strings to this cmdlet. + +## OUTPUTS + +### System.String + +By default, this cmdlet streams strings. When the `-Raw` switch is used, it returns a single multi-line string. + +## NOTES + +## RELATED LINKS + +[__ConvertTo-BrotliString__](https://github.com/santisq/PSCompression/) + +[__BrotliSharpLib__](https://github.com/master131/BrotliSharpLib) + +[__System.IO.Compression__](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression) diff --git a/docs/en-US/ConvertFrom-DeflateString.md b/docs/en-US/ConvertFrom-DeflateString.md new file mode 100644 index 0000000..46e4a95 --- /dev/null +++ b/docs/en-US/ConvertFrom-DeflateString.md @@ -0,0 +1,139 @@ +--- +external help file: PSCompression.dll-Help.xml +Module Name: PSCompression +online version: https://github.com/santisq/PSCompression +schema: 2.0.0 +--- + +# ConvertFrom-DeflateString + +## SYNOPSIS + +Expands Deflate Base64 compressed input strings. + +## SYNTAX + +```powershell +ConvertFrom-DeflateString + [-InputObject] + [-Encoding ] + [-Raw] + [] +``` + +## DESCRIPTION + +The `ConvertFrom-DeflateString` cmdlet expands Base64 encoded Deflate compressed strings using the [`DeflateStream` Class](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression.deflatestream). This cmdlet is the counterpart of [`ConvertTo-DeflateString`](./ConvertTo-DeflateString.md). + +## EXAMPLES + +### Example 1: Expanding a Deflate compressed string + +```powershell +PS ..\pwsh> ConvertFrom-DeflateString ykjNycnn5SrPL8pJ4eVS5OUCAAAA//8DAA== + +hello +world +! +``` + +This example expands a Deflate Base64 encoded string back to its original strings. + +### Example 2: Demonstrates how `-Raw` works + +```powershell +PS ..\pwsh> $strings = 'hello', 'world', '!' + +# New lines are preserved when the cmdlet receives an array of strings. +PS ..\pwsh> $strings | ConvertTo-DeflateString | ConvertFrom-DeflateString + +hello +world +! + +# When using the `-Raw` switch, all strings are returned as a single string +PS ..\pwsh> $strings | ConvertTo-DeflateString -NoNewLine | ConvertFrom-DeflateString -Raw + +helloworld! +``` + +This example shows how the `-Raw` switch concatenates the expanded strings into a single string with newlines preserved. + +## PARAMETERS + +### -Encoding + +Determines the character encoding used when expanding the input strings. + +> [!NOTE] +> The default encoding is `utf8NoBOM`. + +```yaml +Type: Encoding +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: Utf8 +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -InputObject + +Specifies the input string or strings to expand. + +```yaml +Type: String[] +Parameter Sets: (All) +Aliases: + +Required: True +Position: 0 +Default value: None +Accept pipeline input: True (ByValue) +Accept wildcard characters: False +``` + +### -Raw + +Outputs the expanded string as a single string with newlines preserved. By default, newline characters in the expanded string are used as delimiters to separate the input into an array of strings. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters + +This cmdlet supports the common parameters. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +### System.String[] + +You can pipe Deflate Base64 strings to this cmdlet. + +## OUTPUTS + +### System.String + +By default, this cmdlet streams strings. When the `-Raw` switch is used, it returns a single multi-line string. + +## NOTES + +## RELATED LINKS + +[__ConvertTo-DeflateString__](https://github.com/santisq/PSCompression) + +[__System.IO.Compression__](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression?view=net-6.0) + +[__DeflateStream Class__](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression.deflatestream) diff --git a/docs/en-US/ConvertFrom-GzipString.md b/docs/en-US/ConvertFrom-GzipString.md index 3a1a0a3..45f192d 100644 --- a/docs/en-US/ConvertFrom-GzipString.md +++ b/docs/en-US/ConvertFrom-GzipString.md @@ -15,7 +15,7 @@ Expands Gzip Base64 compressed input strings. ```powershell ConvertFrom-GzipString - -InputObject + [-InputObject] [-Encoding ] [-Raw] [] @@ -55,6 +55,8 @@ PS ..\pwsh> $strings | ConvertTo-GzipString -NoNewLine | ConvertFrom-GzipString helloworld! ``` +This example shows how the `-Raw` switch concatenates the expanded strings into a single string with newlines preserved. + ## PARAMETERS ### -Encoding @@ -115,12 +117,22 @@ This cmdlet supports the common parameters. For more information, see [about_Com ## INPUTS -### String +### System.String You can pipe Gzip Base64 strings to this cmdlet. ## OUTPUTS -### String +### System.String By default, this cmdlet streams strings. When the `-Raw` switch is used, it returns a single multi-line string. + +## NOTES + +## RELATED LINKS + +[__ConvertTo-GzipString__](https://github.com/santisq/PSCompression) + +[__System.IO.Compression__](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression) + +[__GzipStream Class__](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression.gzipstream) diff --git a/docs/en-US/ConvertFrom-ZLibString.md b/docs/en-US/ConvertFrom-ZLibString.md new file mode 100644 index 0000000..6231024 --- /dev/null +++ b/docs/en-US/ConvertFrom-ZLibString.md @@ -0,0 +1,141 @@ +--- +external help file: PSCompression.dll-Help.xml +Module Name: PSCompression +online version: https://github.com/santisq/PSCompression +schema: 2.0.0 +--- + +# ConvertFrom-ZLibString + +## SYNOPSIS + +Expands ZLib Base64 compressed input strings. + +## SYNTAX + +```powershell +ConvertFrom-ZLibString + [-InputObject] + [-Encoding ] + [-Raw] + [] +``` + +## DESCRIPTION + +The `ConvertFrom-ZLibString` cmdlet expands Base64 encoded ZLib compressed strings using a custom Zlib implementation built on the [`DeflateStream` Class](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression.deflatestream). For the implementation details, see the PSCompression source code. This cmdlet is the counterpart of [`ConvertTo-ZLibString`](./ConvertTo-ZLibString.md). + +## EXAMPLES + +### Example 1: Expanding a ZLib compressed string + +```powershell +PS ..\pwsh> ConvertFrom-ZLibString eJzKSM3JyeflKs8vyknh5VLk5QIAAAD//wMAMosEow== + +hello +world +! +``` + +This example expands a ZLib Base64 encoded string back to its original strings. + +### Example 2: Demonstrates how `-Raw` works + +```powershell +PS ..\pwsh> $strings = 'hello', 'world', '!' + +# New lines are preserved when the cmdlet receives an array of strings. +PS ..\pwsh> $strings | ConvertTo-ZLibString | ConvertFrom-ZLibString + +hello +world +! + +# When using the `-Raw` switch, all strings are returned as a single string +PS ..\pwsh> $strings | ConvertTo-ZLibString -NoNewLine | ConvertFrom-ZLibString -Raw + +helloworld! +``` + +This example shows how the `-Raw` switch concatenates the expanded strings into a single string with newlines preserved. + +## PARAMETERS + +### -Encoding + +Determines the character encoding used when expanding the input strings. + +> [!NOTE] +> The default encoding is `utf8NoBOM`. + +```yaml +Type: Encoding +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: Utf8 +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -InputObject + +Specifies the input string or strings to expand. + +```yaml +Type: String[] +Parameter Sets: (All) +Aliases: + +Required: True +Position: 0 +Default value: None +Accept pipeline input: True (ByValue) +Accept wildcard characters: False +``` + +### -Raw + +Outputs the expanded string as a single string with newlines preserved. By default, newline characters in the expanded string are used as delimiters to separate the input into an array of strings. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters + +This cmdlet supports the common parameters. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +### System.String[] + +You can pipe ZLib Base64 strings to this cmdlet. + +## OUTPUTS + +### System.String + +By default, this cmdlet streams strings. When the `-Raw` switch is used, it returns a single multi-line string. + +## NOTES + +## RELATED LINKS + +[__ConvertTo-ZLibString__](https://github.com/santisq/PSCompression) + +[__System.IO.Compression__](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression) + +[__DeflateStream Class__](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression.deflatestream) + +[__ZlibStream Class__](https://github.com/santisq/PSCompression/blob/main/src/PSCompression/ZlibStream.cs) diff --git a/docs/en-US/ConvertTo-BrotliString.md b/docs/en-US/ConvertTo-BrotliString.md new file mode 100644 index 0000000..cf5991a --- /dev/null +++ b/docs/en-US/ConvertTo-BrotliString.md @@ -0,0 +1,208 @@ +--- +external help file: PSCompression.dll-Help.xml +Module Name: PSCompression +online version: https://github.com/santisq/PSCompression +schema: 2.0.0 +--- + +# ConvertTo-BrotliString + +## SYNOPSIS + +Creates a Brotli Base64 compressed string from a specified input string or strings. + +## SYNTAX + +```powershell +ConvertTo-BrotliString + [-InputObject] + [-Encoding ] + [-CompressionLevel ] + [-AsByteStream] + [-NoNewLine] + [] +``` + +## DESCRIPTION + +The `ConvertTo-BrotliString` cmdlet compresses input strings into Brotli Base64 encoded strings or raw bytes using the `BrotliStream` class from the `BrotliSharpLib` library. For expansion of Base64 Brotli strings, see [`ConvertFrom-BrotliString`](./ConvertFrom-BrotliString.md). + +## EXAMPLES + +### Example 1: Compress strings to Brotli compressed Base64 encoded string + +```powershell +PS ..\pwsh> $strings = 'hello', 'world', '!' +PS ..\pwsh> ConvertTo-BrotliString $strings + +CwiAaGVsbG8NCndvcmxkDQohDQoD + +# Or using pipeline input +PS ..\pwsh> $strings | ConvertTo-BrotliString + +CwiAaGVsbG8NCndvcmxkDQohDQoD +``` + +This example demonstrates compressing an array of strings into a single Brotli Base64 encoded string using either positional binding or pipeline input. + +### Example 2: Create a Brotli compressed file from a string + +```powershell +PS ..\pwsh> 'hello world!' | ConvertTo-BrotliString -AsByteStream | Set-Content -FilePath .\helloworld.br -AsByteStream + +# To read the file back you can use `ConvertFrom-BrotliString` following these steps: +PS ..\pwsh> $path = Convert-Path .\helloworld.br +PS ..\pwsh> [System.Convert]::ToBase64String([System.IO.File]::ReadAllBytes($path)) | ConvertFrom-BrotliString + +hello world! +``` + +Demonstrates how `-AsByteStream` outputs a byte array that can be saved to a file using `Set-Content` or `Out-File`. Note that the byte array is not enumerated. + +> [!NOTE] +> The example uses `-AsByteStream` with `Set-Content`, which is available in PowerShell 7+. In Windows PowerShell 5.1, use `-Encoding Byte` with `Set-Content` or `Out-File` to write the byte array to a file. + +### Example 3: Compress strings using a specific Encoding + +```powershell +PS ..\pwsh> 'ñ' | ConvertTo-BrotliString -Encoding ansi | ConvertFrom-BrotliString +� + +PS ..\pwsh> 'ñ' | ConvertTo-BrotliString -Encoding utf8BOM | ConvertFrom-BrotliString +ñ +``` + +This example shows how different encodings affect the compression and decompression of special characters. The default encoding is `utf8NoBOM`. + +### Example 4: Compressing multiple files into one Brotli Base64 string + +```powershell +# Check the total length of the files +PS ..\pwsh> (Get-Content myLogs\*.txt | Measure-Object Length -Sum).Sum / 1kb +87.216796875 + +# Check the total length after compression +PS ..\pwsh> (Get-Content myLogs\*.txt | ConvertTo-BrotliString).Length / 1kb +35.123456789 +``` + +This example demonstrates compressing the contents of multiple text files into a single Brotli Base64 string and compares the total length before and after compression. + +## PARAMETERS + +### -AsByteStream + +Outputs the compressed byte array to the Success Stream. + +> [!NOTE] +> This parameter is intended for use with cmdlets that accept byte arrays, such as `Out-File` and `Set-Content` with `-Encoding Byte` (Windows PowerShell 5.1) or `-AsByteStream` (PowerShell 7+). + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: Raw + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -CompressionLevel + +Specifies the compression level for the Brotli algorithm, balancing speed and compression size. See [`CompressionLevel` Enum](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression.compressionlevel) for details. + +```yaml +Type: CompressionLevel +Parameter Sets: (All) +Aliases: +Accepted values: Optimal, Fastest, NoCompression, SmallestSize + +Required: False +Position: Named +Default value: Optimal +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Encoding + +Determines the character encoding used when compressing the input strings. + +> [!NOTE] +> The default encoding is `utf8NoBOM`. + +```yaml +Type: Encoding +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: Utf8 +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -InputObject + +Specifies the input string or strings to compress. + +```yaml +Type: String[] +Parameter Sets: (All) +Aliases: + +Required: True +Position: 0 +Default value: None +Accept pipeline input: True (ByValue) +Accept wildcard characters: False +``` + +### -NoNewLine + +The encoded string representation of the input objects is concatenated to form the output. No newline character is added after each input string when this switch is used. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters + +This cmdlet supports the common parameters. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +### System.String[] + +You can pipe strings to this cmdlet. + +## OUTPUTS + +### System.String + +By default, this cmdlet outputs a single Base64 encoded string. + +### System.Byte[] + +When the `-AsByteStream` switch is used, this cmdlet outputs a byte array down the pipeline. + +## NOTES + +## RELATED LINKS + +[__ConvertFrom-BrotliString__](https://github.com/santisq/PSCompression) + +[__BrotliSharpLib__](https://github.com/master131/BrotliSharpLib) + +[__System.IO.Compression__](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression?view=net-6.0) diff --git a/docs/en-US/ConvertTo-DeflateString.md b/docs/en-US/ConvertTo-DeflateString.md new file mode 100644 index 0000000..3d962ec --- /dev/null +++ b/docs/en-US/ConvertTo-DeflateString.md @@ -0,0 +1,208 @@ +--- +external help file: PSCompression.dll-Help.xml +Module Name: PSCompression +online version: https://github.com/santisq/PSCompression +schema: 2.0.0 +--- + +# ConvertTo-DeflateString + +## SYNOPSIS + +Creates a Deflate Base64 compressed string from a specified input string or strings. + +## SYNTAX + +```powershell +ConvertTo-DeflateString + [-InputObject] + [-Encoding ] + [-CompressionLevel ] + [-AsByteStream] + [-NoNewLine] + [] +``` + +## DESCRIPTION + +The `ConvertTo-DeflateString` cmdlet compresses input strings into Deflate Base64 encoded strings or raw bytes using the [`DeflateStream` Class](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression.deflatestream). For expansion of Base64 Deflate strings, see [`ConvertFrom-DeflateString`](./ConvertFrom-DeflateString.md). + +## EXAMPLES + +### Example 1: Compress strings to Deflate compressed Base64 encoded string + +```powershell +PS ..\pwsh> $strings = 'hello', 'world', '!' +PS ..\pwsh> ConvertTo-DeflateString $strings + +ykjNycnn5SrPL8pJ4eVS5OUCAAAA//8DAA== + +# Or using pipeline input +PS ..\pwsh> $strings | ConvertTo-DeflateString + +ykjNycnn5SrPL8pJ4eVS5OUCAAAA//8DAA== +``` + +This example demonstrates compressing an array of strings into a single Deflate Base64 encoded string using either positional binding or pipeline input. + +### Example 2: Create a Deflate compressed file from a string + +```powershell +PS ..\pwsh> 'hello world!' | ConvertTo-DeflateString -AsByteStream | Set-Content -FilePath .\helloworld.deflate -AsByteStream + +# To read the file back you can use `ConvertFrom-BrotliString` following these steps: +PS ..\pwsh> $path = Convert-Path .\helloworld.deflate +PS ..\pwsh> [System.Convert]::ToBase64String([System.IO.File]::ReadAllBytes($path)) | ConvertFrom-DeflateString + +hello world! +``` + +Demonstrates how `-AsByteStream` outputs a byte array that can be saved to a file using `Set-Content` or `Out-File`. Note that the byte array is not enumerated. + +> [!NOTE] +> The example uses `-AsByteStream` with `Set-Content`, which is available in PowerShell 7+. In Windows PowerShell 5.1, use `-Encoding Byte` with `Set-Content` or `Out-File` to write the byte array to a file. + +### Example 3: Compress strings using a specific Encoding + +```powershell +PS ..\pwsh> 'ñ' | ConvertTo-DeflateString -Encoding ansi | ConvertFrom-DeflateString +� + +PS ..\pwsh> 'ñ' | ConvertTo-DeflateString -Encoding utf8BOM | ConvertFrom-DeflateString +ñ +``` + +This example shows how different encodings affect the compression and decompression of special characters. The default encoding is `utf8NoBOM`. + +### Example 4: Compressing multiple files into one Deflate Base64 string + +```powershell +# Check the total length of the files +PS ..\pwsh> (Get-Content myLogs\*.txt | Measure-Object Length -Sum).Sum / 1kb +87.216796875 + +# Check the total length after compression +PS ..\pwsh> (Get-Content myLogs\*.txt | ConvertTo-DeflateString).Length / 1kb +35.123456789 +``` + +This example demonstrates compressing the contents of multiple text files into a single Deflate Base64 string and compares the total length before and after compression. + +## PARAMETERS + +### -AsByteStream + +Outputs the compressed byte array to the Success Stream. + +> [!NOTE] +> This parameter is intended for use with cmdlets that accept byte arrays, such as `Out-File` and `Set-Content` with `-Encoding Byte` (Windows PowerShell 5.1) or `-AsByteStream` (PowerShell 7+). + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: Raw + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -CompressionLevel + +Specifies the compression level for the Deflate algorithm, balancing speed and compression size. See [`CompressionLevel` Enum](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression.compressionlevel) for details. + +```yaml +Type: CompressionLevel +Parameter Sets: (All) +Aliases: +Accepted values: Optimal, Fastest, NoCompression, SmallestSize + +Required: False +Position: Named +Default value: Optimal +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Encoding + +Determines the character encoding used when compressing the input strings. + +> [!NOTE] +> The default encoding is `utf8NoBOM`. + +```yaml +Type: Encoding +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: Utf8 +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -InputObject + +Specifies the input string or strings to compress. + +```yaml +Type: String[] +Parameter Sets: (All) +Aliases: + +Required: True +Position: 0 +Default value: None +Accept pipeline input: True (ByValue) +Accept wildcard characters: False +``` + +### -NoNewLine + +The encoded string representation of the input objects is concatenated to form the output. No newline character is added after each input string when this switch is used. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters + +This cmdlet supports the common parameters. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +### System.String[] + +You can pipe strings to this cmdlet. + +## OUTPUTS + +### System.String + +By default, this cmdlet outputs a single Base64 encoded string. + +### System.Byte[] + +When the `-AsByteStream` switch is used, this cmdlet outputs a byte array down the pipeline. + +## NOTES + +## RELATED LINKS + +[__ConvertFrom-DeflateString__](https://github.com/santisq/PSCompression) + +[__System.IO.Compression__](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression?view=net-6.0) + +[__DeflateStream Class__](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression.deflatestream) diff --git a/docs/en-US/ConvertTo-GzipString.md b/docs/en-US/ConvertTo-GzipString.md index aabea11..6f52368 100644 --- a/docs/en-US/ConvertTo-GzipString.md +++ b/docs/en-US/ConvertTo-GzipString.md @@ -15,7 +15,7 @@ Creates a Gzip Base64 compressed string from a specified input string or strings ```powershell ConvertTo-GzipString - -InputObject + [-InputObject] [-Encoding ] [-CompressionLevel ] [-AsByteStream] @@ -45,14 +45,24 @@ PS ..\pwsh> $strings | ConvertTo-GzipString H4sIAAAAAAAEAMtIzcnJ5+Uqzy/KSeHlUuTlAgBLr/K2EQAAAA== ``` +This example demonstrates compressing an array of strings into a single Brotli Base64 encoded string using either positional binding or pipeline input. + ### Example 2: Create a Gzip compressed file from a string ```powershell -PS ..\pwsh> 'hello world!' | ConvertTo-GzipString -AsByteStream | - Compress-GzipArchive -DestinationPath .\files\file.gz +PS ..\pwsh> 'hello world!' | ConvertTo-GzipString -AsByteStream | Set-Content -FilePath .\helloworld.gz -AsByteStream + +# To read the file back you can use `ConvertFrom-BrotliString` following these steps: +PS ..\pwsh> $path = Convert-Path .\helloworld.gz +PS ..\pwsh> [System.Convert]::ToBase64String([System.IO.File]::ReadAllBytes($path)) | ConvertFrom-GzipString + +hello world! ``` -Demonstrates how `-AsByteStream` works on `ConvertTo-GzipString`, the cmdlet outputs a byte array that is received by `Compress-GzipArchive` and stored in a file. __Note that the byte array is not enumerated__. +Demonstrates how `-AsByteStream` outputs a byte array that can be saved to a file using `Set-Content` or `Out-File`. Note that the byte array is not enumerated. + +> [!NOTE] +> The example uses `-AsByteStream` with `Set-Content`, which is available in PowerShell 7+. In Windows PowerShell 5.1, use `-Encoding Byte` with `Set-Content` or `Out-File` to write the byte array to a file. ### Example 3: Compress strings using a specific Encoding @@ -69,19 +79,17 @@ The default Encoding is `utf8NoBom`. ### Example 4: Compressing multiple files into one Gzip Base64 string ```powershell -PS ..\pwsh> 0..10 | ForEach-Object { - Invoke-RestMethod loripsum.net/api/10/long/plaintext -OutFile .\files\lorem$_.txt -} - -# Check the total Length of the downloaded files -PS ..\pwsh> (Get-Content .\files\lorem*.txt | Measure-Object Length -Sum).Sum / 1kb +# Check the total length of the files +PS ..\pwsh> (Get-Content myLogs\*.txt | Measure-Object Length -Sum).Sum / 1kb 87.216796875 -# Check the total Length after compression -PS ..\pwsh> (Get-Content .\files\lorem*.txt | ConvertTo-GzipString).Length / 1kb -36.94921875 +# Check the total length after compression +PS ..\pwsh> (Get-Content myLogs\*.txt | ConvertTo-GzipString).Length / 1kb +35.123456789 ``` +This example demonstrates compressing the contents of multiple text files into a single Gzip Base64 string and compares the total length before and after compression. + ## PARAMETERS ### -AsByteStream @@ -89,7 +97,7 @@ PS ..\pwsh> (Get-Content .\files\lorem*.txt | ConvertTo-GzipString).Length / 1kb Outputs the compressed byte array to the Success Stream. > [!NOTE] -> This parameter is meant to be used in combination with [`Compress-GzipArchive`](./Compress-GzipArchive.md). +> This parameter is intended for use with cmdlets that accept byte arrays, such as `Out-File` and `Set-Content` with `-Encoding Byte` (Windows PowerShell 5.1) or `-AsByteStream` (PowerShell 7+). ```yaml Type: SwitchParameter @@ -192,3 +200,13 @@ By default, this cmdlet outputs a single string. ### Byte[] When the `-AsByteStream` switch is used this cmdlet outputs a byte array down the pipeline. + +## NOTES + +## RELATED LINKS + +[__ConvertFrom-GzipString__](https://github.com/santisq/PSCompression) + +[__System.IO.Compression__](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression) + +[__GzipStream Class__](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression.gzipstream) diff --git a/docs/en-US/ConvertTo-ZLibString.md b/docs/en-US/ConvertTo-ZLibString.md new file mode 100644 index 0000000..0ad57e9 --- /dev/null +++ b/docs/en-US/ConvertTo-ZLibString.md @@ -0,0 +1,210 @@ +--- +external help file: PSCompression.dll-Help.xml +Module Name: PSCompression +online version: https://github.com/santisq/PSCompression +schema: 2.0.0 +--- + +# ConvertTo-ZLibString + +## SYNOPSIS + +Creates a ZLib Base64 compressed string from a specified input string or strings. + +## SYNTAX + +```powershell +ConvertTo-ZLibString + [-InputObject] + [-Encoding ] + [-CompressionLevel ] + [-AsByteStream] + [-NoNewLine] + [] +``` + +## DESCRIPTION + +The `ConvertTo-ZLibString` cmdlet compresses input strings into ZLib Base64 encoded strings or raw bytes using a custom Zlib implementation built on the [`DeflateStream` Class](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression.deflatestream). For the implementation details, see the PSCompression source code. For expansion of Base64 ZLib strings, see [`ConvertFrom-ZLibString`](./ConvertFrom-ZLibString.md). + +## EXAMPLES + +### Example 1: Compress strings to ZLib compressed Base64 encoded string + +```powershell +PS ..\pwsh> $strings = 'hello', 'world', '!' +PS ..\pwsh> ConvertTo-ZLibString $strings + +eJzKSM3JyeflKs8vyknh5VLk5QIAAAD//wMAMosEow== + +# Or using pipeline input +PS ..\pwsh> $strings | ConvertTo-ZLibString + +eJzKSM3JyeflKs8vyknh5VLk5QIAAAD//wMAMosEow== +``` + +This example demonstrates compressing an array of strings into a single ZLib Base64 encoded string using either positional binding or pipeline input. + +### Example 2: Create a ZLib compressed file from a string + +```powershell +PS ..\pwsh> 'hello world!' | ConvertTo-ZLibString -AsByteStream | Set-Content -FilePath .\helloworld.zlib -AsByteStream + +# To read the file back you can use `ConvertFrom-BrotliString` following these steps: +PS ..\pwsh> $path = Convert-Path .\helloworld.zlib +PS ..\pwsh> [System.Convert]::ToBase64String([System.IO.File]::ReadAllBytes($path)) | ConvertFrom-ZLibString + +hello world! +``` + +Demonstrates how `-AsByteStream` outputs a byte array that can be saved to a file using `Set-Content` or `Out-File`. Note that the byte array is not enumerated. + +> [!NOTE] +> The example uses `-AsByteStream` with `Set-Content`, which is available in PowerShell 7+. In Windows PowerShell 5.1, use `-Encoding Byte` with `Set-Content` or `Out-File` to write the byte array to a file. + +### Example 3: Compress strings using a specific Encoding + +```powershell +PS ..\pwsh> 'ñ' | ConvertTo-ZLibString -Encoding ansi | ConvertFrom-ZLibString +� + +PS ..\pwsh> 'ñ' | ConvertTo-ZLibString -Encoding utf8BOM | ConvertFrom-ZLibString +ñ +``` + +This example shows how different encodings affect the compression and decompression of special characters. The default encoding is `utf8NoBOM`. + +### Example 4: Compressing multiple files into one ZLib Base64 string + +```powershell +# Check the total length of the files +PS ..\pwsh> (Get-Content myLogs\*.txt | Measure-Object Length -Sum).Sum / 1kb +87.216796875 + +# Check the total length after compression +PS ..\pwsh> (Get-Content myLogs\*.txt | ConvertTo-GzipString).Length / 1kb +35.123456789 +``` + +This example demonstrates compressing the contents of multiple text files into a single ZLib Base64 string and compares the total length before and after compression. + +## PARAMETERS + +### -AsByteStream + +Outputs the compressed byte array to the Success Stream. + +> [!NOTE] +> This parameter is intended for use with cmdlets that accept byte arrays, such as `Out-File` and `Set-Content` with `-Encoding Byte` (Windows PowerShell 5.1) or `-AsByteStream` (PowerShell 7+). + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: Raw + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -CompressionLevel + +Specifies the compression level for the ZLib algorithm, balancing speed and compression size. See [`CompressionLevel` Enum](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression.compressionlevel) for details. + +```yaml +Type: CompressionLevel +Parameter Sets: (All) +Aliases: +Accepted values: Optimal, Fastest, NoCompression, SmallestSize + +Required: False +Position: Named +Default value: Optimal +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Encoding + +Determines the character encoding used when compressing the input strings. + +> [!NOTE] +> The default encoding is `utf8NoBOM`. + +```yaml +Type: Encoding +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: Utf8 +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -InputObject + +Specifies the input string or strings to compress. + +```yaml +Type: String[] +Parameter Sets: (All) +Aliases: + +Required: True +Position: 0 +Default value: None +Accept pipeline input: True (ByValue) +Accept wildcard characters: False +``` + +### -NoNewLine + +The encoded string representation of the input objects is concatenated to form the output. No newline character is added after each input string when this switch is used. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters + +This cmdlet supports the common parameters. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +### System.String[] + +You can pipe strings to this cmdlet. + +## OUTPUTS + +### System.String + +By default, this cmdlet outputs a single Base64 encoded string. + +### System.Byte[] + +When the `-AsByteStream` switch is used, this cmdlet outputs a byte array down the pipeline. + +## NOTES + +## RELATED LINKS + +[__ConvertFrom-ZLibString__](https://github.com/santisq/PSCompression) + +[__System.IO.Compression__](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression?view=net-6.0) + +[__DeflateStream Class__](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression.deflatestream) + +[__ZlibStream Class__](https://github.com/santisq/PSCompression/blob/main/src/PSCompression/ZlibStream.cs) diff --git a/docs/en-US/Expand-GzipArchive.md b/docs/en-US/Expand-GzipArchive.md deleted file mode 100644 index af6efac..0000000 --- a/docs/en-US/Expand-GzipArchive.md +++ /dev/null @@ -1,277 +0,0 @@ ---- -external help file: PSCompression-help.xml -Module Name: PSCompression -online version: https://github.com/santisq/PSCompression -schema: 2.0.0 ---- - -# Expand-GzipArchive - -## SYNOPSIS - -Expands a Gzip compressed file from a specified File Path or Paths. - -## SYNTAX - -### Path - -```powershell -Expand-GzipArchive - -Path - [-Raw] - [] -``` - -### PathDestination - -```powershell -Expand-GzipArchive - -Path - -Destination - [-Encoding ] - [-PassThru] - [-Force] - [-Update] - [] -``` - -### LiteralPath - -```powershell -Expand-GzipArchive - -LiteralPath - [-Raw] - [] -``` - -### LiteralPathDestination - -```powershell -Expand-GzipArchive - -LiteralPath - -Destination - [-Encoding ] - [-PassThru] - [-Force] - [-Update] - [] -``` - -## DESCRIPTION - -The `Expand-GzipArchive` cmdlet aims to expand Gzip compressed files to a destination path or to the success stream using the [`GzipStream` Class](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression.gzipstream). This cmdlet is the counterpart of [`Compress-GzipArchive`](Compress-GzipArchive.md). - -## EXAMPLES - -### Example 1: Expanding a Gzip archive to the success stream - -```powershell -PS ..\pwsh> Expand-GzipArchive .\files\file.gz - -hello world! -``` - -Output goes to the Success Stream when `-Destination` is not used. - -### Example 2: Expanding a Gzip archive to a new file - -```powershell -PS ..\pwsh> Expand-GzipArchive .\files\file.gz -Destination .\files\file.txt - -# Checking Length Difference -PS ..\pwsh> Get-Item -Path .\files\file.gz, .\files\file.txt | - Select-Object Name, Length - -Name Length ----- ------ -file.gz 3168 -file.txt 6857 -``` - -### Example 3: Appending content to an existing file - -```powershell -PS ..\pwsh> Expand-GzipArchive *.gz -Destination .\files\file.txt -Update -``` - -### Example 4: Expanding a Gzip archive overwritting an existing file - -```powershell -PS ..\pwsh> Expand-GzipArchive *.gz -Destination .\files\file.txt -Force -``` - -## PARAMETERS - -### -Path - -Specifies the path or paths to the Gzip files to expand. -To specify multiple paths, and include files in multiple locations, use commas to separate the paths. -This Parameter accepts wildcard characters. -Wildcard characters allow you to add all files in a directory to your archive file. - -```yaml -Type: String[] -Parameter Sets: PathDestination, Path -Aliases: - -Required: True -Position: 0 -Default value: None -Accept pipeline input: True (ByValue) -Accept wildcard characters: False -``` - -### -LiteralPath - -Specifies the path or paths to the Gzip files to expand. -Unlike the `-Path` Parameter, the value of `-LiteralPath` is used exactly as it's typed. -No characters are interpreted as wildcards - -```yaml -Type: String[] -Parameter Sets: LiteralPathDestination, LiteralPath -Aliases: PSPath - -Required: True -Position: Named -Default value: None -Accept pipeline input: True (ByPropertyName) -Accept wildcard characters: False -``` - -### -Destination - -The destination path where to expand the Gzip file. -The target folder is created if it does not exist. - -> [!NOTE] -> This parameter is Optional, if not used, this cmdlet outputs to the Success Stream. - -```yaml -Type: String -Parameter Sets: PathDestination, LiteralPathDestination -Aliases: DestinationPath - -Required: True -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Encoding - -Character encoding used when expanding the Gzip content. This parameter is only available when expanding to the Success Stream. - -> [!NOTE] -> The default encoding is __`utf8NoBOM`__. - -```yaml -Type: Encoding -Parameter Sets: Path, LiteralPath -Aliases: - -Required: False -Position: Named -Default value: utf8NoBOM -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Raw - -Outputs the expanded file as a single string with newlines preserved. -By default, newline characters in the expanded string are used as delimiters to separate the input into an array of strings. - -> [!NOTE] -> This parameter is only available when expanding to the Success Stream. - -```yaml -Type: SwitchParameter -Parameter Sets: Path, LiteralPath -Aliases: - -Required: False -Position: Named -Default value: False -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -PassThru - -Outputs the object representing the expanded file. - -```yaml -Type: SwitchParameter -Parameter Sets: PathDestination, LiteralPathDestination -Aliases: - -Required: False -Position: Named -Default value: False -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Force - -The destination file gets overwritten if exists, otherwise created when this switch is used. - -> [!NOTE] -> If `-Force` and `-Update` are used together this cmdlet will append content to the destination file. - -```yaml -Type: SwitchParameter -Parameter Sets: PathDestination, LiteralPathDestination -Aliases: - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Update - -Contents of the expanded file or files are appended to the destination path if exists, otherwise the destination is created. - -> [!NOTE] -> If `-Force` and `-Update` are used together this cmdlet will append content to the destination file. - -```yaml -Type: SwitchParameter -Parameter Sets: PathDestination, LiteralPathDestination -Aliases: - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters - -This cmdlet supports the common parameters. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -### String - -You can pipe paths to this cmdlet. Output from `Get-ChildItem` or `Get-Item` can be piped to this cmdlet. - -## OUTPUTS - -### String - -This cmdlet outputs an array of string to the success stream when `-Destination` is not used and a single multi-line string when used with the `-Raw` switch. - -### None - -This cmdlet produces no output when expanding to a file and `-PassThru` is not used. - -### FileInfo - -When the `-PassThru` switch is used this cmdlet outputs the `FileInfo` instance representing the expanded file. diff --git a/docs/en-US/Expand-TarArchive.md b/docs/en-US/Expand-TarArchive.md new file mode 100644 index 0000000..daafdd5 --- /dev/null +++ b/docs/en-US/Expand-TarArchive.md @@ -0,0 +1,256 @@ +--- +external help file: PSCompression.dll-Help.xml +Module Name: PSCompression +online version: https://github.com/santisq/PSCompression +schema: 2.0.0 +--- + +# Expand-TarArchive + +## SYNOPSIS + +Extracts files from a tar archive, optionally compressed with gzip, bzip2, Zstandard, or lzip. + +## SYNTAX + +### Path + +```powershell +Expand-TarArchive + [-Path] + [[-Destination] ] + [-Algorithm ] + [-Force] + [-PassThru] + [] +``` + +### LiteralPath + +```powershell +Expand-TarArchive + -LiteralPath + [[-Destination] ] + [-Algorithm ] + [-Force] + [-PassThru] + [] +``` + +## DESCRIPTION + +The `Expand-TarArchive` cmdlet extracts files and directories from a tar archive, with support for compressed tar formats using gzip (`.tar.gz`), bzip2 (`.tar.bz2`), Zstandard (`.tar.zst`), lzip (`.tar.lz`), or uncompressed tar (`.tar`). It uses libraries such as `SharpZipLib` for tar handling, `System.IO.Compression` for gzip, `SharpCompress` for lzip, and `ZstdSharp` for Zstandard. This cmdlet is the counterpart to [`Compress-TarArchive`](./Compress-TarArchive.md). By default, files are extracted to the current directory unless a `-Destination` is specified. Use `-Force` to overwrite existing files and `-PassThru` to output the extracted file and directory objects. + +> [!NOTE] +> When the `-Algorithm` parameter is not specified, the cmdlet infers the compression algorithm from the file extension (e.g., `.tar.gz` for gzip, `.tar.zst` for Zstandard, `.tar` for uncompressed). See the `-Algorithm` parameter for supported extensions. + +## EXAMPLES + +### Example 1: Extract an uncompressed tar archive + +```powershell +PS C:\> Expand-TarArchive -Path .\archive.tar + +PS C:\> Get-ChildItem + Directory: C:\ + +Mode LastWriteTime Length Name +---- ------------- ------ ---- +d---- 2025-06-23 7:00 PM folder1 +-a--- 2025-06-23 7:00 PM 1024 file1.txt +-a--- 2025-06-23 7:00 PM 2048 file2.txt +``` + +Extracts the contents of `archive.tar` to the current directory, creating `folder1`, `file1.txt`, and `file2.txt`. + +### Example 2: Extract a gzip-compressed tar archive to a specific destination + +```powershell +PS C:\> Expand-TarArchive -Path .\archive.tar.gz -Destination .\extracted + +PS C:\> Get-ChildItem .\extracted + Directory: C:\extracted + +Mode LastWriteTime Length Name +---- ------------- ------ ---- +d---- 2025-06-23 7:00 PM folder1 +-a--- 2025-06-23 7:00 PM 1024 file1.txt +-a--- 2025-06-23 7:00 PM 2048 file2.txt +``` + +Extracts `archive.tar.gz` to the `extracted` directory using the gzip algorithm, creating the directory if it doesn’t exist. + +### Example 3: Extract multiple Zstandard-compressed tar archives with PassThru + +```powershell +PS C:\> Get-ChildItem *.tar.zst | Expand-TarArchive -PassThru + + Directory: C:\ + +Mode LastWriteTime Length Name +---- ------------- ------ ---- +d---- 2025-06-23 7:00 PM folder1 +-a--- 2025-06-23 7:00 PM 1024 file1.txt +-a--- 2025-06-23 7:00 PM 2048 file2.txt +d---- 2025-06-23 7:00 PM folder2 +-a--- 2025-06-23 7:00 PM 4096 file3.txt +``` + +Extracts all `.tar.zst` files in the current directory and outputs the extracted files and directories to the pipeline. + +### Example 4: Overwrite existing files with Force + +```powershell +PS C:\> Expand-TarArchive -Path .\archive.tar -Destination .\extracted -Force + +PS C:\> Get-ChildItem .\extracted + Directory: C:\extracted + +Mode LastWriteTime Length Name +---- ------------- ------ ---- +-a--- 2025-06-23 7:00 PM 1024 file1.txt +``` + +Extracts `archive.tar` to the `extracted` directory, overwriting any existing `file1.txt` due to the `-Force` parameter. + +## PARAMETERS + +### -Algorithm + +Specifies the compression algorithm used for the tar archive. Accepted values and their corresponding file extensions are: + +- `gz`: Gzip compression (`.gz`, `.gzip`, `.tgz`). +- `bz2`: Bzip2 compression (`.bz2`, `.bzip2`, `.tbz2`, `.tbz`) +- `zst`: Zstandard compression (`.zst`). +- `lz`: Lzip compression (`.lz`). +- `none`: Uncompressed tar archive (`.tar`). + +> [!NOTE] +> If not specified, the cmdlet infers the algorithm from the file extension. If the extension is unrecognized, it defaults to `none` (uncompressed tar). + +```yaml +Type: Algorithm +Parameter Sets: (All) +Aliases: +Accepted values: gz, bz2, zst, lz, none + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Destination + +Specifies the path to the directory where the archive contents are extracted. If not provided, the current directory is used. If the directory does not exist, it is created. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: False +Position: 1 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Force + +Overwrites existing files in the destination directory without prompting. If not specified, the cmdlet skips files that already exist. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -LiteralPath + +Specifies the exact path(s) to the tar archive(s) to extract. Unlike `-Path`, `-LiteralPath` does not support wildcard characters and treats the path as a literal string. Use this parameter when the archive name contains special characters. + +```yaml +Type: String[] +Parameter Sets: LiteralPath +Aliases: PSPath + +Required: True +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -PassThru + +Outputs `System.IO.FileInfo` and `System.IO.DirectoryInfo` objects representing the extracted files and directories to the pipeline. By default, no output is produced unless an error occurs. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Path + +Specifies the path(s) to the tar archive(s) to extract. Supports wildcard characters, allowing multiple archives to be processed. Paths can be provided via pipeline input. + +```yaml +Type: String[] +Parameter Sets: Path +Aliases: + +Required: True +Position: 0 +Default value: None +Accept pipeline input: True (ByValue) +Accept wildcard characters: True +``` + +### CommonParameters + +This cmdlet supports the common parameters. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +### System.String[] + +You can pipe paths to tar archives to this cmdlet via the `-Path` parameter or provide literal paths via the `-LiteralPath` parameter. + +## OUTPUTS + +### None + +By default, this cmdlet returns no output. + +### System.IO.FileSystemInfo + +When the `-PassThru` parameter is used, the cmdlet outputs objects representing the extracted files and directories. + +## NOTES + +## RELATED LINKS + +[__Compress-TarArchive__](https://github.com/santisq/PSCompression) + +[__SharpZipLib__](https://github.com/icsharpcode/SharpZipLib) + +[__SharpCompress__](https://github.com/adamhathcock/sharpcompress) + +[__ZstdSharp__](https://github.com/oleg-st/ZstdSharp) + +[__System.IO.Compression__](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression?view=net-6.0) diff --git a/docs/en-US/Expand-TarEntry.md b/docs/en-US/Expand-TarEntry.md new file mode 100644 index 0000000..f64d840 --- /dev/null +++ b/docs/en-US/Expand-TarEntry.md @@ -0,0 +1,197 @@ +--- +external help file: PSCompression.dll-Help.xml +Module Name: PSCompression +online version: https://github.com/santisq/PSCompression +schema: 2.0.0 +--- + +# Expand-TarEntry + +## SYNOPSIS + +Expands tar archive entries to a destination directory. + +## SYNTAX + +```powershell +Expand-TarEntry + -InputObject + [[-Destination] ] + [-Force] + [-PassThru] + [] +``` + +## DESCRIPTION + +The `Expand-TarEntry` cmdlet extracts tar entries output by the [`Get-TarEntry`](./Get-TarEntry.md) cmdlet to a destination directory. Expanded entries maintain their original folder structure based on their relative path within the tar archive. This cmdlet supports both uncompressed (`.tar`) and compressed tar archives (e.g., `.tar.gz`, `.tar.bz2`) processed by `Get-TarEntry`. + +## EXAMPLES + +### Example 1: Extract all `.txt` files from a tar archive to the current directory + +```powershell +PS C:\> Get-TarEntry .\archive.tar -Include *.txt | Expand-TarEntry +``` + +Extracts all `.txt` files from `archive.tar` to the current directory, preserving their relative paths. + +### Example 2: Extract all `.txt` files from a tar archive to a specific directory + +```powershell +PS C:\> Get-TarEntry .\archive.tar.gz -Include *.txt | Expand-TarEntry -Destination .\extracted +``` + +Extracts all `.txt` files from `archive.tar.gz` to the `extracted` directory, creating the directory if it doesn’t exist. + +### Example 3: Extract all entries excluding `.txt` files from a tar archive + +```powershell +PS C:\> Get-TarEntry .\archive.tar -Exclude *.txt | Expand-TarEntry +``` + +Extracts all entries except `.txt` files from `archive.tar` to the current directory. + +### Example 4: Extract entries overwriting existing files + +```powershell +PS C:\> Get-TarEntry .\archive.tar -Include *.txt | Expand-TarEntry -Force +``` + +Demonstrates how the `-Force` switch overwrites existing files in the destination directory. + +### Example 5: Extract entries and output the expanded items + +```powershell +PS C:\> Get-TarEntry .\archive.tar -Exclude *.txt | Expand-TarEntry -PassThru + + Directory: C:\ + +Mode LastWriteTime Length Name +---- ------------- ------ ---- +d---- 2025-06-23 7:00 PM folder1 +-a--- 2025-06-23 7:00 PM 2048 image.png +``` + +By default, this cmdlet produces no output. When `-PassThru` is used, it outputs `FileInfo` and `DirectoryInfo` objects representing the extracted entries. + +### Example 6: Extract a specific entry from a compressed tar archive + +```powershell +PS C:\> $stream = Invoke-WebRequest https://example.com/archive.tar.gz +PS C:\> $file = $stream | Get-TarEntry -Include readme.md -Algorithm gz | Expand-TarEntry -PassThru +PS C:\> Get-Content $file.FullName + +# My Project +This is the README file for my project. +``` + +Extracts the `readme.md` file from a gzip-compressed tar archive retrieved via a web request and displays its contents. + +> [!NOTE] +> When `Get-TarEntry` processes a stream, it defaults to the `gz` (gzip) algorithm. Specify the `-Algorithm` parameter (e.g., `-Algorithm bz2` for bzip2) to match the compression type of the tar archive, or an error may occur if the stream is not gzip-compressed. + +## PARAMETERS + +### -Destination + +The destination directory where tar entries are extracted. If not specified, entries are extracted to their relative path in the current directory, creating any necessary subdirectories. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: False +Position: 0 +Default value: $PWD +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Force + +Overwrites existing files in the destination directory when this switch is used. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -InputObject + +The tar entries to extract. These are instances of `TarEntryBase` (`TarEntryFile` or `TarEntryDirectory`) output by the [`Get-TarEntry`](./Get-TarEntry.md) cmdlet. + +> [!NOTE] +> This parameter accepts pipeline input from `Get-TarEntry`. Binding by name is also supported. + +```yaml +Type: TarEntryBase[] +Parameter Sets: (All) +Aliases: + +Required: True +Position: Named +Default value: None +Accept pipeline input: True (ByValue) +Accept wildcard characters: False +``` + +### -PassThru + +Outputs `FileInfo` and `DirectoryInfo` objects representing the extracted entries when this switch is used. By default, the cmdlet produces no output. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters + +This cmdlet supports the common parameters. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +### PSCompression.Abstractions.TarEntryBase[] + +You can pipe instances of `TarEntryFile` or `TarEntryDirectory` from [`Get-TarEntry`](./Get-TarEntry.md) to this cmdlet. + +## OUTPUTS + +### None + +By default, this cmdlet produces no output. + +### System.IO.FileSystemInfo + +When the `-PassThru` switch is used, the cmdlet outputs objects representing the extracted files and directories. + +## NOTES + +## RELATED LINKS + +[__Get-TarEntry__](https://github.com/santisq/PSCompression) + +[__Expand-TarArchive__](https://github.com/santisq/PSCompression) + +[__SharpZipLib__](https://github.com/icsharpcode/SharpZipLib) + +[__SharpCompress__](https://github.com/adamhathcock/sharpcompress) + +[__ZstdSharp__](https://github.com/oleg-st/ZstdSharp) + +[__System.IO.Compression__](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression) diff --git a/docs/en-US/Expand-ZipEntry.md b/docs/en-US/Expand-ZipEntry.md index 96c2300..3039aba 100644 --- a/docs/en-US/Expand-ZipEntry.md +++ b/docs/en-US/Expand-ZipEntry.md @@ -66,7 +66,7 @@ By default this cmdlet produces no output. When `-PassThru` is used, this cmdlet ```powershell PS ..\pwsh> $package = Invoke-WebRequest https://www.powershellgallery.com/api/v2/package/PSCompression -PS ..\pwsh> $file = $package | Get-ZipEntry -Include *.psd1 | Expand-ZipEntry -PassThru -Force +PS ..\pwsh> $file = $package | Get-ZipEntry -Include *.psd1 | Expand-ZipEntry -PassThru PS ..\pwsh> Get-Content $file.FullName -Raw | Invoke-Expression Name Value @@ -168,7 +168,7 @@ This cmdlet supports the common parameters. For more information, see [about_Com ## INPUTS -### ZipEntryBase +### PSCompression.Abstractions.ZipEntryBase You can pipe instances of `ZipEntryFile` or `ZipEntryDirectory` to this cmdlet. These instances are produced by [`Get-ZipEntry`](Get-ZipEntry.md) and [`New-ZipEntry`](New-ZipEntry.md) cmdlets. @@ -178,6 +178,6 @@ You can pipe instances of `ZipEntryFile` or `ZipEntryDirectory` to this cmdlet. By default, this cmdlet produces no output. -### FileSystemInfo +### System.IO.FileSystemInfo The cmdlet outputs the `FileInfo` and `DirectoryInfo` instances of the extracted entries when `-PassThru` switch is used. diff --git a/docs/en-US/Get-TarEntry.md b/docs/en-US/Get-TarEntry.md new file mode 100644 index 0000000..4b9adb7 --- /dev/null +++ b/docs/en-US/Get-TarEntry.md @@ -0,0 +1,318 @@ +--- +external help file: PSCompression.dll-Help.xml +Module Name: PSCompression +online version: https://github.com/santisq/PSCompression +schema: 2.0.0 +--- + +# Get-TarEntry + +## SYNOPSIS + +Lists tar archive entries from a specified path or input stream. + +## SYNTAX + +### Path (Default) + +```powershell +Get-TarEntry + [-Path] + [-Algorithm ] + [-Type ] + [-Include ] + [-Exclude ] + [] +``` + +### Stream + +```powershell +Get-TarEntry + [-InputStream] + [-Algorithm ] + [-Type ] + [-Include ] + [-Exclude ] + [] +``` + +### LiteralPath + +```powershell +Get-TarEntry + -LiteralPath + [-Algorithm ] + [-Type ] + [-Include ] + [-Exclude ] + [] +``` + +## DESCRIPTION + +The `Get-TarEntry` cmdlet lists entries in tar archives, including both uncompressed (`.tar`) and compressed formats (e.g., `.tar.gz`, `.tar.bz2`, `.tar.zst`, `.tar.lz`). It supports input from file paths or streams and outputs `TarEntryFile` or `TarEntryDirectory` objects, which can be piped to cmdlets like [`Expand-TarEntry`](./Expand-TarEntry.md) and [`Get-TarEntryContent`](./Get-TarEntryContent.md). The cmdlet uses libraries such as `SharpZipLib` for tar handling, `System.IO.Compression` for gzip, `SharpCompress` for lzip, and `ZstdSharp` for Zstandard. Use `-Include` and `-Exclude` to filter entries by name and `-Type` to filter by entry type (file or directory). + +## EXAMPLES + +### Example 1: List entries for a specified tar archive + +```powershell +PS ..\pwsh> Get-TarEntry .\archive.tar + + Directory: /folder1/ + +Type LastWriteTime Size Name +---- ------------- ---- ---- +Directory 6/23/2025 11:08 PM folder1 +Archive 6/23/2025 11:08 PM 1.00 KB file1.txt +Archive 6/23/2025 11:08 PM 2.00 KB file2.txt +``` + +Lists all entries in `archive.tar`, including directories and files. + +### Example 2: List entries from all gzip-compressed tar archives in the current directory + +```powershell +PS ..\pwsh> Get-TarEntry *.tar.gz + + Directory: /folder1/ + +Type LastWriteTime Size Name +---- ------------- ---- ---- +Directory 6/23/2025 11:08 PM folder1 +Archive 6/23/2025 11:08 PM 1.00 KB file1.txt +Archive 6/23/2025 11:08 PM 2.00 KB file2.txt +``` + +> [!TIP] +> The `-Path` parameter supports wildcards. + +### Example 3: List all file entries from a tar archive + +```powershell +PS C:\> Get-TarEntry .\archive.tar -Type Archive + + Directory: /folder1/ + +Type LastWriteTime Size Name +---- ------------- ---- ---- +Archive 6/23/2025 11:08 PM 1.00 KB file1.txt +Archive 6/23/2025 11:08 PM 2.00 KB file2.txt +``` + +Filters entries to show only files using `-Type Archive`. + +### Example 4: Filter entries with Include and Exclude parameters + +```powershell +PS C:\> Get-TarEntry .\archive.tbz2 -Include folder1/* -Exclude *.txt + + Directory: /folder1/ + +Type LastWriteTime Size Name +---- ------------- ---- ---- +Directory 2025-06-23 7:00 PM folder1 +Archive 2025-06-23 7:00 PM 3.00 KB image.png +``` + +Filters entries to include only those under `folder1/` but excludes `.txt` files. + +> [!NOTE] +> If not specified, the cmdlet infers the compression algorithm from the file extension: `gz` for `.gz`, `.gzip`, `.tgz`; `bz2` for `.bz2`, `.bzip2`, `.tbz2`, `.tbz`; `zst` for `.zst`; `lz` for `.lz`; `none` for `.tar`. If the extension is unrecognized, it defaults to `none` (uncompressed tar). + +### Example 5: List entries from an input stream + +```powershell +PS C:\> $stream = Invoke-WebRequest https://example.com/archive.tar.gz +PS C:\> $stream | Get-TarEntry -Algorithm gz | Select-Object -First 3 + + Directory: /docs/ + +Type LastWriteTime Size Name +---- ------------- ---- ---- +Directory 2025-06-23 7:00 PM docs/ +Archive 2025-06-23 7:00 PM 1.50 KB readme.md +Archive 2025-06-23 7:00 PM 2.50 KB license.txt +``` + +Lists the first three entries from a gzip-compressed tar archive stream, specifying `-Algorithm gz`. + +> [!NOTE] +> When processing a stream, the cmdlet defaults to the `gz` (gzip) algorithm. Specify `-Algorithm` (e.g., `-Algorithm bz2` for bzip2) to match the compression type, or an error may occur if the stream is not gzip-compressed. + +## PARAMETERS + +### -Algorithm + +Specifies the compression algorithm used for the tar archive. Accepted values and their corresponding file extensions are: + +- `gz`: Gzip compression (`.gz`, `.gzip`, `.tgz`). +- `bz2`: Bzip2 compression (`.bz2`, `.bzip2`, `.tbz2`, `.tbz`) +- `zst`: Zstandard compression (`.zst`). +- `lz`: Lzip compression (`.lz`). +- `none`: Uncompressed tar archive (`.tar`). + +> [!NOTE] +> If not specified, the cmdlet infers the algorithm from the file extension. If the extension is unrecognized, it defaults to `none` (uncompressed tar). + +```yaml +Type: Algorithm +Parameter Sets: (All) +Aliases: +Accepted values: gz, bz2, zst, lz, none + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Exclude + +Specifies an array of string patterns to match as the cmdlet lists entries. Matching entries are excluded from the output. Wildcard characters are supported. + +> [!NOTE] +> Inclusion and exclusion patterns are applied to the entries’ relative paths. Exclusions are applied after inclusions. + +```yaml +Type: String[] +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: True +``` + +### -Include + +Specifies an array of string patterns to match as the cmdlet lists entries. Matching entries are included in the output. Wildcard characters are supported. + +> [!NOTE] +> Inclusion and exclusion patterns are applied to the entries’ relative paths. Exclusions are applied after inclusions. + +```yaml +Type: String[] +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: True +``` + +### -InputStream + +Specifies an input stream containing a tar archive. + +> [!NOTE] +> +> - Output from `Invoke-WebRequest` is automatically bound to this parameter. +> - The cmdlet defaults to the `gz` (gzip) algorithm for streams. Specify `-Algorithm` to match the compression type (e.g., `-Algorithm zst` for Zstandard) to avoid errors. + +```yaml +Type: Stream +Parameter Sets: Stream +Aliases: RawContentStream + +Required: True +Position: 0 +Default value: None +Accept pipeline input: True (ByPropertyName, ByValue) +Accept wildcard characters: False +``` + +### -LiteralPath + +Specifies one or more paths to tar archives. The value is used exactly as typed, with no wildcard character interpretation. + +```yaml +Type: String[] +Parameter Sets: LiteralPath +Aliases: PSPath + +Required: True +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -Path + +Specifies one or more paths to tar archives. Wildcard characters are supported. + +```yaml +Type: String[] +Parameter Sets: Path +Aliases: + +Required: True +Position: 0 +Default value: None +Accept pipeline input: True (ByValue) +Accept wildcard characters: True +``` + +### -Type + +Filters entries by type: `Archive` for files or `Directory` for directories. + +```yaml +Type: EntryType +Parameter Sets: (All) +Aliases: +Accepted values: Directory, Archive + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters + +This cmdlet supports the common parameters. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +### System.IO.Stream + +You can pipe a stream containing a tar archive to this cmdlet, such as output from `Invoke-WebRequest`. + +### System.String[] + +You can pipe strings containing paths to tar archives, such as output from `Get-ChildItem` or `Get-Item`. + +## OUTPUTS + +### PSCompression.TarEntryDirectory + +### PSCompression.TarEntryFile + +Outputs objects representing directories or files in the tar archive. + +## NOTES + +## RELATED LINKS + +[__Expand-TarEntry__](https://github.com/santisq/PSCompression) + +[__Expand-TarArchive__](https://github.com/santisq/PSCompression) + +[__Get-TarEntryContent__](https://github.com/santisq/PSCompression) + +[__SharpZipLib__](https://github.com/icsharpcode/SharpZipLib) + +[__SharpCompress__](https://github.com/adamhathcock/sharpcompress) + +[__ZstdSharp__](https://github.com/oleg-st/ZstdSharp) + +[__System.IO.Compression__](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression?view=net-6.0) diff --git a/docs/en-US/Get-TarEntryContent.md b/docs/en-US/Get-TarEntryContent.md new file mode 100644 index 0000000..70970e8 --- /dev/null +++ b/docs/en-US/Get-TarEntryContent.md @@ -0,0 +1,243 @@ +--- +external help file: PSCompression.dll-Help.xml +Module Name: PSCompression +online version: https://github.com/santisq/PSCompression +schema: 2.0.0 +--- + +# Get-TarEntryContent + +## SYNOPSIS + +Gets the content of a tar archive entry. + +## SYNTAX + +### Stream (Default) + +```powershell +Get-TarEntryContent + -Entry + [-Encoding ] + [-Raw] + [] +``` + +### Bytes + +```powershell +Get-TarEntryContent + -Entry + [-Raw] + [-AsByteStream] + [-BufferSize ] + [] +``` + +## DESCRIPTION + +The `Get-TarEntryContent` cmdlet retrieves the content of one or more `TarEntryFile` instances. This cmdlet is designed to be used with [`Get-TarEntry`](./Get-TarEntry.md) as the entry point. + +> [!TIP] +> Entries output by `Get-TarEntry` can be piped to this cmdlet. + +## EXAMPLES + +### Example 1: Get the content of a tar archive entry + +```powershell +PS C:\> Get-TarEntry .\archive.tar -Include folder1/file1.txt | Get-TarEntryContent + +Line 1 of file1.txt +Line 2 of file1.txt +``` + +The `-Include` parameter from `Get-TarEntry` targets a specific entry by its relative path, and the output is piped to `Get-TarEntryContent`. By default, the cmdlet streams content line by line. + +### Example 2: Get raw content of a tar archive entry + +```powershell +PS C:\> Get-TarEntry .\archive.tar -Include folder1/file1.txt | Get-TarEntryContent -Raw + +Line 1 of file1.txt +Line 2 of file1.txt +``` + +The cmdlet outputs a single multi-line string when the `-Raw` switch is used instead of line-by-line streaming. + +### Example 3: Get the bytes of a tar archive entry as a stream + +```powershell +PS C:\> $bytes = Get-TarEntry .\archive.tar -Include folder1/helloworld.txt | Get-TarEntryContent -AsByteStream +PS C:\> $bytes +104 +101 +108 +108 +111 +32 +119 +111 +114 +108 +100 +33 +13 +10 + +PS C:\> [System.Text.Encoding]::UTF8.GetString($bytes) +hello world! +``` + +The `-AsByteStream` switch is useful for reading non-text tar entries. + +### Example 4: Get contents of all `.md` files as byte arrays + +```powershell +PS C:\> $bytes = Get-TarEntry .\archive.tar.gz -Algorithm gz -Include *.md | Get-TarEntryContent -AsByteStream -Raw +PS C:\> $bytes[0].GetType() + +IsPublic IsSerial Name BaseType +-------- -------- ---- -------- +True True Byte[] System.Array + +PS C:\> $bytes[1].Length +7767 +``` + +When the `-Raw` and `-AsByteStream` switches are used together, the cmdlet outputs `byte[]` as single objects for each tar entry. + +### Example 5: Get content from an input stream + +```powershell +PS C:\> $stream = Invoke-WebRequest https://example.com/archive.tar.gz +PS C:\> $content = $stream | Get-TarEntry -Include readme.md -Algorithm gz | Get-TarEntryContent -Raw +PS C:\> $content + +# My Project +This is the README file for my project. +``` + +> [!NOTE] +> When `Get-TarEntry` processes a stream, it defaults to the `gz` (gzip) algorithm. Specify `-Algorithm` (e.g., `-Algorithm bz2` for bzip2) to match the compression type, or an error may occur if the stream is not gzip-compressed. + +## PARAMETERS + +### -AsByteStream + +Specifies that the content should be read as a stream of bytes. + +```yaml +Type: SwitchParameter +Parameter Sets: Bytes +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -BufferSize + +Determines the number of bytes read into the buffer before outputting the stream of bytes. This parameter applies only when `-Raw` is not used. The default buffer size is 128 KiB. + +```yaml +Type: Int32 +Parameter Sets: Bytes +Aliases: + +Required: False +Position: Named +Default value: 128000 +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Encoding + +Specifies the character encoding used to read the entry content. . The default encoding is `utf8NoBOM`. + +> [!NOTE] +> +> - This parameter applies only when `-AsByteStream` is not used. +> - The default encoding is __`utf8NoBOM`__. + +```yaml +Type: Encoding +Parameter Sets: Stream +Aliases: + +Required: False +Position: Named +Default value: utf8NoBOM +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Entry + +The tar entry or entries to get the content from. This parameter is designed to accept pipeline input from `Get-TarEntry` but can also be used as a named parameter. + +```yaml +Type: TarEntryFile[] +Parameter Sets: (All) +Aliases: + +Required: True +Position: Named +Default value: None +Accept pipeline input: True (ByValue) +Accept wildcard characters: False +``` + +### -Raw + +Returns the entire contents of an entry as a single string with newlines preserved, ignoring newline characters. By default, newline characters are used to separate the content into an array of strings. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters + +This cmdlet supports the common parameters. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +### PSCompression.TarEntryFile[] + +You can pipe instances of `TarEntryFile` to this cmdlet, produced by [`Get-TarEntry`](./Get-TarEntry.md). + +## OUTPUTS + +### System.String + +By default, this cmdlet returns the content as an array of strings, one per line. When the `-Raw` parameter is used, it returns a single string. + +### System.Byte + +This cmdlet returns the content as bytes when the `-AsByteStream` parameter is used. + +## NOTES + +## RELATED LINKS + +[__Get-TarEntry__](https://github.com/santisq/PSCompression) + +[__Expand-TarEntry__](https://github.com/santisq/PSCompression) + +[__SharpZipLib__](https://github.com/icsharpcode/SharpZipLib) + +[__SharpCompress__](https://github.com/adamhathcock/sharpcompress) + +[__ZstdSharp__](https://github.com/oleg-st/ZstdSharp) diff --git a/docs/en-US/Get-ZipEntry.md b/docs/en-US/Get-ZipEntry.md index 28bb1f8..3308690 100644 --- a/docs/en-US/Get-ZipEntry.md +++ b/docs/en-US/Get-ZipEntry.md @@ -40,7 +40,7 @@ Get-ZipEntry ```powershell Get-ZipEntry -InputStream - [-Type ] + [-Type ] [-Include ] [-Exclude ] [] @@ -84,7 +84,7 @@ PS ..\pwsh> Get-ZipEntry .\PSCompression.zip -Include PSCompression/docs/en-us* Type LastWriteTime CompressedSize Size Name ---- ------------- -------------- ---- ---- -Directory 2/22/2024 1:19 PM 0.00 B 0.00 B en-US +Directory 2/22/2024 1:19 PM 0.00 B en-US Archive 2/22/2024 1:19 PM 2.08 KB 6.98 KB Compress-GzipArchive.md Archive 2/22/2024 1:19 PM 2.74 KB 8.60 KB Compress-ZipArchive.md Archive 2/22/2024 1:19 PM 1.08 KB 2.67 KB ConvertFrom-GzipString.md @@ -105,7 +105,7 @@ PS ..\pwsh> Get-ZipEntry .\PSCompression.zip -Include PSCompression/docs/en-us* Type LastWriteTime CompressedSize Size Name ---- ------------- -------------- ---- ---- -Directory 2/22/2024 1:19 PM 0.00 B 0.00 B en-US +Directory 2/22/2024 1:19 PM 0.00 B en-US Archive 2/22/2024 1:19 PM 1.08 KB 2.67 KB ConvertFrom-GzipString.md Archive 2/22/2024 1:19 PM 1.67 KB 4.63 KB ConvertTo-GzipString.md Archive 2/22/2024 1:19 PM 1.74 KB 6.28 KB Expand-GzipArchive.md @@ -170,10 +170,10 @@ Archive 11/6/2024 10:29 PM 635.00 B 1.55 KB 3212d87de0 ### -Type -Lists entries of a specified type, `Archive` or `Directory`. +Filters entries by type: `Archive` for files or `Directory` for directories. ```yaml -Type: ZipEntryType +Type: EntryType Parameter Sets: (All) Aliases: Accepted values: Directory, Archive @@ -187,11 +187,10 @@ Accept wildcard characters: False ### -Exclude -Specifies an array of one or more string patterns to be matched as the cmdlet lists entries. Any matching item is excluded from the output. Wildcard characters are accepted. +Specifies an array of string patterns to match as the cmdlet lists entries. Matching entries are excluded from the output. Wildcard characters are supported. > [!NOTE] -> Inclusion and Exclusion patterns are applied to the entries relative path. -Exclusions are applied after the inclusions. +> Inclusion and exclusion patterns are applied to the entries’ relative paths. Exclusions are applied after inclusions. ```yaml Type: String[] @@ -207,11 +206,10 @@ Accept wildcard characters: True ### -Include -Specifies an array of one or more string patterns to be matched as the cmdlet lists entries. Any matching item is included in the output. Wildcard characters are accepted. +Specifies an array of string patterns to match as the cmdlet lists entries. Matching entries are included in the output. Wildcard characters are supported. > [!NOTE] -> Inclusion and Exclusion patterns are applied to the entries relative path. -Exclusions are applied after the inclusions. +> Inclusion and exclusion patterns are applied to the entries’ relative paths. Exclusions are applied after inclusions. ```yaml Type: String[] @@ -259,7 +257,7 @@ Accept wildcard characters: True ### -InputStream -Specifies an input stream. +Specifies an input stream containing a zip archive. > [!TIP] > Output from `Invoke-WebRequest` is bound to this paremeter automatically. @@ -292,6 +290,6 @@ You can pipe a Stream to this cmdlet. Output from `Invoke-WebRequest` can be pip ## OUTPUTS -### ZipEntryDirectory +### PSCompression.ZipEntryDirectory -### ZipEntryFile +### PSCompression.ZipEntryFile diff --git a/docs/en-US/Get-ZipEntryContent.md b/docs/en-US/Get-ZipEntryContent.md index 5a33a51..a8d3772 100644 --- a/docs/en-US/Get-ZipEntryContent.md +++ b/docs/en-US/Get-ZipEntryContent.md @@ -132,7 +132,7 @@ AliasesToExport {gziptofile, gzipfromfile, gziptostring, gzipfrom ### -BufferSize -This parameter determines the total number of bytes read into the buffer before outputting the stream of bytes. __This parameter is applicable only when `-Raw` is not used.__ The buffer default value is __128 KiB.__ +Determines the number of bytes read into the buffer before outputting the stream of bytes. This parameter applies only when `-Raw` is not used. The default buffer size is 128 KiB. ```yaml Type: Int32 @@ -148,11 +148,11 @@ Accept wildcard characters: False ### -Encoding -The character encoding used to read the entry content. +Specifies the character encoding used to read the entry content. . The default encoding is `utf8NoBOM`. > [!NOTE] > -> - __This parameter is applicable only when `-AsByteStream` is not used.__ +> - This parameter applies only when `-AsByteStream` is not used. > - The default encoding is __`utf8NoBOM`__. ```yaml @@ -169,7 +169,7 @@ Accept wildcard characters: False ### -Raw -Ignores newline characters and returns the entire contents of an entry in one string with the newlines preserved. By default, newline characters in a file are used as delimiters to separate the input into an array of strings. +Returns the entire contents of an entry as a single string with newlines preserved, ignoring newline characters. By default, newline characters are used to separate the content into an array of strings. ```yaml Type: SwitchParameter @@ -185,7 +185,7 @@ Accept wildcard characters: False ### -Entry -The entry or entries to get the content from. This parameter can be and is meant to be bound from pipeline however can be also used as a named parameter. +The zip entry or entries to get the content from. This parameter is designed to accept pipeline input from `Get-ZipEntry` but can also be used as a named parameter. ```yaml Type: ZipEntryFile[] @@ -221,16 +221,16 @@ This cmdlet supports the common parameters. See [about_CommonParameters](http:// ## INPUTS -### ZipEntryFile +### PSCompression.ZipEntryFile You can pipe instances of `ZipEntryFile` to this cmdlet. These instances are produced by [`Get-ZipEntry`](Get-ZipEntry.md) and [`New-ZipEntry`](New-ZipEntry.md) cmdlets. ## OUTPUTS -### String +### System.String By default, this cmdlet returns the content as an array of strings, one per line. When the `-Raw` parameter is used, it returns a single string. -### Byte +### System.Byte This cmdlet returns the content as bytes when the `-AsByteStream` parameter is used. diff --git a/docs/en-US/New-ZipEntry.md b/docs/en-US/New-ZipEntry.md index cf72907..926b06e 100644 --- a/docs/en-US/New-ZipEntry.md +++ b/docs/en-US/New-ZipEntry.md @@ -249,12 +249,12 @@ This cmdlet supports the common parameters. For more information, see [about_Com ## INPUTS -### String +### System.String You can pipe a value for the new zip entry to this cmdlet. ## OUTPUTS -### ZipEntryDirectory +### PSCompression.ZipEntryDirectory -### ZipEntryFile +### PSCompression.ZipEntryFile diff --git a/docs/en-US/Remove-ZipEntry.md b/docs/en-US/Remove-ZipEntry.md index 0c9dd7b..60ad969 100644 --- a/docs/en-US/Remove-ZipEntry.md +++ b/docs/en-US/Remove-ZipEntry.md @@ -109,7 +109,7 @@ This cmdlet supports the common parameters. For more information, see [about_Com ## INPUTS -### ZipEntryBase +### PSCompression.Abstractions.ZipEntryBase You can pipe instances of `ZipEntryFile` and `ZipEntryDirectory` to this cmdlet. These instances are produced by [`Get-ZipEntry`](Get-ZipEntry.md) and [`New-ZipEntry`](New-ZipEntry.md) cmdlets. diff --git a/docs/en-US/Rename-ZipEntry.md b/docs/en-US/Rename-ZipEntry.md index b74c4bf..4dc2464 100644 --- a/docs/en-US/Rename-ZipEntry.md +++ b/docs/en-US/Rename-ZipEntry.md @@ -213,7 +213,7 @@ This cmdlet supports the common parameters. For more information, see [about_Com ## INPUTS -### ZipEntryBase +### PSCompression.Abstractions.ZipEntryBase You can pipe instances of `ZipEntryFile` or `ZipEntryDirectory` to this cmdlet. These instances are produced by [`Get-ZipEntry`](Get-ZipEntry.md) and [`New-ZipEntry`](New-ZipEntry.md) cmdlets. @@ -223,8 +223,8 @@ You can pipe instances of `ZipEntryFile` or `ZipEntryDirectory` to this cmdlet. By default, this cmdlet produces no output. -### ZipEntryFile +### PSCompression.ZipEntryFile -### ZipEntryDirectory +### PSCompression.ZipEntryDirectory This cmdlet outputs the renamed entries when the `-PassThru` switch is used. diff --git a/docs/en-US/Set-ZipEntryContent.md b/docs/en-US/Set-ZipEntryContent.md index 60d3281..b95e612 100644 --- a/docs/en-US/Set-ZipEntryContent.md +++ b/docs/en-US/Set-ZipEntryContent.md @@ -224,7 +224,7 @@ This cmdlet supports the common parameters. For more information, see [about_Com ## INPUTS -### Object +### System.Object You can pipe strings or bytes to this cmdlet. @@ -234,6 +234,6 @@ You can pipe strings or bytes to this cmdlet. This cmdlet produces no output by default . -### ZipEntryFile +### PSCompression.ZipEntryFile This cmdlet outputs the updated entry when the `-PassThru` switch is used. diff --git a/src/PSCompression/Abstractions/ToCompressedStringCommandBase.cs b/src/PSCompression/Abstractions/ToCompressedStringCommandBase.cs index be3d91e..735e2a4 100644 --- a/src/PSCompression/Abstractions/ToCompressedStringCommandBase.cs +++ b/src/PSCompression/Abstractions/ToCompressedStringCommandBase.cs @@ -69,8 +69,9 @@ protected override void EndProcessing() _compressStream?.Dispose(); _outstream.Dispose(); - if (AsByteStream.IsPresent) + if (AsByteStream) { + // On purpose, we don't want to enumerate the byte[] for efficiency WriteObject(_outstream.ToArray(), enumerateCollection: false); return; } From 3075a0e97309e4ae63e18d00d0ec67fe48d2fe0a Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Mon, 23 Jun 2025 21:06:18 -0300 Subject: [PATCH 44/53] updates aliases. solves #43 --- module/PSCompression.psd1 | 35 +++++++++++-------- .../Commands/ExpandTarArchiveCommand.cs | 1 + .../Commands/ExpandTarEntryCommand.cs | 1 + .../Commands/ExpandZipEntryCommand.cs | 1 + .../Commands/GetTarEntryContentCommand.cs | 2 +- .../Commands/GetZipEntryContentCommand.cs | 2 +- .../Commands/NewZipEntryCommand.cs | 1 + .../Commands/RemoveZipEntryCommand.cs | 1 + .../Commands/RenameZipEntryCommand.cs | 1 + .../Commands/SetZipEntryContentCommand.cs | 1 + 10 files changed, 30 insertions(+), 16 deletions(-) diff --git a/module/PSCompression.psd1 b/module/PSCompression.psd1 index cd86614..0f46d37 100644 --- a/module/PSCompression.psd1 +++ b/module/PSCompression.psd1 @@ -103,20 +103,27 @@ # 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 = @( - 'togzipstring' - 'fromgzipstring' - 'zipcompress' - 'fromzlibstring' - 'tozlibstring' - 'fromdeflatestring' - 'todeflatestring' - 'frombrotlistring' - 'tobrotlistring' - 'zipge' - 'zipgc' - 'tarcompress' - 'targe' - 'targc' + 'tarcompress' # Compress-TarArchive + 'zipcompress' # Compress-ZipArchive + 'frombrotlistring' # ConvertFrom-BrotliString + 'fromdeflatestring' # ConvertFrom-DeflateString + 'fromgzipstring' # ConvertFrom-GzipString + 'fromzlibstring' # ConvertFrom-ZlibString + 'tobrotlistring' # ConvertTo-BrotliString + 'todeflatestring' # ConvertTo-DeflateString + 'togzipstring' # ConvertTo-GzipString + 'tozlibstring' # ConvertTo-ZlibString + 'untar' # Expand-TarArchive + 'untarentry' # Expand-TarEntry + 'unzipentry' # Expand-ZipEntry + 'targe' # Get-TarEntry + 'targec' # Get-TarEntryContent + 'zipge' # Get-ZipEntry + 'zipgec' # Get-ZipEntryContent + 'zipne' # New-ZipEntry + 'ziprm' # Remove-ZipEntry + 'zipren' # Rename-ZipEntry + 'zipsc' # Set-ZipEntryContent ) # DSC resources to export from this module diff --git a/src/PSCompression/Commands/ExpandTarArchiveCommand.cs b/src/PSCompression/Commands/ExpandTarArchiveCommand.cs index 615fb5b..669e9eb 100644 --- a/src/PSCompression/Commands/ExpandTarArchiveCommand.cs +++ b/src/PSCompression/Commands/ExpandTarArchiveCommand.cs @@ -14,6 +14,7 @@ namespace PSCompression.Commands; [Cmdlet(VerbsData.Expand, "TarArchive")] [OutputType(typeof(FileInfo), typeof(DirectoryInfo))] +[Alias("untar")] public sealed class ExpandTarArchiveCommand : CommandWithPathBase { private bool _shouldInferAlgo; diff --git a/src/PSCompression/Commands/ExpandTarEntryCommand.cs b/src/PSCompression/Commands/ExpandTarEntryCommand.cs index ecbc42c..321ff8c 100644 --- a/src/PSCompression/Commands/ExpandTarEntryCommand.cs +++ b/src/PSCompression/Commands/ExpandTarEntryCommand.cs @@ -6,6 +6,7 @@ namespace PSCompression.Commands; [Cmdlet(VerbsData.Expand, "TarEntry")] [OutputType(typeof(FileInfo), typeof(DirectoryInfo))] +[Alias("untarentry")] public sealed class ExpandTarEntryCommand : ExpandEntryCommandBase { protected override FileSystemInfo Extract(TarEntryBase entry) => diff --git a/src/PSCompression/Commands/ExpandZipEntryCommand.cs b/src/PSCompression/Commands/ExpandZipEntryCommand.cs index dd60197..a981300 100644 --- a/src/PSCompression/Commands/ExpandZipEntryCommand.cs +++ b/src/PSCompression/Commands/ExpandZipEntryCommand.cs @@ -7,6 +7,7 @@ namespace PSCompression.Commands; [Cmdlet(VerbsData.Expand, "ZipEntry")] [OutputType(typeof(FileInfo), typeof(DirectoryInfo))] +[Alias("unzipentry")] public sealed class ExpandZipEntryCommand : ExpandEntryCommandBase, IDisposable { private readonly ZipArchiveCache _cache = new(); diff --git a/src/PSCompression/Commands/GetTarEntryContentCommand.cs b/src/PSCompression/Commands/GetTarEntryContentCommand.cs index 82bcded..e997f9a 100644 --- a/src/PSCompression/Commands/GetTarEntryContentCommand.cs +++ b/src/PSCompression/Commands/GetTarEntryContentCommand.cs @@ -10,7 +10,7 @@ namespace PSCompression.Commands; [Cmdlet(VerbsCommon.Get, "TarEntryContent", DefaultParameterSetName = "Stream")] [OutputType(typeof(string), ParameterSetName = ["Stream"])] [OutputType(typeof(byte), ParameterSetName = ["Bytes"])] -[Alias("targc")] +[Alias("targec")] public sealed class GetTarEntryContentCommand : GetEntryContentCommandBase { private byte[]? _buffer; diff --git a/src/PSCompression/Commands/GetZipEntryContentCommand.cs b/src/PSCompression/Commands/GetZipEntryContentCommand.cs index c69ef7d..29e530e 100644 --- a/src/PSCompression/Commands/GetZipEntryContentCommand.cs +++ b/src/PSCompression/Commands/GetZipEntryContentCommand.cs @@ -8,7 +8,7 @@ namespace PSCompression.Commands; [Cmdlet(VerbsCommon.Get, "ZipEntryContent", DefaultParameterSetName = "Stream")] [OutputType(typeof(string), ParameterSetName = ["Stream"])] [OutputType(typeof(byte), ParameterSetName = ["Bytes"])] -[Alias("zipgc")] +[Alias("zipgec")] public sealed class GetZipEntryContentCommand : GetEntryContentCommandBase, IDisposable { private readonly ZipArchiveCache _cache = new(); diff --git a/src/PSCompression/Commands/NewZipEntryCommand.cs b/src/PSCompression/Commands/NewZipEntryCommand.cs index 44b9b9c..3de6656 100644 --- a/src/PSCompression/Commands/NewZipEntryCommand.cs +++ b/src/PSCompression/Commands/NewZipEntryCommand.cs @@ -13,6 +13,7 @@ namespace PSCompression.Commands; [Cmdlet(VerbsCommon.New, "ZipEntry", DefaultParameterSetName = "Value")] [OutputType(typeof(ZipEntryDirectory), typeof(ZipEntryFile))] +[Alias("zipne")] public sealed class NewZipEntryCommand : PSCmdlet, IDisposable { private readonly List _entries = []; diff --git a/src/PSCompression/Commands/RemoveZipEntryCommand.cs b/src/PSCompression/Commands/RemoveZipEntryCommand.cs index 981f67a..4caba19 100644 --- a/src/PSCompression/Commands/RemoveZipEntryCommand.cs +++ b/src/PSCompression/Commands/RemoveZipEntryCommand.cs @@ -8,6 +8,7 @@ namespace PSCompression.Commands; [Cmdlet(VerbsCommon.Remove, "ZipEntry", SupportsShouldProcess = true)] [OutputType(typeof(void))] +[Alias("ziprm")] public sealed class RemoveZipEntryCommand : PSCmdlet, IDisposable { private readonly ZipArchiveCache _cache = new(ZipArchiveMode.Update); diff --git a/src/PSCompression/Commands/RenameZipEntryCommand.cs b/src/PSCompression/Commands/RenameZipEntryCommand.cs index f59c620..ef44eb2 100644 --- a/src/PSCompression/Commands/RenameZipEntryCommand.cs +++ b/src/PSCompression/Commands/RenameZipEntryCommand.cs @@ -10,6 +10,7 @@ namespace PSCompression.Commands; [Cmdlet(VerbsCommon.Rename, "ZipEntry", SupportsShouldProcess = true)] [OutputType(typeof(ZipEntryFile), typeof(ZipEntryDirectory))] +[Alias("zipren")] public sealed class RenameZipEntryCommand : PSCmdlet, IDisposable { private readonly ZipArchiveCache _zipArchiveCache = new(ZipArchiveMode.Update); diff --git a/src/PSCompression/Commands/SetZipEntryContentCommand.cs b/src/PSCompression/Commands/SetZipEntryContentCommand.cs index ea9cc9f..5bc1bf7 100644 --- a/src/PSCompression/Commands/SetZipEntryContentCommand.cs +++ b/src/PSCompression/Commands/SetZipEntryContentCommand.cs @@ -7,6 +7,7 @@ namespace PSCompression.Commands; [Cmdlet(VerbsCommon.Set, "ZipEntryContent", DefaultParameterSetName = "StringValue")] [OutputType(typeof(ZipEntryFile))] +[Alias("zipsc")] public sealed class SetZipEntryContentCommand : PSCmdlet, IDisposable { private ZipContentWriter? _zipWriter; From 336b28140916b1084728a224f6f2408d664de30f Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Mon, 23 Jun 2025 21:31:26 -0300 Subject: [PATCH 45/53] updates readme and changelog --- CHANGELOG.md | 12 ++- README.md | 288 ++++++++++++++++++++++++++------------------------- 2 files changed, 158 insertions(+), 142 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 19ab710..1893963 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # CHANGELOG -## 06/17/2025 +## 06/23/2025 - Added commands supporting several algorithms to compress and decompress strings: - `ConvertFrom-BrotliString` & `ConvertTo-BrotliString` (using to BrotliSharpLib) @@ -11,9 +11,13 @@ - `Get-TarEntryContent`: Retrieves the content of a tar entry. - `Expand-TarEntry`: Extracts a tar entry to a file. - Added commands to compress files and folders into `.tar` archives and extract `.tar` archives with various compression algorithms: - - `Compress-TarArchive` and `Expand-TarArchive`: Supported compression algorithms include `gz`, `bz2`, `zst`, `lz`, and `none` (no compression). + - `Compress-TarArchive` & `Expand-TarArchive`: Supported compression algorithms include `gz`, `bz2`, `zst`, `lz`, and `none` (no compression). - Removed commands: - - `Compress-GzipArchive` and `Expand-GzipArchive`: These were deprecated as they only supported single-file compression, which is now better handled by the module’s `.tar` archive functionality. + - `Compress-GzipArchive` & `Expand-GzipArchive`: These were deprecated as they only supported single-file compression, which is now better handled by the module’s `.tar` archive functionality. For a workaround to compress or decompress single files using gzip, see [Example 2 in `ConvertTo-GzipString`][example2converttogzipstring], which demonstrates using: + + ```powershell + [System.Convert]::ToBase64String([System.IO.File]::ReadAllBytes($path)) | ConvertFrom-GzipString + ``` This update was made possible by the following projects. If you find them helpful, please consider starring their repositories: @@ -60,3 +64,5 @@ This update was made possible by the following projects. If you find them helpfu - Moved from `[PSCompression.ZipEntryExtensions]::NormalizePath` to `[PSCompression.Extensions.PathExtensions]::NormalizePath`. - `Get-ZipEntry` command: - Renamed Parameter `-EntryType` to `-Type`. + +[example2converttogzipstring]: https://github.com/santisq/PSCompression/blob/main/docs/en-US/ConvertTo-GzipString.md#example-2-create-a-gzip-compressed-file-from-a-string diff --git a/README.md b/README.md index 4184f9e..c9fb564 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

PSCompression

-Zip and GZip utilities for PowerShell +Zip and Tar utilities for PowerShell

[![build](https://github.com/santisq/PSCompression/actions/workflows/ci.yml/badge.svg)](https://github.com/santisq/PSCompression/actions/workflows/ci.yml) @@ -11,157 +11,155 @@
-PSCompression is a PowerShell Module that provides Zip and Gzip utilities for compression, expansion and management. It also solves a few issues with Zip compression existing in _built-in PowerShell_. +`PSCompression` is a PowerShell module that provides utilities for creating, managing, and extracting zip and tar archives, as well as compressing and decompressing strings. It overcomes limitations in built-in PowerShell archive cmdlets (e.g., 2 GB zip file limits) and supports multiple compression algorithms, including gzip, bzip2, Zstandard, lzip, Brotli, Deflate, and Zlib. Built for cross-platform use, it’s compatible with Windows, Linux, and macOS. -## What does this Module offer? +## Features -### Zip Cmdlets +- __Zip Archive Management__: Create, list, extract, retrieve content, modify, and remove entries in zip archives with pipeline support. +- __Tar Archive Management__: Compress and extract tar archives with support for `gz`, `bz2`, `zst`, `lz`, and uncompressed (`none`) formats. +- __Tar Entry Management__: List, extract, and retrieve content from individual tar entries. +- __String Compression__: Compress and decompress strings using Brotli, Deflate, Gzip, and Zlib algorithms. -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
CmdletDescription
- -[`Get-ZipEntry`](docs/en-US/Get-ZipEntry.md) - - - -Main entry point for the `*-ZipEntry` cmdlets in this module. It can list zip archive entries from specified paths or input stream. - -
- -[`Expand-ZipEntry`](docs/en-US/Expand-ZipEntry.md) - - - -Expands zip entries to a destination directory. - -
- -[`Get-ZipEntryContent`](docs/en-US/Get-ZipEntryContent.md) - - - -Gets the content of one or more zip entries. - -
- -[`New-ZipEntry`](docs/en-US/New-ZipEntry.md) - -Creates zip entries from specified path or paths.
- -[`Remove-ZipEntry`](docs/en-US/Remove-ZipEntry.md) - -Removes zip entries from one or more zip archives.
- -[`Rename-ZipEntry`](docs/en-US/Rename-ZipEntry.md) - -Renames zip entries from one or more zip archives.
- -[`Set-ZipEntryContent`](docs/en-US/Set-ZipEntryContent.md) - -Sets or appends content to a zip entry.
+# Cmdlets -[`Compress-ZipArchive`](docs/en-US/Compress-ZipArchive.md) +## Cmdlets - +### Zip Archive Cmdlets -Similar capabilities as -[`Compress-Archive`](docs/en-US/https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.archive/compress-archive?view=powershell-7.2) -and overcomes a few issues with the built-in cmdlet (2 GB limit and more). - -
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CmdletAliasDescription
Compress-ZipArchivezipcompressCompresses files and folders into a zip archive, overcoming built-in PowerShell limitations.
Expand-ZipEntryunzipentryExtracts individual zip entries to a destination directory.
Get-ZipEntryzipgeLists zip archive entries from paths or streams, serving as the entry point for zip cmdlets.
Get-ZipEntryContentzipgecRetrieves the content of zip entries as text or bytes.
New-ZipEntryzipneAdds new entries to a zip archive from files or paths.
Remove-ZipEntryziprmRemoves entries from one or more zip archives.
Rename-ZipEntryziprenRenames entries in one or more zip archives.
Set-ZipEntryContentzipscSets or appends content to a zip entry.
-
-### Gzip Cmdlets +### Tar Archive Cmdlets -
- - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CmdletDescription
- -[`Compress-GzipArchive`](docs/en-US/Compress-GzipArchive.md) - - -Can compress one or more specified file paths into a Gzip file. -
- -[`ConvertFrom-GzipString`](docs/en-US/ConvertFrom-GzipString.md) - - -Expands Gzip Base64 input strings. -
- -[`ConvertTo-GzipString`](docs/en-US/ConvertTo-GzipString.md) - - -Can compress input strings into Gzip Base64 strings or raw bytes. -
- -[`Expand-GzipArchive`](docs/en-US/Expand-GzipArchive.md) - - +
CmdletAliasDescription
Compress-TarArchivetarcompressCompresses files and folders into a tar archive with optional compression (gz, bz2, zst, lz, none).
Expand-TarArchiveuntarExtracts a tar archive with support for gz, bz2, zst, lz, and uncompressed formats.
Expand-TarEntryuntarentryExtracts individual tar entries to a destination directory.
Get-TarEntrytargeLists tar archive entries from paths or streams, serving as the entry point for tar cmdlets.
Get-TarEntryContenttargcRetrieves the content of tar entries as text or bytes.
-Expands Gzip compressed files to a destination path or to the [success stream](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_output_streams?view=powershell-7.3#success-stream). +### String Compression Cmdlets - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CmdletAliasDescription
ConvertFrom-BrotliStringfrombrotlistringDecompresses a Brotli-compressed string.
ConvertFrom-DeflateStringfromdeflatestringDecompresses a Deflate-compressed string.
ConvertFrom-GzipStringfromgzipstringDecompresses a Gzip-compressed string.
ConvertFrom-ZlibStringfromzlibstringDecompresses a Zlib-compressed string.
ConvertTo-BrotliStringtobrotlistringCompresses a string using Brotli.
ConvertTo-DeflateStringtodeflatestringCompresses a string using Deflate.
ConvertTo-GzipStringtogzipstringCompresses a string using Gzip to Base64 or raw bytes.
ConvertTo-ZlibStringtozlibstringCompresses a string using Zlib.
-
+ +> __Note__: The `Compress-GzipArchive` and `Expand-GzipArchive` cmdlets have been removed, as their single-file gzip functionality is now handled by `Compress-TarArchive` and `Expand-TarArchive`. For a workaround to compress or decompress single files using gzip, see [Example 2 in `ConvertTo-GzipString`](./docs/en-US/ConvertTo-GzipString.md#example-2-create-a-gzip-compressed-file-from-a-string). ## Documentation @@ -189,6 +187,18 @@ Set-Location ./PSCompression This module has no external requirements and is compatible with __Windows PowerShell 5.1__ and [__PowerShell 7+__](https://github.com/PowerShell/PowerShell). +## Acknowledgments + +This module is powered by the following open-source projects: + +- [SharpZipLib](https://github.com/icsharpcode/SharpZipLib) +- [SharpCompress](https://github.com/adamhathcock/sharpcompress) +- [BrotliSharpLib](https://github.com/master131/BrotliSharpLib) +- [ZstdSharp](https://github.com/oleg-st/ZstdSharp) +- [System.IO.Compression](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression) + +If you find these projects helpful, consider starring their repositories! + ## Contributing Contributions are more than welcome, if you wish to contribute, fork this repository and submit a pull request with the changes. From 5f6cc38e092ce1040790e67d9b30d7df195df41a Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Mon, 23 Jun 2025 21:34:06 -0300 Subject: [PATCH 46/53] updates readme and changelog --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c9fb564..070fc49 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ Description - Compress-ZipArchive + Compress-ZipArchive zipcompress Compresses files and folders into a zip archive, overcoming built-in PowerShell limitations. From 5a877d57f11bf7c95fbe8ce7d5c89cd7037e67cf Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Mon, 23 Jun 2025 21:35:27 -0300 Subject: [PATCH 47/53] updates readme and changelog --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 070fc49..88c9e44 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ Description - Compress-TarArchive + Compress-TarArchive tarcompress Compresses files and folders into a tar archive with optional compression (gz, bz2, zst, lz, none). @@ -118,7 +118,7 @@ Description - ConvertFrom-BrotliString + ConvertFrom-BrotliString frombrotlistring Decompresses a Brotli-compressed string. @@ -159,7 +159,8 @@ -> __Note__: The `Compress-GzipArchive` and `Expand-GzipArchive` cmdlets have been removed, as their single-file gzip functionality is now handled by `Compress-TarArchive` and `Expand-TarArchive`. For a workaround to compress or decompress single files using gzip, see [Example 2 in `ConvertTo-GzipString`](./docs/en-US/ConvertTo-GzipString.md#example-2-create-a-gzip-compressed-file-from-a-string). +> [!NOTE] +> The `Compress-GzipArchive` and `Expand-GzipArchive` cmdlets have been removed, as their single-file gzip functionality is now handled by `Compress-TarArchive` and `Expand-TarArchive`. For a workaround to compress or decompress single files using gzip, see [Example 2 in `ConvertTo-GzipString`](./docs/en-US/ConvertTo-GzipString.md#example-2-create-a-gzip-compressed-file-from-a-string). ## Documentation From 80271d8c7d16f2a7e664c02e68eacaae31b813ef Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Mon, 23 Jun 2025 21:36:27 -0300 Subject: [PATCH 48/53] updates readme and changelog --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 88c9e44..494308e 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ Description - Compress-TarArchive + Compress-TarArchive tarcompress Compresses files and folders into a tar archive with optional compression (gz, bz2, zst, lz, none). From a47fda274271f1aa238479fa0bcdda4a25e01ada Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Mon, 23 Jun 2025 21:36:58 -0300 Subject: [PATCH 49/53] updates readme and changelog --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 494308e..db07e8e 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ Description - Compress-ZipArchive + Compress-ZipArchive zipcompress Compresses files and folders into a zip archive, overcoming built-in PowerShell limitations. @@ -83,7 +83,7 @@ Description - Compress-TarArchive + Compress-TarArchive tarcompress Compresses files and folders into a tar archive with optional compression (gz, bz2, zst, lz, none). @@ -118,7 +118,7 @@ Description - ConvertFrom-BrotliString + ConvertFrom-BrotliString frombrotlistring Decompresses a Brotli-compressed string. From 8a3e0bfd2ba79732fe58aef74ce1a0f8f8f8846b Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Mon, 23 Jun 2025 21:39:59 -0300 Subject: [PATCH 50/53] updates readme and changelog --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index db07e8e..e3dadec 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ Description - Compress-ZipArchive + Compress-ZipArchive zipcompress Compresses files and folders into a zip archive, overcoming built-in PowerShell limitations. @@ -83,7 +83,7 @@ Description - Compress-TarArchive + Compress-TarArchive tarcompress Compresses files and folders into a tar archive with optional compression (gz, bz2, zst, lz, none). @@ -118,7 +118,7 @@ Description - ConvertFrom-BrotliString + ConvertFrom-BrotliString frombrotlistring Decompresses a Brotli-compressed string. @@ -128,7 +128,7 @@ Decompresses a Deflate-compressed string. - ConvertFrom-GzipString + ConvertFrom-GzipString fromgzipstring Decompresses a Gzip-compressed string. From 1ba9bca82326a1e43867dbae061e48dda3d21c34 Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Mon, 23 Jun 2025 21:41:19 -0300 Subject: [PATCH 51/53] updates readme and changelog --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e3dadec..19e1ec0 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ Description - Compress-ZipArchive + Compress-ZipArchive zipcompress Compresses files and folders into a zip archive, overcoming built-in PowerShell limitations. @@ -83,7 +83,7 @@ Description - Compress-TarArchive + Compress-TarArchive tarcompress Compresses files and folders into a tar archive with optional compression (gz, bz2, zst, lz, none). @@ -118,7 +118,7 @@ Description - ConvertFrom-BrotliString + ConvertFrom-BrotliString frombrotlistring Decompresses a Brotli-compressed string. From 7fb72a9ef75f977bef617e3454908d02b4d24ec6 Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Mon, 23 Jun 2025 21:42:47 -0300 Subject: [PATCH 52/53] updates readme and changelog --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 19e1ec0..f56f3e1 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ Description - Compress-ZipArchive + Compress-ZipArchive zipcompress Compresses files and folders into a zip archive, overcoming built-in PowerShell limitations. @@ -83,7 +83,7 @@ Description - Compress-TarArchive + Compress-TarArchive tarcompress Compresses files and folders into a tar archive with optional compression (gz, bz2, zst, lz, none). @@ -118,7 +118,7 @@ Description - ConvertFrom-BrotliString + ConvertFrom-BrotliString frombrotlistring Decompresses a Brotli-compressed string. From 8e95c8bdfd14685538021e94f1d3993bdb59ad67 Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Mon, 23 Jun 2025 21:50:30 -0300 Subject: [PATCH 53/53] updates readme and changelog --- README.md | 2 +- module/PSCompression.psd1 | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f56f3e1..669a26a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

PSCompression

-Zip and Tar utilities for PowerShell +Zip, tar, and string compression utilities for PowerShell!

[![build](https://github.com/santisq/PSCompression/actions/workflows/ci.yml/badge.svg)](https://github.com/santisq/PSCompression/actions/workflows/ci.yml) diff --git a/module/PSCompression.psd1 b/module/PSCompression.psd1 index 0f46d37..66574f7 100644 --- a/module/PSCompression.psd1 +++ b/module/PSCompression.psd1 @@ -29,7 +29,7 @@ Copyright = '(c) Santiago Squarzon. All rights reserved.' # Description of the functionality provided by this module - Description = 'Zip and GZip utilities for PowerShell!' + Description = 'Zip, tar, and string compression utilities for PowerShell!' # Minimum version of the PowerShell engine required by this module PowerShellVersion = '5.1' @@ -143,13 +143,26 @@ 'powershell' 'zip' 'zip-compression' + 'tar' + 'tar-compression' 'gzip' 'gzip-compression' + 'bzip2' + 'bzip2-compression' + 'zstd' + 'zstd-compression' + 'lzip' + 'lzip-compression' + 'brotli' + 'brotli-compression' 'zlib' 'zlib-compression' 'deflate' 'deflate-compression' 'compression' + 'decompression' + 'archive' + 'cross-platform' 'csharp' )