Initial commit to new repository
This commit is contained in:
189
src/AxCopilot/Handlers/RenameHandler.cs
Normal file
189
src/AxCopilot/Handlers/RenameHandler.cs
Normal file
@@ -0,0 +1,189 @@
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Windows;
|
||||
using AxCopilot.SDK;
|
||||
using AxCopilot.Services;
|
||||
using AxCopilot.Themes;
|
||||
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 파일 일괄 이름변경 핸들러. "rename" 프리픽스로 사용합니다.
|
||||
/// 지정된 폴더 내 파일을 패턴으로 일괄 이름변경합니다.
|
||||
/// 예: rename → 사용법 안내
|
||||
/// rename C:\work\*.xlsx → 해당 폴더의 xlsx 파일 목록
|
||||
/// rename C:\work\*.xlsx 보고서_{n} → 보고서_1.xlsx, 보고서_2.xlsx ...
|
||||
/// {n}=순번, {date}=오늘 날짜, {orig}=원본명
|
||||
/// Enter → 실행 전 미리보기, Shift+Enter → 실행.
|
||||
/// </summary>
|
||||
public class RenameHandler : IActionHandler
|
||||
{
|
||||
public string? Prefix => "rename";
|
||||
|
||||
public PluginMetadata Metadata => new(
|
||||
"Rename",
|
||||
"파일 일괄 이름변경 — rename",
|
||||
"1.0",
|
||||
"AX");
|
||||
|
||||
public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
|
||||
{
|
||||
var q = query.Trim();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(q))
|
||||
{
|
||||
return Task.FromResult<IEnumerable<LauncherItem>>(
|
||||
[
|
||||
new LauncherItem(
|
||||
"파일 일괄 이름변경",
|
||||
"rename [폴더\\패턴] [새이름 템플릿]",
|
||||
null, null, Symbol: Symbols.Rename),
|
||||
new LauncherItem(
|
||||
"사용 예시",
|
||||
"rename C:\\work\\*.xlsx 보고서_{n} → 보고서_1.xlsx, 보고서_2.xlsx ...",
|
||||
null, null, Symbol: Symbols.Info),
|
||||
new LauncherItem(
|
||||
"변수: {n} 순번, {date} 날짜, {orig} 원본명",
|
||||
"rename D:\\photos\\*.jpg {date}_{n} → 2026-03-27_1.jpg ...",
|
||||
null, null, Symbol: Symbols.Info),
|
||||
]);
|
||||
}
|
||||
|
||||
// 파싱: [경로\패턴] [템플릿]
|
||||
var parts = q.Split(' ', 2, StringSplitOptions.TrimEntries);
|
||||
var pattern = parts[0];
|
||||
var template = parts.Length > 1 ? parts[1] : null;
|
||||
|
||||
// 경로 분리
|
||||
var dir = Path.GetDirectoryName(pattern);
|
||||
var glob = Path.GetFileName(pattern);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(dir) || !Directory.Exists(dir))
|
||||
{
|
||||
return Task.FromResult<IEnumerable<LauncherItem>>(
|
||||
[
|
||||
new LauncherItem(
|
||||
"폴더를 찾을 수 없습니다",
|
||||
$"경로: {dir ?? "(비어 있음)"}",
|
||||
null, null, Symbol: Symbols.Warning)
|
||||
]);
|
||||
}
|
||||
|
||||
string[] files;
|
||||
try { files = Directory.GetFiles(dir, glob); }
|
||||
catch { files = Array.Empty<string>(); }
|
||||
|
||||
if (files.Length == 0)
|
||||
{
|
||||
return Task.FromResult<IEnumerable<LauncherItem>>(
|
||||
[
|
||||
new LauncherItem(
|
||||
"일치하는 파일이 없습니다",
|
||||
$"패턴: {glob} · 폴더: {dir}",
|
||||
null, null, Symbol: Symbols.Warning)
|
||||
]);
|
||||
}
|
||||
|
||||
Array.Sort(files);
|
||||
|
||||
// 템플릿이 없으면 파일 목록만 표시
|
||||
if (string.IsNullOrWhiteSpace(template))
|
||||
{
|
||||
var items = files.Take(10).Select((f, i) => new LauncherItem(
|
||||
Path.GetFileName(f),
|
||||
dir,
|
||||
null, null,
|
||||
Symbol: Symbols.File)).ToList<LauncherItem>();
|
||||
|
||||
items.Insert(0, new LauncherItem(
|
||||
$"총 {files.Length}개 파일 발견",
|
||||
"뒤에 새 이름 템플릿을 추가하세요 (예: 보고서_{n})",
|
||||
null, null, Symbol: Symbols.Info));
|
||||
|
||||
return Task.FromResult<IEnumerable<LauncherItem>>(items);
|
||||
}
|
||||
|
||||
// 미리보기 생성
|
||||
var today = DateTime.Today.ToString("yyyy-MM-dd");
|
||||
var previews = new List<(string From, string To)>();
|
||||
for (int i = 0; i < files.Length; i++)
|
||||
{
|
||||
var origName = Path.GetFileNameWithoutExtension(files[i]);
|
||||
var ext = Path.GetExtension(files[i]);
|
||||
var newName = template
|
||||
.Replace("{n}", (i + 1).ToString())
|
||||
.Replace("{date}", today)
|
||||
.Replace("{orig}", origName);
|
||||
// 확장자 자동 유지 (템플릿에 확장자가 없으면)
|
||||
if (!Path.HasExtension(newName))
|
||||
newName += ext;
|
||||
previews.Add((Path.GetFileName(files[i]), newName));
|
||||
}
|
||||
|
||||
var result = new List<LauncherItem>();
|
||||
|
||||
// 실행 항목
|
||||
result.Add(new LauncherItem(
|
||||
$"총 {files.Length}개 파일 이름변경 실행",
|
||||
$"Enter로 실행 · {previews[0].From} → {previews[0].To} ...",
|
||||
null, ValueTuple.Create(dir, files, previews.Select(p => p.To).ToArray()),
|
||||
Symbol: Symbols.Rename));
|
||||
|
||||
// 미리보기 (최대 8개)
|
||||
foreach (var (from, to) in previews.Take(8))
|
||||
{
|
||||
result.Add(new LauncherItem(
|
||||
$"{from} → {to}",
|
||||
"미리보기",
|
||||
null, null,
|
||||
Symbol: Symbols.File));
|
||||
}
|
||||
|
||||
if (files.Length > 8)
|
||||
{
|
||||
result.Add(new LauncherItem(
|
||||
$"... 외 {files.Length - 8}개",
|
||||
"",
|
||||
null, null,
|
||||
Symbol: Symbols.Info));
|
||||
}
|
||||
|
||||
return Task.FromResult<IEnumerable<LauncherItem>>(result);
|
||||
}
|
||||
|
||||
public Task ExecuteAsync(LauncherItem item, CancellationToken ct)
|
||||
{
|
||||
if (item.Data is not ValueTuple<string, string[], string[]> data)
|
||||
return Task.CompletedTask;
|
||||
|
||||
var (dir, files, newNames) = data;
|
||||
int renamed = 0;
|
||||
int failed = 0;
|
||||
|
||||
for (int i = 0; i < files.Length && i < newNames.Length; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
var dest = Path.Combine(dir, newNames[i]);
|
||||
if (File.Exists(dest))
|
||||
{
|
||||
failed++;
|
||||
continue;
|
||||
}
|
||||
File.Move(files[i], dest);
|
||||
renamed++;
|
||||
}
|
||||
catch
|
||||
{
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
|
||||
var msg = failed > 0
|
||||
? $"{renamed}개 이름변경 완료, {failed}개 실패 (이미 존재하거나 접근 불가)"
|
||||
: $"{renamed}개 파일 이름변경 완료";
|
||||
NotificationService.Notify("AX Copilot", msg);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user