README.md 추가 및 CLI 도구에 대한 사용법 설명: 파일 이름 변환기 기능 구현, 디렉토리 목록 가져오기 기능 추가

This commit is contained in:
2024-12-17 02:57:18 +09:00
parent 2794eb1c45
commit 7afcf19178
7 changed files with 188 additions and 1342 deletions

29
README.md Normal file
View File

@@ -0,0 +1,29 @@
# 파일 이름 변환기
백그라운드에서 파일을 감지하고 변환하여 파일 이름을 NFD에서 NFC 인코딩으로 자동 변환하는 macOS 패키지입니다.
npm 패키지는 명령어를 통해 사용할 수 있는 CLI 도구를 제공합니다.
Application 패키지는 macOS에서 백그라운드 프로세스로 실행되며, 파일 변환을 자동으로 처리합니다.
## 특징
- 자동 파일 감지
- 백그라운드 변환 프로세스
- NFD에서 NFC 변환 지원
## 설치
```bash
# 설치 지침을 여기에 작성하세요
npm install -g @pieroot/nfd2nfc
```
## 사용법
```bash
```
## 라이선스
MIT 라이선스

View File

@@ -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
View File

@@ -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
View 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

File diff suppressed because it is too large Load Diff

View File

@@ -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"
}

View File

@@ -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) =>