Initial commit to new repository

This commit is contained in:
2026-04-03 18:22:19 +09:00
commit 4458bb0f52
7672 changed files with 452440 additions and 0 deletions

View 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;
}
}