从0-1实现文件下载CLI工具(3)

简介: 参数转换传递下面是defaultCommand的逻辑,只需要将相关参数处理后透传给定义的download方法即可,option 不支持 number 所以需要对数字字符串做一些转换

从0-1实现文件下载CLI工具(2):https://developer.aliyun.com/article/1395069?spm=a2c6h.13148508.setting.29.50c84f0e0ugg2G

参数转换传递

下面是defaultCommand的逻辑,只需要将相关参数处理后透传给定义的download方法即可,option 不支持 number 所以需要对数字字符串做一些转换

export default function defaultCommand(url: string, options: CLIOptions) {
  const { filename, location, timeout, proxy, override } = options
  downloadByUrl(url, {
    maxRedirects: +location,
    timeout: +timeout,
    proxy,
    override,
    filename
  })
    .error((err) => {
      console.log('error url:', url)
      console.log('error msg:', redStr(err.message))
      process.exit()
    })
    .end((filepath) => {
      console.log('file save:', underlineStr(yellowStr(filepath)))
    })
}

下面是这块使用演示

image.png

下载进度展示

小文件还能无感等待,大文件咱就得整个进度条来显示了,方遍了解进度。

npm中检索,除了推荐了老牌库 progress,还看到了1个 cli-progress

咱们这里就用后者(最近更新时间看着近一些)

最简单的示例与结果如下

import cliProgress from 'cli-progress'
const progressBar = new cliProgress.SingleBar({})
downloadByUrl(url)
  .progress((cur, rec, sum) => {
    // 初始化
    if (progressBar.getProgress() === 0) {
      progressBar.start(sum, 0)
    }
    // 更新进度
    progressBar.update(rec)
    // 结束
    if (rec === sum) {
      progressBar.stop()
    }
  })

image.png

展示内容过于简单,可以自定义一下显示,展示文件大小和下载速度,参考文档,结合内置的一些值设定初始化如下

const format = '[{bar}] {percentage}% | ETA: {eta}s | {rec}/{sum} | Speed {speed}'
const progressBar = new cliProgress.SingleBar(
  {
    format,
    barsize: 16
  },
  cliProgress.Presets.shades_classic
)

紧接着是start时设置sumspeed默认值

// 初始化的时候计算总大小
progressBar.start(sum, 0, {
  sum: formatSize(sum)
})
// 过程中更新进度
progressBar.update(rec, {
  rec: formatSize(rec),
  speed: speed(cur)
})

formatSize方法实现如下(来源于谷歌推荐代码),短小精悍,将B转换为其它单位展示。

export function formatSize(
  size: number,
  pointLength?: number,
  units?: string[]
) {
  let unit
  units = units || ['B', 'K', 'M', 'G', 'TB']
  // eslint-disable-next-line no-cond-assign
  while ((unit = units.shift()) && size > 1024) {
    size /= 1024
  }
  return (
    (unit === 'B'
      ? size
      : size.toFixed(pointLength === undefined ? 2 : pointLength)) + unit!
  )
}
formatSize(1234) // 1.21K
formatSize(10240) // 10.00K

计算下载速度

speed方法实现如下

  • 使用闭包
  • 一段时间计算一次速度(1000ms / 时间周期 * 周期内下载量B)
/**
 * @param cycle 多久算一次(ms)
 */
function getSpeedCalculator(cycle = 500) {
  let startTime = 0
  let endTime = 0 
  let speed = 'N/A' // 记录速度
  let sum = 0 // 计算之前收到了多少B
  return (chunk: number) => {
    sum += chunk
    if (startTime === 0) {
      startTime = Date.now()
    }
    endTime = Date.now()
    // 计算一次
    if (endTime - startTime >= cycle) {
      speed = `${formatSize((1000 / (endTime - startTime)) * sum)}/s`
      startTime = Date.now()
      sum = 0
    }
    return speed
  }
}
// 获取到计算速度的方法
const speed = getSpeedCalculator()
setTimeout(speed, 200, 4000)
setTimeout(speed, 300, 5000)
setTimeout(speed, 1000, 10240)
setTimeout(() => {
  console.log(speed(0)) // 23.49K/s
}, 1100)

优化后的下载效果如下

image.png

持久化配置存储

proxytimeout参数不希望每次都设置,就需要将这些配置存起来,下次直接读取。

通常的CLI工具都会在/Users/$username/.xxx目录中存放自己的配置文件,即HOME目录下。

image.png

同理我们可以开辟一个文件存放.efstrc

const configPath = path.join(
  process.env.HOME || process.env.USERPROFILE || process.cwd(),
  '.efstrc'
)

读写配置实现如下,利用Array.prototype.reduce方法在遍历的过程中做存取值操作

  • 支持多级的key的读写
  • 兼容异常场景,返回空或空对象
