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>
|
||||
<tr>
|
||||
<th>디렉토리 경로</th>
|
||||
<th>마지막 갱신 시간</th>
|
||||
<th>제거</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -96,19 +95,14 @@
|
||||
const directoriesList = document.getElementById('directories-list');
|
||||
const logDiv = document.getElementById('log');
|
||||
|
||||
// 디렉토리 선택 버튼 클릭 시
|
||||
selectDirButton.addEventListener('click', async () => {
|
||||
const result = await window.electronAPI.selectDirectories();
|
||||
if (!result.canceled) {
|
||||
for (const path of result.paths) {
|
||||
addDirectoryToList(path, new Date().toISOString());
|
||||
}
|
||||
refreshDirectories(); // 즉시 갱신
|
||||
}
|
||||
});
|
||||
|
||||
// 디렉토리를 목록에 추가하는 함수
|
||||
function addDirectoryToList(dirPath, lastUpdateTime) {
|
||||
// 이미 목록에 있는 경우 중복 추가 방지
|
||||
function addDirectoryToList(dirPath) {
|
||||
if (document.querySelector(`[data-path="${ dirPath }"]`)) {
|
||||
return;
|
||||
}
|
||||
@@ -119,9 +113,6 @@
|
||||
const pathCell = document.createElement('td');
|
||||
pathCell.textContent = dirPath;
|
||||
|
||||
const timeCell = document.createElement('td');
|
||||
timeCell.textContent = new Date(lastUpdateTime).toLocaleString();
|
||||
|
||||
const removeCell = document.createElement('td');
|
||||
const removeBtn = document.createElement('button');
|
||||
removeBtn.className = 'remove-button';
|
||||
@@ -138,27 +129,34 @@
|
||||
removeCell.appendChild(removeBtn);
|
||||
|
||||
directoryRow.appendChild(pathCell);
|
||||
directoryRow.appendChild(timeCell);
|
||||
directoryRow.appendChild(removeCell);
|
||||
directoriesList.appendChild(directoryRow);
|
||||
}
|
||||
|
||||
// 로그 메시지를 로그 섹션에 추가하는 함수
|
||||
function appendLog(message) {
|
||||
logDiv.textContent += `${ message }\n`;
|
||||
logDiv.scrollTop = logDiv.scrollHeight; // 자동 스크롤
|
||||
}
|
||||
|
||||
// 디렉토리 목록 업데이트 이벤트 수신
|
||||
window.electronAPI.onUpdateDirectories((directories) => {
|
||||
// 기존 목록 초기화
|
||||
function refreshDirectories() {
|
||||
window.electronAPI.getDirectories().then((directories) => {
|
||||
directoriesList.innerHTML = '';
|
||||
for (const [dirPath, lastUpdateTime] of Object.entries(directories)) {
|
||||
addDirectoryToList(dirPath, lastUpdateTime);
|
||||
for (const dirPath of Object.keys(directories)) {
|
||||
addDirectoryToList(dirPath);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
// Initially load directories
|
||||
refreshDirectories();
|
||||
// Then refresh every 10 seconds
|
||||
setInterval(refreshDirectories, 10000);
|
||||
});
|
||||
|
||||
// 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) => {
|
||||
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() {
|
||||
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) => {
|
||||
new Notification({
|
||||
title: "Unhandled Promise Rejection",
|
||||
body: reason.message || "Unknown error",
|
||||
}).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;
|
||||
});
|
||||
}
|
||||
|
||||
115
normalize.js
Normal file → Executable file
115
normalize.js
Normal file → Executable file
@@ -1,14 +1,57 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fs = require("fs").promises;
|
||||
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) {
|
||||
const ignoredItems = [".git", "node_modules", ".env"];
|
||||
return ignoredItems.includes(itemName);
|
||||
}
|
||||
|
||||
// 파일 이름을 정규화하는 함수
|
||||
// Function to normalize file names
|
||||
async function normalizeFileName(filePath) {
|
||||
const dir = path.dirname(filePath);
|
||||
const oldName = path.basename(filePath);
|
||||
@@ -18,21 +61,25 @@ async function normalizeFileName(filePath) {
|
||||
const newPath = path.join(dir, newName);
|
||||
try {
|
||||
await fs.rename(filePath, newPath);
|
||||
if (args.verbose) {
|
||||
console.log(`Renamed: "${oldName}" -> "${newName}"`);
|
||||
}
|
||||
return newPath;
|
||||
} catch (error) {
|
||||
console.error(`이름 변경 실패 ("${oldName}"):`, error);
|
||||
console.error(`Failed to rename "${oldName}":`, error);
|
||||
return filePath;
|
||||
}
|
||||
}
|
||||
return filePath;
|
||||
}
|
||||
|
||||
// 디렉토리를 재귀적으로 처리하는 함수
|
||||
// Function to process directories recursively
|
||||
async function processDirectory(dirPath) {
|
||||
// console.log(`디렉토리 처리 시작: "${dirPath}"`);
|
||||
if (args.verbose) {
|
||||
console.log(`Processing directory: "${dirPath}"`);
|
||||
}
|
||||
try {
|
||||
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
||||
|
||||
for (const entry of entries) {
|
||||
const fullPath = path.join(dirPath, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
@@ -46,55 +93,13 @@ async function processDirectory(dirPath) {
|
||||
}
|
||||
await normalizeFileName(dirPath);
|
||||
} catch (error) {
|
||||
console.error(`디렉토리 처리 중 오류 발생 ("${dirPath}"):`, error);
|
||||
console.error(`Error processing directory "${dirPath}":`, error);
|
||||
}
|
||||
}
|
||||
|
||||
// 디렉토리를 감시하는 함수
|
||||
function watchDirectory(directory) {
|
||||
const watcher = chokidar.watch(directory, {
|
||||
ignored: (pathStr) => {
|
||||
const baseName = path.basename(pathStr);
|
||||
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}"`);
|
||||
// Process the given path based on arguments
|
||||
if (args.directory) {
|
||||
processPath(args.directory);
|
||||
} else if (args.file) {
|
||||
processPath(args.file);
|
||||
}
|
||||
|
||||
// 명령줄 인자로 경로를 받거나 기본값 사용
|
||||
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
37
package.json
37
package.json
@@ -1,28 +1,47 @@
|
||||
{
|
||||
"name": "nfd2nfc",
|
||||
"name": "@pieroot/nfd2nfc",
|
||||
"version": "1.0.0",
|
||||
"main": "main.js",
|
||||
"description": "Convert NFD to NFC",
|
||||
"dependencies": {
|
||||
"chokidar": "^4.0.1",
|
||||
"electron": "^33.2.1",
|
||||
"readdirp": "^4.0.2",
|
||||
"sqlite3": "^5.1.7"
|
||||
"readdirp": "^4.0.2"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "electron .",
|
||||
"package": "electron-packager . NFD2NF --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",
|
||||
"pkg": "pkg normalize.js --target node16-macos-x64,node16-linux-x64,node16-win-x64 --output ./dist/NFD2NFC"
|
||||
"package": "electron-packager . NFD2NFC --platform=darwin --arch=arm64,x64 --icon=build/icons/MacIcon.icns --overwrite --prune=true --out=dist",
|
||||
"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",
|
||||
"prepare-release": "npm run pkg && npm run package"
|
||||
},
|
||||
"bin": {
|
||||
"nfd2nfc": "normalize.js"
|
||||
},
|
||||
"directories": {
|
||||
"output": "dist",
|
||||
"buildResources": "build"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "pieroot",
|
||||
"keywords": [
|
||||
"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",
|
||||
"devDependencies": {
|
||||
"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", {
|
||||
selectDirectories: () => ipcRenderer.invoke("select-directories"),
|
||||
removeDirectory: (dirPath) => ipcRenderer.invoke("remove-directory", dirPath),
|
||||
getDirectories: () => ipcRenderer.invoke("get-directories"),
|
||||
onLog: (callback) =>
|
||||
ipcRenderer.on("log-message", (event, message) => callback(message)),
|
||||
onUpdateDirectories: (callback) =>
|
||||
|
||||
Reference in New Issue
Block a user