Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 13 additions & 13 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ defaults:
jobs:
build:
name: 👷 Build
runs-on: ubuntu-24.04
runs-on: windows-2025
outputs:
nupkg_artifact_id: ${{ steps.upload_nupkg.outputs.artifact-id }}
steps:
Expand Down Expand Up @@ -51,18 +51,18 @@ jobs:
}
- name: 🧪 Test
run: |
$config = New-PesterConfiguration
$config.run.throw = $true
$config.output.Verbosity = 'Detailed'
$config.TestResult.Enabled = $true
$config.TestResult.OutputFormat = 'NUnitXml'
Invoke-Pester -Configuration $config
# - name: 📈 Report Test Results
# uses: dorny/test-reporter@v2
# with:
# name: Pester Tests
# path: testResults.xml
# reporter: dotnet-nunit
try {Invoke-Build Pester}
catch {
"::error file=$PSScriptRoot/build.ps1::Build failed with error: $($_.Exception.Message) `n $(Get-Error | Out-String)" -replace "`n","%0A"
throw
}
- name: 🧪 Test - WinPS
run: |
try {Invoke-Build Pester-WinPS}
catch {
"::error file=$PSScriptRoot/build.ps1::Build failed with error: $($_.Exception.Message) `n $(Get-Error | Out-String)" -replace "`n","%0A"
throw
}
- name: 📤 Upload Module Zip
uses: actions/upload-artifact@v7
with:
Expand Down
23 changes: 20 additions & 3 deletions ExcelFast.build.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ param(
[switch]$SkipPS51
)


if ($ENV:CI -or $ENV:GITHUB_ACTIONS) {

}

Set-BuildHeader {
param($Path)
"👷 $Path $(Get-BuildSynopsis $Task)"
Expand Down Expand Up @@ -263,7 +268,14 @@ Task Docs Build, {

Task Pester {
#Run in a separate job so as not to lock the assemblies
Start-Job -ScriptBlock { Invoke-Pester } | Receive-Job -Wait -AutoRemoveJob
Start-Job -ScriptBlock {
$config = New-PesterConfiguration
$config.run.throw = $true
$config.output.Verbosity = 'Detailed'
$config.TestResult.Enabled = $true
$config.TestResult.OutputFormat = 'NUnitXml'
Invoke-Pester -Configuration $config
} | Receive-Job -Wait -AutoRemoveJob
}

Task Pester-WinPS {
Expand All @@ -277,10 +289,15 @@ Task Pester-WinPS {
Write-Host -ForegroundColor Cyan 'Pester not found. Installing Pester...'
Install-Module Pester -MinimumVersion 5.0 -Force -Scope CurrentUser -ErrorAction Stop
}
Invoke-Pester
$config = New-PesterConfiguration
$config.run.throw = $true
$config.output.Verbosity = 'Detailed'
$config.TestResult.Enabled = $true
$config.TestResult.OutputFormat = 'NUnitXml'
Invoke-Pester -Configuration $config
}
}

Task CompileAll CompilePS7, CompilePS51
Task Test Pester, Pester-WinPS
Task . Clean, Build, Docs, Test
Task . Clean, Build, Docs
67 changes: 36 additions & 31 deletions Source/PowerShell/Cmdlets/ExportCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ namespace ExcelFast.PowerShell.Cmdlets;

using System;
using System.Threading.Channels;
using System.Collections.Concurrent;

using ExcelFast.Extensions;

using MiniExcelLibs;
using MiniExcelLib;
using MiniExcelLib.OpenXml;

using FilePath = Path;

Expand Down Expand Up @@ -57,19 +57,19 @@ public class ExportCommand : TaskCmdlet

private int rowsExported;

#if NET472
private readonly BlockingCollection<Dictionary<string, object>> exportQueue;
#else
// #if NET472
// private readonly BlockingCollection<Dictionary<string, object>> exportQueue;
// #else
private readonly Channel<Dictionary<string, object>> exportQueue;
#endif
// #endif

public ExportCommand()
{
#if NET472
exportQueue = [];
#else
// #if NET472
// exportQueue = [];
// #else
exportQueue = Channel.CreateBounded<Dictionary<string, object>>(new BoundedChannelOptions(InputQueueSize));
#endif
// #endif
}

protected override async Task Begin()
Expand Down Expand Up @@ -149,11 +149,11 @@ protected override async Task End()

try
{
#if NET472
exportQueue.CompleteAdding();
#else
// #if NET472
// exportQueue.CompleteAdding();
// #else
exportQueue.Writer.TryComplete();
#endif
// #endif

Debug("Waiting for export task to complete.");

Expand Down Expand Up @@ -193,7 +193,16 @@ private async Task ProcessInputObjects()
continue;
}

Dictionary<string, object> row = inputObject.ToFlatDictionary();
await Post(() =>
{
var row = inputObject.ToFlatDictionary();
// #if NET472
// exportQueue.Add(row, PipelineStopToken);
// #else
exportQueue.Writer.TryWrite(row);
// #endif
});

try
{
rowsExported++;
Expand All @@ -202,11 +211,6 @@ private async Task ProcessInputObjects()
continue;
}

#if NET472
exportQueue.Add(row, PipelineStopToken);
#else
await exportQueue.Writer.WriteAsync(row, PipelineStopToken);
#endif
StartExportTaskIfNeeded();
}
catch (OperationCanceledException)
Expand All @@ -224,19 +228,20 @@ private void StartExportTaskIfNeeded()
return;
}

