3F / MvsSln

🧩 Customizable VisualStudio .sln parser with project support (.vcxproj, .csproj., …). Pluggable lightweight r/w handlers at runtime, and more …
MIT License
135 stars 27 forks source link

Example on how to modify Sln files #3

Closed fhaag closed 2 years ago

fhaag commented 6 years ago

Could you add a simple example of how to actually modify an .sln file? I am looking for something like, e.g. load an .sln file, search through the names of projects included in it, and remove one of these project inclusions, then save the .sln file again. How is this done?

Unfortunately, I failed to see any hint to that in the readme file. There is some talk about custom "handlers" that can apparently be used to write something, but all I am getting from the Sln object is a Results object with (read-only) enumerables.

3F commented 6 years ago

There is some talk about custom "handlers"

The custom handlers may be applied (this is not required for your case) for any non-standard logic, or for any new not yet supported data from .sln format, and so on. That is, you don't need to implement this yourself for any common operations with known data because it's already implemented for ISlnResult via Core.SlnHandlers.

Map is the main idea of any modifications.

And the Core.ObjHandlers just provides "committing" new data vice versa from any related collection.

I am looking for something like, e.g. load an .sln file, search through the names of projects included in it, and remove one of these project inclusions, then save the .sln file again. How is this done?

For example:

using(var sln = new Sln(@"original.sln", SlnItems.All))
{
    // new collection from available projects but without project 'UnLib'
    var projects = sln.Result.ProjectItems.Where(p => p.name != "UnLib"); 

    // prepare write-handlers
    var whandlers = new Dictionary<Type, HandlerValue>() {
        [typeof(LProject)] = new HandlerValue(new WProject(projects, sln.Result.ProjectDependencies)),
    };

    // save result
    using(var w = new SlnWriter(@"modified.sln", whandlers)) {
        w.Write(sln.Result.Map);
    }
}
// That's all. You should get .sln without `UnLib` project.
wouterroos commented 5 years ago

Hi,

When using the example above on an 'empty' solution, sln.Result.ProjectDependencies is null and thus the code will throw an exception when adding a new project. Is this the proper way to add new projects to a solution that doesn't contain any project (or project dependencies) yet?

3F commented 5 years ago

sln.Result.ProjectDependencies is null and thus the code will throw an exception

You should notice much more null-values when loading an 'empty' solution :)

An sln.Result just represents various collections, so you can just initialize something from scratch if you need it. For example, for mentioned ProjectDependencies and WProject from my example above:

  1. WProject requires any ISlnProjectDependencies instance.
  2. The default public implemention exactly is LProjectDependencies, ie. nothing easer than just:
new WProject(projects, new LProjectDependencies())

etc.

Is this the proper way to add new projects to a solution that doesn't contain any project (or project dependencies) yet?

However, you need a little bit more if we're talking about adding new projects into absolutely 'empty' solution (when no initial records about projects at all), like:

MinimumVisualStudioVersion = 10.0.40219.1
Global
    GlobalSection(SolutionProperties) = preSolution
        HideSolutionNode = FALSE
    EndGlobalSection
EndGlobal

Today we need add information about planned projects into prepared map data. For example:


// some project collection from scratch ~
var projects = new[] { new ProjectItem("MyProject", ProjectType.Cs, "path") };

// place LProject handler inside map:
sln.Result.Map.Insert
(
    (int)sln.Result.Map.First(i => i.Raw == "Global").Line - 1,
    new Section(new LProject(), null)
);

// now you can use SlnWriter as before

2.2 does not provide any wrapper for some automation of this. But I think we need an additional layer anyway.

Moreover, in #9 I already voiced about preparing some "intermediate wrapper for creating new write-handlers from result"

wouterroos commented 5 years ago

Thanks, I got it to work.

3F commented 2 months ago

Since this issue is mentioned too much, please note that there are new related features in 2.7+

SMap

New map wrapper in order to control ISections easily. For example,

smap.Add
(
    SMap.AddType.Before,
    typeof(LExtensibilityGlobals),
    new Section(new LMyHandler()) // place it before LExtensibilityGlobals
);

see tests for details

ISlnResult -> ISlnWhData

ISlnWhData is new minimal subset of the data for the default handlers. That is, in addition to standard handler definitions, I mean:

Dictionary<Type, HandlerValue> whandlers = new()
{
    [typeof(LVisualStudioVersion)] = new(new WVisualStudioVersion(SlnHeader.MakeDefault())),
    [typeof(LProject)] = new(new WProject(projects)),
    ...
};

using SlnWriter w = new(solutionFile, whandlers));

the modern SlnWriter now understands also ISlnWhData. And while ISlnWhData can be implemented manually, 2.7 also provides LhDataHelper helper in order to use it in a chain like style:

LhDataHelper hdata = new();
hdata.SetHeader(SlnHeader.MakeDefault())
        .SetProjects(projects)
        .SetProjectConfigs(prjConfs)
        .SetSolutionConfigs(slnConf);

using SlnWriter w = new(solutionFile, hdata);
await w.WriteAsync(sln.Result.Map);

And new DefaultHandlers can also help to generate the default w\handlers using this data

Dictionary<Type, HandlerValue> whandlers = DefaultHandlers.MakeFrom(data)

Modern SlnWriter

SlnWriter now also provides WriteAsString() & WriteAsStringAsync() to save the result as string instead of file.

using SlnWriter w = new(handlers);
string data = await w.WriteAsStringAsync(sln.Result.Map);

I hope that now modifications can actually be done in a few steps.

More about: