diff --git a/Deploy/buildlpx.cmd b/Deploy/buildlpx.cmd index 55a3cc5..e9eba72 100644 --- a/Deploy/buildlpx.cmd +++ b/Deploy/buildlpx.cmd @@ -1,4 +1,4 @@ -@set version=6.7.0 +@set version=6.8.0 @set zip="%ProgramFiles%\7-Zip\7z.exe" @set output="CsvLINQPadDriver.%version%.lpx6" @@ -6,7 +6,7 @@ del %output% set releaseDir=..\bin\Release\netcoreapp3.1 -%zip% a -tzip -mx=9 "%output%" header.xml %releaseDir%\*.dll %releaseDir%\*Connection.png ..\README.md +%zip% a -tzip -mx=9 "%output%" header.xml %releaseDir%\*.dll %releaseDir%\*Connection.png ..\README.md ..\LICENSE @echo Package %output% created. @pause diff --git a/README.md b/README.md index 5aab1d1..b254d93 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ * [Usage](#usage) * [Configuration Options](#configuration-options) * [General](#general) + * [Files](#files) * [Format](#format) * [Memory](#memory) * [Generation](#generation) @@ -145,21 +146,28 @@ CSV context can be added to LINQPad 6 same way as any other context. - Click `Add connection`. - Select `CSV Context Driver` and click `Next`. -- Enter CSV file names or Drag&Drop (Ctrl adds files) from Explorer. Optionally configure other options. +- Enter CSV file names or Drag&Drop (`Ctrl` adds files) from Explorer. Optionally configure other options. - Query your data. ## Configuration Options ## ### General ### -- **CSV files** - list of CSV files and directories. Type one file/dir per line or Drag&Drop (Ctrl adds files) from explorer. Supports special wildcards: `*` and `**`. +- **CSV files** - list of CSV files and directories. Type one file/dir per line or Drag&Drop (`Ctrl` adds files) from explorer. Supports special wildcards: `*` and `**`. - `c:\x\*.csv` - all files in folder `c:\x`. - `c:\x\**.csv` - all files in folder `c:\x` and all sub-directories. +- No BOM encoding - specifies encoding for files without [BOM](https://en.wikipedia.org/wiki/Byte_order_mark). `UTF-8` is default. + +### Files ### + +- Order by - specifies files sort order. Affects similar files order. + ### Format ### - CSV separator - character used to separate columns in files. Can be `,`, `\t`, etc. Auto-detected if empty. - Ignore files with invalid format - files with strange content not similar to CSV format will be ignored. +- Allow comments - lines starting with `#` will be ignored. ### Memory ### @@ -170,7 +178,7 @@ CSV context can be added to LINQPad 6 same way as any other context. ### Generation ### -- Generate single class for similar files - single class will be generated for similar files which allows to query them as single one. Might not work well for files with relations. +- Generate single class for similar files - single class will be generated for similar files which allows to query them as a single one. Might not work well for files with relations. - String comparison - string comparison for `Equals` and `GetHashCode` methods. ### Relations ### @@ -261,7 +269,7 @@ string this[int index] { get; set; } string this[string index] { get; set; } ``` -See below. +See [properties access](#properties-access) below. > Relations are not participated. @@ -299,7 +307,7 @@ Authors.First() ### Extension Methods ### -Driver provides extension methods for converting string to nullable types: +Driver provides extension methods for converting string to `T?`. `CultureInfo.InvariantCulture` is used by default. ```csharp // Bool. @@ -311,6 +319,9 @@ int? ToInt(CultureInfo? cultureInfo = null); // Long. long? ToLong(CultureInfo? cultureInfo = null); +// Float. +float? ToFloat(CultureInfo? cultureInfo = null); + // Double. double? ToDouble(CultureInfo? cultureInfo = null); @@ -353,8 +364,9 @@ TimeSpan? ToTimeSpan( ## Known Issues ## +- Default encoding for files without BOM is UTF-8. - Some strange Unicode characters in column names may cause errors in generated data context source code. -- Writing changed objects back to CSV is not directly supported, there is no `SubmitChanges()` . But you can use LINQPad's `Util.WriteCsv`. +- Writing changed objects back to CSV is not directly supported, there is no `SubmitChanges()`. But you can use LINQPad's `Util.WriteCsv`. - Similar files single class generation does not detect relations correctly. However, you can query over related multiple files. ## Authors ## diff --git a/Src/CsvLINQPadDriver/CodeGen/CsvCSharpCodeGenerator.cs b/Src/CsvLINQPadDriver/CodeGen/CsvCSharpCodeGenerator.cs index e65d4a7..a73ab5b 100644 --- a/Src/CsvLINQPadDriver/CodeGen/CsvCSharpCodeGenerator.cs +++ b/Src/CsvLINQPadDriver/CodeGen/CsvCSharpCodeGenerator.cs @@ -33,24 +33,27 @@ private CsvCSharpCodeGenerator(string contextNameSpace, string contextTypeName, _properties = properties; } + public record TypeCodeResult(string TypeName, string Code, string CodeName, string FilePath); + public record Result(string Code, IReadOnlyCollection> CodeGroups); + // ReSharper disable once RedundantAssignment - public static (string Code, IReadOnlyCollection> CodeGroups) - GenerateCode(CsvDatabase db, ref string nameSpace, ref string typeName, ICsvDataContextDriverProperties props) => + public static Result GenerateCode(CsvDatabase db, ref string nameSpace, ref string typeName, ICsvDataContextDriverProperties props) => new CsvCSharpCodeGenerator(nameSpace, typeName = DefaultContextTypeName, props).GenerateSrcFile(db); - private (string, IReadOnlyCollection>) GenerateSrcFile(CsvDatabase csvDatabase) + private Result GenerateSrcFile(CsvDatabase csvDatabase) { var csvTables = csvDatabase.Tables; var groups = csvTables .Select(table => GenerateTableRowDataTypeClass(table, _properties.HideRelationsFromDump, _properties.StringComparison)) - .GroupBy(typeCode => typeCode.Type) + .GroupBy(typeCode => typeCode.TypeName) .ToImmutableList(); - return ($@"using System; -using System.Linq; + return new Result($@"using System; using System.Collections.Generic; +using CsvLINQPadDriver; + namespace {_contextNameSpace} {{ /// CSV Data Context @@ -67,6 +70,8 @@ public class {_contextTypeName} : {typeof(CsvDataContextBase).GetCodeTypeClassNa {GetBoolConst(_properties.IsStringInternEnabled)}, {GetBoolConst(_properties.IsCacheEnabled)}, {table.CsvSeparator.AsValidCSharpCode()}, + {nameof(NoBomEncoding)}.{_properties.NoBomEncoding}, + {GetBoolConst(_properties.AllowComments)}, {table.FilePath.AsValidCSharpCode()}, new {typeof(CsvColumnInfoList<>).GetCodeTypeClassName(GetClassName(table))} {{ {string.Join(string.Empty, table.Columns.Select(c => $@"{{ {c.Index}, x => x.{c.CodeName} }}, "))} @@ -88,12 +93,12 @@ static string GetBoolConst(bool val) => val ? "true" : "false"; } - private static (string Type, string Code, string CodeName) GenerateTableRowDataTypeClass(CsvTable table, bool hideRelationsFromDump, StringComparison stringComparison) + private static TypeCodeResult GenerateTableRowDataTypeClass(CsvTable table, bool hideRelationsFromDump, StringComparison stringComparison) { var className = GetClassName(table); var properties = table.Columns.Select(GetPropertyName).ToImmutableList(); - return (className, $@" + return new TypeCodeResult(className, $@" public sealed record {className} : {typeof(ICsvRowBase).GetCodeTypeClassName()} {{{string.Join(string.Empty, table.Columns.Select(csvColumn => $@" public string {GetPropertyName(csvColumn)} {{ get; set; }}"))} @@ -106,7 +111,7 @@ public sealed record {className} : {typeof(ICsvRowBase).GetCodeTypeClassName()} [{typeof(HideFromDumpAttribute).GetCodeTypeClassName()}]" : string.Empty)} public IEnumerable<{csvRelation.TargetTable.GetCodeRowClassName()}> {csvRelation.CodeName} {{ get; set; }}") )} - }}", table.CodeName!); + }}", table.CodeName!, table.FilePath); static string GetPropertyName(ICsvNames csvColumn) => csvColumn.CodeName!; diff --git a/Src/CsvLINQPadDriver/CodeGen/CsvTableBase.cs b/Src/CsvLINQPadDriver/CodeGen/CsvTableBase.cs index be62790..cec62a4 100644 --- a/Src/CsvLINQPadDriver/CodeGen/CsvTableBase.cs +++ b/Src/CsvLINQPadDriver/CodeGen/CsvTableBase.cs @@ -21,21 +21,26 @@ public abstract class CsvTableBase : CsvTableBase, IEnumerable { private static CsvRowMappingBase? _cachedCsvRowMappingBase; - private char CsvSeparator { get; } + private readonly char _csvSeparator; + private readonly NoBomEncoding _noBomEncoding; + private readonly bool _allowComments; - protected string FilePath { get; } + protected readonly string FilePath; - protected CsvTableBase(bool isStringInternEnabled, char csvSeparator, string filePath, IEnumerable propertiesInfo, Action relationsInit) + protected CsvTableBase(bool isStringInternEnabled, char csvSeparator, NoBomEncoding noBomEncoding, bool allowComments, string filePath, IEnumerable propertiesInfo, Action relationsInit) : base(isStringInternEnabled) { - CsvSeparator = csvSeparator; + _csvSeparator = csvSeparator; + _noBomEncoding = noBomEncoding; + _allowComments = allowComments; + FilePath = filePath; _cachedCsvRowMappingBase ??= new CsvRowMappingBase(propertiesInfo, relationsInit); } protected IEnumerable ReadData() => - FileUtils.CsvReadRows(FilePath, CsvSeparator, IsStringInternEnabled, _cachedCsvRowMappingBase!); + FileUtils.CsvReadRows(FilePath, _csvSeparator, IsStringInternEnabled, _noBomEncoding, _allowComments, _cachedCsvRowMappingBase!); // ReSharper disable once UnusedMember.Global public abstract IEnumerable WhereIndexed(Func getProperty, string propertyName, params string[] values); diff --git a/Src/CsvLINQPadDriver/CodeGen/CsvTableEnumerable.cs b/Src/CsvLINQPadDriver/CodeGen/CsvTableEnumerable.cs index f4831c4..9599f7d 100644 --- a/Src/CsvLINQPadDriver/CodeGen/CsvTableEnumerable.cs +++ b/Src/CsvLINQPadDriver/CodeGen/CsvTableEnumerable.cs @@ -7,8 +7,8 @@ namespace CsvLINQPadDriver.CodeGen internal class CsvTableEnumerable : CsvTableBase where TRow : ICsvRowBase, new() { - public CsvTableEnumerable(bool isStringInternEnabled, char csvSeparator, string filePath, IEnumerable propertiesInfo, Action relationsInit) - : base(isStringInternEnabled, csvSeparator, filePath, propertiesInfo, relationsInit) + public CsvTableEnumerable(bool isStringInternEnabled, char csvSeparator, NoBomEncoding noBomEncoding, bool allowComments, string filePath, IEnumerable propertiesInfo, Action relationsInit) + : base(isStringInternEnabled, csvSeparator, noBomEncoding, allowComments, filePath, propertiesInfo, relationsInit) { } diff --git a/Src/CsvLINQPadDriver/CodeGen/CsvTableFactory.cs b/Src/CsvLINQPadDriver/CodeGen/CsvTableFactory.cs index c33aee7..d867706 100644 --- a/Src/CsvLINQPadDriver/CodeGen/CsvTableFactory.cs +++ b/Src/CsvLINQPadDriver/CodeGen/CsvTableFactory.cs @@ -10,12 +10,15 @@ public static CsvTableBase CreateTable( bool isStringInternEnabled, bool isCacheEnabled, char csvSeparator, + NoBomEncoding noBomEncoding, + bool allowComments, string filePath, IEnumerable propertiesInfo, Action relationsInit) where TRow : ICsvRowBase, new() => isCacheEnabled - ? (CsvTableBase)new CsvTableList(isStringInternEnabled, csvSeparator, filePath, propertiesInfo, relationsInit) - : new CsvTableEnumerable(isStringInternEnabled, csvSeparator, filePath, propertiesInfo, relationsInit); + // ReSharper disable once RedundantCast + ? (CsvTableBase)new CsvTableList(isStringInternEnabled, csvSeparator, noBomEncoding, allowComments, filePath, propertiesInfo, relationsInit) + : new CsvTableEnumerable(isStringInternEnabled, csvSeparator, noBomEncoding, allowComments, filePath, propertiesInfo, relationsInit); } } \ No newline at end of file diff --git a/Src/CsvLINQPadDriver/CodeGen/CsvTableList.cs b/Src/CsvLINQPadDriver/CodeGen/CsvTableList.cs index f120ac1..8b32dec 100644 --- a/Src/CsvLINQPadDriver/CodeGen/CsvTableList.cs +++ b/Src/CsvLINQPadDriver/CodeGen/CsvTableList.cs @@ -12,8 +12,8 @@ internal class CsvTableList : CsvTableBase, IList private readonly IDictionary> _indices = new Dictionary>(); private readonly Lazy> _dataCache; - public CsvTableList(bool isStringInternEnabled, char csvSeparator, string filePath, IEnumerable propertiesInfo, Action relationsInit) - : base(isStringInternEnabled, csvSeparator, filePath, propertiesInfo, relationsInit) => + public CsvTableList(bool isStringInternEnabled, char csvSeparator, NoBomEncoding noBomEncoding, bool allowComments, string filePath, IEnumerable propertiesInfo, Action relationsInit) + : base(isStringInternEnabled, csvSeparator, noBomEncoding, allowComments, filePath, propertiesInfo, relationsInit) => _dataCache = new Lazy>(() => ReadData().Cache($"{typeof(TRow).Name}:{FilePath}")); private IList DataCache => @@ -30,7 +30,7 @@ public override IEnumerable WhereIndexed(Func getProperty, s _indices.Add(propertyName, propertyIndex); } - var result = values.SelectMany(value => propertyIndex[value]); + var result = values.SelectMany(value => propertyIndex![value]); return values.Length > 1 ? result.Distinct() : result; } diff --git a/Src/CsvLINQPadDriver/ConnectionDialog.xaml b/Src/CsvLINQPadDriver/ConnectionDialog.xaml index 4e3bcf8..c54d16b 100644 --- a/Src/CsvLINQPadDriver/ConnectionDialog.xaml +++ b/Src/CsvLINQPadDriver/ConnectionDialog.xaml @@ -2,22 +2,28 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:system="clr-namespace:System;assembly=System.Runtime" + xmlns:wpf="clr-namespace:CsvLINQPadDriver.Wpf" Title="CSV Files Connection" Icon="Connection.ico" Background="{x:Static SystemColors.ControlBrush}" - Width="640" - Height="520" + Width="680" + Height="580" WindowStartupLocation="CenterScreen" FocusManager.FocusedElement="{Binding ElementName=FilesTextBox}" Loaded="ConnectionDialog_OnLoaded"> + + + + + @@ -34,13 +40,30 @@ -