more fluent interface for options

This commit is contained in:
Adam Hathcock
2026-02-10 11:10:04 +00:00
parent 2e364ac0eb
commit bd99c1ab27
11 changed files with 493 additions and 168 deletions

View File

@@ -20,14 +20,18 @@ using (var archive = RarArchive.OpenArchive("file.rar"))
using (var archive = SevenZipArchive.OpenArchive("file.7z"))
using (var archive = GZipArchive.OpenArchive("file.gz"))
// With options
var options = new ReaderOptions
// With fluent options (preferred)
var options = ReaderOptions.ForEncryptedArchive("password")
.WithArchiveEncoding(new ArchiveEncoding { Default = Encoding.GetEncoding(932) });
using (var archive = ZipArchive.OpenArchive("encrypted.zip", options))
// Alternative: object initializer
var options2 = new ReaderOptions
{
Password = "password",
LeaveStreamOpen = true,
ArchiveEncoding = new ArchiveEncoding { Default = Encoding.GetEncoding(932) }
};
using (var archive = ZipArchive.OpenArchive("encrypted.zip", options))
```
### Creating Archives
@@ -44,16 +48,21 @@ using (var archive = ZipArchive.CreateArchive())
using (var archive = TarArchive.CreateArchive())
using (var archive = GZipArchive.CreateArchive())
// With options
var options = new WriterOptions(CompressionType.Deflate)
{
CompressionLevel = 9,
LeaveStreamOpen = false
};
// With fluent options (preferred)
var options = WriterOptions.ForZip()
.WithCompressionLevel(9)
.WithLeaveStreamOpen(false);
using (var archive = ZipArchive.CreateArchive())
{
archive.SaveTo("output.zip", options);
}
// Alternative: constructor with object initializer
var options2 = new WriterOptions(CompressionType.Deflate)
{
CompressionLevel = 9,
LeaveStreamOpen = false
};
```
---
@@ -222,33 +231,65 @@ using (var writer = WriterFactory.OpenWriter(stream, ArchiveType.Zip, Compressio
### ReaderOptions
Use factory presets and fluent helpers for common configurations:
```csharp
var options = new ReaderOptions
{
Password = "password", // For encrypted archives
LeaveStreamOpen = true, // Don't close wrapped stream
ArchiveEncoding = new ArchiveEncoding // Custom character encoding
{
Default = Encoding.GetEncoding(932)
}
};
// External stream with password and custom encoding
var options = ReaderOptions.ForExternalStream()
.WithPassword("password")
.WithArchiveEncoding(new ArchiveEncoding { Default = Encoding.GetEncoding(932) });
using (var archive = ZipArchive.OpenArchive("file.zip", options))
{
// ...
}
// Common presets
var safeOptions = ReaderOptions.SafeExtract; // No overwrite
var flatOptions = ReaderOptions.FlatExtract; // No directory structure
// Factory defaults:
// - file path / FileInfo overloads use LeaveStreamOpen = false
// - stream overloads use LeaveStreamOpen = true
```
Alternative: traditional object initializer:
```csharp
var options = new ReaderOptions
{
Password = "password",
LeaveStreamOpen = true,
ArchiveEncoding = new ArchiveEncoding { Default = Encoding.GetEncoding(932) }
};
```
### WriterOptions
Factory methods provide a clean, discoverable way to create writer options:
```csharp
// Factory methods for common archive types
var zipOptions = WriterOptions.ForZip() // ZIP with Deflate
.WithCompressionLevel(9) // 0-9 for Deflate
.WithLeaveStreamOpen(false); // Close stream when done
var tarOptions = WriterOptions.ForTar(CompressionType.GZip) // TAR with GZip
.WithLeaveStreamOpen(false);
var gzipOptions = WriterOptions.ForGZip() // GZip file
.WithCompressionLevel(6);
archive.SaveTo("output.zip", zipOptions);
```
Alternative: traditional constructor with object initializer:
```csharp
var options = new WriterOptions(CompressionType.Deflate)
{
CompressionLevel = 9, // 0-9 for Deflate
LeaveStreamOpen = true, // Don't close stream
CompressionLevel = 9,
LeaveStreamOpen = true,
};
archive.SaveTo("output.zip", options);
```
@@ -326,7 +367,7 @@ ArchiveType.ZStandard
try
{
using (var archive = ZipArchive.Open("archive.zip",
new ReaderOptions { Password = "password" }))
ReaderOptions.ForEncryptedArchive("password")))
{
archive.WriteToDirectory(@"C:\output");
}
@@ -353,7 +394,7 @@ var progress = new Progress<ProgressReport>(report =>
Console.WriteLine($"Extracting {report.EntryPath}: {report.PercentComplete}%");
});
var options = new ReaderOptions { Progress = progress };
var options = ReaderOptions.ForOwnedFile().WithProgress(progress);
using (var archive = ZipArchive.OpenArchive("archive.zip", options))
{
archive.WriteToDirectory(@"C:\output");

View File

@@ -18,14 +18,9 @@ Most archive formats store filenames and metadata as bytes. SharpCompress must c
using SharpCompress.Common;
using SharpCompress.Readers;
// Configure encoding before opening archive
var options = new ReaderOptions
{
ArchiveEncoding = new ArchiveEncoding
{
Default = Encoding.GetEncoding(932) // cp932 for Japanese
}
};
// Configure encoding using fluent factory method (preferred)
var options = ReaderOptions.ForEncoding(
new ArchiveEncoding { Default = Encoding.GetEncoding(932) }); // cp932 for Japanese
using (var archive = ZipArchive.OpenArchive("japanese.zip", options))
{
@@ -34,6 +29,12 @@ using (var archive = ZipArchive.OpenArchive("japanese.zip", options))
Console.WriteLine(entry.Key); // Now shows correct characters
}
}
// Alternative: object initializer
var options2 = new ReaderOptions
{
ArchiveEncoding = new ArchiveEncoding { Default = Encoding.GetEncoding(932) }
};
```
### ArchiveEncoding Properties
@@ -47,10 +48,8 @@ using (var archive = ZipArchive.OpenArchive("japanese.zip", options))
**Archive API:**
```csharp
var options = new ReaderOptions
{
ArchiveEncoding = new ArchiveEncoding { Default = Encoding.GetEncoding(932) }
};
var options = ReaderOptions.ForEncoding(
new ArchiveEncoding { Default = Encoding.GetEncoding(932) });
using (var archive = ZipArchive.OpenArchive("file.zip", options))
{
// Use archive with correct encoding
@@ -59,10 +58,8 @@ using (var archive = ZipArchive.OpenArchive("file.zip", options))
**Reader API:**
```csharp
var options = new ReaderOptions
{
ArchiveEncoding = new ArchiveEncoding { Default = Encoding.GetEncoding(932) }
};
var options = ReaderOptions.ForEncoding(
new ArchiveEncoding { Default = Encoding.GetEncoding(932) });
using (var stream = File.OpenRead("file.zip"))
using (var reader = ReaderFactory.OpenReader(stream, options))
{

View File

@@ -94,11 +94,11 @@ Note: Extracting a solid rar or 7z file needs to be done in sequential order to
`ExtractAllEntries` is primarily intended for solid archives (like solid Rar) or 7Zip archives, where sequential extraction provides the best performance. For general/simple extraction with any supported archive type, use `archive.WriteToDirectory()` instead.
```C#
using (var archive = RarArchive.OpenArchive("Test.rar", new ReaderOptions
{
ExtractFullPath = true,
Overwrite = true
}))
// Using fluent factory method for extraction options
using (var archive = RarArchive.OpenArchive("Test.rar",
ReaderOptions.ForOwnedFile()
.WithExtractFullPath(true)
.WithOverwrite(true)))
{
// Simple extraction with RarArchive; this WriteToDirectory pattern works for all archive types
archive.WriteToDirectory(@"D:\temp");
@@ -130,12 +130,11 @@ var progress = new Progress<ProgressReport>(report =>
Console.WriteLine($"Extracting {report.EntryPath}: {report.PercentComplete}%");
});
using (var archive = RarArchive.OpenArchive("archive.rar", new ReaderOptions
{
Progress = progress,
ExtractFullPath = true,
Overwrite = true
})) // Must be solid Rar or 7Zip
using (var archive = RarArchive.OpenArchive("archive.rar",
ReaderOptions.ForOwnedFile()
.WithProgress(progress)
.WithExtractFullPath(true)
.WithOverwrite(true))) // Must be solid Rar or 7Zip
{
archive.WriteToDirectory(@"D:\output");
}
@@ -181,10 +180,10 @@ using (var reader = ReaderFactory.OpenReader(stream))
```C#
using (Stream stream = File.OpenWrite("C:\\temp.tgz"))
using (var writer = WriterFactory.OpenWriter(stream, ArchiveType.Tar, new WriterOptions(CompressionType.GZip)
{
LeaveStreamOpen = true
}))
using (var writer = WriterFactory.OpenWriter(
stream,
ArchiveType.Tar,
WriterOptions.ForTar(CompressionType.GZip).WithLeaveStreamOpen(true)))
{
writer.WriteAll("D:\\temp", "*", SearchOption.AllDirectories);
}
@@ -193,15 +192,15 @@ using (var writer = WriterFactory.OpenWriter(stream, ArchiveType.Tar, new Writer
### Extract zip which has non-utf8 encoded filename(cp932)
```C#
var opts = new SharpCompress.Readers.ReaderOptions();
var encoding = Encoding.GetEncoding(932);
opts.ArchiveEncoding = new SharpCompress.Common.ArchiveEncoding();
opts.ArchiveEncoding.CustomDecoder = (data, x, y) =>
{
return encoding.GetString(data);
};
var tr = SharpCompress.Archives.Zip.ZipArchive.OpenArchive("test.zip", opts);
foreach(var entry in tr.Entries)
var opts = new ReaderOptions()
.WithArchiveEncoding(new ArchiveEncoding
{
CustomDecoder = (data, x, y) => encoding.GetString(data)
});
using var archive = ZipArchive.OpenArchive("test.zip", opts);
foreach(var entry in archive.Entries)
{
Console.WriteLine($"{entry.Key}");
}

View File

@@ -8,10 +8,15 @@ namespace SharpCompress.Readers;
/// Options for configuring reader behavior when opening archives.
/// </summary>
/// <remarks>
/// This class is immutable. Use the <c>with</c> expression to create modified copies:
/// This class is immutable. Use factory presets and fluent helpers for common configurations:
/// <code>
/// var options = new ReaderOptions { Password = "secret" };
/// options = options with { LeaveStreamOpen = false };
/// var options = ReaderOptions.ForExternalStream()
/// .WithPassword("secret")
/// .WithLookForHeader(true);
/// </code>
/// Or use object initializers for simple cases:
/// <code>
/// var options = new ReaderOptions { Password = "secret", LeaveStreamOpen = false };
/// </code>
/// </remarks>
public sealed record ReaderOptions : IReaderOptions
@@ -169,6 +174,29 @@ public sealed record ReaderOptions : IReaderOptions
/// </summary>
public static ReaderOptions FlatExtract => new() { ExtractFullPath = false, Overwrite = true };
/// <summary>
/// Creates ReaderOptions for reading encrypted archives.
/// </summary>
/// <param name="password">The password for encrypted archives.</param>
public static ReaderOptions ForEncryptedArchive(string? password = null) =>
new ReaderOptions().WithPassword(password);
/// <summary>
/// Creates ReaderOptions for archives with custom character encoding.
/// </summary>
/// <param name="encoding">The encoding for archive entry names.</param>
public static ReaderOptions ForEncoding(IArchiveEncoding encoding) =>
new ReaderOptions().WithArchiveEncoding(encoding);
/// <summary>
/// Creates ReaderOptions for self-extracting archives that require header search.
/// </summary>
public static ReaderOptions ForSelfExtractingArchive(string? password = null) =>
new ReaderOptions()
.WithLookForHeader(true)
.WithPassword(password)
.WithRewindableBufferSize(1_048_576); // 1MB for SFX archives
/// <summary>
/// Default symbolic link handler that logs a warning message.
/// </summary>
@@ -179,66 +207,9 @@ public sealed record ReaderOptions : IReaderOptions
);
}
/// <summary>
/// Creates a new ReaderOptions instance with the specified password.
/// </summary>
/// <param name="password">The password for encrypted archives.</param>
public ReaderOptions(string? password) => Password = password;
/// <summary>
/// Creates a new ReaderOptions instance with the specified password and header search option.
/// </summary>
/// <param name="password">The password for encrypted archives.</param>
/// <param name="lookForHeader">Whether to search for the archive header.</param>
public ReaderOptions(string? password, bool lookForHeader)
{
Password = password;
LookForHeader = lookForHeader;
}
/// <summary>
/// Creates a new ReaderOptions instance with the specified encoding.
/// </summary>
/// <param name="encoding">The encoding for archive entry names.</param>
public ReaderOptions(IArchiveEncoding encoding) => ArchiveEncoding = encoding;
/// <summary>
/// Creates a new ReaderOptions instance with the specified password and encoding.
/// </summary>
/// <param name="password">The password for encrypted archives.</param>
/// <param name="encoding">The encoding for archive entry names.</param>
public ReaderOptions(string? password, IArchiveEncoding encoding)
{
Password = password;
ArchiveEncoding = encoding;
}
/// <summary>
/// Creates a new ReaderOptions instance with the specified stream open behavior.
/// </summary>
/// <param name="leaveStreamOpen">Whether to leave the stream open after reading.</param>
public ReaderOptions(bool leaveStreamOpen)
{
LeaveStreamOpen = leaveStreamOpen;
}
/// <summary>
/// Creates a new ReaderOptions instance with the specified stream open behavior and password.
/// </summary>
/// <param name="leaveStreamOpen">Whether to leave the stream open after reading.</param>
/// <param name="password">The password for encrypted archives.</param>
public ReaderOptions(bool leaveStreamOpen, string? password)
{
LeaveStreamOpen = leaveStreamOpen;
Password = password;
}
/// <summary>
/// Creates a new ReaderOptions instance with the specified buffer size.
/// </summary>
/// <param name="bufferSize">The buffer size for stream operations.</param>
public ReaderOptions(int bufferSize)
{
BufferSize = bufferSize;
}
// Note: Parameterized constructors have been removed.
// Use fluent With*() helpers or object initializers instead:
// new ReaderOptions().WithPassword("secret").WithLookForHeader(true)
// or
// new ReaderOptions { Password = "secret", LookForHeader = true }
}

