mirror of
https://github.com/jung-geun/NFD2NFC.git
synced 2025-12-19 20:14:39 +09:00
README.md 추가 및 CLI 도구에 대한 사용법 설명: 파일 이름 변환기 기능 구현, 디렉토리 목록 가져오기 기능 추가
This commit is contained in:
29
README.md
Normal file
29
README.md
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# 파일 이름 변환기
|
||||||
|
|
||||||
|
백그라운드에서 파일을 감지하고 변환하여 파일 이름을 NFD에서 NFC 인코딩으로 자동 변환하는 macOS 패키지입니다.
|
||||||
|
|
||||||
|
npm 패키지는 명령어를 통해 사용할 수 있는 CLI 도구를 제공합니다.
|
||||||
|
Application 패키지는 macOS에서 백그라운드 프로세스로 실행되며, 파일 변환을 자동으로 처리합니다.
|
||||||
|
|
||||||
|
## 특징
|
||||||
|
|
||||||
|
- 자동 파일 감지
|
||||||
|
- 백그라운드 변환 프로세스
|
||||||
|
- NFD에서 NFC 변환 지원
|
||||||
|
|
||||||
|
## 설치
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 설치 지침을 여기에 작성하세요
|
||||||
|
npm install -g @pieroot/nfd2nfc
|
||||||
|
```
|
||||||
|
|
||||||
|
## 사용법
|
||||||
|
|
||||||
|
```bash
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## 라이선스
|
||||||
|
|
||||||
|
MIT 라이선스
|
||||||
46
index.html
46
index.html
@@ -77,7 +77,6 @@
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>디렉토리 경로</th>
|
<th>디렉토리 경로</th>
|
||||||
<th>마지막 갱신 시간</th>
|
|
||||||
<th>제거</th>
|
<th>제거</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -96,19 +95,14 @@
|
|||||||
const directoriesList = document.getElementById('directories-list');
|
const directoriesList = document.getElementById('directories-list');
|
||||||
const logDiv = document.getElementById('log');
|
const logDiv = document.getElementById('log');
|
||||||
|
|
||||||
// 디렉토리 선택 버튼 클릭 시
|
|
||||||
selectDirButton.addEventListener('click', async () => {
|
selectDirButton.addEventListener('click', async () => {
|
||||||
const result = await window.electronAPI.selectDirectories();
|
const result = await window.electronAPI.selectDirectories();
|
||||||
if (!result.canceled) {
|
if (!result.canceled) {
|
||||||
for (const path of result.paths) {
|
refreshDirectories(); // 즉시 갱신
|
||||||
addDirectoryToList(path, new Date().toISOString());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 디렉토리를 목록에 추가하는 함수
|
function addDirectoryToList(dirPath) {
|
||||||
function addDirectoryToList(dirPath, lastUpdateTime) {
|
|
||||||
// 이미 목록에 있는 경우 중복 추가 방지
|
|
||||||
if (document.querySelector(`[data-path="${ dirPath }"]`)) {
|
if (document.querySelector(`[data-path="${ dirPath }"]`)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -119,9 +113,6 @@
|
|||||||
const pathCell = document.createElement('td');
|
const pathCell = document.createElement('td');
|
||||||
pathCell.textContent = dirPath;
|
pathCell.textContent = dirPath;
|
||||||
|
|
||||||
const timeCell = document.createElement('td');
|
|
||||||
timeCell.textContent = new Date(lastUpdateTime).toLocaleString();
|
|
||||||
|
|
||||||
const removeCell = document.createElement('td');
|
const removeCell = document.createElement('td');
|
||||||
const removeBtn = document.createElement('button');
|
const removeBtn = document.createElement('button');
|
||||||
removeBtn.className = 'remove-button';
|
removeBtn.className = 'remove-button';
|
||||||
@@ -138,27 +129,34 @@
|
|||||||
removeCell.appendChild(removeBtn);
|
removeCell.appendChild(removeBtn);
|
||||||
|
|
||||||
directoryRow.appendChild(pathCell);
|
directoryRow.appendChild(pathCell);
|
||||||
directoryRow.appendChild(timeCell);
|
|
||||||
directoryRow.appendChild(removeCell);
|
directoryRow.appendChild(removeCell);
|
||||||
directoriesList.appendChild(directoryRow);
|
directoriesList.appendChild(directoryRow);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 로그 메시지를 로그 섹션에 추가하는 함수
|
function refreshDirectories() {
|
||||||
function appendLog(message) {
|
window.electronAPI.getDirectories().then((directories) => {
|
||||||
logDiv.textContent += `${ message }\n`;
|
directoriesList.innerHTML = '';
|
||||||
logDiv.scrollTop = logDiv.scrollHeight; // 자동 스크롤
|
for (const dirPath of Object.keys(directories)) {
|
||||||
|
addDirectoryToList(dirPath);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 디렉토리 목록 업데이트 이벤트 수신
|
window.addEventListener('DOMContentLoaded', () => {
|
||||||
window.electronAPI.onUpdateDirectories((directories) => {
|
// Initially load directories
|
||||||
// 기존 목록 초기화
|
refreshDirectories();
|
||||||
directoriesList.innerHTML = '';
|
// Then refresh every 10 seconds
|
||||||
for (const [dirPath, lastUpdateTime] of Object.entries(directories)) {
|
setInterval(refreshDirectories, 10000);
|
||||||
addDirectoryToList(dirPath, lastUpdateTime);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 로그 메시지 수신 시
|
// Listen for refresh-directories event
|
||||||
|
window.electronAPI.on('refresh-directories', refreshDirectories);
|
||||||
|
|
||||||
|
function appendLog(message) {
|
||||||
|
logDiv.textContent += `${ message }\n`;
|
||||||
|
logDiv.scrollTop = logDiv.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
window.electronAPI.onLog((message) => {
|
window.electronAPI.onLog((message) => {
|
||||||
appendLog(message);
|
appendLog(message);
|
||||||
});
|
});
|
||||||
|
|||||||
51
main.js
51
main.js
@@ -266,28 +266,6 @@ function watchDirectory(directory) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 창을 생성하는 함수
|
|
||||||
function createWindow() {
|
|
||||||
mainWindow = new BrowserWindow({
|
|
||||||
width: 550, // 너비 조정
|
|
||||||
height: 600, // 높이 조정
|
|
||||||
webPreferences: {
|
|
||||||
preload: path.join(__dirname, "preload.js"), // 보안상 추천
|
|
||||||
nodeIntegration: false,
|
|
||||||
contextIsolation: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
mainWindow.loadFile("index.html");
|
|
||||||
|
|
||||||
// 개발자 도구 열기 (배포 시 제거 권장)
|
|
||||||
mainWindow.webContents.openDevTools();
|
|
||||||
|
|
||||||
mainWindow.on("closed", function () {
|
|
||||||
mainWindow = null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function setTray() {
|
function setTray() {
|
||||||
const iconPath = path.join(__dirname, "build/Macicon.iconset/icon_32x32.png"); // Define iconPath here
|
const iconPath = path.join(__dirname, "build/Macicon.iconset/icon_32x32.png"); // Define iconPath here
|
||||||
|
|
||||||
@@ -416,9 +394,38 @@ ipcMain.handle("remove-directory", async (event, dirPath) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcMain.handle("get-directories", async () => {
|
||||||
|
return watchedDirectories;
|
||||||
|
});
|
||||||
|
|
||||||
process.on("unhandledRejection", (reason, promise) => {
|
process.on("unhandledRejection", (reason, promise) => {
|
||||||
new Notification({
|
new Notification({
|
||||||
title: "Unhandled Promise Rejection",
|
title: "Unhandled Promise Rejection",
|
||||||
body: reason.message || "Unknown error",
|
body: reason.message || "Unknown error",
|
||||||
}).show();
|
}).show();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 창을 생성하는 함수
|
||||||
|
function createWindow() {
|
||||||
|
mainWindow = new BrowserWindow({
|
||||||
|
width: 550, // 너비 조정
|
||||||
|
height: 550, // 높이 조정
|
||||||
|
webPreferences: {
|
||||||
|
preload: path.join(__dirname, "preload.js"), // 보안상 추천
|
||||||
|
nodeIntegration: false,
|
||||||
|
contextIsolation: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
mainWindow.loadFile("index.html");
|
||||||
|
|
||||||
|
// 개발자 도구 열기 (배포 시 제거 권장)
|
||||||
|
// mainWindow.webContents.openDevTools();
|
||||||
|
mainWindow.on("show", () => {
|
||||||
|
mainWindow.webContents.send("get-directories");
|
||||||
|
});
|
||||||
|
|
||||||
|
mainWindow.on("closed", function () {
|
||||||
|
mainWindow = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
117
normalize.js
Normal file → Executable file
117
normalize.js
Normal file → Executable file
@@ -1,14 +1,57 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
const fs = require("fs").promises;
|
const fs = require("fs").promises;
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const chokidar = require("chokidar");
|
const minimist = require("minimist");
|
||||||
|
|
||||||
// 무시할 파일/디렉토리를 결정하는 함수
|
// Parse command-line arguments
|
||||||
|
const args = minimist(process.argv.slice(2), {
|
||||||
|
alias: { d: 'directory', f: 'file', v: 'verbose', h: 'help' }
|
||||||
|
});
|
||||||
|
|
||||||
|
// Function to display help message
|
||||||
|
function displayHelp() {
|
||||||
|
console.log(`
|
||||||
|
Usage: node index.js [options]
|
||||||
|
Options:
|
||||||
|
-d, --directory Specify a directory to process
|
||||||
|
-f, --file Specify a file to process
|
||||||
|
-v, --verbose Enable verbose logging
|
||||||
|
-h, --help Display this help message
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for help flag or no arguments
|
||||||
|
if (args.help || (!args.directory && !args.file)) {
|
||||||
|
displayHelp();
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main processing logic
|
||||||
|
async function processPath(targetPath) {
|
||||||
|
if (!targetPath) {
|
||||||
|
console.error("Please provide a path using -d or -f");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const stats = await fs.lstat(targetPath);
|
||||||
|
if (stats.isDirectory()) {
|
||||||
|
await processDirectory(targetPath);
|
||||||
|
} else if (stats.isFile()) {
|
||||||
|
await normalizeFileName(targetPath);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error processing path "${targetPath}":`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to determine if a file/directory should be ignored
|
||||||
function shouldIgnore(itemName) {
|
function shouldIgnore(itemName) {
|
||||||
const ignoredItems = [".git", "node_modules", ".env"];
|
const ignoredItems = [".git", "node_modules", ".env"];
|
||||||
return ignoredItems.includes(itemName);
|
return ignoredItems.includes(itemName);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 파일 이름을 정규화하는 함수
|
// Function to normalize file names
|
||||||
async function normalizeFileName(filePath) {
|
async function normalizeFileName(filePath) {
|
||||||
const dir = path.dirname(filePath);
|
const dir = path.dirname(filePath);
|
||||||
const oldName = path.basename(filePath);
|
const oldName = path.basename(filePath);
|
||||||
@@ -18,21 +61,25 @@ async function normalizeFileName(filePath) {
|
|||||||
const newPath = path.join(dir, newName);
|
const newPath = path.join(dir, newName);
|
||||||
try {
|
try {
|
||||||
await fs.rename(filePath, newPath);
|
await fs.rename(filePath, newPath);
|
||||||
|
if (args.verbose) {
|
||||||
|
console.log(`Renamed: "${oldName}" -> "${newName}"`);
|
||||||
|
}
|
||||||
return newPath;
|
return newPath;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`이름 변경 실패 ("${oldName}"):`, error);
|
console.error(`Failed to rename "${oldName}":`, error);
|
||||||
return filePath;
|
return filePath;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return filePath;
|
return filePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 디렉토리를 재귀적으로 처리하는 함수
|
// Function to process directories recursively
|
||||||
async function processDirectory(dirPath) {
|
async function processDirectory(dirPath) {
|
||||||
// console.log(`디렉토리 처리 시작: "${dirPath}"`);
|
if (args.verbose) {
|
||||||
|
console.log(`Processing directory: "${dirPath}"`);
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
||||||
|
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
const fullPath = path.join(dirPath, entry.name);
|
const fullPath = path.join(dirPath, entry.name);
|
||||||
if (entry.isDirectory()) {
|
if (entry.isDirectory()) {
|
||||||
@@ -46,55 +93,13 @@ async function processDirectory(dirPath) {
|
|||||||
}
|
}
|
||||||
await normalizeFileName(dirPath);
|
await normalizeFileName(dirPath);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`디렉토리 처리 중 오류 발생 ("${dirPath}"):`, error);
|
console.error(`Error processing directory "${dirPath}":`, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 디렉토리를 감시하는 함수
|
// Process the given path based on arguments
|
||||||
function watchDirectory(directory) {
|
if (args.directory) {
|
||||||
const watcher = chokidar.watch(directory, {
|
processPath(args.directory);
|
||||||
ignored: (pathStr) => {
|
} else if (args.file) {
|
||||||
const baseName = path.basename(pathStr);
|
processPath(args.file);
|
||||||
return shouldIgnore(baseName);
|
}
|
||||||
},
|
|
||||||
persistent: true,
|
|
||||||
ignoreInitial: false, // 초기 파일 추가 이벤트를 감지
|
|
||||||
awaitWriteFinish: {
|
|
||||||
stabilityThreshold: 200, // 파일이 완전히 작성될 때까지 대기 (ms)
|
|
||||||
pollInterval: 100,
|
|
||||||
},
|
|
||||||
depth: Infinity, // 하위 디렉토리까지 감시
|
|
||||||
});
|
|
||||||
|
|
||||||
watcher
|
|
||||||
.on("add", async (filePath) => {
|
|
||||||
// console.log(`파일 추가됨: "${filePath}"`);
|
|
||||||
await normalizeFileName(filePath);
|
|
||||||
})
|
|
||||||
.on("change", async (filePath) => {
|
|
||||||
// console.log(`파일 변경됨: "${filePath}"`);
|
|
||||||
await normalizeFileName(filePath);
|
|
||||||
})
|
|
||||||
.on("unlink", (filePath) => {
|
|
||||||
// console.log(`파일 삭제됨: "${filePath}"`);
|
|
||||||
})
|
|
||||||
.on("addDir", async (dirPath) => {
|
|
||||||
// console.log(`디렉토리 추가됨: "${dirPath}"`);
|
|
||||||
await processDirectory(dirPath); // 새 디렉토리를 추가되자마자 처리
|
|
||||||
})
|
|
||||||
.on("unlinkDir", (dirPath) => {
|
|
||||||
// console.log(`디렉토리 삭제됨: "${dirPath}"`);
|
|
||||||
})
|
|
||||||
.on("error", (error) => console.error(`Watcher error: ${error}`))
|
|
||||||
.on("ready", () => {
|
|
||||||
console.log("초기 스캔 완료. 변경 감시 중...");
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`감시 시작: "${directory}"`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 명령줄 인자로 경로를 받거나 기본값 사용
|
|
||||||
const targetPath = process.argv[2] || "./convert";
|
|
||||||
|
|
||||||
// 디렉토리 감시 시작
|
|
||||||
watchDirectory(targetPath);
|
|
||||||
1247
package-lock.json
generated
1247
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
39
package.json
39
package.json
@@ -1,28 +1,47 @@
|
|||||||
{
|
{
|
||||||
"name": "nfd2nfc",
|
"name": "@pieroot/nfd2nfc",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
"description": "Convert NFD to NFC",
|
"description": "Convert NFD to NFC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chokidar": "^4.0.1",
|
"chokidar": "^4.0.1",
|
||||||
"electron": "^33.2.1",
|
"electron": "^33.2.1",
|
||||||
"readdirp": "^4.0.2",
|
"readdirp": "^4.0.2"
|
||||||
"sqlite3": "^5.1.7"
|
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "electron .",
|
"start": "electron .",
|
||||||
"package": "electron-packager . NFD2NF --platform=darwin --arch=arm64,x64 --icon=build/icons/MacIcon.icns --overwrite --prune=true --out=dist",
|
"package": "electron-packager . NFD2NFC --platform=darwin --arch=arm64,x64 --icon=build/icons/MacIcon.icns --overwrite --prune=true --out=dist",
|
||||||
"package dev": "electron-packager . NFD2NF --platform=darwin --arch=arm64,x64 --icon=build/icons/MacIcon-dev.icns --overwrite --prune=true --out=dist --asar --app-bundle-id=com.pieroot.nfd2nfc",
|
"package dev": "electron-packager . NFD2NFC --platform=darwin --arch=arm64,x64 --icon=build/icons/MacIcon-dev.icns --overwrite --prune=true --out=dist --asar --app-bundle-id=com.pieroot.nfd2nfc",
|
||||||
"pkg": "pkg normalize.js --target node16-macos-x64,node16-linux-x64,node16-win-x64 --output ./dist/NFD2NFC"
|
"pkg": "pkg normalize.js --target node16-macos-x64,node16-linux-x64,node16-win-x64 --output ./dist/NFD2NFC",
|
||||||
|
"prepare-release": "npm run pkg && npm run package"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"nfd2nfc": "normalize.js"
|
||||||
},
|
},
|
||||||
"directories": {
|
"directories": {
|
||||||
"output": "dist",
|
"output": "dist",
|
||||||
"buildResources": "build"
|
"buildResources": "build"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [
|
||||||
"author": "pieroot",
|
"NFD",
|
||||||
|
"NFC",
|
||||||
|
"Unicode",
|
||||||
|
"Normalization",
|
||||||
|
"macOS",
|
||||||
|
"Linux",
|
||||||
|
"korean"
|
||||||
|
],
|
||||||
|
"author": "jung-geun <pieroot.02@gmail.com>",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/jung-geun/NFD2NFC.git"
|
||||||
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"electron-packager": "^17.1.2"
|
"electron-packager": "^17.1.2"
|
||||||
}
|
},
|
||||||
}
|
"bugs": {
|
||||||
|
"url": "https://github.com/jung-geun/NFD2NFC/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/jung-geun/NFD2NFC#readme"
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ const { contextBridge, ipcRenderer } = require("electron");
|
|||||||
contextBridge.exposeInMainWorld("electronAPI", {
|
contextBridge.exposeInMainWorld("electronAPI", {
|
||||||
selectDirectories: () => ipcRenderer.invoke("select-directories"),
|
selectDirectories: () => ipcRenderer.invoke("select-directories"),
|
||||||
removeDirectory: (dirPath) => ipcRenderer.invoke("remove-directory", dirPath),
|
removeDirectory: (dirPath) => ipcRenderer.invoke("remove-directory", dirPath),
|
||||||
|
getDirectories: () => ipcRenderer.invoke("get-directories"),
|
||||||
onLog: (callback) =>
|
onLog: (callback) =>
|
||||||
ipcRenderer.on("log-message", (event, message) => callback(message)),
|
ipcRenderer.on("log-message", (event, message) => callback(message)),
|
||||||
onUpdateDirectories: (callback) =>
|
onUpdateDirectories: (callback) =>
|
||||||
|
|||||||
Reference in New Issue
Block a user