Skip to content

Commit

Permalink
feat: add basic implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
rdavisau committed Apr 20, 2019
1 parent af4bb00 commit 036fb41
Show file tree
Hide file tree
Showing 5 changed files with 279 additions and 0 deletions.
29 changes: 29 additions & 0 deletions src/DumpEditable/DumpEditable.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net47</TargetFramework>
<AssemblyName>LINQPad.DumpEditable</AssemblyName>
<RootNamespace>LINQPad.DumpEditable</RootNamespace>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<Version>0.0.1</Version>
<Authors>Ryan Davis</Authors>
<Product>DumpEditable.LINQPad</Product>
<Description>Extensible inline editor output for LINQPad</Description>
<Copyright>(c) Ryan Davis 2019</Copyright>
<PackageLicenseExpression></PackageLicenseExpression>
<PackageProjectUrl>https://github.com/rdavisau/linqpad-dump-editable</PackageProjectUrl>
<RepositoryUrl>https://github.com/rdavisau/linqpad-dump-editable</RepositoryUrl>
<PackageTags>LINQPad, Dump, editor</PackageTags>
<PackageId>DumpEditable.LINQPad</PackageId>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="LINQPad" Version="5.26.1" />
</ItemGroup>

<ItemGroup>
<Reference Include="Microsoft.VisualBasic" />
</ItemGroup>

</Project>
128 changes: 128 additions & 0 deletions src/DumpEditable/EditableDumpContainer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace LINQPad.DumpEditable
{
public partial class EditableDumpContainer<T> : DumpContainer
{
private readonly T _obj;
private readonly bool _failSilently;
private readonly Dictionary<PropertyInfo, Action<T, PropertyInfo, object>> _changeHandlers
= new Dictionary<PropertyInfo, Action<T, PropertyInfo, object>>();

private readonly List<EditorRule> _editorRules = new List<EditorRule>();

public Action OnChanged { get; set; }
public Action<T, PropertyInfo, object> OnPropertyValueChanged { get; set; }

public static void AddGlobalEditorRule(EditorRule rule)
=> GlobalEditorRules.Insert(0, rule);

public void AddChangeHandler<U>(Expression<Func<T, U>> selector,
Action<T, PropertyInfo, U> onChangedAction)
{
var pi = (selector.Body as MemberExpression)?.Member as PropertyInfo;
if (pi is null)
throw new Exception($"Invalid expression passed to {nameof(AddChangeHandler)}");

_changeHandlers[pi] = (obj, p, val) => onChangedAction(obj, p, (U) val);
}

public void AddEditorRule(EditorRule rule)
=> _editorRules.Insert(0, rule);

public EditableDumpContainer(T obj, bool failSilently = false)
{
_obj = obj;
_failSilently = failSilently;

SetContent();
}

private void SetContent()
{
object content = _obj;

try
{
content =
_obj
.GetType()
.GetProperties()
.Select(p =>
new
{
Property = p.Name,
Value = GetEditor(_obj, p)
})
.ToList();
}
catch
{
if (!_failSilently)
throw;
}

Content = content;
}

private object GetEditor(object o, PropertyInfo p)
{
var allRules = Enumerable.Concat(_editorRules, GlobalEditorRules);

foreach (var editor in allRules)
if (editor.Match(o, p))
return editor.Editor(o, p, () =>
{
SetContent();
var newVal = p.GetValue(o);
if (_changeHandlers.TryGetValue(p, out var handler))
handler.Invoke((T)o,p,newVal);
OnPropertyValueChanged?.Invoke(_obj, p, newVal);
OnChanged?.Invoke();;
});

return o;
}
}

public partial class EditableDumpContainer<T>
{
private static readonly List<EditorRule> GlobalEditorRules = GetDefaultEditors();
private static List<EditorRule> GetDefaultEditors() =>
new List<EditorRule>
{
EditorRule.ForEnums(),
EditorRule.ForTypeWithStringBasedEditor<int>(int.TryParse),
EditorRule.ForTypeWithStringBasedEditor<uint>(uint.TryParse),
EditorRule.ForTypeWithStringBasedEditor<short>(short.TryParse),
EditorRule.ForTypeWithStringBasedEditor<ushort>(ushort.TryParse),
EditorRule.ForTypeWithStringBasedEditor<double>(double.TryParse),
EditorRule.ForTypeWithStringBasedEditor<decimal>(decimal.TryParse),
EditorRule.ForTypeWithStringBasedEditor<float>(float.TryParse),
EditorRule.ForTypeWithStringBasedEditor<long>(long.TryParse),
EditorRule.ForTypeWithStringBasedEditor<ulong>(ulong.TryParse),
EditorRule.ForTypeWithStringBasedEditor<DateTime>(DateTime.TryParse),
EditorRule.ForTypeWithStringBasedEditor<DateTimeOffset>(DateTimeOffset.TryParse),
EditorRule.ForTypeWithStringBasedEditor((string input, out string output) =>
{
output = input;
return true;
}),
};

}

public static class EditableDumpContainer
{
public static EditableDumpContainer<T> For<T>(T obj, bool failSilently = false)
=> new EditableDumpContainer<T>(obj, failSilently);
}
}
75 changes: 75 additions & 0 deletions src/DumpEditable/EditorRule.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
using System;
using System.Linq;
using System.Reflection;
using Microsoft.VisualBasic;

