Skip to content

Commit

Permalink
Implemented optional --backup, --force, and --dry-run command line op…
Browse files Browse the repository at this point in the history
…tions
  • Loading branch information
icnocop committed Nov 2, 2024
1 parent af23454 commit fe74025
Show file tree
Hide file tree
Showing 14 changed files with 280 additions and 95 deletions.
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
// <copyright file="FileSystemService.cs" company="Rami Abughazaleh">
// <copyright file="FileService.cs" company="Rami Abughazaleh">
// Copyright (c) Rami Abughazaleh. All rights reserved.
// </copyright>

namespace PackageReferenceVersionToAttributeExtension.Services
namespace PackageReferenceVersionToAttribute
{
using System.IO;
using Microsoft.Extensions.Logging;
using PackageReferenceVersionToAttribute;

/// <summary>
/// Provides support for operations on the file system.
/// </summary>
public class FileSystemService(ILogger<FileSystemService> logger) : IFileService
public class FileService : IFileService
{
private readonly ILogger<FileSystemService> logger = logger;
private readonly ILogger<FileService> logger;

/// <summary>
/// Initializes a new instance of the <see cref="FileService"/> class.
/// </summary>
/// <param name="logger">The logger.</param>
public FileService(ILogger<FileService> logger)
{
this.logger = logger;
}

/// <inheritdoc/>
public void RemoveReadOnlyAttribute(string filePath)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
</PackageReference>
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="8.0.0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
Expand Down
35 changes: 30 additions & 5 deletions src/PackageReferenceVersionToAttribute/ProjectConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ namespace PackageReferenceVersionToAttribute
using System.Xml;
using System.Xml.Linq;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

/// <summary>
/// Provides functionality to convert project files, modifying elements and attributes as required.
Expand All @@ -21,21 +22,25 @@ public class ProjectConverter
private readonly ILogger<ProjectConverter> logger;
private readonly IFileService fileService;
private readonly ISourceControlService sourceControlService;
private readonly ProjectConverterOptions options;

/// <summary>
/// Initializes a new instance of the <see cref="ProjectConverter"/> class.
/// </summary>
/// <param name="logger">The logger used to log information and errors during the conversion process.</param>
/// <param name="fileService">Service for file operations, such as backing up and modifying file attributes.</param>
/// <param name="sourceControlService">Service for source control operations, such as checking out files.</param>
/// <param name="options">The options.</param>
public ProjectConverter(
ILogger<ProjectConverter> logger,
IFileService fileService,
ISourceControlService sourceControlService)
ISourceControlService sourceControlService,
IOptions<ProjectConverterOptions> options)
{
this.logger = logger;
this.fileService = fileService;
this.sourceControlService = sourceControlService;
this.options = options.Value;
}

/// <summary>
Expand Down Expand Up @@ -80,12 +85,20 @@ public async Task ConvertAsync(IEnumerable<string> projectFilePaths)

bool modified = false;

// backup project file
this.fileService.BackupFile(projectFilePath);
if (this.options.Backup)
{
// backup project file
this.fileService.BackupFile(projectFilePath);
}

// check out file from source control
await this.sourceControlService.CheckOutFileAsync(projectFilePath);

if (this.options.Force)
{
this.fileService.RemoveReadOnlyAttribute(projectFilePath);
}

foreach (var packageReference in packageReferences)
{
var versionElement = packageReference.Element(ns != null ? ns + "Version" : "Version");
Expand Down Expand Up @@ -117,9 +130,21 @@ public async Task ConvertAsync(IEnumerable<string> projectFilePaths)
NewLineHandling = NewLineHandling.Replace,
};

using var writer = XmlWriter.Create(projectFilePath, settings);
if (this.options.DryRun)
{
// Output the modified document to the console for review
using var stringWriter = new StringWriter();
using var xmlWriter = XmlWriter.Create(stringWriter, settings);

document.Save(writer); // Preserves original formatting, avoids extra lines
document.WriteTo(xmlWriter);
xmlWriter.Flush();
this.logger.LogInformation(stringWriter.ToString());
}
else
{
using var writer = XmlWriter.Create(projectFilePath, settings);
document.Save(writer); // Preserves original formatting, avoids extra lines
}
}
}
catch (Exception ex)
Expand Down
29 changes: 29 additions & 0 deletions src/PackageReferenceVersionToAttribute/ProjectConverterOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// <copyright file="ProjectConverterOptions.cs" company="Rami Abughazaleh">
// Copyright (c) Rami Abughazaleh. All rights reserved.
// </copyright>

