diff --git a/GFramework.Core/Logging/AbstractLogger.cs b/GFramework.Core/Logging/AbstractLogger.cs
index 3ca42a22..90dee078 100644
--- a/GFramework.Core/Logging/AbstractLogger.cs
+++ b/GFramework.Core/Logging/AbstractLogger.cs
@@ -6,16 +6,42 @@ namespace GFramework.Core.Logging;
/// 日志抽象基类,封装日志级别判断、格式化与异常处理逻辑。
/// 平台日志器只需实现 Write 方法即可。
///
-public abstract class AbstractLogger(
- string? name = null,
- LogLevel minLevel = LogLevel.Info) : IStructuredLogger
+public abstract class AbstractLogger : IStructuredLogger
{
///
/// 根日志记录器的名称常量
///
public const string RootLoggerName = "ROOT";
- private readonly string _name = name ?? RootLoggerName;
+ private readonly Func _minLevelProvider;
+ private readonly string _name;
+
+ ///
+ /// 使用固定最小日志级别初始化日志器。
+ ///
+ /// 日志器名称。
+ /// 最小日志级别。
+ protected AbstractLogger(
+ string? name = null,
+ LogLevel minLevel = LogLevel.Info)
+ : this(name, () => minLevel)
+ {
+ }
+
+ ///
+ /// 使用动态最小日志级别提供器初始化日志器。
+ ///
+ /// 日志器名称。
+ /// 最小日志级别提供器。
+ protected AbstractLogger(
+ string? name,
+ Func minLevelProvider)
+ {
+ ArgumentNullException.ThrowIfNull(minLevelProvider);
+
+ _name = name ?? RootLoggerName;
+ _minLevelProvider = minLevelProvider;
+ }
#region Metadata
@@ -47,7 +73,7 @@ public abstract class AbstractLogger(
/// 如果指定级别大于等于最小级别则返回true,否则返回false
protected bool IsEnabled(LogLevel level)
{
- return level >= minLevel;
+ return level >= _minLevelProvider();
}
///
@@ -568,4 +594,4 @@ public abstract class AbstractLogger(
#region Core Pipeline (Private)
#endregion
-}
\ No newline at end of file
+}
diff --git a/GFramework.Godot.Tests/Logging/GodotLogTemplateTests.cs b/GFramework.Godot.Tests/Logging/GodotLogTemplateTests.cs
index adface63..8e9f53e2 100644
--- a/GFramework.Godot.Tests/Logging/GodotLogTemplateTests.cs
+++ b/GFramework.Godot.Tests/Logging/GodotLogTemplateTests.cs
@@ -16,7 +16,8 @@ public sealed class GodotLogTemplateTests
LogLevel.Warning,
"Game.Services.Inventory",
"Loaded",
- "orange");
+ "orange",
+ string.Empty);
var result = template.Render(context);
@@ -32,7 +33,8 @@ public sealed class GodotLogTemplateTests
LogLevel.Debug,
"Game",
"Ready",
- "cyan");
+ "cyan",
+ string.Empty);
var result = template.Render(context);
@@ -48,7 +50,8 @@ public sealed class GodotLogTemplateTests
LogLevel.Info,
"UI",
"Ready",
- "white");
+ "white",
+ string.Empty);
var result = template.Render(context);
@@ -64,7 +67,8 @@ public sealed class GodotLogTemplateTests
LogLevel.Info,
"Game",
"Ready",
- "white");
+ "white",
+ string.Empty);
var result = template.Render(context);
@@ -80,13 +84,31 @@ public sealed class GodotLogTemplateTests
LogLevel.Info,
"Game",
"Ready",
- "white");
+ "white",
+ string.Empty);
var result = template.Render(context);
Assert.That(result, Is.EqualTo("INFO "));
}
+ [Test]
+ public void Render_Should_Append_Properties_Placeholder()
+ {
+ var template = GodotLogTemplate.Parse("{message}{properties}");
+ var context = new GodotLogRenderContext(
+ new DateTime(2026, 5, 2, 1, 2, 3, DateTimeKind.Utc),
+ LogLevel.Info,
+ "Game",
+ "Ready",
+ "white",
+ " | Scene=Boot");
+
+ var result = template.Render(context);
+
+ Assert.That(result, Is.EqualTo("Ready | Scene=Boot"));
+ }
+
[Test]
public void Options_ForMinimumLevel_Should_Preserve_Fixed_Minimum_Level()
{
diff --git a/GFramework.Godot/Logging/GodotLogRenderContext.cs b/GFramework.Godot/Logging/GodotLogRenderContext.cs
index 8ea0460b..b7339aa8 100644
--- a/GFramework.Godot/Logging/GodotLogRenderContext.cs
+++ b/GFramework.Godot/Logging/GodotLogRenderContext.cs
@@ -8,4 +8,5 @@ internal readonly record struct GodotLogRenderContext(
LogLevel Level,
string Category,
string Message,
- string Color);
+ string Color,
+ string Properties);
diff --git a/GFramework.Godot/Logging/GodotLogTemplate.cs b/GFramework.Godot/Logging/GodotLogTemplate.cs
index c05fa5ba..260da77f 100644
--- a/GFramework.Godot/Logging/GodotLogTemplate.cs
+++ b/GFramework.Godot/Logging/GodotLogTemplate.cs
@@ -91,6 +91,7 @@ internal sealed class GodotLogTemplate
"color" => static (builder, context) => builder.Append(context.Color),
"level" => static (builder, context) => builder.Append(context.Level),
"message" => static (builder, context) => builder.Append(context.Message),
+ "properties" => static (builder, context) => builder.Append(context.Properties),
"timestamp" => static (builder, context) => builder.Append(context.Timestamp.ToString(
"yyyy-MM-dd HH:mm:ss.fff",
CultureInfo.InvariantCulture)),
diff --git a/GFramework.Godot/Logging/GodotLogger.cs b/GFramework.Godot/Logging/GodotLogger.cs
index 551a9dba..3a9ef33c 100644
--- a/GFramework.Godot/Logging/GodotLogger.cs
+++ b/GFramework.Godot/Logging/GodotLogger.cs
@@ -1,4 +1,7 @@
using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
using GFramework.Core.Abstractions.Logging;
using GFramework.Core.Logging;
using Godot;
@@ -10,7 +13,8 @@ namespace GFramework.Godot.Logging;
///
public sealed class GodotLogger : AbstractLogger
{
- private readonly GodotLoggerOptions _options;
+ private readonly Func _minLevelProvider;
+ private readonly Func _optionsProvider;
///
/// Initializes a logger that preserves the historical fixed-format template.
@@ -18,7 +22,10 @@ public sealed class GodotLogger : AbstractLogger
/// The logger name.
/// The minimum enabled log level.
public GodotLogger(string? name = null, LogLevel minLevel = LogLevel.Info)
- : this(name, GodotLoggerOptions.ForMinimumLevel(minLevel))
+ : this(
+ name ?? RootLoggerName,
+ () => GodotLoggerOptions.ForMinimumLevel(minLevel),
+ () => minLevel)
{
}
@@ -28,9 +35,21 @@ public sealed class GodotLogger : AbstractLogger
/// The logger name.
/// The logger options.
public GodotLogger(string? name, GodotLoggerOptions options)
- : base(name ?? RootLoggerName, (options ?? throw new ArgumentNullException(nameof(options))).GetEffectiveMinLevel())
+ : this(
+ name ?? RootLoggerName,
+ () => options ?? throw new ArgumentNullException(nameof(options)),
+ () => (options ?? throw new ArgumentNullException(nameof(options))).GetEffectiveMinLevel())
{
- _options = options;
+ }
+
+ internal GodotLogger(
+ string name,
+ Func optionsProvider,
+ Func minLevelProvider)
+ : base(name, minLevelProvider ?? throw new ArgumentNullException(nameof(minLevelProvider)))
+ {
+ _optionsProvider = optionsProvider ?? throw new ArgumentNullException(nameof(optionsProvider));
+ _minLevelProvider = minLevelProvider;
}
///
@@ -41,18 +60,59 @@ public sealed class GodotLogger : AbstractLogger
/// The optional exception.
protected override void Write(LogLevel level, string message, Exception? exception)
{
- var templateText = _options.Mode == GodotLoggerMode.Debug
- ? _options.DebugOutputTemplate
- : _options.ReleaseOutputTemplate;
+ WriteEntry(level, message, exception, properties: null);
+ }
+
+ ///
+ /// Uses Godot-aware structured rendering instead of the base string concatenation fallback.
+ ///
+ public override void Log(LogLevel level, string message, params (string Key, object? Value)[] properties)
+ {
+ if (level < _minLevelProvider())
+ {
+ return;
+ }
+
+ WriteEntry(level, message, exception: null, properties);
+ }
+
+ ///
+ /// Uses Godot-aware structured rendering instead of the base string concatenation fallback.
+ ///
+ public override void Log(
+ LogLevel level,
+ string message,
+ Exception? exception,
+ params (string Key, object? Value)[] properties)
+ {
+ if (level < _minLevelProvider())
+ {
+ return;
+ }
+
+ WriteEntry(level, message, exception, properties);
+ }
+
+ private void WriteEntry(
+ LogLevel level,
+ string message,
+ Exception? exception,
+ (string Key, object? Value)[]? properties)
+ {
+ var options = _optionsProvider();
+ var templateText = options.Mode == GodotLoggerMode.Debug
+ ? options.DebugOutputTemplate
+ : options.ReleaseOutputTemplate;
var context = new GodotLogRenderContext(
DateTime.UtcNow,
level,
Name(),
message,
- _options.GetColor(level));
+ options.GetColor(level),
+ FormatProperties(properties));
var rendered = GodotLogTemplate.Parse(templateText).Render(context);
- if (_options.Mode == GodotLoggerMode.Debug)
+ if (options.Mode == GodotLoggerMode.Debug)
{
WriteDebug(level, rendered);
}
@@ -67,6 +127,54 @@ public sealed class GodotLogger : AbstractLogger
}
}
+ private static string FormatProperties((string Key, object? Value)[]? properties)
+ {
+ var merged = MergeProperties(properties);
+ if (merged.Count == 0)
+ {
+ return string.Empty;
+ }
+
+ return " | " + string.Join(", ", merged.Select(static pair => $"{pair.Key}={FormatValue(pair.Value)}"));
+ }
+
+ private static IReadOnlyDictionary MergeProperties((string Key, object? Value)[]? properties)
+ {
+ var contextProperties = LogContext.Current;
+ if ((properties == null || properties.Length == 0) && contextProperties.Count == 0)
+ {
+ return EmptyProperties;
+ }
+
+ var merged = new Dictionary(contextProperties, StringComparer.Ordinal);
+ if (properties != null)
+ {
+ foreach (var property in properties)
+ {
+ merged[property.Key] = property.Value;
+ }
+ }
+
+ return merged;
+ }
+
+ private static readonly IReadOnlyDictionary EmptyProperties =
+ new Dictionary(StringComparer.Ordinal);
+
+ private static string FormatValue(object? value)
+ {
+ if (value == null)
+ {
+ return "null";
+ }
+
+ return value switch
+ {
+ IFormattable formattable => formattable.ToString(null, CultureInfo.InvariantCulture),
+ _ => value.ToString() ?? string.Empty
+ };
+ }
+
private static void WriteDebug(LogLevel level, string rendered)
{
GD.PrintRich(rendered);
diff --git a/GFramework.Godot/Logging/GodotLoggerFactoryProvider.cs b/GFramework.Godot/Logging/GodotLoggerFactoryProvider.cs
index 1b3ab0ed..5dfb48fa 100644
--- a/GFramework.Godot/Logging/GodotLoggerFactoryProvider.cs
+++ b/GFramework.Godot/Logging/GodotLoggerFactoryProvider.cs
@@ -1,6 +1,6 @@
using System;
+using System.Collections.Concurrent;
using GFramework.Core.Abstractions.Logging;
-using GFramework.Core.Logging;
namespace GFramework.Godot.Logging;
@@ -9,14 +9,15 @@ namespace GFramework.Godot.Logging;
///
public sealed class GodotLoggerFactoryProvider : ILoggerFactoryProvider
{
- private readonly ILoggerFactory _cachedFactory;
+ private readonly ConcurrentDictionary _loggers = new(StringComparer.Ordinal);
+ private readonly Func _settingsProvider;
///
/// Initializes a Godot logger provider with the default logger factory.
///
public GodotLoggerFactoryProvider()
+ : this(static () => GodotLoggerSettings.Default)
{
- _cachedFactory = CreateCachedFactory(new GodotLoggerFactory());
}
///
@@ -24,8 +25,13 @@ public sealed class GodotLoggerFactoryProvider : ILoggerFactoryProvider
///
/// The logger options.
public GodotLoggerFactoryProvider(GodotLoggerOptions options)
+ : this(CreateStaticSettingsProvider(options))
{
- _cachedFactory = CreateCachedFactory(new GodotLoggerFactory(options));
+ }
+
+ internal GodotLoggerFactoryProvider(Func settingsProvider)
+ {
+ _settingsProvider = settingsProvider ?? throw new ArgumentNullException(nameof(settingsProvider));
}
///
@@ -40,12 +46,31 @@ public sealed class GodotLoggerFactoryProvider : ILoggerFactoryProvider
/// A logger configured with .
public ILogger CreateLogger(string name)
{
- return _cachedFactory.GetLogger(name, MinLevel);
+ ArgumentNullException.ThrowIfNull(name);
+
+ return _loggers.GetOrAdd(
+ name,
+ static (loggerName, provider) => new GodotLogger(
+ loggerName,
+ provider.GetOptions,
+ () => provider.GetEffectiveMinLevel(loggerName)),
+ this);
}
- private static ILoggerFactory CreateCachedFactory(ILoggerFactory innerFactory)
+ private GodotLoggerOptions GetOptions()
{
- ArgumentNullException.ThrowIfNull(innerFactory);
- return new CachedLoggerFactory(innerFactory);
+ return _settingsProvider().Options;
+ }
+
+ private LogLevel GetEffectiveMinLevel(string categoryName)
+ {
+ return _settingsProvider().GetEffectiveMinLevel(categoryName, MinLevel);
+ }
+
+ private static Func CreateStaticSettingsProvider(GodotLoggerOptions options)
+ {
+ ArgumentNullException.ThrowIfNull(options);
+ var settings = GodotLoggerSettings.FromOptions(options);
+ return () => settings;
}
}
diff --git a/GFramework.Godot/Logging/GodotLoggerOptions.cs b/GFramework.Godot/Logging/GodotLoggerOptions.cs
index ccf5a2f1..d8ac0382 100644
--- a/GFramework.Godot/Logging/GodotLoggerOptions.cs
+++ b/GFramework.Godot/Logging/GodotLoggerOptions.cs
@@ -39,7 +39,7 @@ public sealed class GodotLoggerOptions
///
#pragma warning disable MA0016 // Keep configuration mutable for object initializer and serializer scenarios.
public string DebugOutputTemplate { get; set; } =
- "[{timestamp:yyyy-MM-dd HH:mm:ss.fff}] [color={color}][{level:u3}][/color] [{category:l16}] {message}";
+ "[{timestamp:yyyy-MM-dd HH:mm:ss.fff}] [color={color}][{level:u3}][/color] [{category:l16}] {message}{properties}";
#pragma warning restore MA0016
///
@@ -47,7 +47,7 @@ public sealed class GodotLoggerOptions
///
#pragma warning disable MA0016 // Keep configuration mutable for object initializer and serializer scenarios.
public string ReleaseOutputTemplate { get; set; } =
- "[{timestamp:yyyy-MM-dd HH:mm:ss.fff}] [{level:u3}] [{category:l16}] {message}";
+ "[{timestamp:yyyy-MM-dd HH:mm:ss.fff}] [{level:u3}] [{category:l16}] {message}{properties}";
#pragma warning restore MA0016
///
@@ -69,8 +69,8 @@ public sealed class GodotLoggerOptions
Mode = GodotLoggerMode.Debug,
DebugMinLevel = minLevel,
ReleaseMinLevel = minLevel,
- DebugOutputTemplate = "[{timestamp:yyyy-MM-dd HH:mm:ss.fff}] {level:padded} [{category}] {message}",
- ReleaseOutputTemplate = "[{timestamp:yyyy-MM-dd HH:mm:ss.fff}] {level:padded} [{category}] {message}",
+ DebugOutputTemplate = "[{timestamp:yyyy-MM-dd HH:mm:ss.fff}] {level:padded} [{category}] {message}{properties}",
+ ReleaseOutputTemplate = "[{timestamp:yyyy-MM-dd HH:mm:ss.fff}] {level:padded} [{category}] {message}{properties}",
Colors = new Dictionary(DefaultColors)
};
}
diff --git a/ai-plan/public/README.md b/ai-plan/public/README.md
index c77c8a14..447d2d23 100644
--- a/ai-plan/public/README.md
+++ b/ai-plan/public/README.md
@@ -38,6 +38,10 @@ help the current worktree land on the right recovery documents without scanning
- Purpose: continue the data repository persistence hardening plus the settings / serialization follow-up backlog.
- Tracking: `ai-plan/public/data-repository-persistence/todos/data-repository-persistence-tracking.md`
- Trace: `ai-plan/public/data-repository-persistence/traces/data-repository-persistence-trace.md`
+- `godot-logging-compliance-polish`
+ - Purpose: continue Godot logging host integration, configuration reload, structured-output polish, and follow-up work without forking the Core logging model.
+ - Tracking: `ai-plan/public/godot-logging-compliance-polish/todos/godot-logging-compliance-polish-tracking.md`
+ - Trace: `ai-plan/public/godot-logging-compliance-polish/traces/godot-logging-compliance-polish-trace.md`
- `semantic-release-versioning`
- Purpose: migrate release version calculation from fixed patch bumps to semantic-release while keeping the existing tag-driven NuGet publish flow.
- Tracking: `ai-plan/public/semantic-release-versioning/todos/semantic-release-versioning-tracking.md`
@@ -59,6 +63,9 @@ help the current worktree land on the right recovery documents without scanning
- Branch: `feat/data-repository-persistence`
- Worktree hint: `GFramework-data-repository-persistence`
- Priority 1: `data-repository-persistence`
+- Branch: `feat/godot-logging-compliance-polish`
+ - Worktree hint: `GFramework`
+ - Priority 1: `godot-logging-compliance-polish`
- Branch: `feat/semantic-release-versioning`
- Worktree hint: `GFramework`
- Priority 1: `semantic-release-versioning`
diff --git a/docs/zh-CN/godot/logging.md b/docs/zh-CN/godot/logging.md
index 039f9472..3811feba 100644
--- a/docs/zh-CN/godot/logging.md
+++ b/docs/zh-CN/godot/logging.md
@@ -5,11 +5,16 @@ description: 以当前 GFramework.Godot.Logging 源码与 CoreGrid 接线为准
# Godot 日志系统
-`GFramework.Godot` 当前的日志能力很收敛:它不是一套独立于 Core 的新日志框架,而是把现有 `ILogger` 调用面接到
-Godot 控制台。
+`GFramework.Godot` 当前的日志能力仍然以 Core 的 `ILogger` 调用面为中心,但已经不再只是一个薄输出适配层。
+除了把日志写到 Godot 控制台,它现在还补上了 Godot 宿主常见的接入便利层:
-换句话说,Godot 侧真正新增的是 provider / factory / logger 这层输出适配,而不是新的日志 API。业务代码仍然继续使用
-`LoggerFactoryResolver.Provider.CreateLogger(...)` 或 `[Log]` 生成的 `ILogger` 字段。
+- `GodotLog` 静态入口
+- 配置文件自动发现
+- 运行期配置热重载
+- 延迟 logger 解析,适合 `static readonly` 字段
+
+业务代码仍然继续使用 `LoggerFactoryResolver.Provider.CreateLogger(...)`、`GodotLog.CreateLogger(...)` 或 `[Log]`
+生成的 `ILogger` 字段;Godot 侧没有额外引入第二套业务日志 API。
## 当前公开入口
@@ -57,11 +62,34 @@ public sealed class GodotLoggerFactoryProvider : ILoggerFactoryProvider
}
```
-它内部用 `CachedLoggerFactory` 包装 `GodotLoggerFactory`。缓存 key 由 `name` 和 `MinLevel` 共同组成,所以:
+当前 provider 会按 logger 名称缓存实例,但 logger 本身会在写入时读取当前配置快照,所以:
-- 同名、同 `MinLevel` 的 logger 会复用实例
-- 调整 `MinLevel` 后,新创建的 logger 会走新的缓存 key
-- 已经持有的旧 logger 不会被原地改写
+- 同名 logger 会复用实例
+- 调整 provider 最小级别或热更新配置后,已持有的 logger 会立即看到新行为
+- 不需要为了刷新模板、颜色或级别而重新创建 logger
+
+### `GodotLog`
+
+`GodotLog` 是新增的 Godot 宿主友好入口:
+
+```csharp
+using GFramework.Godot.Logging;
+
+GodotLog.Configure(options =>
+{
+ options.Mode = GodotLoggerMode.Debug;
+});
+
+GodotLog.UseAsDefaultProvider();
+
+var logger = GodotLog.CreateLogger();
+```
+
+它提供三件事:
+
+- 在第一次真正创建 provider 前允许代码覆写 `GodotLoggerOptions`
+- 自动按 `GODOT_LOGGER_CONFIG` -> 可执行目录 `appsettings.json` -> `res://appsettings.json` 顺序发现配置
+- 返回延迟解析 logger,避免 `static readonly` 字段过早锁死配置
## 最小接入路径
@@ -121,7 +149,7 @@ public partial class SettingsPanel : Control
```
如果你已经在用 `GFramework.Core.SourceGenerators`,也可以继续让 `[Log]` 生成字段。Godot provider 只改变输出落点,
-不会改变 `[Log]` 的生成契约。
+不会改变 `[Log]` 的生成契约。需要静态字段延迟初始化时,也可以直接用 `GodotLog.CreateLogger()`。
### 3. Scene / UI 迁移日志会自动复用同一套 provider
@@ -152,7 +180,9 @@ RegisterHandler(new LoggingTransitionHandler());
| `Error` | `GD.PrintErr(...)` | 输出到错误流 |
| `Fatal` | `GD.PushError(...)` | 进入 Godot 错误通道 |
-异常追加格式也来自当前实现本身:
+结构化属性如果通过 `IStructuredLogger` 或 `LogContext` 传入,也会追加到模板里的 `{properties}` 占位符。
+
+异常追加格式仍然来自当前实现本身:
```text
[2026-04-22 10:30:47.012] ERROR [SaveSystem] 保存游戏失败
@@ -182,14 +212,16 @@ System.IO.IOException: ...
## 当前边界
-- 当前推荐接法是把 `GodotLoggerFactoryProvider` 放进 `ArchitectureConfiguration.LoggerProperties`;直接赋值
- `LoggerFactoryResolver.Provider` 仍然可用,但不该再写成默认采用路径
+- 当前推荐接法仍然是把 `GodotLoggerFactoryProvider` 放进 `ArchitectureConfiguration.LoggerProperties`;如果项目是纯
+ Godot 宿主,也可以在入口直接调用 `GodotLog.UseAsDefaultProvider()`
- `GFramework.Godot.Logging` 只解决 Godot 控制台输出,不提供文件落盘、JSON formatter、异步 appender 或按 namespace
的复杂过滤
- `GodotLogger` 只改变输出方式,不改变 `ILogger` 接口本身;业务代码不需要切换到 Godot 专用日志 API
- `[Log]`、`[ContextAware]` 这类字段注入能力不属于 `GFramework.Godot.Logging`
- Scene / UI 的 `LoggingTransitionHandler` 位于 `GFramework.Game`,Godot 侧只是通过 provider 让它们输出到 Godot 控制台
- 当前 `GodotLogger` 使用的是 UTC 时间戳;如果项目需要本地时区展示,需要自定义 provider / logger,而不是假定当前实现会自动转换
+- 当前配置热重载只覆盖 Godot logger 自身的模板、颜色、模式和级别;它没有把 `Microsoft.Extensions.Logging` 的整个
+ options / builder 模型搬进来
## 继续阅读
diff --git a/refactor-scripts/update-namespaces.py b/refactor-scripts/update-namespaces.py
index b9b7c205..7b69a07e 100644
--- a/refactor-scripts/update-namespaces.py
+++ b/refactor-scripts/update-namespaces.py
@@ -1,154 +1,154 @@
-#!/usr/bin/env python3
-"""
-更新所有 C# 文件中的命名空间声明和 using 语句
-"""
-
-import os
-import re
-from pathlib import Path
-
-ROOT_DIR = "/mnt/f/gewuyou/System/Documents/WorkSpace/GameDev/GFramework"
-
-# 命名空间替换规则(按优先级排序,长的先匹配)
-NAMESPACE_RULES = [
- # CQRS 子命名空间
- (r'\.cqrs\.notification\b', '.CQRS.Notification'),
- (r'\.cqrs\.command\b', '.CQRS.Command'),
- (r'\.cqrs\.request\b', '.CQRS.Request'),
- (r'\.cqrs\.query\b', '.CQRS.Query'),
- (r'\.cqrs\.behaviors\b', '.CQRS.Behaviors'),
- (r'\.cqrs\b', '.CQRS'),
-
- # 嵌套命名空间
- (r'\.coroutine\.instructions\b', '.Coroutine.Instructions'),
- (r'\.coroutine\.extensions\b', '.Coroutine.Extensions'),
- (r'\.coroutine\b', '.Coroutine'),
-
- (r'\.events\.filters\b', '.Events.Filters'),
- (r'\.events\b', '.Events'),
-
- (r'\.logging\.appenders\b', '.Logging.Appenders'),
- (r'\.logging\.filters\b', '.Logging.Filters'),
- (r'\.logging\.formatters\b', '.Logging.Formatters'),
- (r'\.logging\b', '.Logging'),
-
- (r'\.functional\.async\b', '.Functional.Async'),
- (r'\.functional\.control\b', '.Functional.Control'),
- (r'\.functional\.functions\b', '.Functional.Functions'),
- (r'\.functional\.pipe\b', '.Functional.Pipe'),
- (r'\.functional\.result\b', '.Functional.Result'),
- (r'\.functional\b', '.Functional'),
-
- (r'\.services\.modules\b', '.Services.Modules'),
- (r'\.services\b', '.Services'),
-
- (r'\.extensions\.signal\b', '.Extensions.Signal'),
- (r'\.extensions\b', '.Extensions'),
-
- (r'\.setting\.data\b', '.Setting.Data'),
- (r'\.setting\.events\b', '.Setting.Events'),
- (r'\.setting\b', '.Setting'),
-
- (r'\.scene\.handler\b', '.Scene.Handler'),
- (r'\.scene\b', '.Scene'),
-
- (r'\.ui\.handler\b', '.UI.Handler'),
- (r'\.ui\b', '.UI'),
-
- (r'\.data\.events\b', '.Data.Events'),
- (r'\.data\b', '.Data'),
-
- # 单层命名空间
- (r'\.architecture\b', '.Architecture'),
- (r'\.bases\b', '.Bases'),
- (r'\.command\b', '.Command'),
- (r'\.configuration\b', '.Configuration'),
- (r'\.constants\b', '.Constants'),
- (r'\.enums\b', '.Enums'),
- (r'\.environment\b', '.Environment'),
- (r'\.internals\b', '.Internals'),
- (r'\.ioc\b', '.IoC'),
- (r'\.lifecycle\b', '.Lifecycle'),
- (r'\.model\b', '.Model'),
- (r'\.pause\b', '.Pause'),
- (r'\.pool\b', '.Pool'),
- (r'\.properties\b', '.Properties'),
- (r'\.property\b', '.Property'),
- (r'\.query\b', '.Query'),
- (r'\.registries\b', '.Registries'),
- (r'\.resource\b', '.Resource'),
- (r'\.rule\b', '.Rule'),
- (r'\.serializer\b', '.Serializer'),
- (r'\.state\b', '.State'),
- (r'\.storage\b', '.Storage'),
- (r'\.system\b', '.System'),
- (r'\.time\b', '.Time'),
- (r'\.utility\b', '.Utility'),
- (r'\.versioning\b', '.Versioning'),
- (r'\.asset\b', '.Asset'),
- (r'\.components\b', '.Components'),
- (r'\.systems\b', '.Systems'),
- (r'\.ecs\b', '.ECS'),
- (r'\.integration\b', '.Integration'),
- (r'\.mediator\b', '.Mediator'),
- (r'\.tests\b', '.Tests'),
- (r'\.analyzers\b', '.Analyzers'),
- (r'\.diagnostics\b', '.Diagnostics'),
- (r'\.generator\b', '.Generator'),
- (r'\.info\b', '.Info'),
-]
-
-def update_file(file_path):
- """更新单个文件中的命名空间"""
- try:
- with open(file_path, 'r', encoding='utf-8') as f:
- content = f.read()
-
- original_content = content
- replacements = 0
-
- for pattern, replacement in NAMESPACE_RULES:
- matches = re.findall(pattern, content, re.IGNORECASE)
- if matches:
- content = re.sub(pattern, replacement, content, flags=re.IGNORECASE)
- replacements += len(matches)
-
- if content != original_content:
- with open(file_path, 'w', encoding='utf-8') as f:
- f.write(content)
- return replacements
-
- return 0
- except Exception as e:
- print(f"错误处理文件 {file_path}: {e}")
- return 0
-
-def main():
- print("开始更新命名空间...")
-
- # 查找所有 C# 文件
- cs_files = []
- for root, dirs, files in os.walk(ROOT_DIR):
- # 跳过 bin, obj, Generated 目录
- dirs[:] = [d for d in dirs if d not in ['bin', 'obj', 'Generated', '.git', 'node_modules']]
-
- for file in files:
- if file.endswith('.cs'):
- cs_files.append(os.path.join(root, file))
-
- print(f"找到 {len(cs_files)} 个 C# 文件")
-
- updated_files = 0
- total_replacements = 0
-
- for file_path in cs_files:
- replacements = update_file(file_path)
- if replacements > 0:
- updated_files += 1
- total_replacements += replacements
- print(f"更新: {os.path.basename(file_path)} ({replacements} 处替换)")
-
- print(f"\n完成!更新了 {updated_files} 个文件,共 {total_replacements} 处替换")
-
-if __name__ == '__main__':
- main()
+#!/usr/bin/env python3
+"""
+更新所有 C# 文件中的命名空间声明和 using 语句
+"""
+
+import os
+import re
+from pathlib import Path
+
+ROOT_DIR = "/mnt/f/gewuyou/System/Documents/WorkSpace/GameDev/GFramework"
+
+# 命名空间替换规则(按优先级排序,长的先匹配)
+NAMESPACE_RULES = [
+ # CQRS 子命名空间
+ (r'\.cqrs\.notification\b', '.CQRS.Notification'),
+ (r'\.cqrs\.command\b', '.CQRS.Command'),
+ (r'\.cqrs\.request\b', '.CQRS.Request'),
+ (r'\.cqrs\.query\b', '.CQRS.Query'),
+ (r'\.cqrs\.behaviors\b', '.CQRS.Behaviors'),
+ (r'\.cqrs\b', '.CQRS'),
+
+ # 嵌套命名空间
+ (r'\.coroutine\.instructions\b', '.Coroutine.Instructions'),
+ (r'\.coroutine\.extensions\b', '.Coroutine.Extensions'),
+ (r'\.coroutine\b', '.Coroutine'),
+
+ (r'\.events\.filters\b', '.Events.Filters'),
+ (r'\.events\b', '.Events'),
+
+ (r'\.logging\.appenders\b', '.Logging.Appenders'),
+ (r'\.logging\.filters\b', '.Logging.Filters'),
+ (r'\.logging\.formatters\b', '.Logging.Formatters'),
+ (r'\.logging\b', '.Logging'),
+
+ (r'\.functional\.async\b', '.Functional.Async'),
+ (r'\.functional\.control\b', '.Functional.Control'),
+ (r'\.functional\.functions\b', '.Functional.Functions'),
+ (r'\.functional\.pipe\b', '.Functional.Pipe'),
+ (r'\.functional\.result\b', '.Functional.Result'),
+ (r'\.functional\b', '.Functional'),
+
+ (r'\.services\.modules\b', '.Services.Modules'),
+ (r'\.services\b', '.Services'),
+
+ (r'\.extensions\.signal\b', '.Extensions.Signal'),
+ (r'\.extensions\b', '.Extensions'),
+
+ (r'\.setting\.data\b', '.Setting.Data'),
+ (r'\.setting\.events\b', '.Setting.Events'),
+ (r'\.setting\b', '.Setting'),
+
+ (r'\.scene\.handler\b', '.Scene.Handler'),
+ (r'\.scene\b', '.Scene'),
+
+ (r'\.ui\.handler\b', '.UI.Handler'),
+ (r'\.ui\b', '.UI'),
+
+ (r'\.data\.events\b', '.Data.Events'),
+ (r'\.data\b', '.Data'),
+
+ # 单层命名空间
+ (r'\.architecture\b', '.Architecture'),
+ (r'\.bases\b', '.Bases'),
+ (r'\.command\b', '.Command'),
+ (r'\.configuration\b', '.Configuration'),
+ (r'\.constants\b', '.Constants'),
+ (r'\.enums\b', '.Enums'),
+ (r'\.environment\b', '.Environment'),
+ (r'\.internals\b', '.Internals'),
+ (r'\.ioc\b', '.IoC'),
+ (r'\.lifecycle\b', '.Lifecycle'),
+ (r'\.model\b', '.Model'),
+ (r'\.pause\b', '.Pause'),
+ (r'\.pool\b', '.Pool'),
+ (r'\.properties\b', '.Properties'),
+ (r'\.property\b', '.Property'),
+ (r'\.query\b', '.Query'),
+ (r'\.registries\b', '.Registries'),
+ (r'\.resource\b', '.Resource'),
+ (r'\.rule\b', '.Rule'),
+ (r'\.serializer\b', '.Serializer'),
+ (r'\.state\b', '.State'),
+ (r'\.storage\b', '.Storage'),
+ (r'\.system\b', '.System'),
+ (r'\.time\b', '.Time'),
+ (r'\.utility\b', '.Utility'),
+ (r'\.versioning\b', '.Versioning'),
+ (r'\.asset\b', '.Asset'),
+ (r'\.components\b', '.Components'),
+ (r'\.systems\b', '.Systems'),
+ (r'\.ecs\b', '.ECS'),
+ (r'\.integration\b', '.Integration'),
+ (r'\.mediator\b', '.Mediator'),
+ (r'\.tests\b', '.Tests'),
+ (r'\.analyzers\b', '.Analyzers'),
+ (r'\.diagnostics\b', '.Diagnostics'),
+ (r'\.generator\b', '.Generator'),
+ (r'\.info\b', '.Info'),
+]
+
+def update_file(file_path):
+ """更新单个文件中的命名空间"""
+ try:
+ with open(file_path, 'r', encoding='utf-8') as f:
+ content = f.read()
+
+ original_content = content
+ replacements = 0
+
+ for pattern, replacement in NAMESPACE_RULES:
+ matches = re.findall(pattern, content, re.IGNORECASE)
+ if matches:
+ content = re.sub(pattern, replacement, content, flags=re.IGNORECASE)
+ replacements += len(matches)
+
+ if content != original_content:
+ with open(file_path, 'w', encoding='utf-8') as f:
+ f.write(content)
+ return replacements
+
+ return 0
+ except Exception as e:
+ print(f"错误处理文件 {file_path}: {e}")
+ return 0
+
+def main():
+ print("开始更新命名空间...")
+
+ # 查找所有 C# 文件
+ cs_files = []
+ for root, dirs, files in os.walk(ROOT_DIR):
+ # 跳过 bin, obj, Generated 目录
+ dirs[:] = [d for d in dirs if d not in ['bin', 'obj', 'Generated', '.git', 'node_modules']]
+
+ for file in files:
+ if file.endswith('.cs'):
+ cs_files.append(os.path.join(root, file))
+
+ print(f"找到 {len(cs_files)} 个 C# 文件")
+
+ updated_files = 0
+ total_replacements = 0
+
+ for file_path in cs_files:
+ replacements = update_file(file_path)
+ if replacements > 0:
+ updated_files += 1
+ total_replacements += replacements
+ print(f"更新: {os.path.basename(file_path)} ({replacements} 处替换)")
+
+ print(f"\n完成!更新了 {updated_files} 个文件,共 {total_replacements} 处替换")
+
+if __name__ == '__main__':
+ main()