StingyJack / Scripty

Tools to let you use Roslyn-powered C# scripts for code generation
MIT License
0 stars 1 forks source link

Please note: it's been a while since a Scripty release. I'm not entirely sure when (or if) development on this project will resume. While I would love to pick up development here again at some point, I don't know if it makes sense to dedicate any more time given that the Roslyn team has committed to ship source generators as part of .NET 5.

In the meantime some other projects in this area include:

Also note that the Scripty Visual Studio extension has been removed from the Visual Studio Extension Gallery due to reports of incompatibilities with the most recent versions of Visual Studio. It will be replaced when the next release comes out, but in the meantime you can download it directly from the GitHub version 0.7.4 release page.


Tools to let you use Roslyn-powered C# scripts for code generation. You can think of it as a scripted alternative to T4 templates.

Note that this document corresponds to the currently active source code branch, which may be in development. To view documentation for the latest release of Scripty make sure you are viewing the master branch.

Quick Start

There are two different ways to use Scripty, and they can be used together or separately depending on your use case.

The first is to use the "custom tool" functionality contained in the Scripty Visual Studio extension. Just add that extension to Visual Studio and it's ready to go. Once the extension is installed, you have to "turn it on" for specific files. You do this by manually entering ScriptyGenerator as the "Custom Tool" for a file or files in the project (presumably .csx files, though it'll work for any extension). The "Custom Tool" setting can be found in the file properties in Visual Studio. Once that's set, just right-click on the file and select "Run Custom Tool". Note that the scripts will not be evaluated on every build automatically using this approach.

The other way to use Scripty is with the MSBuild task. In this case, add the Scripty.MsBuild NuGet package to your project. It will automatically pick up any .csx files in your project at build time, evaluate them, and add the results to your compilation. You won't actually see the results in your project. If you want the output of the scripts to show up in the project after using the MSBuild task, you'll need to manually add them after the first build.

I'd recommend looking at the Scripty Visual Studio extension first since it's more obvious when the generation happens and you can immediately see the results. Then once you're comfortable, maybe experiment with the build task. You can also use both together to run code generation on-demand with the custom tool and also on every build with the MsBuild task. Also note that you shouldn't need to install the Roslyn SDK or manually add any of the Roslyn packages or projects to the solution. Everything is self contained in the Scripty extensions and packages.

Scripts

Scripty scripts are just standard Roslyn C# scripts with some special global properties to make them suitable for powering code generation. All the standard C# scripting conventions still apply such as using the #r preprocessor directive to load assemblies and the #load directive to load external script files. They are also generally given .csx extensions just like normal C# scripts. This makes it easy to bootstrap evaluating them outside Scripty if you need to, providing whichever Scripty globals you use in the script yourself.

It is now also possible to use the #load directive to load c# files (.cs). This makes it convenient to use intellisense when writing files to be used by Scripty. An example of this usage can be seen below.

The following references are added to every script by default:

The following namespaces are imported to every script by default:

Script File Extension

By default, Scripty looks for files with a .csx extension. This is the same extension used by standard C# scripts and there are a couple reasons why an alternate extension wasn't chosen:

If you don't like this behavior, it's easy to change the extension Scripty uses. For the MSBuild task you can change the extension or only process a subset of files by adding an ItemGroup item named ScriptyFile and including whatever files you want the task to process. The custom tool is opt-in to begin with since you have to set the custom tool property. You can have the custom tool process any file, .csx extension or not, by setting the Custom Tool property.

Global Properties

The following global properties are available when evaluating your script with Scripty:

Libraries

Scripty support is provided via a variety of libraries (and corresponding NuGet packages) for different scenarios.

Scripty.MsBuild

This installs an MsBuild task into your project that will evaluate script files on each build.

By default, all files in the project with the .csx extension are evaluated. You can customize this with the ScriptyFiles ItemGroup (for example, if you have .csx files that aren't part of code generation or that you intend to load with #load and thus shouldn't be evaluated directly):

<ItemGroup>
    <ScriptyFiles Include="codegen.csx" />
</ItemGroup>

Files that get generated using the MsBuild task are included during compilation (as long as their Compile flag is set in the script), but not in the project (unless your script modifies the project to include them). If you'd like to have them in the project as well (for example, to enable Intellisense) just include them manually after the first generation. You may want to also commit an empty placeholder file to any shared repository if you do include generated files in the project so that people obtaining the project won't get a missing file prior to their first build.

By default, the MSBuild task will cause the build to fail if there are any problems evaluating scripts. You can override this behavior and continue with the build on errors by placing the following in your project file:

<PropertyGroup>
  <ScriptyContinueOnError>true</ScriptyContinueOnError>
</PropertyGroup>

Scripty.CustomTool

This library provides a single file generator (otherwise known as a custom tool) in Visual Studio that can be used to evaluate Scripty scripts whenever the underlying script file changes. Unlike Scripty.MsBuild, generated output from the scripts will get automatically included in the project. This library and Scripty.MsBuild can be used together to provide script evaluation on changes and on build.

The generator is provided as a Visual Studio extension and you can install it from the gallery (just search for "Scripty"). To use it, set the "Custom Tool" property for any .csx file to "ScriptyGenerator". After setting the custom tool. Scripty will automatically run whenever the underlying script is saved or when you right-click the script and select "Run Custom Tool."