View File

@@ -0,0 +1,127 @@
using System;
using SharpCompress.Common;
using SharpCompress.Common.Options;
namespace SharpCompress.Readers;
/// <summary>
/// Extension methods for fluent configuration of reader options.
/// </summary>
public static class ReaderOptionsExtensions
{
/// <summary>
/// Creates a copy with the specified LeaveStreamOpen value.
/// </summary>
public static ReaderOptions WithLeaveStreamOpen(
this ReaderOptions options,
bool leaveStreamOpen
) => options with { LeaveStreamOpen = leaveStreamOpen };
/// <summary>
/// Creates a copy with the specified password.
/// </summary>
public static ReaderOptions WithPassword(this ReaderOptions options, string? password) =>
options with
{
Password = password,
};
/// <summary>
/// Creates a copy with the specified archive encoding.
/// </summary>
public static ReaderOptions WithArchiveEncoding(
this ReaderOptions options,
IArchiveEncoding encoding
) => options with { ArchiveEncoding = encoding };
/// <summary>
/// Creates a copy with the specified LookForHeader value.
/// </summary>
public static ReaderOptions WithLookForHeader(this ReaderOptions options, bool lookForHeader) =>
options with
{
LookForHeader = lookForHeader,
};
/// <summary>
/// Creates a copy with the specified DisableCheckIncomplete value.
/// </summary>
public static ReaderOptions WithDisableCheckIncomplete(
this ReaderOptions options,
bool disableCheckIncomplete
) => options with { DisableCheckIncomplete = disableCheckIncomplete };
/// <summary>
/// Creates a copy with the specified buffer size.
/// </summary>
public static ReaderOptions WithBufferSize(this ReaderOptions options, int bufferSize) =>
options with
{
BufferSize = bufferSize,
};
/// <summary>
/// Creates a copy with the specified extension hint.
/// </summary>
public static ReaderOptions WithExtensionHint(
this ReaderOptions options,
string? extensionHint
) => options with { ExtensionHint = extensionHint };
/// <summary>
/// Creates a copy with the specified progress reporter.
/// </summary>
public static ReaderOptions WithProgress(
this ReaderOptions options,
IProgress<ProgressReport>? progress
) => options with { Progress = progress };
/// <summary>
/// Creates a copy with the specified rewindable buffer size.
/// </summary>
public static ReaderOptions WithRewindableBufferSize(
this ReaderOptions options,
int? rewindableBufferSize
) => options with { RewindableBufferSize = rewindableBufferSize };
/// <summary>
/// Creates a copy with the specified overwrite setting.
/// </summary>
public static ReaderOptions WithOverwrite(this ReaderOptions options, bool overwrite) =>
options with
{
Overwrite = overwrite,
};
/// <summary>
/// Creates a copy with the specified extract full path setting.
/// </summary>
public static ReaderOptions WithExtractFullPath(
this ReaderOptions options,
bool extractFullPath
) => options with { ExtractFullPath = extractFullPath };
/// <summary>
/// Creates a copy with the specified preserve file time setting.
/// </summary>
public static ReaderOptions WithPreserveFileTime(
this ReaderOptions options,
bool preserveFileTime
) => options with { PreserveFileTime = preserveFileTime };
/// <summary>
/// Creates a copy with the specified preserve attributes setting.
/// </summary>
public static ReaderOptions WithPreserveAttributes(
this ReaderOptions options,
bool preserveAttributes
) => options with { PreserveAttributes = preserveAttributes };
/// <summary>
/// Creates a copy with the specified symbolic link handler.
/// </summary>
public static ReaderOptions WithSymbolicLinkHandler(
this ReaderOptions options,
Action<string, string>? handler
) => options with { SymbolicLinkHandler = handler };
}