namespace PackageReferenceVersionToAttribute
{
/// <summary>
/// Represents the options for configuring the project converter behavior.
/// </summary>
public class ProjectConverterOptions
{
/// <summary>
/// Gets or sets a value indicating whether to back up project files before conversion.
/// </summary>
public bool Backup { get; set; }

/// <summary>
/// Gets or sets a value indicating whether to force conversion of project files,
/// even if they are marked as read-only.
/// </summary>
public bool Force { get; set; }

/// <summary>
/// Gets or sets a value indicating whether to perform a dry run,
/// simulating the conversion without making any changes.
/// </summary>
public bool DryRun { get; set; }
}
}
40 changes: 23 additions & 17 deletions src/PackageReferenceVersionToAttributeExtension/Package.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ namespace PackageReferenceVersionToAttributeExtension
using EnvDTE80;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Shell;
using PackageReferenceVersionToAttribute;
Expand Down Expand Up @@ -49,25 +50,30 @@ protected override void InitializeServices(IServiceCollection services)
{
base.InitializeServices(services);

// register services
services.AddSingleton<IFileService, FileSystemService>();
services.AddSingleton<ISourceControlService, ProjectService>();

services.AddSingleton((serviceProvider)
=> VS.GetRequiredService<DTE, DTE2>());

services.AddSingleton<BaseCommand>();
services.AddSingleton<ProjectService>();
services.AddSingleton<ProjectConverter>();
services.AddLogging(configure =>
var options = new ProjectConverterOptions
{
configure.ClearProviders();
configure.Services.AddSingleton<ILoggerProvider, CustomLoggerProvider>();
configure.SetMinimumLevel(LogLevel.Trace);
});
Backup = true,
Force = true,
};

// register services
services.AddSingleton(Options.Create(options))
.AddSingleton<IFileService, FileService>()
.AddSingleton<ISourceControlService, ProjectService>()
.AddSingleton((serviceProvider)
=> VS.GetRequiredService<DTE, DTE2>())
.AddSingleton<BaseCommand>()
.AddSingleton<ProjectService>()
.AddSingleton<ProjectConverter>()
.AddLogging(configure =>
{
configure.ClearProviders();
configure.Services.AddSingleton<ILoggerProvider, CustomLoggerProvider>();
configure.SetMinimumLevel(LogLevel.Trace);
})

// register commands
services.RegisterCommands(ServiceLifetime.Singleton, Assembly.GetExecutingAssembly());
// register commands
.RegisterCommands(ServiceLifetime.Singleton, Assembly.GetExecutingAssembly());
}

/// <inheritdoc/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Commands\ProjectNodeCommand.cs" />
<Compile Include="Package.cs" />
<Compile Include="Services\FileSystemService.cs" />
<Compile Include="Logging\OutputWindowLogger.cs" />
<Compile Include="Services\ProjectService.cs" />
<Compile Include="source.extension.cs">
Expand Down Expand Up @@ -112,7 +111,6 @@
<IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="IDisposableAnalyzers" Version="4.0.8">

<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
Expand All @@ -122,7 +120,6 @@
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556">

<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,6 @@ public async Task CheckOutFileAsync(string filePath)

this.dte.SourceControl.CheckOutItem(filePath);
}

this.fileService.RemoveReadOnlyAttribute(filePath);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ namespace PackageReferenceVersionToAttributeExtensionTests
{
using System;
using System.ComponentModel.Design;
using System.IO;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.VisualStudio.Sdk.TestFramework;
Expand Down Expand Up @@ -136,21 +137,22 @@ public async Task ExecuteAsync_WithOneSelectedProject_SucceedsAsync()
// Arrange
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();

const string Contents = """
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="PackageA">
<Version>1.2.3</Version>
</PackageReference>
</ItemGroup>
</Project>
""";
using MockProject project = new(this.loggerFactory)
{
Name = "ProjectA",
Contents = """
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="PackageA">
<Version>1.2.3</Version>
</PackageReference>
</ItemGroup>
</Project>
""",
Contents = Contents,
};
this.mockVisualStudio.AddProjects(project);
this.mockVisualStudio.AddSelections(project);
Expand All @@ -171,6 +173,10 @@ public async Task ExecuteAsync_WithOneSelectedProject_SucceedsAsync()
</Project>
""",
project.Contents);

string backupFilePath = $"{project.Path}.bak";
Assert.IsTrue(File.Exists(backupFilePath));
Assert.AreEqual(Contents, File.ReadAllText(backupFilePath));
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ public MockProject(ILoggerFactory loggerFactory)
/// </summary>
public MockHierarchyItem RootItem { get; }

/// <summary>
/// Gets the path.
/// </summary>
public string Path => this.tempFile.FilePath;

/// <summary>
/// Gets or sets the contents.
/// </summary>
Expand Down
24 changes: 0 additions & 24 deletions src/PackageReferenceVersionToAttributeTool/NullFileService.cs

This file was deleted.

Loading

0 comments on commit fe74025

Please sign in to comment.