diff --git a/src/CsvProc9000/Csv/CsvField.cs b/src/CsvProc9000/Csv/CsvField.cs index 2603b8c..a85c95e 100644 --- a/src/CsvProc9000/Csv/CsvField.cs +++ b/src/CsvProc9000/Csv/CsvField.cs @@ -1,4 +1,4 @@ namespace CsvProc9000.Csv { - public record CsvField(string FieldName, string Value); + public record CsvField(string Name, string Value); } \ No newline at end of file diff --git a/src/CsvProc9000/Csv/CsvFile.cs b/src/CsvProc9000/Csv/CsvFile.cs index 35e3669..8667e4e 100644 --- a/src/CsvProc9000/Csv/CsvFile.cs +++ b/src/CsvProc9000/Csv/CsvFile.cs @@ -1,5 +1,9 @@ using System; using System.Collections.Generic; +using System.IO.Abstractions; +using System.Linq; +using System.Text; +using System.Threading.Tasks; using JetBrains.Annotations; namespace CsvProc9000.Csv @@ -10,10 +14,10 @@ public class CsvFile public CsvFile([NotNull] string fileName) { - FileName = fileName ?? throw new ArgumentNullException(nameof(fileName)); + OriginalFileName = fileName ?? throw new ArgumentNullException(nameof(fileName)); } - public string FileName { get; } + public string OriginalFileName { get; } public IEnumerable Rows => _rows; @@ -23,5 +27,48 @@ public void AddRow([NotNull] CsvRow row) _rows.Add(row); } + + public async Task SaveToAsync( + IFileSystem fileSystem, + string destinationFileName, + string delimiter) + { + var contentStringBuilder = new StringBuilder(); + + var fieldNames = Rows + .SelectMany(row => row.Fields) + .Select(field => field.Name) + .Distinct() + .ToList(); + + // add header row + contentStringBuilder.AppendJoin(delimiter, fieldNames); + contentStringBuilder.AppendLine(); + + foreach (var row in Rows) + { + var firstIteration = true; + + foreach (var fieldName in fieldNames) + { + // append the delimiter to the previous field when get here not in the first iteration + if (firstIteration) firstIteration = false; + else contentStringBuilder.Append(delimiter); + + var field = row.Fields.FirstOrDefault(f => f.Name == fieldName); + var fieldValue = string.Empty; + + if (field != null) + fieldValue = field.Value; + + contentStringBuilder.Append(fieldValue); + } + + contentStringBuilder.AppendLine(); + } + + var content = contentStringBuilder.ToString(); + await fileSystem.File.WriteAllTextAsync(destinationFileName, content); + } } } \ No newline at end of file diff --git a/src/CsvProc9000/Options/CsvProcessorOptions.cs b/src/CsvProc9000/Options/CsvProcessorOptions.cs index af09791..39eb69c 100644 --- a/src/CsvProc9000/Options/CsvProcessorOptions.cs +++ b/src/CsvProc9000/Options/CsvProcessorOptions.cs @@ -10,13 +10,16 @@ public class CsvProcessorOptions [UsedImplicitly] public string InboxDelimiter { get; set; } + + [UsedImplicitly] + public bool DeleteInboxFile { get; set; } = true; [UsedImplicitly] public string Outbox { get; set; } [UsedImplicitly] public string OutboxDelimiter { get; set; } - + [UsedImplicitly] public List Rules { get; set; } } diff --git a/src/CsvProc9000/Processors/CsvProcessor.cs b/src/CsvProc9000/Processors/CsvProcessor.cs index 459ee73..1d9c6f2 100644 --- a/src/CsvProc9000/Processors/CsvProcessor.cs +++ b/src/CsvProc9000/Processors/CsvProcessor.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.IO.Abstractions; using System.Linq; @@ -40,7 +40,7 @@ public async Task ProcessAsync(IFileInfo file) await ProcessInternalAsync(file); } - private bool CanProcess(IFileInfo file) + private bool CanProcess(IFileSystemInfo file) { if (!file.Exists) { @@ -48,12 +48,6 @@ private bool CanProcess(IFileInfo file) return false; } - if (file.IsReadOnly) - { - _logger.LogDebug("Cannot process file {File}, because it is read-only", file.FullName); - return false; - } - // ReSharper disable once InvertIf if (!file.Extension.Equals(".csv")) { @@ -75,17 +69,39 @@ private async Task ProcessInternalAsync(IFileSystemInfo file) _logger.LogDebug("Processor: Applying rules to file {File}...", file.FullName); ApplyRulesToFile(csvFile); + + await SaveResultAsync(file, csvFile); } + - // if (_fileSystem.Directory.Exists(_processorOptions.Outbox)) - // _fileSystem.Directory.CreateDirectory(_processorOptions.Outbox); + private async Task SaveResultAsync(IFileSystemInfo file, CsvFile csvFile) + { + var fileName = file.Name; + var destinationFileName = _fileSystem.Path.Combine(_processorOptions.Outbox, fileName); + + _logger.LogInformation("Processor: Saving result to {Destination}...", destinationFileName); + + if (_fileSystem.Directory.Exists(_processorOptions.Outbox)) + _fileSystem.Directory.CreateDirectory(_processorOptions.Outbox); + + await csvFile.SaveToAsync(_fileSystem, destinationFileName, _processorOptions.OutboxDelimiter); + + if (!file.Exists) return; + if (!_processorOptions.DeleteInboxFile) return; + + _logger.LogDebug("Processor: Deleting original file {File} from Inbox {Inbox}...", + file.FullName, _processorOptions.Inbox); + + _fileSystem.File.Delete(file.FullName); + } + private void ApplyRulesToFile(CsvFile csvFile) { if (_processorOptions.Rules == null || !_processorOptions.Rules.Any()) { _logger.LogWarning("Processor: Cannot process file {File} because there are no rules defined", - csvFile.FileName); + csvFile.OriginalFileName); return; } @@ -111,7 +127,7 @@ private void ApplyRuleToRow(CsvRow row, Rule rule, CsvFile file) if (!MeetsRowConditions(row, rule, file)) return; _logger.LogTrace("Processor: File {File} meets rule at index {RuleIndex}. Applying rule...", - file.FileName, _processorOptions.Rules.IndexOf(rule)); + file.OriginalFileName, _processorOptions.Rules.IndexOf(rule)); foreach (var (columnName, fieldValue) in rule.Steps) { @@ -132,7 +148,7 @@ private bool MeetsRowConditions(CsvRow row, Rule rule, CsvFile file) { foreach (var condition in rule.Conditions) { - var field = row.Fields.FirstOrDefault(field => field.FieldName == condition.Field); + var field = row.Fields.FirstOrDefault(field => field.Name == condition.Field); if (field == null) { _logger.LogTrace(