Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions plugins/crazy-zxx.yigengming/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 Batch Rename Studio contributors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
134 changes: 134 additions & 0 deletions plugins/crazy-zxx.yigengming/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
# 易更名

带中文窗口的文件和文件夹批量创建、重命名插件。支持顺序组合规则、实时预览、冲突检查、撤销与中断恢复,以及跟随系统/手动浅色或深色主题。

## 安装与启动

在 Win顺的插件设置中从社区 Release 安装 `crazy-zxx.yigengming-1.3.0.wsplugin` 并启用“易更名”。开发时将本目录放到 `%LOCALAPPDATA%\WinShun\plugins\crazy-zxx.yigengming\`,启用开发者模式。

- `br `、`rename `、`易更名 `、`批量重命名 `:打开重命名窗口,关键字后可附带文件或文件夹路径。
- `bc `、`批量创建 `:打开创建窗口,关键字后可附带已有目标目录。
- 窗口右上角“主题”可选跟随系统、浅色、深色;选择自动保存。

运行环境为 Windows 10/11、Windows PowerShell 5.1、.NET Framework 4.8,均不需要额外下载。单批最多 10000 项、最多 100 条规则。遵循传统 Windows 路径长度限制;跳过符号链接/联接点,带重解析点的 OneDrive 路径可能受限。

## 重命名操作

1. 拖入多个文件/文件夹,或点击“添加文件”“添加文件夹”。
2. 要处理目录内的项目,点击“导入目录内容”;可选递归子目录,以及仅导入文件、仅导入文件夹或两者都导入。
3. 使用自然名称排序、路径排序或 ↑ / ↓ 调整顺序。序号按照预览列表顺序生成,整批统一计数。
4. 在右侧添加规则,可添加任意数量的同类规则。选择规则编辑参数,用复选框启用/停用,用 ↑ / ↓ 调整规则顺序。
5. 检查新名称和状态;冲突、非法名称或源文件变化都会阻止执行。将鼠标移到行上可以查看完整路径或错误原因。
6. 点击“执行重命名”,核对项目数量后确认。

文件夹可以与其子文件夹、子文件同时加入,插件会计算最终父子路径。可以只改大小写,也可以互换名称或循环交换三个以上的名称。

![重命名窗口](docs/preview.png)

## 规则与组合

每条规则接收上一条规则的输出,按列表从上到下执行。每条规则都可以单独指定作用范围:

- **主名称**:默认值。文件保留最后一个点后的扩展名;文件夹处理整个名称。
- **完整名称**:包含文件扩展名。
- **扩展名**:不包含点。对文件夹跳过;结果为空时移除扩展名。

`.gitignore` 这样的单点开头名称视为没有扩展名;`archive.tar.gz` 的最后扩展名是 `gz`。

| 规则 | 支持的参数与行为 |
|---|---|
| 序号 | 起始值、正/负/零步长、补零位数、前缀/后缀/指定位置/替换、分隔符 |
| 大小写 | 全大写、全小写、英文单词首字母大写、大小写反转 |
| 文字替换 | 普通文字全量替换,可忽略大小写;替换内容中的 `$1` 按普通文字处理 |
| 插入 | 前缀、后缀、指定位置或替换现有内容 |
| 随机字符 | 自定义字符集、1–128 位、前缀/后缀/指定位置/替换、分隔符 |
| 截取 / 删除 | 指定起点和长度,保留或删除该片段 |
| 正则匹配 | .NET 正则替换或提取;支持 `$1`、`${组名}`、`$0`,可忽略大小写 |
| 名称模板 | 当前名称、原始名称、原扩展名、原父目录、序号、日期 |
| 去除首尾空白 | 删除规则作用范围的首尾空白 |

位置从 **0** 开始,负数从末尾倒数。超出范围的起点会截到边界,截取长度会截到可用长度。位置以 .NET Unicode 文本元素计数,避免拆开普通 emoji 的 UTF-16 代理对。序号和随机规则的“分隔符”用于前缀与后缀模式。

正则未匹配时保留原内容。每次正则匹配有 200 毫秒超时保护,复杂表达式会报错而不是无限等待;编辑规则时会取消过期的预览任务。

随机值由本次窗口的随机种子、原路径和规则位置决定,常规预览刷新不会重新抽取;点击“更多 → 重新生成随机值”才会更换种子。执行使用已生成的目标名称。

### 示例:整理照片

将 `IMG_20261001.JPG` 变为 `旅行_20261001_001.jpg`:

1. 文字替换:主名称中查找 `IMG_`,替换为 `旅行_`。
2. 大小写:仅扩展名,全部小写。
3. 序号:主名称、后缀、起始 1、步长 1、3 位、分隔符 `_`。

`presets/照片整理.json` 可以直接通过“载入规则”使用。另附文件夹编号、随机后缀、正则日期整理、空白清理方案。载入会替换整个规则列表;保存会将完整规则顺序和参数写入 JSON。

### 模板变量

| 变量 | 含义 |
|---|---|
| `{name}` | 当前规则输入内容;在创建名称模板中为输入模板本身 |
| `{original}` | 原始主名称,不含原扩展名 |
| `{ext}` | 原始扩展名,不含点 |
| `{parent}` | 原始父目录名称 |
| `{n}` / `{n:000}` | 从 1 开始的全局序号,可设置数字格式 |
| `{date:yyyyMMdd}` | 日期;使用 .NET 日期格式,时间固定为本次窗口打开时的本地时间 |

名称模板规则的作用范围默认是“主名称”,所以通常不要再手动拼接 `.{ext}`。如果需要自己生成包括扩展名的完整名称,改选“完整名称”。

## 批量创建

切换到“批量创建”,选择**现有目标目录**、文件或文件夹,并选择输入方式:

- **模板 + 数量**:例如 `项目_{n:000}.txt`、数量 20,生成 `项目_001.txt` 至 `项目_020.txt`。
- **按行创建**:每行一个名称,忽略空白行;每行也支持序号和日期模板变量。

右侧规则同样作用于生成的名称,可继续组合替换、序号、大小写、随机字符等。创建的文件为空文件;给空文件添加 `.docx` 等后缀不会生成对应文档的内部格式。不会通过名称创建子路径,名称中的 `\`、`/` 会报错。

![创建窗口](docs/create-preview.png)

## 撤销、恢复与数据

- “撤销最近操作”可撤销最近一批已完成操作,支持跨窗口、跨启动保存;连续点击可逐批撤销更早的操作。
- 重命名的撤销使用文件系统标识检查项目是否被替换,不会覆盖后来在原位置新建的项目。同一文件在原位置修改内容后仍可撤销名称。
- 撤销创建时,整批项目必须仍然存在、标识一致且内容为空;有内容的文件或文件夹会导致拒绝自动删除。不会递归删除目录。
- 重命名先将源项目移到同目录下的私有暂存名称,再移到最终名称,以处理名称互换、大小写和父子目录。失败时按相反顺序回滚。
- 每次文件移动前记录并刷盘追加日志;大批次不会在每次移动时重写整个批次快照。完成后保存完整事务快照。
- 若 WinShun 被退出、程序被结束或掉电,重新打开窗口后点击“更多 → 恢复中断操作”。未恢复的事务会阻止新操作。遇到外部占用或文件替换时会保留日志并显示原因,供手动核对。
- 存储设备故障或文件被其他程序改动时,回滚可能需要人工处理;恢复日志会保留已知原路径、目标路径、暂存路径和文件标识。创建暂存项与第一次记录其标识之间若恰好中断,会要求人工核对该暂存项。

宿主运行时使用 `initialize.params.dataDir` 作为数据目录。独立运行时默认使用:

```text
%LOCALAPPDATA%\WinShun\plugins\crazy-zxx.yigengming.data\
operations.lock
theme.txt
transactions\<事务ID>.json
transactions\<事务ID>.jsonl
```

JSON 保存事务快照,JSONL 保存追加的恢复事件。不要单独删除某个事务的 JSONL。历史记录只在该数据目录保留,移除数据目录后无法跨启动撤销。窗口中的“更多 → 导出预览 CSV”可另行保存原路径与目标路径清单。

## 运行实现与权限

入口是 UTF-8 BOM 的 `main.ps1`,只使用 Windows 自带组件。本目录不包含 EXE/DLL、预编译缓存、下载器或混淆代码。启动时通过 .NET CodeDOM/CSharpCodeProvider 调用系统 C# 编译器,将可审查的 `src/*.cs` 编译成临时托管类库,在当前 PowerShell 进程的独立 AppDomain 中加载;临时文件在退出后清理。AppDomain 使用 `runtime.config` 保留 .NET 4.8 的高 DPI 配置。编译器会短暂启动子进程;工作窗口随后在同一常驻 PowerShell 进程的 STA 线程运行。

| 权限 | 用途 |
|---|---|
| `ui` | 显示 WinForms 工作窗口、系统文件选择器和确认提示 |
| `files:read` | 读取用户选择的项目、目录内容、规则方案和恢复日志;读取 Windows 应用主题设置 |
| `files:write` | 执行用户确认的重命名/创建/撤销/恢复,保存规则、主题、事务日志和用户选择的 CSV 导出 |
| `clipboard:read`、`clipboard:write` | Windows 标准文本编辑控件的复制和粘贴;不主动扫描剪贴板 |
| `processes` | 仅本地 CodeDOM 编译所需的系统 C# 编译器子进程;不下载或启动外部工具,也不为窗口另开进程 |

不申请 `network`,不进行任何联网请求,不发送文件名、路径或内容。使用自己的 JSON-RPC 实现,未依赖或导入 WinShun PowerShell SDK。源代码采用 MIT,保留原版权声明。

## 独立运行与验证

```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File .\main.ps1 -Ui
python tests/protocol_test.py
powershell -NoProfile -ExecutionPolicy Bypass -File .\main.ps1 -Validate -Screenshot .\docs\create-preview.png
```

Python 3 仅用于协议测试,不是运行依赖。`-Validate` 编译测试源码,并在相同 AppDomain/DPI 配置下执行规则、真实临时文件和实际控件检查。测试不会批量修改用户文件。仓库规范检查在仓库根目录执行 `python tools/check.py`。详细结果见 [验证记录](docs/TEST-REPORT.md)。
Binary file added plugins/crazy-zxx.yigengming/app.ico
Binary file not shown.
36 changes: 36 additions & 0 deletions plugins/crazy-zxx.yigengming/docs/TEST-REPORT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# 易更名验证记录

验证日期:2026-10-24

## 已完成

- `powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File .\main.ps1 -Validate -Screenshot .\docs\create-preview.png`
- 66 项核心规则、文件操作、撤销、恢复和持久化测试通过。
- 46 项 WinForms 界面测试通过。
- 覆盖 PerMonitorV2 DPI、144 DPI 字体、浅色/深色/跟随系统主题、主题切换、按钮圆角、窗口缩窄时工具栏左移、规则组合、创建文件夹和冲突检查。
- `python tests/protocol_test.py`
- Windows PowerShell 5.1 入口、UTF-8 JSON-RPC、`initialize`、`query`、`cancel`、`settingsChanged`、未知方法错误和 `shutdown` 通过。
- 50 次查询通过;测试启动耗时约 0.6–0.8 秒,低于 manifest 的 10 秒限制。
- 使用 512 MB Windows Job 限额,并按 manifest 允许本地编译器子进程。
- `python tests/windows_job.py --allow-processes powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File .\main.ps1 -Validate`
- 在 512 MB Job 限额下,66 项核心测试和 46 项 UI 测试通过。
- `python tools/check.py --changed crazy-zxx.yigengming --base origin/main`
- 0 个错误、0 条警告。

## 作者提交时的宿主验证说明(2026-10-24)

已下载并校验 Win顺 `v0.13.2` 官方便携版(SHA-256:`a8635855fcd13dfd204005d80bee463031e450f537780fe0407b2aab3e4da3da`),并使用独立 `--profile` 目录尝试启动。当前隔离环境中宿主窗口未稳定完成插件搜索交互,因此没有勾选“在 Win顺 里试过了”。提交内容保留了可直接放入 `%LOCALAPPDATA%\\WinShun\\plugins\\` 的开发目录和协议测试,供维护者在实际 Win顺环境中复核。

以上为作者提交时的历史记录;后续维护者验证如下。

## 维护者补测(2026-10-26)

- 原始 PowerShell 入口的 50 次协议查询通过;在声明的 512 MB Job 限额下,66 项核心测试和 46 项 UI 测试通过。
- 使用 Win顺项目现有 `wsbench --plugin ... --query "br " --runs 3`,原始 PowerShell 插件通过真实宿主启动和查询:冷启动约 2.4 秒,常驻往返约 0.1 毫秒;进程为普通中完整性令牌,并处于 Job 中。
- 按钮操作验收使用临时 WinForms 测试宿主:直接编译本插件原样 `src/*.cs`,只增加入口包装,复用 `runtime.config`,由真实 `WinShunPluginHost` 启动,并以 `initialize.dataDir` 指定独立临时数据目录。该可执行文件仅用于维护者测试,不随插件分发。
- 在上述同源码 GUI 中添加序号规则,预览并确认重命名一个包含两份测试文件的目录,然后确认撤销;核对原名称及内部文件内容恢复。
- 在独立目标目录中按模板和组合规则创建两份空文件;核对文件名、数量和大小。创建后再次预览能显示已有目标冲突。结束时无未完成事务。
- 打包使用干净 Git 快照,检查 `.wsplugin` 的 manifest、文件路径、禁止文件类型和 SHA256 与目录索引一致。

PowerShell 入口、宿主查询与同源码 GUI 操作分别验证。尚未对用户生产配置下的启动器键盘交互、所有文件选择方式及网络盘做完整验收。

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added plugins/crazy-zxx.yigengming/docs/preview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added plugins/crazy-zxx.yigengming/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
73 changes: 73 additions & 0 deletions plugins/crazy-zxx.yigengming/main.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
param(
[switch]$Ui,
[switch]$Validate,
[string]$Screenshot,
[string]$RenderDemo,
[ValidateSet('System','Light','Dark')][string]$Theme = 'System'
)
$ErrorActionPreference = 'Stop'
[Console]::InputEncoding = New-Object System.Text.UTF8Encoding($false)
[Console]::OutputEncoding = New-Object System.Text.UTF8Encoding($false)
$errorWriter = New-Object System.IO.StreamWriter([Console]::OpenStandardError(), [Console]::OutputEncoding)
$errorWriter.AutoFlush = $true
[Console]::SetError($errorWriter)
$OutputEncoding = [Console]::OutputEncoding
$temporary = Join-Path ([System.IO.Path]::GetTempPath()) ('WinShun-YiGengMing-' + [Guid]::NewGuid().ToString('N'))
$domain = $null
$provider = $null
$result = 1
try {
[System.IO.Directory]::CreateDirectory($temporary) | Out-Null
Add-Type -AssemblyName Microsoft.CSharp
$provider = New-Object Microsoft.CSharp.CSharpCodeProvider
$parameters = New-Object System.CodeDom.Compiler.CompilerParameters
$parameters.GenerateExecutable = $false
$parameters.GenerateInMemory = $false
$parameters.OutputAssembly = Join-Path $temporary 'YiGengMing.Runtime.dll'
$parameters.TempFiles = New-Object System.CodeDom.Compiler.TempFileCollection($temporary, $false)
$parameters.CompilerOptions = '/optimize+ /codepage:65001 /utf8output'
foreach ($reference in @('System.dll','System.Core.dll','System.Drawing.dll','System.Windows.Forms.dll','System.Web.Extensions.dll')) {
$parameters.ReferencedAssemblies.Add($reference) | Out-Null
}
$sources = @(Get-ChildItem -LiteralPath (Join-Path $PSScriptRoot 'src') -Filter '*.cs' -File | ForEach-Object FullName)
if ($Validate) {
$sources += Join-Path $PSScriptRoot 'tests\CoreTests.cs'
$sources += Join-Path $PSScriptRoot 'tests\UiTests.cs'
}
$compiled = $provider.CompileAssemblyFromFile($parameters, [string[]]$sources)
if ($compiled.Errors.HasErrors) {
foreach ($entry in $compiled.Errors) { [Console]::Error.WriteLine($entry.ToString()) }
throw '易更名源码编译失败;请确认已安装 .NET Framework 4.8。'
}
$setup = New-Object System.AppDomainSetup
$setup.ApplicationBase = $temporary
$setup.ShadowCopyFiles = 'true'
$setup.CachePath = Join-Path $temporary 'cache'
$setup.ConfigurationFile = Join-Path $PSScriptRoot 'runtime.config'
$setup.TargetFrameworkName = '.NETFramework,Version=v4.8'
$domain = [AppDomain]::CreateDomain('YiGengMing', $null, $setup)
# Load our just-compiled bytes rather than using LoadFrom with a sandbox-virtualized
# temp path. This needs no remote-source trust or changes to Windows security settings.
$assemblyBytes = [System.IO.File]::ReadAllBytes($parameters.OutputAssembly)
$assembly = [System.Reflection.Assembly]::Load($assemblyBytes)
$hostObject = $domain.CreateInstanceAndUnwrap($assembly.FullName, 'WinShun.BatchRenamer.CommunityHost')
$hostObject.Initialize($PSScriptRoot)
if ($Validate) {
$result = $hostObject.Validate($Screenshot)
}
else {
[string[]]$arguments = @('--serve')
if ($Ui) { $arguments = @('--ui') }
elseif ($RenderDemo) { $arguments = @('--render-demo', [System.IO.Path]::GetFullPath($RenderDemo), $Theme) }
$result = $hostObject.Run($arguments)
}
}
catch { [Console]::Error.WriteLine($_.Exception.ToString()) }
finally {
if ($domain) { [AppDomain]::Unload($domain) }
if ($provider) { $provider.Dispose() }
# Only this invocation's random compiler directory is removed, never user-selected paths.
try { if ([System.IO.Directory]::Exists($temporary)) { [System.IO.Directory]::Delete($temporary, $true) } }
catch { [Console]::Error.WriteLine('无法清理临时编译目录:' + $temporary) }
}
exit $result
Loading
Loading