View File

@@ -10,10 +10,9 @@ namespace SharpCompress.Writers.GZip;
/// Options for configuring GZip writer behavior.
/// </summary>
/// <remarks>
/// This class is immutable. Use the <c>with</c> expression to create modified copies:
/// This class is immutable. Use factory methods for creation:
/// <code>
/// var options = new GZipWriterOptions { CompressionLevel = 9 };
/// options = options with { LeaveStreamOpen = false };
/// var options = WriterOptions.ForGZip().WithLeaveStreamOpen(false).WithCompressionLevel(9);
/// </code>
/// </remarks>
public sealed record GZipWriterOptions : IWriterOptions
@@ -90,14 +89,11 @@ public sealed record GZipWriterOptions : IWriterOptions
CompressionLevel = (int)compressionLevel;
}
/// <summary>
/// Creates a new GZipWriterOptions instance with the specified stream open behavior.
/// </summary>
/// <param name="leaveStreamOpen">Whether to leave the stream open after writing.</param>
public GZipWriterOptions(bool leaveStreamOpen)
{
LeaveStreamOpen = leaveStreamOpen;
}
// Note: Constructor with boolean leaveStreamOpen parameter removed.
// Use the fluent WithLeaveStreamOpen() helper or object initializer instead:
// new GZipWriterOptions() { LeaveStreamOpen = false }
// or
// WriterOptions.ForGZip().WithLeaveStreamOpen(false)
/// <summary>
/// Creates a new GZipWriterOptions instance from an existing WriterOptions instance.

