mirror of
https://github.com/jung-geun/NFD2NFC.git
synced 2025-12-19 20:14:39 +09:00
macOS 관련 파일 무시 규칙 추가 및 파일 이름 정규화 기능 개선
This commit is contained in:
39
.gitignore
vendored
39
.gitignore
vendored
@@ -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
0
convert/.gitkeep
Normal file
106
normalize.js
106
normalize.js
@@ -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
44
package-lock.json
generated
Normal 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
50
package.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user