From 5f0a43967d3fd9a30ea9a9fa58561ef67777b742 Mon Sep 17 00:00:00 2001 From: Jon Sequeira Date: Thu, 1 Aug 2019 15:38:25 -0700 Subject: [PATCH 1/3] don't launch browser when in Jupyter mode --- MLS.Agent/CommandLine/JupyterCommand.cs | 2 +- MLS.Agent/CommandLine/StartupOptions.cs | 6 +++++- MLS.Agent/Startup.cs | 4 ++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/MLS.Agent/CommandLine/JupyterCommand.cs b/MLS.Agent/CommandLine/JupyterCommand.cs index 05be29307..c50765c16 100644 --- a/MLS.Agent/CommandLine/JupyterCommand.cs +++ b/MLS.Agent/CommandLine/JupyterCommand.cs @@ -15,7 +15,7 @@ public static Task Do( CommandLineParser.StartServer startServer = null, InvocationContext context = null) { - startServer?.Invoke(new StartupOptions(), context); + startServer?.Invoke(new StartupOptions(isJupyter: true), context); return Task.FromResult(0); } diff --git a/MLS.Agent/CommandLine/StartupOptions.cs b/MLS.Agent/CommandLine/StartupOptions.cs index d3088c280..7393833a0 100644 --- a/MLS.Agent/CommandLine/StartupOptions.cs +++ b/MLS.Agent/CommandLine/StartupOptions.cs @@ -45,7 +45,8 @@ public StartupOptions( string package = null, string packageVersion = null, ParseResult parseResult = null, - ushort? port = null) + ushort? port = null, + bool isJupyter = false) { _parseResult = parseResult; LogPath = logPath; @@ -63,8 +64,10 @@ public StartupOptions( Package = package; PackageVersion = packageVersion; Port = port; + IsJupyter = isJupyter; } + public bool EnablePreviewFeatures { get; } public string Id { get; } public string RegionId { get; } @@ -72,6 +75,7 @@ public StartupOptions( public PackageSource AddPackageSource { get; } public Uri Uri { get; set; } public bool Production { get; } + public bool IsJupyter { get; } public bool IsLanguageService { get; set; } public string Key { get; } public string ApplicationInsightsKey { get; } diff --git a/MLS.Agent/Startup.cs b/MLS.Agent/Startup.cs index 4119f9bd7..d22d01fd1 100644 --- a/MLS.Agent/Startup.cs +++ b/MLS.Agent/Startup.cs @@ -186,7 +186,7 @@ public void Configure( operation.Succeed(); - if (StartupOptions.Mode == StartupMode.Try) + if (StartupOptions.Mode == StartupMode.Try && !StartupOptions.IsJupyter) { var uri = new Uri(app.ServerFeatures.Get().Addresses.First()); Clock.Current @@ -233,7 +233,7 @@ RelativeFilePath FindReadmeFileAtRoot() { var files = directoryAccessor .GetAllFilesRecursively() - .Where(f => (StringComparer.InvariantCultureIgnoreCase.Compare(f.FileName, "readme.md") == 0) && IsRoot(f.Directory)) + .Where(f => StringComparer.InvariantCultureIgnoreCase.Compare(f.FileName, "readme.md") == 0 && IsRoot(f.Directory)) .ToList(); return files.FirstOrDefault(); From f0df00a3b06cfa0a8cb19a04d56d76a15876f69a Mon Sep 17 00:00:00 2001 From: Jon Sequeira Date: Thu, 1 Aug 2019 15:40:13 -0700 Subject: [PATCH 2/3] add Tag tests, rename TagName -> Tag --- .../FormatterTests.cs | 1 - .../PocketViewTests.cs | 9 +- .../TagTests.cs | 304 ++++++++++++++++++ .../HtmlAttributes.cs | 2 +- .../ITag.cs | 2 +- .../PocketView.cs | 4 +- Microsoft.DotNet.Interactive.Rendering/Tag.cs | 22 +- .../TagExtensions.cs | 30 +- .../KernelExtensions.cs | 2 +- .../Kernel/CSharpKernelTests.cs | 2 - WorkspaceServer/Kernel/CSharpKernel.cs | 1 - 11 files changed, 331 insertions(+), 48 deletions(-) create mode 100644 Microsoft.DotNet.Interactive.Rendering.Tests/TagTests.cs diff --git a/Microsoft.DotNet.Interactive.Rendering.Tests/FormatterTests.cs b/Microsoft.DotNet.Interactive.Rendering.Tests/FormatterTests.cs index 69a2bd0c4..c41a46482 100644 --- a/Microsoft.DotNet.Interactive.Rendering.Tests/FormatterTests.cs +++ b/Microsoft.DotNet.Interactive.Rendering.Tests/FormatterTests.cs @@ -11,7 +11,6 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Xunit; -using static Microsoft.DotNet.Interactive.Rendering.PocketViewTags; namespace Microsoft.DotNet.Interactive.Rendering.Tests { diff --git a/Microsoft.DotNet.Interactive.Rendering.Tests/PocketViewTests.cs b/Microsoft.DotNet.Interactive.Rendering.Tests/PocketViewTests.cs index 2d1946320..d91056e37 100644 --- a/Microsoft.DotNet.Interactive.Rendering.Tests/PocketViewTests.cs +++ b/Microsoft.DotNet.Interactive.Rendering.Tests/PocketViewTests.cs @@ -1,4 +1,6 @@ -using System; +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + using FluentAssertions; using System.Linq; using Xunit; @@ -131,7 +133,7 @@ public void A_transform_can_be_used_to_create_an_alias() _.foolink = PocketView.Transform( (tag, model) => { - tag.TagName = "a"; + tag.Name = "a"; tag.HtmlAttributes.Add("href", "http://foo.biz"); }); @@ -148,7 +150,7 @@ public void A_transform_can_be_used_to_create_an_expandable_tag() _.textbox = PocketView.Transform( (tag, model) => { - tag.TagName = "div"; + tag.Name = "div"; tag.Content = w => { w.Write(label[@for: model.name](model.name)); @@ -234,7 +236,6 @@ public void Input_elements_are_rendered_as_self_closing_when_calling_using_prope [Fact] public void HtmlAttributes_mixed_with_indexer_args_can_be_used_for_attributes() { - // TODO-JOSEQU: (HtmlAttributes_mixed_with_indexer_args_can_be_used_for_attributes) var attr = new HtmlAttributes(); string output = section[attr.Class("info"), style: "color:red"]("some content").ToString(); diff --git a/Microsoft.DotNet.Interactive.Rendering.Tests/TagTests.cs b/Microsoft.DotNet.Interactive.Rendering.Tests/TagTests.cs new file mode 100644 index 000000000..7aae6f855 --- /dev/null +++ b/Microsoft.DotNet.Interactive.Rendering.Tests/TagTests.cs @@ -0,0 +1,304 @@ +using FluentAssertions; +using Xunit; + +namespace Microsoft.DotNet.Interactive.Rendering.Tests +{ + public class TagTests + { + [Fact] + public void Name_property_is_set_by_constructor() + { + var tag = new Tag("label"); + + tag.Name.Should().Be("label"); + } + + [Fact] + public void ToString_renders_start_tag_when_there_is_no_content() + { + var p = new Tag("p"); + + p.ToString().Should().Contain("

