Go 的命令行解析 flag 包如何扩展新类型呢?

请添加图片描述

上篇文章 说到,除布尔类型 Flag,flag 支持的还有整型(int、int64、uint、uint64)、浮点型(float64)、字符串(string)和时长(duration)。

flag 内置支持能满足大部分的需求,但某些场景,需要自定义解析规则。一个优秀的库肯定要支持扩展的。本文将介绍如何为 flag 扩展一个新的类型支持?

扩展目标

gvg 这个小工具中,list 子命令支持获取 Go 的版本列表。但版本的信息来源有多处,比如 installed(已安装)、local(本地仓库)和 remote(远程仓库)。

查看下 list 的帮助信息,如下:

NAME:
   gvg list - list go versions

USAGE:
   gvg list [command options] [arguments...]

OPTIONS:
   --origin value  the origin of version information , such as installed, local, remote (default: "installed")

可以看出,list 子命令支持一个 Flag 选项,--origin。它用于指定版本信息的来源,允许值的范围是 installedlocalremote

如果要求不严格,用 StringVar 也可以实现。但问题是,使用 String,即使输入不在指定范围也能成功解析,不够严谨。虽说在获取后也可以检查,但还是不够灵活、可配置型也差。

接下来,我们要实现一个新的类型的 Flag,使选项的值必需在指定范围,否则要给出一定的错误提示信息。

实现思路

如何展一个新类型呢?

可以参考 flag 包内置类型的实现思路,比如 flag.DurationVarDuration 不是基础类型,解析结果是存放到了 time.Duration 类型中,可能更有参考价值。

进入到 flag.DurationVar 查看源码,如下:

func DurationVar(p *time.Duration, name string, value time.Duration, usage string) {
	CommandLine.Var(newDurationValue(value, p), name, usage)
}

通过 newDurationValue 创建了一个类型为 durationValue 的变量,并传入到了 CommandLine.Var 方法中。

如果继续往下追,会根据 Value 创建一个 Flag 变量。 如下:

func (f *FlagSet) Var(value Value, name string, usage string) {
	flag := &Flag{name, usage, value, value.String()}
	...
}

Var 的定义可以看出,它的第一个参数类型是 Value 接口类型,也就说,durationValue 是实现了 Value 接口的类型。

注意,源码中出现的 FlagSet 可以先忽略,它是下篇介绍子命令时重点关注的对象。

看下 Value 的定义,如下:

type Value interface {
	String() string
	Set(string) error
}

那么,durationValue 的实现代码如何?

// 传入参数分别是默认值和获取 Flag 值的变量地址
func newDurationValue(val time.Duration, p *time.Duration) *durationValue {
	// 将默认值设置到 p 上
	*p = val
	// 使用 p 创建新的类型,保证可以获取到解析的结果
	return (*durationValue)(p)
}

// Set 方法负责解析传入的值
func (d *durationValue) Set(s string) error {
	v, err := time.ParseDuration(s)
	if err != nil {
		err = errParse
	}
	*d = durationValue(v)
	return err
}

// 获取真正的值
func (d *durationValue) String() string { return (*time.Duration)(d).String() }

核心在两个地方。

一个是创建新类型变量时,要使用传入的变量地址创建新类型变量,以实现将解析结果放到其中,让前端能获取到,二是 Set 方法中实现命令行传入字符串的解析。

逻辑梳理

看完上个小节,基本已经了解如何扩展一个新类型了。本质是是实现 Value 接口。

再看下之前提到的几个变量,分别是存放解析结果的指针、解析命令行输入的 Value 和表示一个选项的 Flag。对应于 flag.DurationVar,这个变量的类型分别是 *time.DurationdurationValueFlag

比如有 duration=1h,大致流程是首先从 os.Args 获取参数,按规则解析出选项名称 duration,查找是否存在名称为 durationFlag,如果存在,使用 Flag.Value.Set 解析 1h,如果不满足 duration 的要求,将给出错误提示。

实现新类型

现在实现文章开头要求的目标。

新类型定义如下:

type stringEnumValue struct {
	options []string
	p   *string
}

名为 StringEnumValue,即字符串枚举。它有 optionsp 两个成员,options 指定一定范围的值,pstring 指针,保存解析结果的变量的地址。

下面定义创建 StringEnumValue 变量的函数 newStringEnumValue,代码如下:

func newStringEnumValue(val string, p *string, options []string) *StringEnumValue {
	*option = val
	return &stringEnumValue{options: options, p: p}
}

