diff --git a/source/Nuke.Tooling/ToolingExtensions.cs b/source/Nuke.Tooling/ToolingExtensions.cs
index 44c8b8b9c..86ae0f568 100644
--- a/source/Nuke.Tooling/ToolingExtensions.cs
+++ b/source/Nuke.Tooling/ToolingExtensions.cs
@@ -6,7 +6,10 @@
using System.Linq;
using JetBrains.Annotations;
using Nuke.Common.IO;
+using Nuke.Common.Utilities;
using Nuke.Common.Utilities.Collections;
+using Serilog;
+using Serilog.Events;
namespace Nuke.Common.Tooling;
@@ -65,4 +68,13 @@ public static void Open(this AbsolutePath path)
var verb = EnvironmentInfo.IsUnix ? "open" : path.DirectoryExists() ? "explorer.exe" : "call";
ProcessTasks.StartShell($"{verb} {path}");
}
+
+ ///
+ /// Prints the content of a file using the specified .
+ ///
+ public static AbsolutePath Print(this AbsolutePath path, LogEventLevel level = LogEventLevel.Information)
+ {
+ Log.Write(level, "Content of {Path}".Append(Environment.NewLine).Append(path.ReadAllText()), path);
+ return path;
+ }
}
diff --git a/source/Nuke.Utilities/Collections/Enumerable.Random.cs b/source/Nuke.Utilities/Collections/Enumerable.Random.cs
new file mode 100644
index 000000000..c38d28ae7
--- /dev/null
+++ b/source/Nuke.Utilities/Collections/Enumerable.Random.cs
@@ -0,0 +1,33 @@
+// Copyright 2023 Maintainers of NUKE.
+// Distributed under the MIT License.
+// https://github.com/nuke-build/nuke/blob/master/LICENSE
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Nuke.Common.Utilities.Collections;
+
+partial class EnumerableExtensions
+{
+ private static readonly Random s_randomNumberGenerator = new Random();
+
+ public static T Random(this IEnumerable collection)
+ {
+ var array = collection.ToArray();
+ return array[s_randomNumberGenerator.Next(array.Length)];
+ }
+
+ public static ICollection Randomize(this ICollection collection)
+ {
+ var list = collection.ToList();
+ var count = list.Count;
+ while (count > 1) {
+ count--;
+ var k = s_randomNumberGenerator.Next(count + 1);
+ (list[k], list[count]) = (list[count], list[k]);
+ }
+
+ return list;
+ }
+}
diff --git a/source/Nuke.Utilities/Object.Apply.cs b/source/Nuke.Utilities/Object.Apply.cs
new file mode 100644
index 000000000..ed5f1722e
--- /dev/null
+++ b/source/Nuke.Utilities/Object.Apply.cs
@@ -0,0 +1,16 @@
+// Copyright 2023 Maintainers of NUKE.
+// Distributed under the MIT License.
+// https://github.com/nuke-build/nuke/blob/master/LICENSE
+
+using System;
+using System.Linq;
+
+namespace Nuke.Common.Utilities;
+
+partial class ObjectExtensions
+{
+ public static TOutput Apply(this TInput input, Func transform)
+ {
+ return transform.Invoke(input);
+ }
+}
diff --git a/source/Nuke.Utilities/Task.WaitAll.cs b/source/Nuke.Utilities/Task.WaitAll.cs
new file mode 100644
index 000000000..6ca6b3215
--- /dev/null
+++ b/source/Nuke.Utilities/Task.WaitAll.cs
@@ -0,0 +1,31 @@
+// Copyright 2023 Maintainers of NUKE.
+// Distributed under the MIT License.
+// https://github.com/nuke-build/nuke/blob/master/LICENSE
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Threading.Tasks;
+using JetBrains.Annotations;
+
+namespace Nuke.Common.Utilities;
+
+[PublicAPI]
+[DebuggerNonUserCode]
+[DebuggerStepThrough]
+public static partial class TaskExtensions
+{
+ public static void WaitAll(this IEnumerable tasks)
+ {
+ var tasksArray = tasks.ToArray();
+ Task.WaitAll(tasksArray);
+ }
+
+ public static IReadOnlyCollection WaitAll(this IEnumerable> tasks)
+ {
+ var tasksArray = tasks.ToArray();
+ Task.WaitAll(tasksArray);
+ return tasksArray.Select(x => x.Result).ToList();
+ }
+}
diff --git a/source/Nuke.Utilities/Text/String.Truncate.cs b/source/Nuke.Utilities/Text/String.Truncate.cs
new file mode 100644
index 000000000..c35207631
--- /dev/null
+++ b/source/Nuke.Utilities/Text/String.Truncate.cs
@@ -0,0 +1,16 @@
+// Copyright 2023 Maintainers of NUKE.
+// Distributed under the MIT License.
+// https://github.com/nuke-build/nuke/blob/master/LICENSE
+
+using System;
+using System.Linq;
+
+namespace Nuke.Common.Utilities;
+
+partial class StringExtensions
+{
+ public static string Truncate(this string str, int maxChars)
+ {
+ return str.Length <= maxChars ? str : str.Substring(0, maxChars) + "…";
+ }
+}
diff --git a/source/Nuke.Utilities/Url.WithUtmValues.cs b/source/Nuke.Utilities/Url.WithUtmValues.cs
new file mode 100644
index 000000000..5748533b9
--- /dev/null
+++ b/source/Nuke.Utilities/Url.WithUtmValues.cs
@@ -0,0 +1,33 @@
+// Copyright 2023 Maintainers of NUKE.
+// Distributed under the MIT License.
+// https://github.com/nuke-build/nuke/blob/master/LICENSE
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using JetBrains.Annotations;
+
+namespace Nuke.Common.Utilities;
+
+[PublicAPI]
+[DebuggerNonUserCode]
+[DebuggerStepThrough]
+public static class UrlExtensions
+{
+ public static Uri WithUtmValues(this Uri uri, string medium, string source, string campaign = null, string content = null)
+ {
+ var lastSegment = uri.Segments.Last().Trim('/').Apply(x => x.IsNullOrWhiteSpace() ? null : x);
+
+ var dictionary = new Dictionary
+ {
+ ["utm_medium"] = medium.NotNullOrWhiteSpace(),
+ ["utm_source"] = source.NotNullOrWhiteSpace(),
+ ["utm_campaign"] = campaign ?? lastSegment,
+ ["utm_content"] = content ?? (campaign != null ? lastSegment : null),
+ };
+
+ var query = dictionary.Where(x => x.Value != null).Select(x => $"{x.Key}={x.Value}").Join("&");
+ return new Uri(uri.AbsoluteUri + "?" + query);
+ }
+}