mirror of
https://github.com/jung-geun/NFD2NFC.git
synced 2025-12-19 20:14:39 +09:00
파일 이름 변환기 기능 추가: README 문서 작성 및 CLI 도구와 백그라운드 프로세스 구현
This commit is contained in:
35
nfd2nfc/README.md
Normal file
35
nfd2nfc/README.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# 파일 이름 변환기
|
||||
|
||||
백그라운드에서 파일을 감지하고 변환하여 파일 이름을 NFD에서 NFC 인코딩으로 자동 변환하는 macOS 패키지입니다.
|
||||
|
||||
npm 패키지는 명령어를 통해 사용할 수 있는 CLI 도구를 제공합니다.
|
||||
Application 패키지는 macOS에서 백그라운드 프로세스로 실행되며, 파일 변환을 자동으로 처리합니다.
|
||||
|
||||
## 특징
|
||||
|
||||
- 자동 파일 감지
|
||||
- 백그라운드 변환 프로세스
|
||||
- NFD에서 NFC 변환 지원
|
||||
|
||||
## 설치
|
||||
|
||||
```bash
|
||||
# 설치 지침을 여기에 작성하세요
|
||||
npm install -g @pieroot/nfd2nfc
|
||||
# or
|
||||
npm i @pieroot/nfd2nfc
|
||||
```
|
||||
|
||||
## 사용법
|
||||
|
||||
```bash
|
||||
nfd2nfc [options] <path>
|
||||
|
||||
Options:
|
||||
-V, --version output the version number
|
||||
-h, --help display help for command
|
||||
```
|
||||
|
||||
## 라이선스
|
||||
|
||||
MIT 라이선스
|
||||
127
nfd2nfc/normalize.js
Executable file
127
nfd2nfc/normalize.js
Executable file
@@ -0,0 +1,127 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fs = require("fs").promises;
|
||||
const path = require("path");
|
||||
const minimist = require("minimist");
|
||||
const cliProgress = require("cli-progress");
|
||||
|
||||
// Parse command-line arguments
|
||||
const args = minimist(process.argv.slice(2), {
|
||||
alias: { v: "verbose", h: "help" },
|
||||
boolean: ["verbose", "help"],
|
||||
});
|
||||
|
||||
// Function to display help message
|
||||
function displayHelp() {
|
||||
console.log(`
|
||||
Usage: node index.js [path] [options]
|
||||
Options:
|
||||
-v, --verbose Enable verbose logging with progress bar
|
||||
-h, --help Display this help message
|
||||
Provide a path directly as an argument
|
||||
`);
|
||||
}
|
||||
|
||||
// Check for help flag
|
||||
if (args.help) {
|
||||
displayHelp();
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Main processing logic
|
||||
async function processPath(targetPath) {
|
||||
try {
|
||||
const stats = await fs.lstat(targetPath);
|
||||
const depth = 0;
|
||||
|
||||
if (stats.isDirectory()) {
|
||||
await processDirectory(targetPath, depth);
|
||||
} 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);
|
||||
const newName = oldName.normalize("NFC");
|
||||
|
||||
if (oldName !== newName && !shouldIgnore(oldName)) {
|
||||
const newPath = path.join(dir, newName);
|
||||
try {
|
||||
await fs.rename(filePath, newPath);
|
||||
return newPath;
|
||||
} catch (error) {
|
||||
console.error(`Failed to rename "${oldName}":`, error);
|
||||
return filePath;
|
||||
}
|
||||
}
|
||||
return filePath;
|
||||
}
|
||||
|
||||
let entriesLength = 0;
|
||||
|
||||
// Function to process directories recursively
|
||||
async function processDirectory(dirPath, depth = 0) {
|
||||
try {
|
||||
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
||||
const filesToProcess = entries.filter(
|
||||
(entry) => !entry.isDirectory()
|
||||
).length;
|
||||
let processedFiles = 0;
|
||||
if (args.verbose && depth == 0) {
|
||||
entriesLength = entries.length;
|
||||
console.log("Processing directory:", dirPath);
|
||||
progressBar.start(entriesLength, 0);
|
||||
}
|
||||
|
||||
for (const entry of entries) {
|
||||
const fullPath = path.join(dirPath, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
if (!shouldIgnore(entry.name)) {
|
||||
await processDirectory(fullPath, depth + 1);
|
||||
}
|
||||
} else {
|
||||
await normalizeFileName(fullPath);
|
||||
}
|
||||
if (args.verbose && filesToProcess > 0 && depth == 0) {
|
||||
processedFiles++;
|
||||
progressBar.update((processedFiles / entriesLength) * entriesLength);
|
||||
}
|
||||
}
|
||||
await normalizeFileName(dirPath);
|
||||
|
||||
if (args.verbose && depth == 0) {
|
||||
progressBar.stop();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error processing directory "${dirPath}":`, error);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize progress bar
|
||||
const progressBar = new cliProgress.SingleBar(
|
||||
{
|
||||
format: "{bar} {percentage}% | {value}/{total}",
|
||||
clearOnComplete: false,
|
||||
},
|
||||
cliProgress.Presets.shades_classic
|
||||
);
|
||||
|
||||
// Handle input: if no flags, assume first non-flag argument is a path
|
||||
const nonFlagArgs = args._;
|
||||
if (nonFlagArgs.length > 0) {
|
||||
processPath(nonFlagArgs[0]);
|
||||
} else {
|
||||
displayHelp();
|
||||
}
|
||||
114
nfd2nfc/normalize_ko.js
Normal file
114
nfd2nfc/normalize_ko.js
Normal file
@@ -0,0 +1,114 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fs = require("fs").promises;
|
||||
const path = require("path");
|
||||
const minimist = require("minimist");
|
||||
|
||||
function containsKorean(text) {
|
||||
// 한글 유니코드 범위: 가-힣, ㄱ-ㅎ, ㅏ-ㅣ
|
||||
return /[가-힣ㄱ-ㅎㅏ-ㅣ]/.test(text);
|
||||
}
|
||||
|
||||
// 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);
|
||||
const newName = oldName.normalize("NFC");
|
||||
|
||||
if (
|
||||
oldName !== newName &&
|
||||
!shouldIgnore(oldName) &&
|
||||
containsKorean(oldName)
|
||||
) {
|
||||
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(`Failed to rename "${oldName}":`, error);
|
||||
return filePath;
|
||||
}
|
||||
}
|
||||
return filePath;
|
||||
}
|
||||
|
||||
// Function to process directories recursively
|
||||
async function processDirectory(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()) {
|
||||
if (!shouldIgnore(entry.name)) {
|
||||
await processDirectory(fullPath);
|
||||
await normalizeFileName(fullPath);
|
||||
}
|
||||
} else {
|
||||
await normalizeFileName(fullPath);
|
||||
}
|
||||
}
|
||||
await normalizeFileName(dirPath);
|
||||
} catch (error) {
|
||||
console.error(`Error processing directory "${dirPath}":`, error);
|
||||
}
|
||||
}
|
||||
|
||||
// Process the given path based on arguments
|
||||
if (args.directory) {
|
||||
processPath(args.directory);
|
||||
} else if (args.file) {
|
||||
processPath(args.file);
|
||||
}
|
||||
91
nfd2nfc/package-lock.json
generated
Normal file
91
nfd2nfc/package-lock.json
generated
Normal file
@@ -0,0 +1,91 @@
|
||||
{
|
||||
"name": "@pieroot/nfd2nfc",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@pieroot/nfd2nfc",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cli-progress": "^3.12.0",
|
||||
"minimist": "^1.2.8"
|
||||
},
|
||||
"bin": {
|
||||
"nfd2nfc": "normalize.js"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/cli-progress": {
|
||||
"version": "3.12.0",
|
||||
"resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.12.0.tgz",
|
||||
"integrity": "sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"string-width": "^4.2.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/is-fullwidth-code-point": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/minimist": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
||||
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/string-width": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
"strip-ansi": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-ansi": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
38
nfd2nfc/package.json
Normal file
38
nfd2nfc/package.json
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"name": "@pieroot/nfd2nfc",
|
||||
"version": "1.0.0",
|
||||
"main": "main.js",
|
||||
"description": "Convert NFD to NFC",
|
||||
"dependencies": {
|
||||
"cli-progress": "^3.12.0",
|
||||
"minimist": "^1.2.8"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "pkg normalize.js --target node16-macos-x64,node16-linux-x64,node16-win-x64 --output ./dist/NFD2NFC"
|
||||
},
|
||||
"bin": {
|
||||
"nfd2nfc": "normalize.js"
|
||||
},
|
||||
"directories": {
|
||||
"output": "dist"
|
||||
},
|
||||
"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",
|
||||
"bugs": {
|
||||
"url": "https://github.com/jung-geun/NFD2NFC/issues"
|
||||
},
|
||||
"homepage": "https://github.com/jung-geun/NFD2NFC#readme"
|
||||
}
|
||||
Reference in New Issue
Block a user