Skip to content

Commit 8c0fac9

Browse files
committed
Feat: Icon downloader
1 parent 08a4203 commit 8c0fac9

File tree

6 files changed

+246
-89
lines changed

6 files changed

+246
-89
lines changed

src/CodeOfChaos.CliArgsParser.Generators/CodeOfChaos.CliArgsParser.Generators.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,13 @@
2323
<DebugType>embedded</DebugType>
2424
<PackageLicenseFile>LICENSE</PackageLicenseFile>
2525
<PackageReadmeFile>README.md</PackageReadmeFile>
26+
<PackageIcon>icon.png</PackageIcon>
2627
</PropertyGroup>
2728

2829
<ItemGroup>
2930
<None Include="..\..\LICENSE" Pack="true" PackagePath="" />
3031
<None Include="..\..\README.md" Pack="true" PackagePath="" />
32+
<None Include="icon.png" Pack="true" PackagePath="" />
3133
</ItemGroup>
3234

3335
<ItemGroup>
Lines changed: 121 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,137 @@
11
// ---------------------------------------------------------------------------------------------------------------------
22
// Imports
33
// ---------------------------------------------------------------------------------------------------------------------
4+
using AterraEngine.Unions;
45
using CodeOfChaos.CliArgsParser.Attributes;
56
using CodeOfChaos.CliArgsParser.Contracts;
7+
using CodeOfChaos.CliArgsParser.Library.Shared;
8+
using System.Xml.Linq;
69

