@oawu/cli-progress
Version:
🚀 轉轉轉,現在跑到哪裡惹?
317 lines (275 loc) • 8.06 kB
JavaScript
/**
* @author OA Wu <comdan66@gmail.com>
* @copyright Copyright (c) 2015 - 2025, @oawu/cli-progress
* @license http://opensource.org/licenses/MIT MIT License
* @link https://www.ioa.tw/
*/
const Xterm = require('@oawu/xterm')
const Print = (...strs) => process.stdout.write(`\r${strs.join('')}`)
const Progress = {
lines: [],
preLines: [],
timer: null,
finish: null,
option: {
space: 3, color: false,
$: {
title: {
value: '',
color: text => text
},
subtitle: {
value: '',
color: text => text
},
percent: {
value: '',
color: text => text
},
header: {
value: '◉',
color: text => Xterm.purple(text)
},
newline: {
value: '↳',
color: text => Xterm.purple(text).dim()
},
dash: {
value: '─',
color: text => Xterm.dim(text)
},
dot: {
value: '…',
color: text => Xterm.lightBlack(text).dim()
},
loading: {
_value: '⠦⠧⠇⠏⠉⠙⠹⠸⠼⠴⠤⠦',
_index: 0,
_length: 12,
get value() {
return this._value[this._index++ % this._length]
},
set value(val) {
this._index = 0
this._length = val.length
this._value = val
},
color: text => Xterm.yellow(text)
},
done: {
value: '完成',
color: text => Xterm.green(text)
},
fail: {
value: '錯誤',
color: text => Xterm.red(text)
},
index: {
value: '',
color: text => Xterm.dim(text)
}
},
},
percent: {
index: null,
total: null,
text: '',
toString(percent) {
if (this.index !== null && this.total !== null) {
percent = Math.ceil(this.index * 100) / this.total
Progress.option.index = `(${this.index}/${this.total})`
percent = parseInt(percent <= 100 ? percent >= 0 ? percent : 0 : 100, 10)
Progress.option.percent = `${percent}%`
return [
Progress.option.index,
Progress.option.percent,
this.text
].filter(t => t !== '').join(` ${Progress.option.dash} `)
}
return this.text !== ''
? ` ${Progress.option.dash} ${this.text}`
: ''
},
appendTo(lines) {
const s = ' '.repeat(Progress.option.space)
if (!lines.length) {
return `${s}${this}`
}
const header = (index, space) => index
? `${space} ${Progress.option.newline}`
: Progress.option.header
const title = (index, str) => index
? (Progress.option.subtitle = str, Progress.option.subtitle)
: (Progress.option.title = str, Progress.option.title)
const percent = index => index
? ''
: this
return [...lines].map(({ index, space, str }) => `\x1b[K${s}${header(index, space)} ${title(index, str)}${percent(index)}`).join("\n")
}
},
clean() {
if (!Progress.option.$.loading._index) {
return ''
}
if (Progress.lines.length > 1) {
return `\x1b[${Progress.lines.length - 1}A`
}
return "\r"
},
advance(val = 1) {
Progress.percent.index += val
if (Progress.percent.index > Progress.percent.total) {
Progress.percent.index = Progress.percent.total
}
return Progress
},
title(...strs) {
if (Progress.timer) {
return Progress
}
Progress.option.$.loading._index = 0
Progress.lines = []
Progress.appendTitle(...strs)
Progress.timer = setInterval(Progress._timer.bind(Progress, false), 85, Progress._timer(false))
return Progress
},
_timer(fromStop) {
if (!fromStop && Progress.finish) {
return Progress.stop()
}
if (Progress.lines.length > 1) {
Print(Progress.clean())
}
if (Progress.preLines.length) {
Progress.lines = Progress.lines.concat(Progress.preLines).map(({ space, str }, index) => ({ space, str, index }))
Progress.preLines = []
}
fromStop
? Print(`${Progress.percent.appendTo(Progress.lines)}\n`)
: Print(`${Progress.percent.appendTo(Progress.lines)}${Progress.option.dot} ${Progress.option.loading} `)
},
appendTitle(...strs) {
Progress.preLines = Progress.preLines.concat(strs.map(line => {
const match = /(?<space>^\s*)(?<str>.*)/gm.exec(line)
return match !== null ? { ...match.groups } : null
})
.filter(line => line !== null))
return Progress
},
total(total) {
Progress.percent.total = total
Progress.percent.index = 0
return Progress
},
stop() {
if (Progress.timer === null) {
return Progress
}
clearInterval(Progress.timer)
Progress._timer(true)
Progress.lines = []
Progress.preLines = []
Progress.percent.index = null
Progress.percent.total = null
Progress.percent.text = ''
Progress.option.$.loading._index = 0
Progress.finish()
Progress.finish = null
Progress.timer = null
return Progress
},
done(message = '完成') {
Progress.percent.index = Progress.percent.total
Progress.option.done = message
Progress.percent.text = Progress.option.done
Progress.stop(Progress.finish = _ => { })
return Progress
},
fail(message = '錯誤', ...errors) {
Progress.option.fail = message === null || message === undefined
? '錯誤'
: message
Progress.percent.text = Progress.option.fail
Progress.stop(Progress.finish = _ => Progress.error(...errors))
return Progress
},
error(...errors) {
if (errors.length <= 0) {
return Progress
}
Print(
`${Progress.option.color
? "\n 【錯誤訊息】\n".red
: "\n 【錯誤訊息】\n"}${errors.map(
error => `${' '.repeat(Progress.option.space)}${Progress.option.header} ${error instanceof Error
? error.stack
: error}\n`).join('')}\n`)
process.emit('SIGINT')
return Progress
},
}
Object.keys(Progress.option.$).forEach(key => Object.defineProperty(Progress.option, key, {
set: val => Progress.option.$[key].value = val,
get: _ => {
const f = function () { }
return f.toString = _ => Progress.option.color ? Progress.option.$[key].color(Progress.option.$[key].value).toString() : Progress.option.$[key].value, Object.defineProperty(f, 'color', { set: func => Progress.option.$[key].color = func }), f
}
}))
Progress.Multi = function (text, percent = 0) {
if (!(this instanceof Progress.Multi)) {
return new Progress.Multi(text, percent)
}
this._text = text
this._percent = percent
}
Object.defineProperty(Progress.Multi.prototype, 'text', {
set(val) {
this.refresh(this._text = val)
}
})
Object.defineProperty(Progress.Multi.prototype, 'percent', {
set(val) {
this.refresh(this._percent = val)
}
})
Progress.Multi.prototype.toString = function () {
return `${this._percent < 100
? this._percent < 10
? ` ${this._percent}`
: ` ${this._percent}`
: `${this._percent}`}% | ${this._text}`
}
Progress.Multi.prototype.refresh = function () {
if (this._index === null || this._index >= process.stdout.rows || Progress.Multi.lock) {
return this
}
process.stdout.write(`\x1b[${this._index}A\r\x1b[K`)
process.stdout.write(`${this}`)
Progress.Multi.back()
return this
}
Progress.Multi.save = _ => process.stdout.write('\x1b[s')
Progress.Multi.back = _ => process.stdout.write(`\x1b[u`)
Progress.Multi.lock = false
Progress.Multi.push = task => Progress.Multi.container.push(task)
Progress.Multi.container = {
$: [],
clean() {
this.$ = []
Progress.Multi.back()
return this
},
push(task) {
Progress.Multi.lock = true
this.$.push(task)
const len = this.$.length
for (let i = 0; i < len; i++) {
this.$[i]._index = len - i
}
process.stdout.write(`${task}\n`)
Progress.Multi.save()
Progress.Multi.lock = false
return task
}
}
module.exports = Progress