macOS 관련 파일 무시 규칙 추가 및 파일 이름 정규화 기능 개선

This commit is contained in:
2024-12-16 22:17:47 +09:00
parent d8c15b6653
commit 24390231bc
5 changed files with 196 additions and 43 deletions

39
.gitignore vendored
View File

@@ -1,5 +1,5 @@
# Created by https://www.toptal.com/developers/gitignore/api/node,visualstudiocode,git # Created by https://www.toptal.com/developers/gitignore/api/macos,git,visualstudiocode,node
# Edit at https://www.toptal.com/developers/gitignore?templates=node,visualstudiocode,git # Edit at https://www.toptal.com/developers/gitignore?templates=macos,git,visualstudiocode,node
### Git ### ### Git ###
# Created by git for backups. To disable backups in Git: # Created by git for backups. To disable backups in Git:
@@ -16,6 +16,39 @@
*_LOCAL_*.txt *_LOCAL_*.txt
*_REMOTE_*.txt *_REMOTE_*.txt
### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### macOS Patch ###
# iCloud generated files
*.icloud
### Node ### ### Node ###
# Logs # Logs
logs logs
@@ -175,4 +208,4 @@ dist
.history .history
.ionide .ionide
# End of https://www.toptal.com/developers/gitignore/api/node,visualstudiocode,git # End of https://www.toptal.com/developers/gitignore/api/macos,git,visualstudiocode,node

0
convert/.gitkeep Normal file
View File

View File

