dotnet / interactive

.NET Interactive combines the power of .NET with many other languages to create notebooks, REPLs, and embedded coding experiences. Share code, explore data, write, and learn across your apps in ways you couldn't before.
MIT License
2.8k stars 374 forks source link

Ignore namespaces when importing .cs files in Jupyter Notebook #3441

Open janschreieradac opened 5 months ago

janschreieradac commented 5 months ago

It has been asked in #479 and is closed,/marked as completed but I don't see how this is completed?

I would still love this feature such they I can use a notebook next to an existing .net solution and not need to remove all namespaces. for me the obvious fix would be to simply ignore namespaces when using #import

jonsequitur commented 5 months ago

I believe #479 was closed because it's external.

The main problem here is that the C# scripting dialect doesn't understand namespace declarations, so importing a file that contains them will result in an error. Changing this amounts to language design change and isn't specific to .NET Interactive, which avoids doing anything that changes the behaviors of its hosted languages.

janschreieradac commented 5 months ago

Thanks for that help. I dug a bit deeper and found #2426 which lead me to the right direction.

My solution/workaround would be to write my own magic command importNamespaced that simply removes the namespace before parsing it. here's the code (updated thanks to Jon's suggestion below):

using System.CommandLine;
using Microsoft.DotNet.Interactive;
using Microsoft.DotNet.Interactive.Utility;
using Microsoft.DotNet.Interactive.Commands;
using System.IO;

var fileArg  = new Argument<FileInfo>("file").ExistingOnly();
var magicCommand = new Command("#!importNamespaced","Imports .cs files and removes any file scoped namespaces") {
   fileArg
};

magicCommand.SetHandler(async (ctx) =>
        {
            var file = ctx.ParseResult.GetValueForArgument(fileArg);
            var filteredLines = File.ReadAllLines(file.FullName)
                                    .Where(l => l.Trim().StartsWith("namespace") is false)
                                    .ToList();
            var modifiedContent = string.Join("\n", filteredLines);
            var currentInvocationContext = (KernelInvocationContext)ctx.BindingContext.GetService(typeof(KernelInvocationContext));
            await currentInvocationContext.HandlingKernel.SendAsync(new SubmitCode(modifiedContent));
        });

Kernel.Root.AddDirective(magicCommand);

the only thing that I would love to avoid is to save the file in c:\temp or any other temp folder. It seems very do-able but for my purposes it would mean that I would have to maintain to much code that originally belongs into .net interactive itself.

I'd appreciate any smart ideas on how to solve this more elegantly, but I am fine if the issue is closed now.

jonsequitur commented 5 months ago

We typically leave issues open if they're unresolved. This doesn't mean we'll fix it, but the conversation might be helpful to others.

As for a more elegant solution, I'd suggest a couple of things. The most useful one is that you don't need a temp file. Something like this should work. (Polyglot submissions might need a bit more complexity but since the question is focused on C#, this should be fine.)

        magicCommand.SetHandler(async (ctx) =>
        {
            var file = ctx.ParseResult.GetValueForArgument(fileArg);

            var filteredLines = File.ReadAllLines(file.FullName)
                                    .Where(l => l.Trim().StartsWith("namespace") is false)
                                    .ToList();

            var modifiedContent = string.Join("\n", filteredLines);

            var currentInvocationContext = (KernelInvocationContext)ctx.BindingContext.GetService(typeof(KernelInvocationContext));

            await currentInvocationContext.HandlingKernel.SendAsync(new SubmitCode(modifiedContent));
        });

The other suggestion, if you wanted to make the overall solution a little more robust, is to use the Roslyn APIs to remove the namespace. This is because there are few different syntax variations you might want to account for, like:

namespace Something;
namespace 
       Something;
namespace Something
{
}

etc.

Hope that helps!