710
namespace CodeOfChaos.CliArgsParser.Library.CommandAtlases.DownloadIcon;
811
// ---------------------------------------------------------------------------------------------------------------------
912
// Code
1013
// ---------------------------------------------------------------------------------------------------------------------
11-
[CliArgsCommand("download-icon")]
12-
[CliArgsDescription("Downloads the icon for the specified project.")]
14+
[CliArgsCommand("nuget-download-icon")]
15+
[CliArgsDescription("Downloads and assigns the icon, to be used as nuget package's icon, for the specified project.")]
1316
public partial class DownloadIconCommand : ICommand<DownloadIconParameters> {
1417

1518
// -----------------------------------------------------------------------------------------------------------------
1619
// Methods
1720
// -----------------------------------------------------------------------------------------------------------------
18-
public Task ExecuteAsync(DownloadIconParameters parameters) => throw new NotImplementedException();
21+
public async Task ExecuteAsync(DownloadIconParameters parameters) {
22+
Console.WriteLine("Downloading Icon...");
23+
SuccessOrFailure getResult = await TryGetIcon(parameters);
24+
if (getResult is { IsFailure: true, AsFailure.Value: var getError }) {
25+
Console.WriteLine(getError);
26+
}
27+
28+
Console.WriteLine("Icon downloaded successfully.");
29+
SuccessOrFailure applyResult = await ApplyIcon(parameters);
30+
if (applyResult is { IsFailure: true, AsFailure.Value: var applyError }) {
31+
Console.WriteLine(applyError);
32+
}
33+
Console.WriteLine("Icon applied successfully.");
34+
}
35+
36+
private static async Task<SuccessOrFailure> ApplyIcon(DownloadIconParameters args) {
37+
string[] projectFiles = CsProjHelpers.AsProjectPaths(args.Root, args.SourceFolder, args.GetProjects());
38+
if (projectFiles.Length == 0) {
39+
return new Failure<string>("No projects specified");
40+
}
41+
42+
await foreach (XDocument document in CsProjHelpers.GetProjectFiles(projectFiles)) {
43+
44+
// Loop through each project file's XML document
45+
foreach (XElement propertyGroup in document.Root?.Elements("PropertyGroup")!) {
46+
XElement? iconElement = propertyGroup.Element("PackageIcon");
47+
48+
// If <PackageIcon> does not exist, create it
49+
if (iconElement is null) {
50+
iconElement = new XElement("PackageIcon", "icon.png");
51+
propertyGroup.Add(iconElement);
52+
}
53+
else {
54+
// Update the value to ensure it's "icon.png"
55+
iconElement.Value = "icon.png";
56+
}
57+
58+
// Look for ItemGroup containing Packable items
59+
XElement? packableItemGroup = document.Root?
60+
.Elements("ItemGroup")
61+
.FirstOrDefault(group => group.Elements("None")
62+
.Any(item => item.Attribute("Pack")?.Value == "true"));
63+
64+
if (packableItemGroup is null) {
65+
// Create the ItemGroup if it doesn't exist
66+
packableItemGroup = new XElement("ItemGroup");
67+
document.Root?.Add(packableItemGroup);
68+
}
69+
70+
// Check if the icon.png item already exists
71+
bool iconExists = packableItemGroup.Elements("None")
72+
.Any(item => item.Attribute("Include")?.Value.EndsWith("icon.png") == true);
73+
74+
if (iconExists) continue;
75+
76+
// Add the icon.png reference if it doesn't exist
77+
var newIconElement = new XElement("None",
78+
new XAttribute("Include", "icon.png"),
79+
new XAttribute("Pack", "true"),
80+
new XAttribute("PackagePath", ""));
81+
packableItemGroup.Add(newIconElement);
82+
}
83+
}
84+
85+
return new Success();
86+
}
87+
88+
private static async Task<SuccessOrFailure> TryGetIcon(DownloadIconParameters parameters) {
89+
try {
90+
// Validate Origin
91+
if (string.IsNullOrWhiteSpace(parameters.Origin)) {
92+
return "Error: The origin of the icon is not specified.";
93+
}
94+
95+
// Assume Origin could be either a URL or a file path
96+
bool isUrl = Uri.TryCreate(parameters.Origin, UriKind.Absolute, out Uri? uriResult) && (uriResult.Scheme == Uri.UriSchemeHttp || uriResult.Scheme == Uri.UriSchemeHttps);
97+
98+
// Placeholder for the final icon's path
99+
string destinationPath = Path.Combine(parameters.Root, parameters.IconFolder, "icon.png");
100+
101+
if (isUrl) {
102+
// Download the file from URL using HttpClient
103+
using var client = new HttpClient();
104+
Console.WriteLine($"Downloading icon from URL: {parameters.Origin}");
105+
106+
using HttpResponseMessage response = await client.GetAsync(parameters.Origin);
107+
if (!response.IsSuccessStatusCode) {
108+
return $"Error: Failed to download the icon. HTTP Status: {response.StatusCode}";
109+
}
110+
111+
byte[] iconBytes = await response.Content.ReadAsByteArrayAsync();
112+
await File.WriteAllBytesAsync(destinationPath, iconBytes);
113+
114+
Console.WriteLine($"Icon downloaded successfully to: {destinationPath}");
115+
}
116+
else {
117+
// Treat as file system path and copy the icon
118+
Console.WriteLine($"Copying icon from local path: {parameters.Origin}");
119+
120+
if (!File.Exists(parameters.Origin)) {
121+
return $"Error: The specified origin file does not exist: {parameters.Origin}";
122+
}
123+
124+
File.Copy(parameters.Origin, destinationPath, true);
125+
Console.WriteLine($"Icon copied successfully to: {destinationPath}");
126+
}
127+
128+
// If all operations succeed
129+
return new Success();
130+
}
131+
catch (Exception ex) {
132+
// Return any unexpected errors
133+
return$"Unexpected error: {ex.Message}";
134+
}
135+
}
136+
19137
}

src/CodeOfChaos.CliArgsParser.Library/CommandAtlases/VersionBump/VersionBumpCommand.cs

Lines changed: 10 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,14 @@
44
using AterraEngine.Unions;
55
using CodeOfChaos.CliArgsParser.Attributes;
66
using CodeOfChaos.CliArgsParser.Contracts;
7-
using System.Diagnostics;
8-
using System.Xml;
7+
using CodeOfChaos.CliArgsParser.Library.Shared;
98
using System.Xml.Linq;
109

