파일 이름 변환기 기능 추가: README 문서 작성 및 CLI 도구와 백그라운드 프로세스 구현

This commit is contained in:
2024-12-18 02:35:22 +09:00
parent 3f849808ac
commit 94059a0947
29 changed files with 3898 additions and 0 deletions

35
nfd2nfc/README.md Normal file
View 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
View 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
View 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
View 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
View 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"
}