View File

@@ -9,10 +9,9 @@ namespace SharpCompress.Writers;
/// Options for configuring writer behavior when creating archives.
/// </summary>
/// <remarks>
/// This class is immutable. Use the <c>with</c> expression to create modified copies:
/// This class is immutable. Use factory methods for creation:
/// <code>
/// var options = new WriterOptions(CompressionType.Zip);
/// options = options with { LeaveStreamOpen = false };
/// var options = WriterOptions.ForZip().WithLeaveStreamOpen(false).WithCompressionLevel(9);
/// </code>
/// </remarks>
public sealed record WriterOptions : IWriterOptions
@@ -92,32 +91,11 @@ public sealed record WriterOptions : IWriterOptions
CompressionLevel = compressionLevel;
}
/// <summary>
/// Creates a new WriterOptions instance with the specified compression type and stream open behavior.
/// </summary>
/// <param name="compressionType">The compression type for the archive.</param>
/// <param name="leaveStreamOpen">Whether to leave the stream open after writing.</param>
public WriterOptions(CompressionType compressionType, bool leaveStreamOpen)
: this(compressionType)
{
LeaveStreamOpen = leaveStreamOpen;
}
/// <summary>
/// Creates a new WriterOptions instance with the specified compression type, level, and stream open behavior.
/// </summary>
/// <param name="compressionType">The compression type for the archive.</param>
/// <param name="compressionLevel">The compression level (algorithm-specific).</param>
/// <param name="leaveStreamOpen">Whether to leave the stream open after writing.</param>
public WriterOptions(
CompressionType compressionType,
int compressionLevel,
bool leaveStreamOpen
)
: this(compressionType, compressionLevel)
{
LeaveStreamOpen = leaveStreamOpen;
}
// Note: Constructors with boolean leaveStreamOpen parameter removed.
// Use the fluent WithLeaveStreamOpen() helper or object initializer instead:
// new WriterOptions(type) { LeaveStreamOpen = false }
// or
// WriterOptions.ForZip().WithLeaveStreamOpen(false)
/// <summary>
/// Implicit conversion from CompressionType to WriterOptions.
@@ -125,4 +103,23 @@ public sealed record WriterOptions : IWriterOptions
/// <param name="compressionType">The compression type.</param>
public static implicit operator WriterOptions(CompressionType compressionType) =>
new(compressionType);
/// <summary>
/// Creates a new ZipWriterOptions for writing ZIP archives.
/// </summary>
/// <param name="compressionType">The compression type for the archive. Defaults to Deflate.</param>
public static WriterOptions ForZip(CompressionType compressionType = CompressionType.Deflate) =>
new(compressionType);
/// <summary>
/// Creates a new WriterOptions for writing TAR archives.
/// </summary>
/// <param name="compressionType">The compression type for the archive. Defaults to None.</param>
public static WriterOptions ForTar(CompressionType compressionType = CompressionType.None) =>
new(compressionType);
/// <summary>
/// Creates a new WriterOptions for writing GZip compressed files.
/// </summary>
public static WriterOptions ForGZip() => new(CompressionType.GZip);
}