Scripty

A console application that can evaluate Scripty scripts. This can be used to integrate Scripty into alternate build systems. It's also used by Scripty.MsBuild to evaluate scripts outside the process space of the currently running build.

>Scripty.exe --help
usage:  <ProjectFilePath> <ScriptFilePaths>...

    <ProjectFilePath>       The full path of the project file.
    <ScriptFilePaths>...    The path(s) of script files to evaluate (can
                            be absolute or relative to the project).

Scripty.Core

This library is the foundation of Scripty and can be used if you want to embed Scripty evaluation. It lets you run the Scripty engine directly, supplying any data needed to set up the special global properties like the current project path.

Cake.Scripty

A Cake addin for Scripty that allows you to evaluate Scripty scripts in Cake. The recommended approach is to use the Scripty.MsBuild library so that you also get Scripty evaluation when building from Visual Studio, If you are calling MsBuild from Cake (which most Cake scripts do) this addin is not needed. However, this addin lets you integrate Scripty evaluation into Cake in situations when you want to completely replace MsBuild.

When using the addin it is important to include both the addin and the Scripty tool. To use the addin call Scripty constructor and then chain the Evaluate method off it.

The constructor takes an string for the project file and an optional ScriptySettings which inherits from ToolSettings with no extra settings added. The Evaluate method takes a param list of script files to process. A simple Cake file would look like:

#addin nuget:?package=Cake.Scripty
#tool nuget:?package=Scripty

var target = Argument("target", "Default");

Task("Default")
.Does(() => {
    Scripty("Project.csproj", new ScriptySettings()
        {
            WorkingDirectory = "./OptionalWorkingDirectory"
        })
        .Evaluate("Script1.csx", "Script2.csx", "Script3.csx");
});

RunTarget(target);

Sample Usage:

Within a Solution and given a script file (TestScript.csx)

//TestScript.csx
#load "TestCSharpFile.csx.cs"
var sc = new ScriptContainer(Context);
await sc.OutputProjectStructure();

And c# file (TestCSharpFile.csx.cs). The .cs extension is critical

//TestCSharpFile.csx.cs
using Microsoft.CodeAnalysis;
using Scripty.Core;
using System;
using System.Linq;
using System.Threading.Tasks;

namespace ScriptContainer
{
    public class ScriptContainer
    {
        private readonly string _documentStructureFile;
        private readonly string _descriptionFile;
        private readonly ScriptContext _context;

        public ScriptContainer(ScriptContext context)
        {
            _documentStructureFile = "GeneratedFile.cs";
            _descriptionFile = "Description.txt";
            _context = context;
        }

        public async Task OutputProjectStructure()
        {
            _context.Output[_descriptionFile].BuildAction = Scripty.Core.Output.BuildAction.GenerateOnly;
            _context.Output[_documentStructureFile].BuildAction = Scripty.Core.Output.BuildAction.Compile;
            _context.Output[_descriptionFile].WriteLine($"I am a text file generated by a scripty template");

            var task = Task.Run(async () =>
            {
                return await _context.Project.Analysis.GetCompilationAsync();
            });
            var c = task.Result;

            //Get NameSpace tp Enumerate / process
            var zAddressNamespaceSymbol = c.GlobalNamespace.GetNamespaceMembers().Single(ns => ns.Name == "MyRootNameSpace");

            RecurseCompilationSymbol(zAddressNamespaceSymbol, 1);
        }

        public void RecurseCompilationSymbol(ISymbol compilationSymbol, int level)
        {
            level++;
            if (compilationSymbol is INamespaceOrTypeSymbol)
            {
                WriteDetailLine(level, compilationSymbol.Kind, compilationSymbol.Name, "");
                foreach (var member in ((INamespaceOrTypeSymbol)compilationSymbol).GetMembers())
                {
                    RecurseCompilationSymbol(member, level);
                }
            }
            else if (compilationSymbol is IPropertySymbol)
            {
                WriteDetailLine(level, compilationSymbol.Kind, compilationSymbol.Name,
                    ((IPropertySymbol) compilationSymbol).GetMethod.ReturnType.Name);
            }
            else
            {
                WriteDetailLine(level, compilationSymbol.Kind, compilationSymbol.Name,"");
            }
        }

        private void WriteDetailLine(int level, SymbolKind symbolKind, string symbolName, string symbolReturnType)
        {
            var symbolKindName = Enum.GetName(typeof(SymbolKind), symbolKind);

            _context.Output[_documentStructureFile].WriteLine($"// {GetStringOfSpaces(level)} {symbolKindName} {symbolName} {symbolReturnType}");
        }

        public static string GetStringOfSpaces(int number)
        {
            number = number * 4;
            string s = "";
            for (int i = 0; i < number; i++)
            {
                s = s + " ";
            }
            return s;
        }    
    }
}

We would have an output of 2 files. Namely "DescriptionText.txt" which will contain the text specified, and "GeneratedFile.cs" which will contain a listing of all namespaces, classes and their properties and methods.

Help!

If you need help, have a feature request, or notice a bug, just submit a GitHub issue.