"); + } + + [Fact] + public void ToString_renders_end_tag_when_there_is_no_content() + { + var p = new Tag("p"); + + p.ToString().Should().Contain("

"); + } + + [Fact] + public void ToString_renders_text_content() + { + var p = new Tag("p", "content"); + + p.ToString().Should().Be("

content

"); + } + + [Fact] + public void ToString_renders_Action_content() + { + var p = new Tag("p", w => w.Write("content")); + + p.ToString().Should().Be("

content

"); + } + + [Fact] + public void Append_appends_content_to_existing_Tag_content() + { + var tag = new Tag("div").Containing("initial content"); + + var html = tag + .Append(new Tag("div").Containing("more content")) + .ToString(); + + html.Should().Be("
initial content
more content
"); + } + + [Fact] + public void Append_array_overload_appends_content_to_existing_Tag_content() + { + var tag = new Tag("div").Containing("initial content"); + + var html = tag + .Append(new Tag("div").Containing("more"), new Tag("div").Containing("content")) + .ToString(); + + html.Should().Be("
initial content
more
content
"); + } + + [Fact] + public void Append_appends_to_tag_with_no_content() + { + var tag = "div".Tag().Append("img".Tag().SelfClosing()).ToString(); + + tag.Should().Be("
"); + } + + [Fact] + public void Prepend_prepends_to_tag_with_no_content() + { + var tag = "div".Tag().Prepend("img".Tag().SelfClosing()).ToString(); + + tag.Should().Be("
"); + } + + [Fact] + public void AppendTo_appends_content_to_target_Tag_content() + { + var tag = new Tag("div").Containing("initial content"); + new Tag("div").Containing("more content").AppendTo(tag); + var html = tag.ToString(); + + html.Should().Be("
initial content
more content
"); + } + + [Fact] + public void AppendTo_appends_content_to_target_Tag_that_has_no_content() + { + var tag = "div".Tag(); + new Tag("div").Containing("some content").AppendTo(tag); + var html = tag.ToString(); + + html.Should().Be("
some content
"); + } + + [Fact] + public void PrependTo_prepends_content_to_target_Tag_that_has_no_content() + { + var tag = "div".Tag(); + new Tag("div").Containing("some content").PrependTo(tag); + var html = tag.ToString(); + + html.Should().Be("
some content
"); + } + + [Fact] + public void Append_returns_the_same_tag() + { + var tag = new Tag("div"); + tag.Append("a".Tag()).Should().Be(tag); + } + + [Fact] + public void AppendTo_returns_the_same_tag() + { + var tag = new Tag("div"); + tag.AppendTo("a".Tag()).Should().Be(tag); + } + + [Fact] + public void AppendTo_returns_unmodified_tag() + { + var target = "div".Tag(); + var before = "a".Tag(); + + var after = before.AppendTo(target); + + after.ToString().Should().Be(before.ToString()); + } + + [Fact] + public void Append_appends_many_tags_into_one() + { + var tag = "ul".Tag().Append("li".Tag(), "li".Tag()); + + var html = tag.ToString(); + + html.Should().Be("
"); + } + + [Fact] + public void Prepend_returns_the_same_tag() + { + var tag = new Tag("div"); + + tag.Prepend("a".Tag()).Should().Be(tag); + } + + [Fact] + public void PrependTo_returns_the_same_tag() + { + var tag = new Tag("div"); + + tag.PrependTo("a".Tag()).Should().Be(tag); + } + + [Fact] + public void PrependTo_returns_unmodified_tag() + { + var target = "div".Tag(); + var before = "a".Tag(); + + var after = before.PrependTo(target); + + after.ToString().Should().Be(before.ToString()); + } + + [Fact] + public void WrapInner_returns_the_same_tag() + { + var tag = new Tag("div"); + tag.WrapInner("a".Tag()).Should().Be(tag); + } + + [Fact] + public void Prepend_prepends_content_to_existing_Tag_content() + { + var tag = new Tag("div").Containing("initial content"); + + var after = tag + .Prepend(new Tag("div").Containing("more content")) + .ToString(); + + after.Should().Be("
more content
initial content
"); + } + + [Fact] + public void WrapInner_wraps_inner_content_in_specified_tag() + { + var tag = new Tag("div").Containing("initial content"); + + var after = tag + .WrapInner("a".Tag().WithAttributes("href", "#")) + .ToString(); + + after.Should().Be("
initial content
"); + } + + [Fact] + public void MergedAttributes_produces_a_tag_having_the_union_of_attributes_differing_by_name() + { + var tag = new Tag("div"); + tag.MergeAttributes(new HtmlAttributes { { "class", "error" } }); + tag.MergeAttributes(new HtmlAttributes { { "style", "display:block" } }); + + var html = tag.ToString(); + + html.Should().Contain("
"); + } + + [Fact] + public void WithAttributes_produces_a_tag_having_the_union_of_attributes_differing_by_name() + { + var tag = new Tag("div") + .WithAttributes(new HtmlAttributes { { "class", "error" } }) + .WithAttributes(new HtmlAttributes { { "style", "display:block" } }); + + var html = tag.ToString(); + + html.Should().Contain("
"); + } + + [Fact] + public void When_called_more_than_once_with_attributes_of_the_same_name_MergeAttributes_does_not_overwrite_by_default() + { + var tag = new Tag("div"); + tag.MergeAttributes(new HtmlAttributes { { "class", "error" } }); + tag.MergeAttributes(new HtmlAttributes { { "class", "alert" } }); + + var html = tag.ToString(); + + html.Should().Contain("
"); + } + + [Fact] + public void When_called_more_than_once_with_attributes_of_the_same_name_WithAttributes_overwrites_by_default() + { + var tag = new Tag("div") + .WithAttributes(new HtmlAttributes { { "class", "error" } }) + .WithAttributes(new HtmlAttributes { { "class", "alert" } }); + + var html = tag.ToString(); + + html.Should().Contain("
"); + } + + [Fact] + public void When_called_more_than_once_with_attributes_of_the_same_name_MergeAttributes_and_overwrite_is_set_to_true_it_overwrites() + { + var tag = new Tag("div"); + tag.MergeAttributes(new HtmlAttributes { { "class", "error" } }); + tag.MergeAttributes(new HtmlAttributes { { "class", "alert" } }, true); + + var html = tag.ToString(); + + html.Should().Contain("
"); + } + + [Fact] + public void IsSelfClosing_renders_selfclosing_when_no_content() + { + var tag = "pizza".Tag(); + tag.IsSelfClosing = true; + + var after = tag.ToString(); + + after.Should().Be(""); + } + + [Fact] + public void IsSelfClosing_renders_full_tag_when_content() + { + var tag = new Tag("pizza").Containing("some content"); + tag.IsSelfClosing = true; + + var after = tag.ToString(); + after.Should().Be("some content"); + } + + [Fact] + public void SelfClosing_extension_sets_IsSelfClosing_property() + { + var tag = "input".Tag(); + + tag.IsSelfClosing.Should().BeFalse(); + + tag.SelfClosing(); + + tag.IsSelfClosing.Should().BeTrue(); + } + } +} \ No newline at end of file diff --git a/Microsoft.DotNet.Interactive.Rendering/HtmlAttributes.cs b/Microsoft.DotNet.Interactive.Rendering/HtmlAttributes.cs index 2bf6a8dc0..d2ddf953e 100644 --- a/Microsoft.DotNet.Interactive.Rendering/HtmlAttributes.cs +++ b/Microsoft.DotNet.Interactive.Rendering/HtmlAttributes.cs @@ -310,7 +310,7 @@ public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, o { switch (binder.Name) { - case "MergeWith": + case nameof(MergeWith): var dictionary = binder .CallInfo diff --git a/Microsoft.DotNet.Interactive.Rendering/ITag.cs b/Microsoft.DotNet.Interactive.Rendering/ITag.cs index cb29ca9bb..da6ae2168 100644 --- a/Microsoft.DotNet.Interactive.Rendering/ITag.cs +++ b/Microsoft.DotNet.Interactive.Rendering/ITag.cs @@ -11,7 +11,7 @@ public interface ITag : IHtmlContent /// Gets HTML tag type. /// /// The type of the tag. - string TagName { get; } + string Name { get; } /// /// Gets the HTML attributes to be rendered into the tag. diff --git a/Microsoft.DotNet.Interactive.Rendering/PocketView.cs b/Microsoft.DotNet.Interactive.Rendering/PocketView.cs index b94910136..c8eabdc3a 100644 --- a/Microsoft.DotNet.Interactive.Rendering/PocketView.cs +++ b/Microsoft.DotNet.Interactive.Rendering/PocketView.cs @@ -262,7 +262,7 @@ public override string ToString() /// Gets HTML tag type. /// /// The type of the tag. - public string TagName + public string Name { get { @@ -271,7 +271,7 @@ public string TagName return ""; } - return _tag.TagName; + return _tag.Name; } } diff --git a/Microsoft.DotNet.Interactive.Rendering/Tag.cs b/Microsoft.DotNet.Interactive.Rendering/Tag.cs index 4c7ac82c2..c72dcd55a 100644 --- a/Microsoft.DotNet.Interactive.Rendering/Tag.cs +++ b/Microsoft.DotNet.Interactive.Rendering/Tag.cs @@ -18,18 +18,18 @@ public class Tag : ITag /// /// Initializes a new instance of the class. /// - /// The name of the tag. - public Tag(string tagName) + /// The name of the tag. + public Tag(string name) { - TagName = tagName; + Name = name; } /// /// Initializes a new instance of the class. /// - /// The name of the tag. + /// The name of the tag. /// The text contained by the tag. - public Tag(string tagName, string text) : this(tagName) + public Tag(string name, string text) : this(name) { Content = writer => writer.Write(text); } @@ -37,9 +37,9 @@ public Tag(string tagName, string text) : this(tagName) /// /// Initializes a new instance of the class. /// - /// Name of the tag. + /// Name of the tag. /// The content. - public Tag(string tagName, Action content) : this(tagName) + public Tag(string name, Action content) : this(name) { Content = content; } @@ -60,7 +60,7 @@ public Tag(string tagName, Action content) : this(tagName) /// /// The type of the tag. /// - public string TagName { get; set; } + public string Name { get; set; } /// /// Gets the HTML attributes to be rendered into the tag. @@ -94,7 +94,7 @@ public virtual void WriteTo(TextWriter writer, HtmlEncoder encoder) protected void WriteSelfClosingTag(TextWriter writer) { writer.Write('<'); - writer.Write(TagName); + writer.Write(Name); HtmlAttributes.WriteTo(writer, HtmlEncoder.Default); writer.Write(" />"); } @@ -102,14 +102,14 @@ protected void WriteSelfClosingTag(TextWriter writer) protected void WriteEndTag(TextWriter writer) { writer.Write("'); } protected void WriteStartTag(TextWriter writer) { writer.Write('<'); - writer.Write(TagName); + writer.Write(Name); HtmlAttributes.WriteTo(writer, HtmlEncoder.Default); writer.Write('>'); } diff --git a/Microsoft.DotNet.Interactive.Rendering/TagExtensions.cs b/Microsoft.DotNet.Interactive.Rendering/TagExtensions.cs index ba838b80b..8acc915f6 100644 --- a/Microsoft.DotNet.Interactive.Rendering/TagExtensions.cs +++ b/Microsoft.DotNet.Interactive.Rendering/TagExtensions.cs @@ -33,30 +33,14 @@ public static TTag WithAttributes(this TTag tag, IDictionary - /// Merges the specified attributes into the tag's existing attributes. - /// - public static TTag WithAttributes(this TTag tag, Action attributes) where TTag : ITag + public static TTag WithAttributes( + this TTag tag, + string name, + object value) + where TTag : ITag { - attributes(tag.HtmlAttributes); - return tag; - } + tag.HtmlAttributes.Add(name, value); - /// - /// Adds the specified class or classes to the tag. - /// - /// The type of the tag. - /// The tag to which classes are to be added. - /// The class or classes to be added. - /// - /// The modified instance. - /// - /// - /// The classes are merged with the existing classes on the instance. - /// - public static TTag Class(this TTag tag, string classes) where TTag : ITag - { - tag.HtmlAttributes.Class(classes); return tag; } @@ -121,7 +105,6 @@ public static TTag AppendTo(this TTag appendTag, Tag toTag) where TTag : I return appendTag; } - /// /// Specifies the contents of a tag. /// @@ -170,7 +153,6 @@ internal static TTag Containing(this TTag tag, Action content) return tag; } - /// /// Prepends the specified tags to the source tag. /// diff --git a/Microsoft.DotNet.Interactive/KernelExtensions.cs b/Microsoft.DotNet.Interactive/KernelExtensions.cs index a617b21c5..ceb01334b 100644 --- a/Microsoft.DotNet.Interactive/KernelExtensions.cs +++ b/Microsoft.DotNet.Interactive/KernelExtensions.cs @@ -39,7 +39,7 @@ public static T UseExtendDirective(this T kernel) }; extend.Handler = CommandHandler.Create((dll, pipelineContext) => - kernel.SendAsync(new LoadExtension(dll))); + kernel.SendAsync(new LoadExtension(dll))); kernel.AddDirective(extend); diff --git a/WorkspaceServer.Tests/Kernel/CSharpKernelTests.cs b/WorkspaceServer.Tests/Kernel/CSharpKernelTests.cs index 30b11cca0..adb6b384e 100644 --- a/WorkspaceServer.Tests/Kernel/CSharpKernelTests.cs +++ b/WorkspaceServer.Tests/Kernel/CSharpKernelTests.cs @@ -16,9 +16,7 @@ using WorkspaceServer.Kernel; using Xunit; using Xunit.Abstractions; -using System.Reactive.Linq; using FluentAssertions.Extensions; -using System.Reactive; namespace WorkspaceServer.Tests.Kernel { diff --git a/WorkspaceServer/Kernel/CSharpKernel.cs b/WorkspaceServer/Kernel/CSharpKernel.cs index 259d698ad..94402f43e 100644 --- a/WorkspaceServer/Kernel/CSharpKernel.cs +++ b/WorkspaceServer/Kernel/CSharpKernel.cs @@ -208,7 +208,6 @@ private void PublishOutput( { var formattedValues = new List { - new FormattedValue( PlainTextFormatter.MimeType, output) }; From cf88a96f586d152a3110b1945e139381215228a7 Mon Sep 17 00:00:00 2001 From: Jon Sequeira Date: Thu, 1 Aug 2019 15:41:06 -0700 Subject: [PATCH 3/3] fix bug where scalar types get destructured during sequence formatting --- .../HtmlFormatterTests.cs | 28 ++++++++++++++++++- .../Destructurer.cs | 24 ++++++++++++++-- .../TypeExtensions.cs | 8 ++++++ 3 files changed, 57 insertions(+), 3 deletions(-) diff --git a/Microsoft.DotNet.Interactive.Rendering.Tests/HtmlFormatterTests.cs b/Microsoft.DotNet.Interactive.Rendering.Tests/HtmlFormatterTests.cs index 5249c4ffa..3c8466510 100644 --- a/Microsoft.DotNet.Interactive.Rendering.Tests/HtmlFormatterTests.cs +++ b/Microsoft.DotNet.Interactive.Rendering.Tests/HtmlFormatterTests.cs @@ -65,7 +65,7 @@ public void Formatter_expands_properties_of_ExpandoObjects() var formatter = HtmlFormatter.Create(); - var output = ((object)expando).ToDisplayString(formatter); + var output = ((object) expando).ToDisplayString(formatter); output.Should().Be("
CountName
2socks
"); } @@ -241,6 +241,32 @@ public void It_formats_dictionaries_as_tables_with_the_key_on_the_y_axis() .Be( "
keyTypeNameId
firstentity one123
secondentity two456
"); } + + [Fact] + public void It_formats_string_sequences_correctly() + { + var strings = new[] { "apple", "banana", "cherry" }; + + strings.ToDisplayString("text/html") + .Should() + .Be( + "
indexvalue
0apple
1banana
2cherry
"); + } + + [Fact(Skip = "wip")] + public void It_formats_arrays_of_disparate_types_correctly() + { + var objects = new object[] { 1, (2, "two"), Enumerable.Range(1, 3) }; + + var obj0 = objects[0].ToDisplayString(); + var obj1 = objects[1].ToDisplayString(); + var obj2 = objects[2].ToDisplayString(); + + objects.ToDisplayString("text/html") + .Should() + .Be( + $"
indexvalue
0{obj0}
1{obj1}
2{obj2}
"); + } } } } \ No newline at end of file diff --git a/Microsoft.DotNet.Interactive.Rendering/Destructurer.cs b/Microsoft.DotNet.Interactive.Rendering/Destructurer.cs index 216bd1fe3..d6980982d 100644 --- a/Microsoft.DotNet.Interactive.Rendering/Destructurer.cs +++ b/Microsoft.DotNet.Interactive.Rendering/Destructurer.cs @@ -13,8 +13,28 @@ internal static class Destructurer { public static IDestructurer Create() => new Destructurer(); - public static IDestructurer Create(Type type) => - (IDestructurer) Activator.CreateInstance(typeof(Destructurer<>).MakeGenericType(type)); + public static IDestructurer Create(Type type) + { + if (type.IsScalar()) + { + return NonDestructurer.Instance; + } + + return (IDestructurer) Activator.CreateInstance(typeof(Destructurer<>).MakeGenericType(type)); + } + } + + internal class NonDestructurer : IDestructurer + { + public static IDestructurer Instance { get; } = new NonDestructurer(); + + public IDictionary Destructure(object instance) + { + return new Dictionary + { + ["value"] = instance + }; + } } internal interface IDestructurer diff --git a/Microsoft.DotNet.Interactive.Rendering/TypeExtensions.cs b/Microsoft.DotNet.Interactive.Rendering/TypeExtensions.cs index d796bf81c..0201e9b5d 100644 --- a/Microsoft.DotNet.Interactive.Rendering/TypeExtensions.cs +++ b/Microsoft.DotNet.Interactive.Rendering/TypeExtensions.cs @@ -89,6 +89,14 @@ public static bool IsAnonymous(this Type type) (type.Attributes & TypeAttributes.NotPublic) == TypeAttributes.NotPublic; } + public static bool IsScalar(this Type type) + { + return type.IsPrimitive || + type == typeof(string) || + (type.IsConstructedGenericType && + type.GetGenericTypeDefinition() == typeof(Nullable<>)); + } + public static bool IsValueTuple(this Type type) { if (type == null)