View File

@@ -0,0 +1,55 @@
using System;
using SharpCompress.Common;
using SharpCompress.Common.Options;
namespace SharpCompress.Writers;
/// <summary>
/// Extension methods for fluent configuration of writer options.
/// </summary>
public static class WriterOptionsExtensions
{
/// <summary>
/// Creates a copy with the specified LeaveStreamOpen value.
/// </summary>
/// <param name="options">The source options.</param>
/// <param name="leaveStreamOpen">Whether to leave the stream open.</param>
/// <returns>A new options instance with the specified LeaveStreamOpen value.</returns>
public static WriterOptions WithLeaveStreamOpen(
this WriterOptions options,
bool leaveStreamOpen
) => options with { LeaveStreamOpen = leaveStreamOpen };
/// <summary>
/// Creates a copy with the specified compression level.
/// </summary>
/// <param name="options">The source options.</param>
/// <param name="compressionLevel">The compression level (algorithm-specific).</param>
/// <returns>A new options instance with the specified compression level.</returns>
public static WriterOptions WithCompressionLevel(
this WriterOptions options,
int compressionLevel
) => options with { CompressionLevel = compressionLevel };
/// <summary>
/// Creates a copy with the specified archive encoding.
/// </summary>
/// <param name="options">The source options.</param>
/// <param name="archiveEncoding">The archive encoding to use.</param>
/// <returns>A new options instance with the specified archive encoding.</returns>
public static WriterOptions WithArchiveEncoding(
this WriterOptions options,
IArchiveEncoding archiveEncoding
) => options with { ArchiveEncoding = archiveEncoding };
/// <summary>
/// Creates a copy with the specified progress reporter.
/// </summary>
/// <param name="options">The source options.</param>
/// <param name="progress">The progress reporter.</param>
/// <returns>A new options instance with the specified progress reporter.</returns>
public static WriterOptions WithProgress(
this WriterOptions options,
IProgress<ProgressReport> progress
) => options with { Progress = progress };
}