@@ -1,82 +1,108 @@
const fs = require("fs").promises; const fs = require("fs").promises;
const path = require("path"); const path = require("path");
const chokidar = require("chokidar");
// 특정 파일/디렉토리를 무시하는 기능 추가 // 무시할 파일/디렉토리를 결정하는 함수
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);
} }
// 파일 이름을 정규화하는 함수
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);
const newName = oldName.normalize("NFC"); const newName = oldName.normalize("NFC");
// console.log(`정규화 시도: "${oldName}" -> "${newName}"`);
if (oldName !== newName && !shouldIgnore(oldName)) { if (oldName !== newName && !shouldIgnore(oldName)) {
const newPath = path.join(dir, newName); const newPath = path.join(dir, newName);
try { try {
// 경로에 공백이 있을 경우를 대비해 이스케이프 처리 await fs.rename(filePath, newPath);
const escapedOldPath = filePath.replace(/ /g, "\\ "); // console.log(`이름 변경 성공: "${oldName}" -> "${newName}"`);
const escapedNewPath = newPath.replace(/ /g, "\\ ");
await fs.rename(escapedOldPath, escapedNewPath);
console.log(`이름 변경: "${oldName}" -> "${newName}"`);
return newPath; return newPath;
} catch (error) { } catch (error) {
console.error(`이름 변경 실패 ("${oldName}"):`, error); console.error(`이름 변경 실패 ("${oldName}"):`, error);
return filePath; return filePath;
} }
} else {
// console.log(`정규화 필요 없음: "${oldName}"`);
} }
return filePath; return filePath;
} }
// 디렉토리를 재귀적으로 처리하는 함수
async function processDirectory(dirPath) { async function processDirectory(dirPath) {
// console.log(`디렉토리 처리 시작: "${dirPath}"`);
try { try {
// 경로에 공백이 있을 경우를 대비해 이스케이프 처리 const entries = await fs.readdir(dirPath, { withFileTypes: true });
const escapedDirPath = dirPath.replace(/ /g, "\\ ");
const entries = await fs.readdir(escapedDirPath, { withFileTypes: true });
const items = entries.map((entry) => ({ for (const entry of entries) {
name: entry.name, const fullPath = path.join(dirPath, entry.name);
fullPath: path.join(dirPath, entry.name), // console.log(`파일 처리 시작: "${fullPath}"`);
isDirectory: entry.isDirectory(), if (entry.isDirectory()) {
})); if (!shouldIgnore(entry.name)) {
await processDirectory(fullPath);
for (const item of items) { await normalizeFileName(fullPath);
if (item.isDirectory) { }
await processDirectory(item.fullPath);
await normalizeFileName(item.fullPath);
} else { } else {
await normalizeFileName(item.fullPath); await normalizeFileName(fullPath);
} }
// console.log(`파일 처리 완료: "${fullPath}"`);
} }
await normalizeFileName(dirPath);
// console.log(`디렉토리 처리 완료: "${dirPath}"`);
} catch (error) { } catch (error) {
console.error(`디렉토리 처리 중 오류 발생 ("${dirPath}"):`, error); console.error(`디렉토리 처리 중 오류 발생 ("${dirPath}"):`, error);
} }
} }
async function processRoot(rootPath) { // 디렉토리를 감시하는 함수
try { function watchDirectory(directory) {
// 경로에 공백이 있을 경우를 대비해 이스케이프 처리 const watcher = chokidar.watch(directory, {
const escapedRootPath = path.resolve(rootPath); ignored: (pathStr) => {
const stats = await fs.stat(escapedRootPath); const baseName = path.basename(pathStr);
return shouldIgnore(baseName);
},
persistent: true,
ignoreInitial: false, // 초기 파일 추가 이벤트를 감지
awaitWriteFinish: {
stabilityThreshold: 200, // 파일이 완전히 작성될 때까지 대기 (ms)
pollInterval: 100,
},
depth: Infinity, // 하위 디렉토리까지 감시
});
if (stats.isDirectory()) { watcher
const normalizedRootPath = await normalizeFileName(rootPath); .on("add", async (filePath) => {
await processDirectory(normalizedRootPath); // console.log(`파일 추가됨: "${filePath}"`);
} else { await normalizeFileName(filePath);
await normalizeFileName(rootPath); })
} .on("change", async (filePath) => {
} catch (error) { // console.log(`파일 변경됨: "${filePath}"`);
console.error(`처리 중 오류 발생 ("${rootPath}"):`, error); 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"; const targetPath = process.argv[2] || "./convert";
// 프로그램 실행 // 디렉토리 감시 시작
processRoot(targetPath) watchDirectory(targetPath);
.then(() => console.log("모든 처리가 완료되었습니다."))
.catch((error) => console.error("프로그램 실행 중 오류 발생:", error));

44
package-lock.json generated Normal file
View File

@@ -0,0 +1,44 @@
{
"name": "nfd-to-nfc-converter",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "nfd-to-nfc-converter",
"version": "1.0.0",
"hasInstallScript": true,
"dependencies": {
"chokidar": "^4.0.1"
}
},
"node_modules/chokidar": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz",
"integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==",
"license": "MIT",
"dependencies": {
"readdirp": "^4.0.1"
},
"engines": {
"node": ">= 14.16.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/readdirp": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz",
"integrity": "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==",
"license": "MIT",
"engines": {
"node": ">= 14.16.0"
},
"funding": {
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
}
}
}

50
package.json Normal file
View File

@@ -0,0 +1,50 @@
{
"name": "nfd-to-nfc-converter",
"version": "1.0.0",
"main": "main.js",
"scripts": {
"start": "electron .",
"build": "electron-builder",
"postinstall": "electron-builder install-app-deps"
},
"build": {
"appId": "com.pieroot.nfdtonfc",
"mac": {
"category": "public.app-category.utilities",
"target": [
"dmg",
"pkg"
],
"hardenedRuntime": true,
"gatekeeperAssess": false,
"entitlements": "build/entitlements.mac.plist",
"entitlementsInherit": "build/entitlements.mac.plist"
},
"files": [
"**/*",
"!**/node_modules/*/{CHANGELOG.md,README.md,README,readme.md,readme}",
"!**/node_modules/*/{test,__tests__,tests,powered-test,example,examples}",
"!**/node_modules/*.d.ts",
"!**/node_modules/.bin",
"!**/*.{iml,o,hprof,orig,pyc,pyo,rbc,swp,csproj,sln,xproj}",
"!.editorconfig",
"!**/._*",
"!**/{.DS_Store,.git,.hg,.svn,CVS,RCS,SCCS,.gitignore,.gitattributes}",
"!**/{__pycache__,thumbs.db,.flowconfig,.idea,.vs,.nyc_output}",
"!**/{appveyor.yml,.travis.yml,circle.yml}",
"!**/{npm-debug.log,yarn.lock,.yarn-integrity,.yarn-metadata.json}"
],
"extraResources": [
{
"from": "scripts",
"to": "scripts",
"filter": [
"**/*"
]
}
]
},
"dependencies": {
"chokidar": "^4.0.1"
}
}