function getCLIConfig(key = '') {
  try {
    const value = JSON.parse(fs.readFileSync(configPath, 'utf-8'))
    return !key
      ? value
      : key.split('.').reduce((pre, k) => {
          return pre?.[key]
        }, value)
  } catch {
    return !key ? {} : ''
  }
}
function setCLIConfig(key: string, value: string) {
  if (!key) {
    return
  }
  const nowCfg = getCLIConfig()
  // 支持传入多级的key
  const keys = key.split('.')
  // 遍历设置的所有都配置都与nowCfg直接或间接的进行了引用关联
  keys.reduce((pre, k, i) => {
    // 赋值
    if (i === keys.length - 1) {
      pre[k] = value
    } else if (!(pre[k] instanceof Object)) {
      pre[k] = {}
    }
    return pre[k]
  }, nowCfg)
  // 输出到文件
  fs.writeFileSync(configPath, JSON.stringify(nowCfg, null, 2))
}
setCLIConfig('proxy', 'http://127.0.0.1:7890')
setCLIConfig('timeout', '2000')
setCLIConfig('github.name', 'ATQQ')
setCLIConfig('github.info.url', 'https://github.com/ATQQ')

image.png

再添加一个移除配置的方法,与设置的的方法类似只是使用delete操作符删除相关的key

function delCLIConfig(key: string) {
  if (!key) {
    return
  }
  const nowCfg = getCLIConfig()
  const keys = key.split('.')
  keys.reduce((pre, k, i) => {
    // 移除
    if (i === keys.length - 1) {
      delete pre[k]
    }
    return pre[k] instanceof Object ? pre[k] : {}
  }, nowCfg)
  fs.writeFileSync(configPath, JSON.stringify(nowCfg, null, 2))
}
delCLIConfig('timeout')
delCLIConfig('github.info.name')
delCLIConfig('github.name')

image.png

有了这3个方法支撑就可以封装成一个config指令用于配置的CRUD

config指令实现

先是定义

program
  .command('config <type> <key> [value]')
  .alias('c')
  .description('crud config <type> in [del,get,set]')
  .action(configCommand)

image.png

configCommand封装实现

export type ConfigType = 'set' | 'get' | 'del'
function defaultCommand(
  type: ConfigType,
  key: string,
  value: string
) {
  if (type === 'set') {
    setCLIConfig(key, value)
  }
  if (type === 'del') {
    delCLIConfig(key)
  }
  if (type === 'get') {
    console.log(getCLIConfig(key) || '')
  }
}

使用演示如下

image.png

config 指令这部分逻辑完全可以分离成一个通用的 commander 模块,在需要的CLI里直接注册即可,简化后大概如下

import { Command } from 'commander'
const program = new Command()
registerConfigCommand(program,'.efstrc')

最后

笔者对这个工具的想法还有很多,后续先把功能🐴出来再写续集,本文就先到这里。

内容有不妥的之处,还请评论区斧正。

CLI完整源码见GitHub


相关文章
|
Linux C语言 开发者
深入解析Linux环境下的scanf()、sscanf()和fscanf()函数
在C语言中,`scanf()`、`sscanf()`和`fscanf()`是用于输入的三个常用函数。它们允许开发者从标准输入、字符串和文件中按照指定的格式读取数据。在Linux环境下,这些函数被广泛用于处理各种输入。本文将详细介绍这三个函数的用法,包括格式化字符串的语法和一些常见的使用场景。
1180 1
|
9月前
|
存储 前端开发 区块链
基于区块链的慈善捐赠平台:透明与信任的未来
基于区块链的慈善捐赠平台:透明与信任的未来
440 24
|
设计模式 缓存 Java
Java高并发处理机制
Java高并发处理机制
112 1
|
前端开发 Ubuntu 应用服务中间件
想要使用 Nginx 部署多个前端项目,可行吗?
想要使用 Nginx 部署多个前端项目,可行吗?
1380 2
|
Kubernetes 图形学 容器
【Agones系列】Agones初体验
本文介绍了Agones并在阿里云容器服务上运行Agones进行游戏服部署
【Agones系列】Agones初体验
|
JavaScript 前端开发
【Axure教程】上传本地图片
【Axure教程】上传本地图片
【Axure教程】上传本地图片
|
存储 安全 API
每日一博 - Token Based Authentication VS HMAC Authentication 实现web安全
每日一博 - Token Based Authentication VS HMAC Authentication 实现web安全
171 0
|
存储 缓存 算法
Go --- GCache缓存官方例子
Go --- GCache缓存官方例子
|
Web App开发
某教程学习笔记(一):04、HTML基础
某教程学习笔记(一):04、HTML基础
118 0
某教程学习笔记(一):04、HTML基础
|
存储 缓存 算法
HLS介绍 - 03 - 以计算为中心的算法
HLS介绍 - 03 - 以计算为中心的算法
352 0
HLS介绍 - 03 - 以计算为中心的算法