除了 valp 两个必要的输入外,还有一个 string 切片类型的数,名为 options,它用于范围的限定。而函数主体,首先设置默认值,然后使用 optionsp 创建变量返回。

Set 是核心方法,解析命令行传入字符串。代码如下:

func (s *StringEnumValue) Set(v string) error {
	for _, option := range s.options {
		if v == option {
			*(s.p) = v
			return nil
		}
	}

	return fmt.Errorf("must be one of %v", s.options)
}

循环检查输入参数 v 是否满足要求。定义如下:

最后是 String() 方法,

func (s *StringEnumValue) String() string {
	return *(s.p)
}

返回 p 指针中的值。前面分析实现思路时,Flag 在设置默认值时就调用了它。

使用 StringEnumValue

直接看代码吧。如下:

var origin string
func init() {
	flag.Var(
		newStringEnumValue(
			"installed", 	// 默认值
			&origin,
			[]string{"installed", "local", "remote"},
		),
		"origin",
		`the origin of version information, such as installed, local, remote (default: "installed")`,
	)
}

func main() {
	flag.Parse()
	fmt.Println(option)
}

重点就是 flag.Var(newStringEnumValue(...),...)。如果觉得有点啰嗦,希望和其他类型新建过程相同,在这个基础上可以再包装。代码如下:

func StringEnumVar(p *string, name string, options []string, defVal string, usage string) {
	flag.Var(newStringEnumValue(defVal, p, options), name, usage)
}

编译测试下,结果如下:

$ gvg --origin=any
invalid value "any" for flag -origin: must be one of [installed local remote]
Usage of gvg:
  -origin value
  	the origin of version information, such as installed, local, remote (default installed)
$ gvg --origin=remote
origin remote

总结

本文介绍了如何为 flag 扩展一个类型支持,通过分析源码理清实现思路。最后创建了一个只接收指定范围值的 Value。

博文地址:Go 命令行解析 flag 包之扩展新类型

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/352532.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

transformer和vit学习笔记

以下记录自己对transformer的学习笔记,可能自己看得懂【久了自己也忘了看不懂】,别人看起来有点乱。以后再优化文档~ 小伙伴请直接去看学习资源: Transformer的理解T-1_哔哩哔哩_bilibili 首先,时序处理:一些模型的出…

Two-factor authentication (2FA) is required for your GitHub account解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

【基于电商履约场景的 DDD 实战】DDD业务建模第二部分:履约的战术设计(梳理整个战术设计流程图)

欢迎关注公众号(通过文章导读关注:【11来了】),及时收到 AI 前沿项目工具及新技术的推送! 在我后台回复 「资料」 可领取编程高频电子书! 在我后台回复「面试」可领取硬核面试笔记! 文章导读地址…

Java笔记 --- 二、Stream流

二、Stream流 结合Lambda表达式,简化集合、数组的操作 获取Stream流对象 单列集合获取Stream流 双列集合获取Stream流 数组获取Stream流 一堆零散的数据获取Stream流 Stream流的静态方法of的形参是一个可变参数,可以传递零散数据,也可以传递…

【Python】02快速上手爬虫案例二:搞定验证码

文章目录 前言1、不要相信什么验证码的库2、以古诗文网为例,获取验证码1)code_result.py2)gsw.py 前言 提示:以古诗文网为例,获取验证码: 登录:https://so.gushiwen.cn/user/login.aspx 1、不…

【C++】类与对象(一)

前言 类与对象(一) 文章目录 一、面向对象和面向过程的对比二、类的引入2.1 C中的结构体2.2 类2.3 类定义方法2.4 修饰限定符2.5 封装2.6 类的实例化2.7 类对象的大小 三、this指针3.1 this 指针的使用 一、面向对象和面向过程的对比 面向过程编程是将程…

【Docker】nacos集群搭建Nginx负载均衡

目录 一、mysql安装与基操 1.1 数据准备 1.2 创建mysql与数据表 二、Nacos集群部署 2.1 创建nacos及配置 2.2 创建Nginx容器 一、mysql安装与基操 1.1 数据准备 拉取mysql docker pull mysql:5.7(版本) 定义挂载目录 mkdir -p /mysql/{conf,data,script} 配置my.c…

【排序4】探秘归并排序:提高程序效率的必备技巧

😊归并排序 🎊1、基本思想🎊2、代码示例🎊3、非递归实现🎊4、归并排序的性能分析🎊5、归并排序的优缺点🎊6、归并排序的应用场景🎊7、总结 🎊1、基本思想 归并排序&…