#if NET472
var queue = exportQueue.GetConsumingEnumerable(PipelineStopToken);
#else
// #if NET472
// var queue = exportQueue.GetConsumingEnumerable(PipelineStopToken);
// #else
var queue = exportQueue.Reader.ReadAllAsync(PipelineStopToken);
#endif
// #endif

Debug("Starting MiniExcel export task.");

exportTask = Task.Run(async () => await MiniExcel.SaveAsAsync(
var exporter = MiniExcel.Exporters.GetOpenXmlExporter();

exportTask = Task.Run(async () => await exporter.ExportAsync(
Destination,
queue,
sheetName: SheetName,
excelType: ExcelType.XLSX,
overwriteFile: Force.IsPresent,
cancellationToken: PipelineStopToken
), PipelineStopToken);
Expand All @@ -245,11 +250,11 @@ private void StartExportTaskIfNeeded()
protected override async Task Clean()
{
Debug("Stopping export process due to pipeline stop request.");
#if NET472
exportQueue.CompleteAdding();
#else
// #if NET472
// exportQueue.CompleteAdding();
// #else
exportQueue.Writer.TryComplete();
#endif
// #endif

if (exportTask is null)
{
Expand All @@ -272,4 +277,4 @@ private async void AssertExportTaskNotFaulted()
{
if (exportTask?.IsFaulted == true) await exportTask; // This will re-throw the exception from the export task to be handled by the cmdlet's error handling
}
}
}
83 changes: 45 additions & 38 deletions Source/PowerShell/Cmdlets/ImportCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ namespace ExcelFast.PowerShell.Cmdlets;

using DocumentFormat.OpenXml.Drawing.Diagrams;

using MiniExcelLibs;
using MiniExcelLib;

using FilePath = Path;
using MiniExcelLib.OpenXml;

[Cmdlet(VerbsData.Import, CmdletDefaultName)]
[Alias("iwb")]
Expand Down Expand Up @@ -162,10 +163,11 @@ internal void ImportWorkbook(string workbookPath)

IEnumerable<string> sheetNamesToImport = [];

IReadOnlyCollection<string> availableSheetNames = MiniExcel.GetSheetNames(providerPath).ToArray();
if (SheetName == null || SheetName.Length == 0)
{
Debug($"No sheet name provided. Importing the first sheet from '{providerPath}'.");
var importer = MiniExcel.Importers.GetOpenXmlImporter();
IReadOnlyCollection<string> availableSheetNames = importer.GetSheetNames(providerPath).ToArray();
if (SheetName == null || SheetName.Length == 0)
{
Debug($"No sheet name provided. Importing the first sheet from '{providerPath}'.");
sheetNamesToImport = [availableSheetNames.FirstOrDefault() ?? "Sheet1"];
}
else
Expand Down Expand Up @@ -265,39 +267,40 @@ private void ImportSheetData(string providerPath, string currentSheetName)
{
IEnumerable<dynamic> rows;

ICollection<string> columns = MiniExcel.GetColumns(
providerPath,
!NoHeaders.IsPresent,
currentSheetName,
startCell: StartCell
);
var importer = MiniExcel.Importers.GetOpenXmlImporter();
ICollection<string> columns = importer.GetColumnNames(
providerPath,
hasHeaderRow: !NoHeaders.IsPresent,
sheetName: currentSheetName,
startCell: StartCell
).ToArray();

bool duplicateColumnSet = !columnSets.Add(columns);
bool duplicateColumnSet = !columnSets.Add(columns);

if (duplicateColumnSet || !columnSets.Any(c => c.SequenceEqual(columns)))
{
Warning($"Sheet '{currentSheetName}' in '{providerPath}' has different columns than previously imported sheets. The resultant object output may be different and not displayed correctly.");
}
if (duplicateColumnSet || !columnSets.Any(c => c.SequenceEqual(columns)))
{
Warning($"Sheet '{currentSheetName}' in '{providerPath}' has different columns than previously imported sheets. The resultant object output may be different and not displayed correctly.");
}

rows = string.IsNullOrEmpty(EndCell)
? MiniExcel.Query(
providerPath,
useHeaderRow: !NoHeaders.IsPresent,
sheetName: currentSheetName,
startCell: StartCell
)
: MiniExcel.QueryRange(
providerPath,
!NoHeaders.IsPresent,
currentSheetName,
startCell: StartCell,
endCell: EndCell
);

if (Raw.IsPresent)
{
// Return the raw enumerable as-is so the consumer can stream/transform using their preferred method.
WriteObject(rows, false);
rows = string.IsNullOrEmpty(EndCell)
? importer.Query(
providerPath,
hasHeaderRow: !NoHeaders.IsPresent,
sheetName: currentSheetName,
startCell: StartCell
)
: importer.QueryRange(
providerPath,
hasHeaderRow: !NoHeaders.IsPresent,
sheetName: currentSheetName,
startCell: StartCell,
endCell: EndCell
);

if (Raw.IsPresent)
{
// Return the raw enumerable as-is so the consumer can stream/transform using their preferred method.
WriteObject(rows, false);
return;
}

Expand Down Expand Up @@ -327,8 +330,12 @@ private void ImportSheetData(string providerPath, string currentSheetName)

static string GetWorkbookPath(IXLWorkbook workbook)
{
string path = workbook.ToString();
path = Regex.Replace(path, @"^XLWorkbook\((.*)\)$", "$1");
return path;
string? path = workbook.ToString();
if (string.IsNullOrEmpty(path))
{
throw new ArgumentException("The provided workbook does not have a valid path. Ensure the workbook is saved and has a valid file path.");
}
path = Regex.Replace(path, @"^XLWorkbook\((.*)\)$", "$1");
return path;
}
}
Loading
Loading