import fs, { Stats, lstat } from 'fs' import readline from 'readline' import path from 'path' const TEMPLATE_FILENAME = 'musis-template.html' const PLACEHOLDER = '
' const DEFAULT_TEMPLATE = ` MusIs
` const rl = readline.createInterface({ input: process.stdin, output: process.stdout }) const fsp = fs.promises function q1 (_recursive?: string[]): Promise { return new Promise((resolve, reject) => { try { rl.question('확장자 입력.(예시: mp3) 그만 입력하려면 엔터>', async (answer) => { if (answer) { if (_recursive) { _recursive.push(answer) return resolve(await q1(_recursive)) } else { return resolve(await q1([ answer ])) } } else { resolve(_recursive || []) } }) } catch (err) { reject(err) } }) } function q2 (cwd: string, _recursive?: string[]): Promise { return new Promise((resolve, reject) => { let subdir = '' try { rl.question('음악 스캔할 하위 폴더 이름. 그만 입력하려면 엔터>', async (answer) => { if (answer) { const stat = await fsp.lstat(subdir = path.join(cwd, answer)) if (!stat.isDirectory()) throw new Error('폴더가 아님: ' + subdir) if (_recursive) { _recursive.push(answer) return resolve(await q2(cwd, _recursive)) } else { return resolve(await q2(cwd, [ answer ])) } } else { resolve(_recursive || []) } }) } catch (err) { if (err.code === 'ENOENT') { reject(new Error('존재하지 않는 폴더: ' + subdir)) } else { reject(err) } } }) } function checkExtension(fileName: string, extensionArray: string[]): boolean { const split = fileName.split('.') const last = split[split.length - 1].toLowerCase() for (let ext of extensionArray) { if (last === ext.toLowerCase()) { return true } } return false } async function dirToHtml (cwd: string, subdir: string, exts: string[]): Promise { const subcwd = path.join(cwd, subdir) // 파일 읽기 console.log('파일 읽어들이는 중...') const files = await fsp.readdir(subcwd) console.log('폴더안의 총 파일 갯수: ' + files.length) const filesNameFilter = files.filter(v => checkExtension(v, exts)) console.log('확장자에 맞는 파일 갯수: ' + filesNameFilter.length) const filesTypeFilter = [] let _path: string let _stat: Stats for (let file of filesNameFilter) { _path = path.join(subcwd, file) _stat = await fsp.lstat(_path) if (_stat.isFile()) filesTypeFilter.push(file) } console.log('폴더 제외 후 갯수: ' + filesTypeFilter.length) if (filesTypeFilter.length === 0) { console.error('찾은 파일 갯수 0. 무시됨') return 3 } console.log('HTML 빌드 중...') let hash = parseInt('' + (Math.random() * 0xFFFFFF)).toString(16).padStart(6, '0') let source = `\n

${ subdir }

    ` for (let music of filesTypeFilter) { source += `\n
  • ${ music }
  • ` } source += `\n
` return source } async function main (): Promise { const cwd = path.resolve('.') console.log('현재 폴더: ' + cwd) // 템플릿 읽거나 없으면 새로 만들기 let template: string const templateFile = path.join(cwd, TEMPLATE_FILENAME) try { template = await fsp.readFile(templateFile, 'utf-8') } catch (err) { if (err.code === 'ENOENT') { fsp.writeFile(templateFile, DEFAULT_TEMPLATE, 'utf-8') console.log(`템플릿 새로 생성(${ TEMPLATE_FILENAME }) 원하는 대로 커스터마이징`) console.log('이후 프로그램 재시작') console.log(`\n(주의: "${ PLACEHOLDER }" 태그 편집 금지)`) return 10 } else { throw err } } // 템플릿 유효성 확인 if (template.indexOf(PLACEHOLDER) === -1) { console.error(`템플릿에서 "${ PLACEHOLDER }" 태그를 찾을 수 없음`) return 10 } // 하위 폴더 선택 const subdirs = await q2(cwd) // 확장자 const exts = await q1() if (exts.length === 0) { console.error('아무 확장자도 입력하지 않았으므로 무시') return 3 } else { console.log('입력된 확장자:', exts) } let sources = '' let result: string|number = 0 for (const subdir of subdirs) { result = await dirToHtml(cwd, subdir, exts) if (typeof result === 'number') return result sources += result } console.log('HTML 파일 생성 중...') const html = template.replace(PLACEHOLDER, sources) await fsp.writeFile(`MusIs.html`, html, 'utf-8') console.log('성공') return 3 } main().then(timeout => { console.log('=종료=') console.log(`콘솔 ${ timeout }초 후 닫힘...`) setTimeout(() => { process.exit(0) }, timeout * 1000) }).catch(err => { console.error(err) console.error('=치명적 에러=') console.error('콘솔 10초 후 닫힘...') setTimeout(() => { process.exit(0) }, 10000) })