1110
namespace CodeOfChaos.CliArgsParser.Library.CommandAtlases.VersionBump;
1211
// ---------------------------------------------------------------------------------------------------------------------
1312
// Code
1413
// ---------------------------------------------------------------------------------------------------------------------
15-
[CliArgsCommand("version-bump")]
14+
[CliArgsCommand("git-version-bump")]
1615
[CliArgsDescription("Bumps the version of the projects specified in the projects argument.")]
1716
public partial class VersionBumpCommand : ICommand<VersionBumpParameters> {
1817

@@ -30,14 +29,14 @@ public async Task ExecuteAsync(VersionBumpParameters parameters) {
3029
SemanticVersionDto updatedVersion = bumpResult.AsSuccess.Value;
3130

3231
Console.WriteLine("Git committing ...");
33-
SuccessOrFailure gitCommitResult = await TryCreateGitCommit(updatedVersion);
32+
SuccessOrFailure gitCommitResult = await GitHelpers.TryCreateGitCommit(updatedVersion);
3433
if (gitCommitResult is { IsFailure: true, AsFailure.Value: var errorCommiting }) {
3534
Console.WriteLine(errorCommiting);
3635
return;
3736
}
3837

3938
Console.WriteLine("Git tagging ...");
40-
SuccessOrFailure gitTagResult = await TryCreateGitTag(updatedVersion);
39+
SuccessOrFailure gitTagResult = await GitHelpers.TryCreateGitTag(updatedVersion);
4140
if (gitTagResult is { IsFailure: true, AsFailure.Value: var errorTagging }) {
4241
Console.WriteLine(errorTagging);
4342
return;
@@ -48,7 +47,7 @@ public async Task ExecuteAsync(VersionBumpParameters parameters) {
4847
if (!parameters.PushToRemote) return;
4948

5049
Console.WriteLine("Pushing to origin ...");
51-
SuccessOrFailure pushResult = await TryPushToOrigin();
50+
SuccessOrFailure pushResult = await GitHelpers.TryPushToOrigin();
5251
if (pushResult is { IsFailure: true, AsFailure.Value: var errorPushing }) {
5352
Console.WriteLine(errorPushing);
5453
return;
@@ -57,87 +56,29 @@ public async Task ExecuteAsync(VersionBumpParameters parameters) {
5756
Console.WriteLine("Pushed to origin successfully.");
5857
}
5958

60-
private static async Task<SuccessOrFailure> TryPushToOrigin() {
61-
var gitTagInfo = new ProcessStartInfo("git", "push origin --tags") {
62-
RedirectStandardOutput = true,
63-
UseShellExecute = false,
64-
CreateNoWindow = true
65-
};
66-
67-
using Process? gitTagProcess = Process.Start(gitTagInfo);
68-
Console.WriteLine(await gitTagProcess?.StandardOutput.ReadToEndAsync()!);
69-
await gitTagProcess.WaitForExitAsync();
70-
71-
if (gitTagProcess.ExitCode != 0) return "Push to origin failed";
72-
73-
return new Success();
74-
}
75-
76-
private static async Task<SuccessOrFailure> TryCreateGitTag(SemanticVersionDto updatedVersion) {
77-
var gitTagInfo = new ProcessStartInfo("git", "tag v" + updatedVersion) {
78-
RedirectStandardOutput = true,
79-
UseShellExecute = false,
80-
CreateNoWindow = true
81-
};
82-
83-
using Process? gitTagProcess = Process.Start(gitTagInfo);
84-
Console.WriteLine(await gitTagProcess?.StandardOutput.ReadToEndAsync()!);
85-
await gitTagProcess.WaitForExitAsync();
86-
87-
if (gitTagProcess.ExitCode != 0) return "Git Tagging failed";
88-
89-
return new Success();
90-
}
91-
92-
private static async Task<SuccessOrFailure> TryCreateGitCommit(SemanticVersionDto updatedVersion) {
93-
var gitCommitInfo = new ProcessStartInfo("git", $"commit -am \"VersionBump : v{updatedVersion}\"") {
94-
RedirectStandardOutput = true,
95-
UseShellExecute = false,
96-
CreateNoWindow = true
97-
};
98-
99-
using Process? gitCommitProcess = Process.Start(gitCommitInfo);
100-
Console.WriteLine(await gitCommitProcess?.StandardOutput.ReadToEndAsync()!);
101-
await gitCommitProcess.WaitForExitAsync();
102-
103-
if (gitCommitProcess.ExitCode != 0) return "Git Commit failed";
104-
105-
return new Success();
106-
}
107-
10859

10960
private static async Task<SuccessOrFailure<SemanticVersionDto>> BumpVersion(VersionBumpParameters args) {
110-
string[] projectFiles = args.GetProjects();
61+
string[] projectFiles = CsProjHelpers.AsProjectPaths(args.Root, args.SourceFolder, args.GetProjects());
11162
if (projectFiles.Length == 0) {
11263
return new Failure<string>("No projects specified");
11364
}
11465

11566
VersionSection sectionToBump = args.Section;
11667
SemanticVersionDto? versionDto = null;
11768

118-
foreach (string projectFile in projectFiles) {
119-
string path = Path.Combine(args.Root, args.SourceFolder, projectFile, projectFile + ".csproj");
120-
if (!File.Exists(path)) {
121-
return new Failure<string>($"Could not find project file {projectFile}");
122-
}
123-
124-
XDocument document;
125-
await using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true)) {
126-
document = await XDocument.LoadAsync(stream, LoadOptions.PreserveWhitespace, CancellationToken.None);
127-
}
128-
69+
await foreach (XDocument document in CsProjHelpers.GetProjectFiles(projectFiles)) {
12970
XElement? versionElement = document
13071
.Descendants("PropertyGroup")
13172
.Elements("Version")
13273
.FirstOrDefault();
13374

13475
if (versionElement == null) {
135-
return new Failure<string>($"File {projectFile} did not contain a version element");
76+
return new Failure<string>($"File did not contain a version element");
13677
}
13778

13879
if (versionDto is null) {
13980
if (!SemanticVersionDto.TryParse(versionElement.Value, out SemanticVersionDto? dto)) {
140-
return new Failure<string>($"File {projectFile} contained an invalid version element: {versionElement.Value}");
81+
return new Failure<string>($"File contained an invalid version element: {versionElement.Value}");
14182
}
14283

14384
dto.BumpVersion(sectionToBump);
@@ -146,20 +87,7 @@ private static async Task<SuccessOrFailure<SemanticVersionDto>> BumpVersion(Vers
14687
}
14788

14889
versionElement.Value = versionDto.ToString();
149-
150-
var settings = new XmlWriterSettings {
151-
Indent = true,
152-
IndentChars = " ",
153-
Async = true,
154-
OmitXmlDeclaration = true
155-
};
156-
157-
await using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, 4096, true)) {
158-
await using var writer = XmlWriter.Create(stream, settings);
159-
document.Save(writer);
160-
}
161-
162-
Console.WriteLine($"Updated {projectFile} version to {versionElement.Value}");
90+
Console.WriteLine($"Updated version to {versionElement.Value}");
16391
}
16492

16593
return versionDto is not null
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// ---------------------------------------------------------------------------------------------------------------------
2+
// Imports
3+
// ---------------------------------------------------------------------------------------------------------------------
4+
using System.Xml;
5+
using System.Xml.Linq;
6+
7+
namespace CodeOfChaos.CliArgsParser.Library.Shared;
8+
9+
// ---------------------------------------------------------------------------------------------------------------------
10+
// Code
11+
// ---------------------------------------------------------------------------------------------------------------------
12+
public static class CsProjHelpers {
13+
public static string[] AsProjectPaths(string root, string sourcefolder, string[] projectNames) {
14+
return projectNames.Select(x => Path.Combine(root, sourcefolder, x, x + ".csproj")).ToArray();
15+
}
16+
17+
public static async IAsyncEnumerable<XDocument> GetProjectFiles(string[] projectPaths) {
18+
var settings = new XmlWriterSettings {
19+
Indent = true,
20+
IndentChars = " ",
21+
Async = true,
22+
OmitXmlDeclaration = true
23+
};
24+
25+
foreach (string path in projectPaths) {
26+
if (!File.Exists(path)) {
27+
throw new FileNotFoundException($"Could not find project file {path}");
28+
}
29+
30+
XDocument document;
31+
await using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true)) {
32+
document = await XDocument.LoadAsync(stream, LoadOptions.PreserveWhitespace, CancellationToken.None);
33+
}
34+
35+
yield return document;
36+
37+
await using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, 4096, true)) {
38+
await using var writer = XmlWriter.Create(stream, settings);
39+
document.Save(writer);
40+
}
41+
}
42+
}
43+
}

0 commit comments

Comments
 (0)