namespace LINQPad.DumpEditable
{
public partial class EditorRule
{
public Func<object, PropertyInfo, bool> Match { get; set; }
public Func<object, PropertyInfo, Action, object> Editor { get; set; }
}

public partial class EditorRule
{
public static EditorRule For(Func<object, PropertyInfo, bool> rule,
Func<object, PropertyInfo, Action, object> getEditor)
=> new EditorRule
{
Match = rule,
Editor = getEditor,
};

public static EditorRule ForType<T>(Func<object, PropertyInfo, Action, object> getEditor)
=> new EditorRule
{
Match = (o, info) => info.PropertyType == typeof(T),
Editor = getEditor,
};
public static EditorRule ForEnums() =>
EditorRule.For(
(_, p) => p.PropertyType.IsEnum,
(o, p, c) =>
Util.HorizontalRun(true,
Enumerable.Concat(
new object[] { p.GetValue(o), "[" },
p.PropertyType
.GetEnumValues()
.OfType<object>()
.Select(v => new Hyperlinq(() => { p.SetValue(o, v); c(); }, $"{v}")))
.Concat(new [] { "]" }))
);

public static EditorRule ForTypeWithStringBasedEditor<T>(ParseFunc<string, T, bool> parseFunc)
=> new EditorRule
{
Match = (o, info) => info.PropertyType == typeof(T),
Editor = (o, info, changed) => GetStringInputBasedEditor(o, info, changed, parseFunc)
};

protected static object GetStringInputBasedEditor<TOut>(object o, PropertyInfo p, Action changeCallback, EditorRule.ParseFunc<string, TOut, bool> parseFunc)
{
var currVal = p.GetValue(o);
var desc = currVal != null ? $"{currVal}" : "null";

var change = new Hyperlinq(() =>
{
var newVal = Interaction.InputBox("Set value for " + p.Name, p.Name, $"{currVal}");
var canConvert = parseFunc(newVal, out var output);
if (!canConvert)
return;
p.SetValue(o, output);
changeCallback?.Invoke();
}, desc);

return Util.HorizontalRun(true, change);
}

public delegate V ParseFunc<T, U, V>(T input, out U output);
}
}
22 changes: 22 additions & 0 deletions src/DumpEditable/Extensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace LINQPad.DumpEditable
{
public static class Extensions
{
public static T DumpEditable<T>(this T obj, bool failSilently = false)
=> DumpEditable(obj, out _, failSilently);

public static T DumpEditable<T>(this T obj, out EditableDumpContainer<T> container, bool failSilently = false)
{
container = new EditableDumpContainer<T>(obj, failSilently);
container.Dump();

return obj;
}
}
}
25 changes: 25 additions & 0 deletions src/linqpad-dump-editable.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.28729.10
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DumpEditable", "DumpEditable\DumpEditable.csproj", "{67C4F4C2-21AB-4A15-8419-6F563A3DE873}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{67C4F4C2-21AB-4A15-8419-6F563A3DE873}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{67C4F4C2-21AB-4A15-8419-6F563A3DE873}.Debug|Any CPU.Build.0 = Debug|Any CPU
{67C4F4C2-21AB-4A15-8419-6F563A3DE873}.Release|Any CPU.ActiveCfg = Release|Any CPU
{67C4F4C2-21AB-4A15-8419-6F563A3DE873}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {DD4C4D88-1121-4B0C-BC54-6CE368E2C92A}
EndGlobalSection
EndGlobal

0 comments on commit 036fb41

Please sign in to comment.