split up extension files

This commit is contained in:
Jesse Plamondon-Willard
2026-03-26 00:48:58 -04:00
parent bdf5b0eb48
commit 87bfc1b093
29 changed files with 272 additions and 243 deletions
@@ -9,6 +9,7 @@ using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Schema;
using StardewModdingAPI.Web.Framework;
using StardewModdingAPI.Web.Framework.Extensions;
using StardewModdingAPI.Web.Framework.Storage;
using StardewModdingAPI.Web.ViewModels.JsonValidator;
@@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using StardewModdingAPI.Toolkit.Utilities;
using StardewModdingAPI.Web.Framework;
using StardewModdingAPI.Web.Framework.Extensions;
using StardewModdingAPI.Web.Framework.LogParsing;
using StardewModdingAPI.Web.Framework.LogParsing.Models;
using StardewModdingAPI.Web.Framework.Storage;
@@ -23,6 +23,7 @@ using StardewModdingAPI.Web.Framework.Clients.ModDrop;
using StardewModdingAPI.Web.Framework.Clients.Nexus;
using StardewModdingAPI.Web.Framework.Clients.UpdateManifest;
using StardewModdingAPI.Web.Framework.ConfigModels;
using StardewModdingAPI.Web.Framework.Extensions;
using StardewModdingAPI.Web.Framework.Metrics;
namespace StardewModdingAPI.Web.Controllers;
@@ -0,0 +1,24 @@
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.Rendering;
using Newtonsoft.Json;
namespace StardewModdingAPI.Web.Framework.Extensions;
/// <summary>Provides extensions for <see cref="RazorPageBase"/>.</summary>
public static class RazorPageBaseExtensions
{
/// <param name="page">The page to extend.</param>
extension(RazorPageBase page)
{
/// <summary>Get a serialized JSON representation of the value.</summary>
/// <param name="value">The value to serialize.</param>
/// <returns>The serialized JSON.</returns>
/// <remarks>This bypasses unnecessary validation (e.g. not allowing null values) in <see cref="IJsonHelper.Serialize"/>.</remarks>
public IHtmlContent ForJson(object? value)
{
string json = JsonConvert.SerializeObject(value);
return new HtmlString(json);
}
}
}
@@ -1,24 +1,14 @@
using System;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Routing;
using Newtonsoft.Json;
namespace StardewModdingAPI.Web.Framework;
namespace StardewModdingAPI.Web.Framework.Extensions;
/// <summary>Provides extensions on ASP.NET Core types.</summary>
public static class Extensions
/// <summary>Provides extensions for <see cref="IUrlHelper"/>.</summary>
public static class UrlHelperExtensions
{
/*********
** Public methods
*********/
/****
** View helpers
****/
/// <param name="helper">The URL helper to extend.</param>
extension(IUrlHelper helper)
{
@@ -60,18 +50,4 @@ public static class Extensions
return helper.Content($"{url}{delimiter}v={Program.CacheBustValue}");
}
}
/// <param name="page">The page to extend.</param>
extension(RazorPageBase page)
{
/// <summary>Get a serialized JSON representation of the value.</summary>
/// <param name="value">The value to serialize.</param>
/// <returns>The serialized JSON.</returns>
/// <remarks>This bypasses unnecessary validation (e.g. not allowing null values) in <see cref="IJsonHelper.Serialize"/>.</remarks>
public IHtmlContent ForJson(object? value)
{
string json = JsonConvert.SerializeObject(value);
return new HtmlString(json);
}
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
@using StardewModdingAPI.Web.Framework
@using StardewModdingAPI.Web.Framework.Extensions
@model StardewModdingAPI.Web.ViewModels.IndexModel
@{
ViewData["Title"] = "SMAPI";
+1 -1
View File
@@ -1,4 +1,4 @@
@using StardewModdingAPI.Web.Framework
@using StardewModdingAPI.Web.Framework.Extensions
@{
ViewData["Title"] = "SMAPI privacy notes";
}
@@ -1,5 +1,5 @@
@using Humanizer
@using StardewModdingAPI.Web.Framework
@using StardewModdingAPI.Web.Framework.Extensions
@using StardewModdingAPI.Web.ViewModels.JsonValidator
@model JsonValidatorModel
@@ -1,6 +1,7 @@
@using Humanizer
@using StardewModdingAPI.Toolkit.Utilities
@using StardewModdingAPI.Web.Framework
@using StardewModdingAPI.Web.Framework.Extensions
@using StardewModdingAPI.Web.Framework.LogParsing.Models
@using StardewModdingAPI.Web.ViewModels
@model StardewModdingAPI.Web.ViewModels.LogParserModel
+1 -1
View File
@@ -1,5 +1,5 @@
@using Humanizer
@using StardewModdingAPI.Web.Framework
@using StardewModdingAPI.Web.Framework.Extensions
@model StardewModdingAPI.Web.ViewModels.ModListModel
@{
ViewData["Title"] = "Mod compatibility";
+1 -1
View File
@@ -1,6 +1,6 @@
@using Microsoft.Extensions.Options
@using StardewModdingAPI.Web.Framework
@using StardewModdingAPI.Web.Framework.ConfigModels
@using StardewModdingAPI.Web.Framework.Extensions
@inject IOptions<SiteConfig> SiteConfig
<!DOCTYPE html>
@@ -3,6 +3,7 @@ using System.Buffers;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using StardewModdingAPI.Framework.Extensions;
using StardewValley;
namespace StardewModdingAPI.Framework.Content;
@@ -11,6 +11,7 @@ using Microsoft.Xna.Framework.Graphics;
using StardewModdingAPI.Events;
using StardewModdingAPI.Framework.Content;
using StardewModdingAPI.Framework.ContentManagers;
using StardewModdingAPI.Framework.Extensions;
using StardewModdingAPI.Framework.Reflection;
using StardewModdingAPI.Framework.Utilities;
using StardewModdingAPI.Metadata;
@@ -10,6 +10,7 @@ using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using StardewModdingAPI.Framework.Content;
using StardewModdingAPI.Framework.Exceptions;
using StardewModdingAPI.Framework.Extensions;
using StardewModdingAPI.Framework.Reflection;
using StardewValley;
@@ -8,6 +8,7 @@ using Microsoft.Xna.Framework.Graphics;
using StardewModdingAPI.Events;
using StardewModdingAPI.Framework.Content;
using StardewModdingAPI.Framework.Exceptions;
using StardewModdingAPI.Framework.Extensions;
using StardewModdingAPI.Framework.Reflection;
using StardewModdingAPI.Framework.Utilities;
using StardewModdingAPI.Internal;
@@ -12,6 +12,7 @@ using Microsoft.Xna.Framework.Graphics;
using SkiaSharp;
using StardewModdingAPI.Framework.Content;
using StardewModdingAPI.Framework.Exceptions;
using StardewModdingAPI.Framework.Extensions;
using StardewModdingAPI.Framework.Reflection;
using StardewModdingAPI.Toolkit.Serialization;
using StardewModdingAPI.Toolkit.Utilities;
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using StardewModdingAPI.Events;
using StardewModdingAPI.Framework.Extensions;
using StardewModdingAPI.Internal;
namespace StardewModdingAPI.Framework.Events;
@@ -0,0 +1,25 @@
using System.Collections.Generic;
using System.Linq;
using StardewValley.Menus;
namespace StardewModdingAPI.Framework.Extensions;
/// <summary>Provides internal extensions for <see cref="IClickableMenu"/>.</summary>
internal static class ClickableMenuExtensions
{
/// <param name="menu">The clickable menu to extend.</param>
extension(IClickableMenu menu)
{
/// <summary>Get a string representation of the menu chain to the given menu (including the specified menu), in parent to child order.</summary>
public string GetMenuChainLabel()
{
static IEnumerable<IClickableMenu> GetAncestors(IClickableMenu menu)
{
for (; menu != null; menu = menu.GetParentMenu())
yield return menu;
}
return string.Join(" > ", GetAncestors(menu).Reverse().Select(p => p.GetType().FullName));
}
}
}
@@ -0,0 +1,20 @@
using StardewModdingAPI.Framework.Events;
namespace StardewModdingAPI.Framework.Extensions;
/// <summary>Provides internal extensions for <see cref="ManagedEvent{T}"/>.</summary>
internal static class ManagedEventExtensions
{
/// <typeparam name="TEventArgs">The event args type to construct.</typeparam>
/// <param name="event">The event to extend.</param>
extension<TEventArgs>(ManagedEvent<TEventArgs> @event)
where TEventArgs : new()
{
/// <summary>Raise the event using the default event args and notify all handlers.</summary>
public void RaiseEmpty()
{
if (@event.HasListeners)
@event.Raise(Singleton<TEventArgs>.Instance);
}
}
}
@@ -0,0 +1,30 @@
using System;
namespace StardewModdingAPI.Framework.Extensions;
/// <summary>Provides internal extensions for <see cref="IModMetadata"/>.</summary>
internal static class ModMetadataExtensions
{
/// <param name="metadata">The mod metadata to extend.</param>
extension(IModMetadata metadata)
{
/// <summary>Log a message using the mod's monitor.</summary>
/// <param name="message">The message to log.</param>
/// <param name="level">The log severity level.</param>
public void LogAsMod(string message, LogLevel level = LogLevel.Trace)
{
if (metadata.Monitor is null)
throw new InvalidOperationException($"Can't log as mod {metadata.DisplayName}: mod is broken or a content pack. Logged message:\n[{level}] {message}");
metadata.Monitor.Log(message, level);
}
/// <summary>Log a message using the mod's monitor, but only if it hasn't already been logged since the last game launch.</summary>
/// <param name="message">The message to log.</param>
/// <param name="level">The log severity level.</param>
public void LogAsModOnce(string message, LogLevel level = LogLevel.Trace)
{
metadata.Monitor?.LogOnce(message, level);
}
}
}
@@ -0,0 +1,24 @@
using System.Collections.Generic;
namespace StardewModdingAPI.Framework.Extensions;
/// <summary>Provides internal extensions for <see cref="IMonitor"/>.</summary>
internal static class MonitorExtensions
{
/// <param name="monitor">The monitor to extend.</param>
extension(IMonitor monitor)
{
/// <summary>Log a message for the player or developer the first time it occurs.</summary>
/// <param name="hash">The hash of logged messages.</param>
/// <param name="message">The message to log.</param>
/// <param name="level">The log severity level.</param>
public void LogOnce(HashSet<string> hash, string message, LogLevel level = LogLevel.Trace)
{
if (!hash.Contains(message))
{
monitor.Log(message, level);
hash.Add(message);
}
}
}
}
@@ -0,0 +1,74 @@
using System;
using System.Threading;
namespace StardewModdingAPI.Framework.Extensions;
/// <summary>Provides internal extensions for <see cref="ReaderWriterLockSlim"/>.</summary>
internal static class ReaderWriterLockSlimExtensions
{
/// <param name="lock">The lock to extend.</param>
extension(ReaderWriterLockSlim @lock)
{
/// <summary>Run code within a read lock.</summary>
/// <param name="action">The action to perform.</param>
public void InReadLock(Action action)
{
@lock.EnterReadLock();
try
{
action();
}
finally
{
@lock.ExitReadLock();
}
}
/// <summary>Run code within a read lock.</summary>
/// <typeparam name="TReturn">The action's return value.</typeparam>
/// <param name="action">The action to perform.</param>
public TReturn InReadLock<TReturn>(Func<TReturn> action)
{
@lock.EnterReadLock();
try
{
return action();
}
finally
{
@lock.ExitReadLock();
}
}
/// <summary>Run code within a write lock.</summary>
/// <param name="action">The action to perform.</param>
public void InWriteLock(Action action)
{
@lock.EnterWriteLock();
try
{
action();
}
finally
{
@lock.ExitWriteLock();
}
}
/// <summary>Run code within a write lock.</summary>
/// <typeparam name="TReturn">The action's return value.</typeparam>
/// <param name="action">The action to perform.</param>
public TReturn InWriteLock<TReturn>(Func<TReturn> action)
{
@lock.EnterWriteLock();
try
{
return action();
}
finally
{
@lock.ExitWriteLock();
}
}
}
}
@@ -0,0 +1,19 @@
using Microsoft.Xna.Framework.Graphics;
using StardewModdingAPI.Framework.Reflection;
namespace StardewModdingAPI.Framework.Extensions;
/// <summary>Provides internal extensions for <see cref="SpriteBatch"/>.</summary>
internal static class SpriteBatchExtensions
{
/// <param name="spriteBatch">The sprite batch to extend.</param>
extension(SpriteBatch spriteBatch)
{
/// <summary>Get whether the sprite batch is between a begin and end pair.</summary>
/// <param name="reflection">The reflection helper with which to access private fields.</param>
public bool IsOpen(Reflector reflection)
{
return reflection.GetField<bool>(spriteBatch, "_beginCalled").GetValue();
}
}
}
@@ -0,0 +1,34 @@
using System.Diagnostics.CodeAnalysis;
using Microsoft.Xna.Framework.Graphics;
namespace StardewModdingAPI.Framework.Extensions;
/// <summary>Provides internal extensions for <see cref="Texture2D"/>.</summary>
internal static class Texture2dExtensions
{
/// <param name="texture">The texture to extend.</param>
extension(Texture2D? texture)
{
/// <summary>Set the texture name field.</summary>
/// <param name="assetName">The asset name to set.</param>
/// <returns>Returns the texture for chaining.</returns>
[return: NotNullIfNotNull(nameof(texture))]
public Texture2D? SetName(IAssetName assetName)
{
texture?.Name = assetName.Name;
return texture;
}
/// <summary>Set the texture name field.</summary>
/// <param name="assetName">The asset name to set.</param>
/// <returns>Returns the texture for chaining.</returns>
[return: NotNullIfNotNull(nameof(texture))]
public Texture2D? SetName(string assetName)
{
texture?.Name = assetName;
return texture;
}
}
}
-211
View File
@@ -1,211 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using Microsoft.Xna.Framework.Graphics;
using StardewModdingAPI.Framework.Events;
using StardewModdingAPI.Framework.Reflection;
using StardewValley.Menus;
namespace StardewModdingAPI.Framework;
/// <summary>Provides extension methods for SMAPI's internal use.</summary>
internal static class InternalExtensions
{
/*********
** Public methods
*********/
/****
** IMonitor
****/
/// <param name="monitor">The monitor to extend.</param>
extension(IMonitor monitor)
{
/// <summary>Log a message for the player or developer the first time it occurs.</summary>
/// <param name="hash">The hash of logged messages.</param>
/// <param name="message">The message to log.</param>
/// <param name="level">The log severity level.</param>
public void LogOnce(HashSet<string> hash, string message, LogLevel level = LogLevel.Trace)
{
if (!hash.Contains(message))
{
monitor.Log(message, level);
hash.Add(message);
}
}
}
/****
** IModMetadata
****/
/// <param name="metadata">The mod metadata to extend.</param>
extension(IModMetadata metadata)
{
/// <summary>Log a message using the mod's monitor.</summary>
/// <param name="message">The message to log.</param>
/// <param name="level">The log severity level.</param>
public void LogAsMod(string message, LogLevel level = LogLevel.Trace)
{
if (metadata.Monitor is null)
throw new InvalidOperationException($"Can't log as mod {metadata.DisplayName}: mod is broken or a content pack. Logged message:\n[{level}] {message}");
metadata.Monitor.Log(message, level);
}
/// <summary>Log a message using the mod's monitor, but only if it hasn't already been logged since the last game launch.</summary>
/// <param name="message">The message to log.</param>
/// <param name="level">The log severity level.</param>
public void LogAsModOnce(string message, LogLevel level = LogLevel.Trace)
{
metadata.Monitor?.LogOnce(message, level);
}
}
/****
** ManagedEvent
****/
/// <typeparam name="TEventArgs">The event args type to construct.</typeparam>
/// <param name="event">The event to extend.</param>
extension<TEventArgs>(ManagedEvent<TEventArgs> @event)
where TEventArgs : new()
{
/// <summary>Raise the event using the default event args and notify all handlers.</summary>
public void RaiseEmpty()
{
if (@event.HasListeners)
@event.Raise(Singleton<TEventArgs>.Instance);
}
}
/****
** ReaderWriterLockSlim
****/
/// <param name="lock">The lock to extend.</param>
extension(ReaderWriterLockSlim @lock)
{
/// <summary>Run code within a read lock.</summary>
/// <param name="action">The action to perform.</param>
public void InReadLock(Action action)
{
@lock.EnterReadLock();
try
{
action();
}
finally
{
@lock.ExitReadLock();
}
}
/// <summary>Run code within a read lock.</summary>
/// <typeparam name="TReturn">The action's return value.</typeparam>
/// <param name="action">The action to perform.</param>
public TReturn InReadLock<TReturn>(Func<TReturn> action)
{
@lock.EnterReadLock();
try
{
return action();
}
finally
{
@lock.ExitReadLock();
}
}
/// <summary>Run code within a write lock.</summary>
/// <param name="action">The action to perform.</param>
public void InWriteLock(Action action)
{
@lock.EnterWriteLock();
try
{
action();
}
finally
{
@lock.ExitWriteLock();
}
}
/// <summary>Run code within a write lock.</summary>
/// <typeparam name="TReturn">The action's return value.</typeparam>
/// <param name="action">The action to perform.</param>
public TReturn InWriteLock<TReturn>(Func<TReturn> action)
{
@lock.EnterWriteLock();
try
{
return action();
}
finally
{
@lock.ExitWriteLock();
}
}
}
/****
** IActiveClickableMenu
****/
/// <param name="menu">The clickable menu to extend.</param>
extension(IClickableMenu menu)
{
/// <summary>Get a string representation of the menu chain to the given menu (including the specified menu), in parent to child order.</summary>
public string GetMenuChainLabel()
{
static IEnumerable<IClickableMenu> GetAncestors(IClickableMenu menu)
{
for (; menu != null; menu = menu.GetParentMenu())
yield return menu;
}
return string.Join(" > ", GetAncestors(menu).Reverse().Select(p => p.GetType().FullName));
}
}
/****
** Sprite batch
****/
/// <param name="spriteBatch">The sprite batch to extend.</param>
extension(SpriteBatch spriteBatch)
{
/// <summary>Get whether the sprite batch is between a begin and end pair.</summary>
/// <param name="reflection">The reflection helper with which to access private fields.</param>
public bool IsOpen(Reflector reflection)
{
return reflection.GetField<bool>(spriteBatch, "_beginCalled").GetValue();
}
}
/****
** Texture2D
****/
/// <param name="texture">The texture to extend.</param>
extension(Texture2D? texture)
{
/// <summary>Set the texture name field.</summary>
/// <param name="assetName">The asset name to set.</param>
/// <returns>Returns the texture for chaining.</returns>
[return: NotNullIfNotNull(nameof(texture))]
public Texture2D? SetName(IAssetName assetName)
{
texture?.Name = assetName.Name;
return texture;
}
/// <summary>Set the texture name field.</summary>
/// <param name="assetName">The asset name to set.</param>
/// <returns>Returns the texture for chaining.</returns>
[return: NotNullIfNotNull(nameof(texture))]
public Texture2D? SetName(string assetName)
{
texture?.Name = assetName;
return texture;
}
}
}
@@ -7,6 +7,7 @@ using System.Reflection;
using Mono.Cecil;
using Mono.Cecil.Cil;
using StardewModdingAPI.Framework.Exceptions;
using StardewModdingAPI.Framework.Extensions;
using StardewModdingAPI.Framework.ModLoading.Framework;
using StardewModdingAPI.Framework.ModLoading.Symbols;
using StardewModdingAPI.Metadata;
+1
View File
@@ -26,6 +26,7 @@ using StardewModdingAPI.Framework.ContentManagers;
using StardewModdingAPI.Framework.Deprecations;
using StardewModdingAPI.Framework.Events;
using StardewModdingAPI.Framework.Exceptions;
using StardewModdingAPI.Framework.Extensions;
using StardewModdingAPI.Framework.Input;
using StardewModdingAPI.Framework.Logging;
using StardewModdingAPI.Framework.Models;
+1
View File
@@ -6,6 +6,7 @@ using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using StardewModdingAPI.Enums;
using StardewModdingAPI.Events;
using StardewModdingAPI.Framework.Extensions;
using StardewModdingAPI.Framework.Input;
using StardewModdingAPI.Framework.Reflection;
using StardewModdingAPI.Framework.Rendering;
+1
View File
@@ -4,6 +4,7 @@ using System.Threading.Tasks;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using StardewModdingAPI.Enums;
using StardewModdingAPI.Framework.Extensions;
using StardewModdingAPI.Internal;
using StardewModdingAPI.Utilities;
using StardewValley;