从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


相关文章
|
5天前
|
前端开发 Java 测试技术
性能工具之 JMeter 上传与下载脚本编写
【4月更文挑战第3天】性能测试工作中,文件上传也是经常见的性能压测场景之一,那么 JMeter 文件上传下载脚本怎么做?
36 2
性能工具之 JMeter 上传与下载脚本编写
|
5天前
|
JavaScript 前端开发 数据安全/隐私保护
NodeJS 下构建 命令行工具(CLI) 与 交互式命令界面 的实践
NodeJS 下构建 命令行工具(CLI) 与 交互式命令界面 的实践
250 1
|
5天前
从0-1实现文件下载CLI工具(2)
示例代码5 Proxy 部分资源访问不顺畅的时候,通常会走服务代理(🪜) 以谷歌的logo资源链接https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_92x30dp.png 要让前面的方法downloadByUrl顺利执行,就需要其走代理服务
|
5天前
|
存储
从0-1实现文件下载CLI工具(1)
前言 在日常学习/生活中,下载资源时,大部分情况是通过别人分享的资源站点,找到下载入口然后触发下载。 当资源通过url传播的时候,一般也是直接打开,通过浏览器触发下载。 资深的冲浪选手,一般会用一些客户端工具(还记得Win上的各种下载器),Mac上笔者有时候会使用 NeatDownloadManager,无 🪜 时也能拥有不错的下载速度
|
5天前
|
安全 网络安全 数据安全/隐私保护
Mendelson AS2 介绍下载和配置
Mendelson AS2 介绍下载和配置
|
7月前
|
数据安全/隐私保护 iOS开发
如何使用 altool 命令行工具上传 IPA 包:
如何使用 altool 命令行工具上传 IPA 包:
29 0
|
安全 关系型数据库 MySQL
如何下载和安装 WordPress 核心文件?
您有两种不同的选择来下载和安装 WordPress。 1.手动下载 WordPress 的更长、更自定义的方式允许您在开始设计之前根据站点的确切需求定制安装。 2.下载和安装 WordPress 的第二个选项是大多数 WordPress 主机免费提供的一键式选项。此选项是两者中较容易的一个,但在某些情况下,会给您留下额外的工作。
如何下载和安装 WordPress 核心文件?
|
缓存 前端开发
前端下载并生成文件
前端下载并生成文件
|
JavaScript jenkins 持续交付
jenkins下载插件下载不了,解决办法
jenkins下载插件下载不了,解决办法
663 0
jenkins下载插件下载不了,解决办法
|
缓存 安全 JavaScript
如何实现上传文件到 nodejs 和文件下载
最近拿 next.js 做个全栈项目,需要文件上传和下载,这里记录下实现方式,也写一下使用原生 node 代码如何实现。