Skip to content

Commit

Permalink
Added folders multiselection.
Browse files Browse the repository at this point in the history
Report ignored invalid files.
Set default string comparison to Ordinal.
  • Loading branch information
Ivan Ivon committed Jul 29, 2021
1 parent c8e593f commit 7325e02
Show file tree
Hide file tree
Showing 22 changed files with 299 additions and 135 deletions.
3 changes: 2 additions & 1 deletion Deploy/buildlpx.cmd
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
@echo off

set version=6.16.0
set version=6.17.0
set fileName=CsvLINQPadDriver.%version%.lpx

set zip="%ProgramFiles%\7-Zip\7z.exe"
Expand Down Expand Up @@ -40,6 +40,7 @@ header.xml ^
%folder%\CsvLINQPadDriver.dll ^
%folder%\Humanizer.dll ^
%folder%\Microsoft.WindowsAPICodePack.dll ^
%folder%\Microsoft.WindowsAPICodePack.Shell.dll ^
%folder%\UnicodeCharsetDetector.dll ^
%folder%\UtfUnknown.dll ^
%additional%
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ CSV files connection can be added to LINQPad 6/5 the same way as any other conne

### CSV Files ###

* CSV files: list of CSV files and folders. Can be added via files/folder dialogs, dedicated hotkeys, by typing one file/folder per line or by Drag&drop (`Ctrl` adds files, `Alt` toggles `*` and `**` masks). Wildcards `?` and `*` are supported; `**.csv` searches in folder and its sub-folders.
* CSV files: list of CSV files and folders. Can be added via files/folder dialogs, context menu, hotkeys, by typing one file/folder per line or by Drag&drop (`Ctrl` adds files, `Alt` toggles `*` and `**` masks). Wildcards `?` and `*` are supported; `**.csv` searches in folder and its sub-folders.
* `c:\Books\Books?.csv`: `Books.csv`, `Books1.csv`, etc. files in folder `c:\Books`
* `c:\Books\*.csv`: all `*.csv` files in folder `c:\Books`
* `c:\Books\**.csv`: all `*.csv` files in folder `c:\Books` and its sub-folders.
Expand Down Expand Up @@ -435,6 +435,7 @@ TimeSpan? ToTimeSpan(
* [UnicodeCharsetDetector](https://github.com/i2van/UnicodeCharsetDetector)
* [UTF.Unknown](https://www.nuget.org/packages/UTF.Unknown)
* [Windows API Code Pack](https://github.com/contre/Windows-API-Code-Pack-1.1)
* [Windows API Code Pack Shell](https://github.com/contre/Windows-API-Code-Pack-1.1)
## License ##

Expand Down
1 change: 0 additions & 1 deletion Src/CsvLINQPadDriver/CodeGen/CsvCSharpCodeGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@

using CsvLINQPadDriver.DataDisplay;
using CsvLINQPadDriver.DataModel;
using CsvLINQPadDriver.Extensions;

#if NETCOREAPP
using System.Collections.Immutable;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
using System.Collections;
using System.Collections.Generic;

namespace CsvLINQPadDriver.Extensions
namespace CsvLINQPadDriver.CodeGen
{
public class LazyEnumerable<T> : IEnumerable<T>
{
Expand Down
121 changes: 69 additions & 52 deletions Src/CsvLINQPadDriver/ConnectionDialog.xaml

Large diffs are not rendered by default.

147 changes: 119 additions & 28 deletions Src/CsvLINQPadDriver/ConnectionDialog.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

using CsvLINQPadDriver.Extensions;
using CsvLINQPadDriver.Wpf;
using CsvLINQPadDriver.Wpf.Extensions;

namespace CsvLINQPadDriver
{
Expand All @@ -26,27 +27,116 @@ internal partial class ConnectionDialog
Visibility.Collapsed;
#endif

public static readonly RoutedUICommand AddFilesCommand = new();
public static readonly RoutedUICommand AddFolderCommand = new();
public static readonly RoutedUICommand AddFolderAndItsSubfoldersCommand = new();
public static readonly RoutedUICommand PasteFromClipboardForFolderAndItsSubfoldersAndProceedCommand = new();
public static readonly RoutedUICommand PasteFromClipboardForFolderAndProceedCommand = new();
public static readonly RoutedUICommand ClearCommand = new();
public static readonly RoutedUICommand HelpCommand = new();
public static readonly RoutedUICommand BrowseCommand = new();
public static readonly RoutedUICommand CtrlLeftClickCommand = new();
public static readonly RoutedUICommand PasteWithFolderAndItsSubfoldersCommand = new();
public static readonly RoutedUICommand WrapFilesTextCommand = new();
// ReSharper disable StringLiteralTypo
public static readonly RoutedUICommandEx AddFilesCommand = new()
{
Text = "Add f_iles",
InputGestureText = "Ctrl+O",
ToolTip = "Add files"
};

public static readonly KeyGesture AddFilesCommandKeyGesture = AddFilesCommand.InputGestureAsKeyGesture;

public static readonly RoutedUICommandEx AddFoldersCommand = new()
{
Text = "Add f_olders",
InputGestureText = "Ctrl+Shift+O",
ToolTip = "Add folders"
};

public static readonly KeyGesture AddFoldersCommandKeyGesture = AddFoldersCommand.InputGestureAsKeyGesture;

public static readonly RoutedUICommandEx AddFoldersWithSubfoldersCommand = new()
{
Text = "Add folders with _sub-folders",
InputGestureText = "Ctrl+Shift+Alt+O",
ToolTip = "Add folders with sub-folders"
};

public static readonly KeyGesture AddFoldersWithSubfoldersCommandKeyGesture = AddFoldersWithSubfoldersCommand.InputGestureAsKeyGesture;

public static readonly RoutedUICommandEx ClearCommand = new()
{
Text = "Clea_r",
InputGestureText = "Ctrl+L",
ToolTip = "Clear"
};

public static readonly KeyGesture ClearCommandKeyGesture = ClearCommand.InputGestureAsKeyGesture;

public static readonly RoutedUICommandEx PasteFoldersWithSubfoldersCommand = new()
{
Text = "Past_e (append **.csv to folders)",
InputGestureText = "Ctrl+Alt+V"
};

public static readonly KeyGesture PasteFoldersWithSubfoldersCommandKeyGesture = PasteFoldersWithSubfoldersCommand.InputGestureAsKeyGesture;

public static readonly RoutedUICommandEx PasteFromClipboardFoldersAndProceedCommand = new()
{
Text = "Clear, paste (append *.csv to folders) a_nd proceed",
InputGestureText = "Ctrl+Shift+V"
};

public static readonly KeyGesture PasteFromClipboardFoldersAndProceedCommandKeyGesture = PasteFromClipboardFoldersAndProceedCommand.InputGestureAsKeyGesture;

public static readonly RoutedUICommandEx PasteFromClipboardFoldersWithSubfoldersAndProceedCommand = new()
{
Text = "Clear, paste (append **.csv to folders) an_d proceed",
InputGestureText = "Ctrl+Shift+Alt+V"
};

public static readonly KeyGesture PasteFromClipboardFoldersWithSubfoldersAndProceedCommandKeyGesture = PasteFromClipboardFoldersWithSubfoldersAndProceedCommand.InputGestureAsKeyGesture;

public static readonly RoutedUICommandEx WrapFilesTextCommand = new()
{
Text = "Word _wrap",
InputGestureText = "Ctrl+W"
};

public static readonly KeyGesture WrapFilesTextCommandKeyGesture = WrapFilesTextCommand.InputGestureAsKeyGesture;

public static readonly RoutedUICommandEx CtrlLeftClickCommand = new()
{
InputGestureText = "Ctrl+LeftClick"
};

public static readonly MouseGesture CtrlLeftClickCommandMouseGesture = CtrlLeftClickCommand.InputGestureAsMouseGesture;

public static readonly RoutedUICommandEx BrowseCommand = new()
{
Text = "_Browse",
InputGestureText = "Ctrl+F",
ToolTip = $"Browse file or folder at the current line ({{0}}) or any line ({CtrlLeftClickCommand.InputGestureText})"
};

public static readonly KeyGesture BrowseCommandKeyGesture = BrowseCommand.InputGestureAsKeyGesture;

public static readonly RoutedUICommandEx HelpCommand = new()
{
InputGestureText = "F1"
};

public static readonly KeyGesture HelpCommandKeyGesture = HelpCommand.InputGestureAsKeyGesture;

public static readonly string ConnectionHelp = $"CSV Files Connection help ({HelpCommand.InputGestureText} for driver help) on GitHub";
// ReSharper restore StringLiteralTypo

private static T IfWin10<T>(T ifTrue, T ifFalse) =>
Environment.OSVersion.Version.Major >= 10 ? ifTrue : ifFalse;

public static readonly string WrapText = IfWin10("⭹", "Wrap");
public static readonly string UnwrapText = IfWin10("⭲", "Unwrap");

private static string GetTurnWrapText(bool on) =>
$"Turn {(on ? "on" : "off")} word wrap ({WrapFilesTextCommand.InputGestureText})";

public static readonly string TurnOnWrapText = GetTurnWrapText(true);
public static readonly string TurnOffWrapText = GetTurnWrapText(false);

public static readonly string WildcardsToolTip = $"Type one file/folder per line. Wildcards ? and * are supported; {FileExtensions.DefaultRecursiveMask} searches in folder and its sub-folders";

private bool _addFolderAndItsSubfoldersDialogOpened;
private bool _addFoldersWithSubfoldersDialogOpened;

public ConnectionDialog(ICsvDataContextDriverProperties csvDataContextDriverProperties)
{
Expand Down Expand Up @@ -157,13 +247,13 @@ private void FilesTextBox_DragDrop(object sender, DragEventArgs e)

var enrichedFiles = GetEnrichedPathsFromUserInput(
(e.Data.GetData(DataFormats.FileDrop, true) ?? e.Data.GetData(DataFormats.StringFormat))!,
IsDragAndDropInFolderAndItsSubfoldersMode() ^ _addFolderAndItsSubfoldersDialogOpened);
IsDragAndDropInFoldersWithSubfoldersMode() ^ _addFoldersWithSubfoldersDialogOpened);

AppendFiles(enrichedFiles);

e.Handled = true;

bool IsDragAndDropInFolderAndItsSubfoldersMode() =>
bool IsDragAndDropInFoldersWithSubfoldersMode() =>
e.KeyStates.HasFlag(DragDropKeyStates.AltKey);
}

Expand All @@ -188,7 +278,7 @@ private void FilesTextBox_OnPreviewExecuted(object sender, ExecutedRoutedEventAr
}
}

private void PasteWithFolderAndItsSubfoldersCommand_OnExecuted(object sender, ExecutedRoutedEventArgs e) =>
private void PasteFoldersWithSubfoldersCommand_OnExecuted(object sender, ExecutedRoutedEventArgs e) =>
InsertFilesFromClipboard(true);

private void InsertFilesFromClipboard(bool folderAndItsSubfolders)
Expand Down Expand Up @@ -224,10 +314,10 @@ private void ConnectionDialog_OnLoaded(object sender, RoutedEventArgs e) =>
private void CommandBinding_OnCanAlwaysExecute(object sender, CanExecuteRoutedEventArgs e) =>
e.CanExecute = true;

private void PasteFromClipboardForFolderAndProceedCommandBinding_OnExecuted(object sender, ExecutedRoutedEventArgs e) =>
private void PasteFromClipboardFoldersAndProceedCommandBinding_OnExecuted(object sender, ExecutedRoutedEventArgs e) =>
PasteAndGo(false);

private void PasteFromClipboardForFolderAndItsSubfoldersAndProceedCommandBinding_OnExecuted(object sender, ExecutedRoutedEventArgs e) =>
private void PasteFromClipboardFoldersWithSubfoldersAndProceedCommandBinding_OnExecuted(object sender, ExecutedRoutedEventArgs e) =>
PasteAndGo(true);

private string GetFilesTextFromClipboard(bool folderAndItsSubfolders) =>
Expand Down Expand Up @@ -266,17 +356,17 @@ private void AddFilesCommandBinding_OnExecuted(object sender, ExecutedRoutedEven
{
var fileType = TypedDataContext.FileType;

if ("Add Files".TryOpenFile(FileExtensions.Filter, fileType.GetExtension(), out var fileName, fileType.GetFilterIndex()))
if (this.TryOpenFiles("Add Files", FileExtensions.Filter, fileType.GetExtension(), out var files, fileType.GetFilterIndex()))
{
AppendFiles(fileName);
AppendFiles(files);
}
}

private void AddFolderCommandBinding_OnExecuted(object sender, ExecutedRoutedEventArgs e) =>
AddFolder(false);
private void AddFoldersCommandBinding_OnExecuted(object sender, ExecutedRoutedEventArgs e) =>
AddFolders(false);

private void AddFolderAndItsSubfoldersCommandBinding_OnExecuted(object sender, ExecutedRoutedEventArgs e) =>
AddFolder(true);
private void AddFoldersWithSubfoldersCommandBinding_OnExecuted(object sender, ExecutedRoutedEventArgs e) =>
AddFolders(true);

private void ClearCommandBinding_OnExecuted(object sender, ExecutedRoutedEventArgs e) =>
FilesTextBox.Clear();
Expand Down Expand Up @@ -366,16 +456,17 @@ bool TryGetLineAtCaret(out string line) =>
!string.IsNullOrWhiteSpace(line = FilesTextBox.GetLineTextAtCaretIndex().GetInlineCommentContent().Trim());
}

private void AddFolder(bool withSubfolders)
private void AddFolders(bool withSubfolders)
{
_addFolderAndItsSubfoldersDialogOpened = withSubfolders;
_addFoldersWithSubfoldersDialogOpened = withSubfolders;

if ($"Add Folder{(withSubfolders ? " and Its Sub-folders" : string.Empty)}".TryBrowseForFolder(out var folder))
if (this.TryBrowseForFolders($"Add Folders{(withSubfolders ? " with Sub-folders" : string.Empty)}", out var folders))
{
AppendFiles(Path.Combine(folder, GetFolderFilesMask(withSubfolders)));
var folderFilesMask = GetFolderFilesMask(withSubfolders);
AppendFiles(folders.Select(folder => Path.Combine(folder, folderFilesMask)).ToArray());
}

_addFolderAndItsSubfoldersDialogOpened = false;
_addFoldersWithSubfoldersDialogOpened = false;
}

private string GetFolderFilesMask(bool withSubfolders) =>
Expand Down
5 changes: 3 additions & 2 deletions Src/CsvLINQPadDriver/CsvDataContextDriver.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Windows.Input;

using Humanizer;

Expand Down Expand Up @@ -58,8 +59,8 @@ public override bool ShowConnectionDialog(IConnectionInfo cxInfo, ConnectionDial
properties.Files =
$"{FileExtensions.InlineComment} Drag&drop here (from add files/folder dialogs as well). Ctrl adds files. Alt toggles * and ** masks.".JoinNewLine(
$"{FileExtensions.InlineComment} {ConnectionDialog.WildcardsToolTip}.",
$"{FileExtensions.InlineComment} Ctrl+V (Ctrl+Alt+V) pastes from clipboard, appends {FileExtensions.DefaultMask} ({FileExtensions.DefaultRecursiveMask}) to folders.",
$"{FileExtensions.InlineComment} Ctrl+Shift+V (Ctrl+Shift+Alt+V) clears, pastes from clipboard, appends {FileExtensions.DefaultMask} ({FileExtensions.DefaultRecursiveMask}) to folders and proceeds.",
$"{FileExtensions.InlineComment} {((KeyGesture)ApplicationCommands.Paste.InputGestures[0]).DisplayString} ({ConnectionDialog.PasteFoldersWithSubfoldersCommand.InputGestureText}) pastes from clipboard, appends {FileExtensions.DefaultMask} ({FileExtensions.DefaultRecursiveMask}) to folders.",
$"{FileExtensions.InlineComment} {ConnectionDialog.PasteFromClipboardFoldersAndProceedCommand.InputGestureText} ({ConnectionDialog.PasteFromClipboardFoldersWithSubfoldersAndProceedCommand.InputGestureText}) clears, pastes from clipboard, appends {FileExtensions.DefaultMask} ({FileExtensions.DefaultRecursiveMask}) to folders and proceeds.",
string.Empty, string.Empty);
}

Expand Down
2 changes: 1 addition & 1 deletion Src/CsvLINQPadDriver/CsvDataContextDriverProperties.cs
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ public bool ShowSameFilesNonGrouped

public StringComparison StringComparison
{
get => GetValue(StringComparison.InvariantCulture);
get => GetValue(StringComparison.Ordinal);
set => SetValue(value);
}

Expand Down
3 changes: 2 additions & 1 deletion Src/CsvLINQPadDriver/DataModel/CsvDataModelGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ IEnumerable<CsvTable> CreateTables()
if (_csvDataContextDriverProperties.IgnoreInvalidFiles &&
!file.IsCsvFormatValid(csvSeparator, noBomEncoding, allowComments, ignoreBadData, autoDetectEncoding))
{
exceptions.Add(file, "has invalid CSV format");
continue;
}

Expand Down Expand Up @@ -114,7 +115,7 @@ IEnumerable<CsvTable> CreateTables()

if (!columns.Any())
{
exceptions.Add(file, "has no columns.");
exceptions.Add(file, "has no columns");
continue;
}

Expand Down
9 changes: 6 additions & 3 deletions Src/CsvLINQPadDriver/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
<Project>
<PropertyGroup>
<Version>6.16.0</Version>
<PackageReleaseNotes>Added more encodings.</PackageReleaseNotes>
<Version>6.17.0</Version>
<PackageReleaseNotes>Added folders multiselection.
Reported ignored invalid files.
Set default string comparison to Ordinal.
</PackageReleaseNotes>
</PropertyGroup>

<PropertyGroup>
Expand All @@ -14,7 +17,6 @@

<PropertyGroup>
<UseWpf>true</UseWpf>
<UseWindowsForms>true</UseWindowsForms>
<Deterministic>true</Deterministic>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<LangVersion>latest</LangVersion>
Expand All @@ -39,6 +41,7 @@
</PackageReference>
<PackageReference Include="LINQPad.Reference" Version="1.1.0" />
<PackageReference Include="Microsoft-WindowsAPICodePack-Core" Version="1.1.4" />
<PackageReference Include="Microsoft-WindowsAPICodePack-Shell" Version="1.1.4" />
<PackageReference Include="UnicodeCharsetDetector.Standard" Version="1.1.1" />
<PackageReference Include="UTF.Unknown" Version="2.3.0" />
</ItemGroup>
Expand Down
4 changes: 2 additions & 2 deletions Src/CsvLINQPadDriver/Extensions/FileExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ public static string GetLongestCommonPrefixPath(this IEnumerable<string> paths)
var filePaths = pathsValid.FirstOrDefault()?.Split(Path.DirectorySeparatorChar) ?? Array.Empty<string>();

var directorySeparator = Path.DirectorySeparatorChar
#if NETFRAMEWORK
#if !NETCOREAPP
.ToString()
#endif
;
Expand Down Expand Up @@ -392,7 +392,7 @@ public static void Add(this ICollection<Exception>? exceptions, string file, Exc
exceptions?.Add(file, $"processing failed: {exception.Message}");

public static void Add(this ICollection<Exception>? exceptions, string file, string message) =>
exceptions?.Add(new Exception($"'{file}' {message}"));
exceptions?.Add(new Exception($"'{file}' {message}".AppendDot()));

private static IEnumerable<string> EnumFiles(string path, ICollection<Exception>? exceptions = null)
{
Expand Down
9 changes: 9 additions & 0 deletions Src/CsvLINQPadDriver/Extensions/TextExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

using Humanizer;

Expand All @@ -18,5 +19,13 @@ public static string Pluralize<T>(this IReadOnlyCollection<T> collection, string
collection.Count > 1
? fallback ?? what.Pluralize()
: what;

public static string AppendDot(this string str) =>
Regex.IsMatch(str, @"\p{P}\s*$")
? str
: str + ".";

public static string ReplaceHotKeyChar(this string str, string? newChar = null) =>
str.Replace("_", newChar);
}
}
Loading

0 comments on commit 7325e02

Please sign in to comment.