View File

@@ -390,7 +390,7 @@ public class ArchiveTests : ReaderTests
)
{
var writerOptions = compressionLevel.HasValue
? new WriterOptions(compressionType, compressionLevel.Value, leaveStreamOpen: true)
? new WriterOptions(compressionType, compressionLevel.Value) { LeaveStreamOpen = true }
: new WriterOptions(compressionType) { LeaveStreamOpen = true };
return WriterFactory.OpenAsyncWriter(
new AsyncOnlyStream(stream),

View File

@@ -108,4 +108,141 @@ public class OptionsUsabilityTests : TestBase
writer.Write("entry.bin", source, options)
);
}
[Fact]
public void WriterOptions_Factory_Methods_Create_Valid_Options()
{
// ForZip
var zipOptions = WriterOptions.ForZip();
Assert.Equal(CompressionType.Deflate, zipOptions.CompressionType);
Assert.True(zipOptions.LeaveStreamOpen);
// ForTar
var tarOptions = WriterOptions.ForTar();
Assert.Equal(CompressionType.None, tarOptions.CompressionType);
// ForGZip
var gzipOptions = WriterOptions.ForGZip();
Assert.Equal(CompressionType.GZip, gzipOptions.CompressionType);
}
[Fact]
public void WriterOptions_Fluent_Methods_Modify_Correctly()
{
var options = WriterOptions.ForZip().WithLeaveStreamOpen(false).WithCompressionLevel(9);
Assert.Equal(CompressionType.Deflate, options.CompressionType);
Assert.Equal(9, options.CompressionLevel);
Assert.False(options.LeaveStreamOpen);
}
[Fact]
public void WriterOptions_Factory_And_Fluent_Equivalent_To_Constructor()
{
// Factory + fluent approach
var factoryApproach = WriterOptions
.ForZip()
.WithLeaveStreamOpen(false)
.WithCompressionLevel(9);
// Traditional constructor approach
var constructorApproach = new WriterOptions(CompressionType.Deflate)
{
CompressionLevel = 9,
LeaveStreamOpen = false,
};
Assert.Equal(factoryApproach.CompressionType, constructorApproach.CompressionType);
Assert.Equal(factoryApproach.CompressionLevel, constructorApproach.CompressionLevel);
Assert.Equal(factoryApproach.LeaveStreamOpen, constructorApproach.LeaveStreamOpen);
}
[Fact]
public void ReaderOptions_Fluent_Methods_Modify_Correctly()
{
var options = new ReaderOptions()
.WithLeaveStreamOpen(false)
.WithPassword("secret")
.WithLookForHeader(true)
.WithBufferSize(65536);
Assert.False(options.LeaveStreamOpen);
Assert.Equal("secret", options.Password);
Assert.True(options.LookForHeader);
Assert.Equal(65536, options.BufferSize);
}
[Fact]
public void ReaderOptions_Fluent_And_Initializer_Equivalent()
{
// Fluent approach
var fluentApproach = new ReaderOptions()
.WithLeaveStreamOpen(false)
.WithPassword("secret")
.WithLookForHeader(true)
.WithOverwrite(false);
// Object initializer approach
var initializerApproach = new ReaderOptions
{
LeaveStreamOpen = false,
Password = "secret",
LookForHeader = true,
Overwrite = false,
};
Assert.Equal(fluentApproach.LeaveStreamOpen, initializerApproach.LeaveStreamOpen);
Assert.Equal(fluentApproach.Password, initializerApproach.Password);
Assert.Equal(fluentApproach.LookForHeader, initializerApproach.LookForHeader);
Assert.Equal(fluentApproach.Overwrite, initializerApproach.Overwrite);
}
[Fact]
public void ReaderOptions_Presets_Have_Correct_Defaults()
{
var external = ReaderOptions.ForExternalStream;
Assert.True(external.LeaveStreamOpen);
var owned = ReaderOptions.ForOwnedFile;
Assert.False(owned.LeaveStreamOpen);
var safe = ReaderOptions.SafeExtract;
Assert.False(safe.Overwrite);
var flat = ReaderOptions.FlatExtract;
Assert.False(flat.ExtractFullPath);
Assert.True(flat.Overwrite);
}
[Fact]
public void ReaderOptions_Factory_ForEncryptedArchive_Sets_Password()
{
var options = ReaderOptions.ForEncryptedArchive("myPassword");
Assert.Equal("myPassword", options.Password);
var noPassword = ReaderOptions.ForEncryptedArchive();
Assert.Null(noPassword.Password);
}
[Fact]
public void ReaderOptions_Factory_ForEncoding_Sets_Encoding()
{
var encoding = new ArchiveEncoding { Default = System.Text.Encoding.UTF8 };
var options = ReaderOptions.ForEncoding(encoding);
Assert.Equal(encoding, options.ArchiveEncoding);
}
[Fact]
public void ReaderOptions_Factory_ForSelfExtractingArchive_Configures_Correctly()
{
var options = ReaderOptions.ForSelfExtractingArchive("password");
Assert.True(options.LookForHeader);
Assert.Equal("password", options.Password);
Assert.Equal(1_048_576, options.RewindableBufferSize);
var noPassword = ReaderOptions.ForSelfExtractingArchive();
Assert.True(noPassword.LookForHeader);
Assert.Null(noPassword.Password);
Assert.Equal(1_048_576, noPassword.RewindableBufferSize);
}
}

View File

@@ -149,7 +149,12 @@ public class ZipReaderTests : ReaderTests
using var stream = new TestStream(
File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.dd.zip"))
);
using (var reader = ReaderFactory.OpenReader(stream))
using (
var reader = ReaderFactory.OpenReader(
stream,
new ReaderOptions().WithLeaveStreamOpen(false)
)
)
{
while (reader.MoveToNextEntry())
{