(笔记总结)C/C++语言的常用库函数(持续记录,积累量变)

写在前面: 由于时间的不足与学习的碎片化,写博客变得有些奢侈。 但是对于记录学习(忘了以后能快速复习)的渴望一天天变得强烈。 既然如此 不如以天为单位,以时间为顺序,仅仅将博客当做一个知识学习的目录&a…

【vue3源码】vue源码探索之旅:项目介绍

简言 记录下我眼中的vue源码项目。 gitHubvue3项目仓库 项目要求: vue版本 3.4.15nodeV18.12.0以上使用pnpm包管理器vitest测试框架Vue3 vue3是渐进式JavaScript框架,易学易用,性能出色,适用场景丰富的 Web 前端框架。 Vue 是一个框架,也是一个生态。其功能覆盖了大部分…

Spark运行架构以及容错机制

Spark运行架构以及容错机制 1. Spark的角色区分1.1 Driver1.2 Excuter 2. Spark-Cluster模式的任务提交流程2.1 Spark On Yarn的任务提交流程2.1.1 yarn相关概念2.1.2 任务提交流程 2.2 Spark On K8S的任务提交流程2.2.1 k8s相关概念2.2.2 任务提交流程 3. Spark-Cluster模式的…

HBase入门:运行机制

文章目录 HBase 系统架构客户端ZooKeeper 服务器Master 主服务器Region 服务器 Region 服务器工作原理用户读写数据的过程缓存的刷新StoreFile合并 Store 的工作原理HLog 的工作原理 HBase 系统架构 HBase 的系统架构包括客户端、ZooKeeper 服务器、Master 主服务器、Region服…

Linux文本三剑客---grep

grep(从文本或字符串种过滤特定内容。) 格式:Usage: grep [OPTION]... PATTERNS [FILE]... 常用选项: -E 等价于 egrep 扩展正则 -i 忽略大小写 -w 匹配单词 -o 仅显示匹配内容 -r 递归匹配 -c 统计匹配的行数 -v 取反 -n 行号 -A…

vue2 事件总线

原图下载:https://download.csdn.net/download/weixin_47401101/88788636

如何在Shopee平台上进行宠物类目的选品丨shopee宠物选品

在Shopee平台上进行宠物类目的选品是一个重要的任务,它直接关系到卖家的销售业绩和市场竞争力。为了成功选择适合的宠物用品,在选品过程中,卖家可以遵循以下策略: 先给大家推荐一款shopee知虾数据运营工具知虾免费体验地址&#…

ZYNQ AC7020C的“点LED”实验

一、创建 Vivado 工程 1、启动 Vivado 2、在 Vivado 开发环境里点击“Create New Project”,创建一个新的工程 3、弹出一个建立新工程的向导,点击“Next” 4、在弹出的对话框中输入工程名和工程存放的目录。需要注意工程路径“Project location”不能有…

瑞丽杂志引领潮流,VOSS眼镜概念店开启奢华新纪元!

近日,由《瑞丽》杂志社举办的2023第4届瑞丽轻奢品牌大赛,以“轻奢•悦藏”为主题的大赛已圆满结束,VOSS眼镜荣获:2023瑞丽轻奢品牌大赛「轻奢时尚风格奖」,作为眼镜行业唯一获此奖项的品牌,VOSS眼镜对此表示…

C++初阶入门之命名空间和缺省参数的详细解析

个人主页:点我进入主页 专栏分类:C语言初阶 C语言进阶 数据结构初阶 Linux C初阶 欢迎大家点赞,评论,收藏。 一起努力,一起奔赴大厂 目录 一.前言 二.命名空间 2.1命名冲突的例子 2.2解决方案 2.3命…

QT发生弹出警告窗口

QTC开发程序弹出警告窗口&#xff0c;如上图 实施代码&#xff1a; #include <QMessageBox> int main() {// 在发生错误的地方QMessageBox::critical(nullptr, "错误", "发生了一个错误&#xff0c;请检查您的操作。");}上面的文字可以更改&#x…

(2024,预训练和微调扩散,图编码器,图特征与CLIP特征对齐)场景图到图像合成:集成 CLIP 指导与扩散模型中的场景图条件

Scene Graph to Image Synthesis- Integrating CLIP Guidance with Graph Conditioning in Diffusion Models 公和众和号&#xff1a;EDPJ&#xff08;进 Q 交流群&#xff1a;922230617 或加 VX&#xff1a;CV_EDPJ 进 V 交流群&#xff09; 目录 0. 摘要 3. 方法 3.1 扩…