: error c2062: 意外的类型“int”_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 versionsUSAGE:gvg list [command options] [arguments...]OPTIONS:--origin value the origin of version information , such as installed, local, remote (default: "installed")
可以看出,list
子命令支持一个 Flag
选项,--origin
。它用于指定版本信息的来源,允许值的范围是 installed
、local
和 remote
。
如果要求不严格,用 StringVar
也可以实现。但问题是,使用 String
,即使输入不在指定范围也能成功解析,不够严谨。虽说在获取后也可以检查,但还是不够灵活、可配置型也差。
接下来,我们要实现一个新的类型的 Flag
,使选项的值必需在指定范围,否则要给出一定的错误提示信息。
实现思路
如何展一个新类型呢?
可以参考 flag 包内置类型的实现思路,比如 flag.DurationVar
。Duration
不是基础类型,解析结果是存放到了 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() stringSet(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.Duration
、durationValue
和 Flag
。
比如有 duration=1h
,大致流程是首先从 os.Args
获取参数,按规则解析出选项名称 duration
,查找是否存在名称为 duration
的 Flag
,如果存在,使用 Flag.Value.Set
解析 1h
,如果不满足 duration
的要求,将给出错误提示。
实现新类型
现在实现文章开头要求的目标。
新类型定义如下:
type stringEnumValue struct {options []stringp *string
}
名为 StringEnumValue
,即字符串枚举。它有 options
和 p
两个成员,options
指定一定范围的值,p
是 string
指针,保存解析结果的变量的地址。
下面定义创建 StringEnumValue
变量的函数 newStringEnumValue
,代码如下:
func newStringEnumValue(val string, p *string, options []string) *StringEnumValue {*option = valreturn &stringEnumValue{options: options, p: p}
}
除了 val
和 p
两个必要的输入外,还有一个 string
切片类型的数,名为 options
,它用于范围的限定。而函数主体,首先设置默认值,然后使用 options
和 p
创建变量返回。
Set
是核心方法,解析命令行传入字符串。代码如下:
func (s *StringEnumValue) Set(v string) error {for _, option := range s.options {if v == option {*(s.p) = vreturn 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 valuethe origin of version information, such as installed, local, remote (default installed)
$ gvg --origin=remote
origin remote
总结
本文介绍了如何为 flag 扩展一个类型支持,通过分析源码理清实现思路。最后创建了一个只接收指定范围值 Value。
欢迎关注我的微信公众号。

相关文章:

java英文字符串大小写转换 必须使用_【Java基础】之字符串大小写转换不利用API....
public class UpStr{static String str "AbcDeFdDSfgdsadeADFSAFCfdsa";public String transformUpperOrLower(String str, String type){//将字符串转换为char数组char[] ch str.toCharArray();if (type null || type.length() 0 || type.equals(""))…

.net core 17
转载于:https://www.cnblogs.com/qingwengang/p/6297486.html

vue中子组件和子组件之间怎么通信_vue.js组件之间如何通信?
vue.js组件之间如何通信?下面本篇文章就来给大家介绍一下Vue.js组件间通信方式。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。平时在使用Vue框架的业务开发中,组件不仅仅要把模板的内容进行复用,更…

以太坊RLP机制分析
目录 1 RLP 定义 2 RLP 编码规则 3 RLP 编码实例 4 RLP 分析 1 RLP 定义 RLP,即 Recursive Length Prefix, 递归长度前缀编码,是以太坊数据序列化的主要方法, 具有较好的数据处理效率,尤其是将长度和类型统一作为前缀,…

Android Studio导入Eclipse项目的两种方法
Android Studio导入Eclipse项目有两种方法,一种是直接把Eclipse项目导入Android Studio,另一种是在Eclipse项目里面进行转换,然后再导入Android Studio。 1. 直接导入 打开Android Studio,如果里面已经打开了项目,选择…
mediawiki java api_维基百科 MediaWiki API 解析
使用开放的 API 做一个自己的小项目,是一个很好的学习方法。但好像开放的 API 选择并不多。这里给大家多一个选择,简单介绍一下维基百科使用的 MediaWiki API。简介先简单介绍几个容易混淆的概念。WikiWiki 是一种在网络上开放且可供多人协同创作的超文本…

elasticdump安装_elasticsearch导出、导入工具-elasticdump
elasticsearch导出、导入工具-elasticdumpelasticsearch 数据导入到本地,或本地数据导入到elasticsearch中,或集群间的数据迁移,可以用elasticsearch的工具—elasticdumpelasticdump 可以用用npm安装本地运行,也可以用docker容器运…

mysql 无法登陆_MySQL root用户无法登录原因及解决办法
MySQL root密码正确,却怎么也bai无法du从本地登录MySQL登录提示ERROR 1045 (28000): Access denied for user rootlocalhost (using password: YES)可能原因是mysql库中bai的user表缺少一个root指向host:localhost的数据项,只有一个root指向h…

Spring Boot启动过程(二)
书接上篇 该说refreshContext(context)了,首先是判断context是否是AbstractApplicationContext派生类的实例,之后调用了强转为AbstractApplicationContext类型并调用它的refresh方法。由于AnnotationConfigEmbeddedWebApplicationContext继承自EmbeddedW…

dom vue 加载完 执行_前端面试题——Vue
前言前几天整理了一些 html css JavaScript 常见的面试题(https://segmentfault.com/u/youdangde_5c8b208a23f95/articles),然后现在也是找了一些在 Vue 方面经常出现的面试题,留给自己查看消化,也分享给有需要的小伙伴。如果文章中有出现纰…

查看某个存储过程
show create procedure 存储过程的名称; ##主从同步是会同步存储过程的 转载于:https://www.cnblogs.com/yangxiaochu/p/9397108.html

java中的分页 效率考虑_面试官:数据量很大,分页查询很慢,有什么优化方案?...
当需要从数据库查询的表有上万条记录的时候,一次性查询所有结果会变得很慢,特别是随着数据量的增加特别明显,这时需要使用分页查询。对于数据库分页查询,也有很多种方法和优化的点。下面简单说一下我知道的一些方法。准备工作为了…

dede 后台 mysql_织梦dedecms使用Mysql8.0无法登录后台的解决办法
1//只允许用户名和密码用0-9,a-z,A-Z,,_,.,-这些字符2$this->userName preg_replace("/[^0-9a-zA-Z_!\.-]/", , $username);3$this->userPwd preg_replace("/[^0-9a-zA-Z_!\.-]/", , $userpwd);4$pwd substr(md5($this->userPwd), 5, 20);56$d…

怎样对拍、如何对拍、对拍模板
我写了一个对拍模板,套上直接可以用,还有使用说明在里面,这里附上github网站。 对拍全套模板 转载于:https://www.cnblogs.com/yichuan-sun/p/9624162.html

二叉树线索化示意图_103-线索化二叉树思路图解
2.网上数据结构和算法的课程不少,但存在两个问题:1)授课方式单一,大多是照着代码念一遍,数据结构和算法本身就比较难理解,对基础好的学员来说,还好一点,对基础不好的学生来说,基本上…

linux环境下搭建osm_web服务器一(Postgresql配置及osm2pgsql原始数据导入):
Postgresql配置及osm2pgsql原始数据导入 2012年,Ubuntu 12.04LTS发布,又一个长效支持版,我们又该更新OpenStreetMap服务器了,这次,将详细在博客中记录配置过程。关于前面对OpenStreetMap的介绍,参考我的博文…

Java开发买低压本还是标压本_标压和低压,笔记本怎么选才最香?
华为最近发布了新款 MateBook 13/14 2020 锐龙版笔记本电脑,与之前的产品相比,它们都采用了 AMD 锐龙标压处理器。在体验这两款产品的同时,我一直在思考两个问题:它们与低压处理器相比强在哪里,以及是否值得购买。按照…

php mysql 备注_php,mysql备注信息1
/*---------------------------------------------------------------------------------------如何彻底地删除表?如果你不需要一个表了,你可以使用DROP.语法如下:DROP TABLE tablename例如:DROP TABLE employee_dataQuery OK,0 rows affected(0.01 sec);--------------------…

JSP和Servlet学习笔记1 - 访问配置
1. 访问 WebContent 目录下的 JSP 文件 在 WebContent 目录下的文件可以直接在浏览器中访问。新建一个 test.jsp 文件 <% page language"java" contentType"text/html; charsetISO-8859-1"pageEncoding"ISO-8859-1"%> <!DOCTYPE htm…

unity人物旋转移动代码_Unity3D研究院之脚本实现模型的平移与旋转(六)
123 说:雨松大大,有个问题想请教一下,我用UNET构建了个小场景,在电脑上可以客户端可以连接到服务器,Windows和Linux都可以,发布到安卓缺连不了,这是问什么呢说:求教一下,…

博客园的第一篇博文
以后所有技术相关的文章都记录在博客园啦,加油!转载于:https://www.cnblogs.com/dabenniu/p/6337549.html

java后台分页插件怎么写_Java分页技术(从后台传json到前台解析显示)
0 这是一篇我在初学习过程中,遇到的动态数据分页显示的问题,前台采用Ajax传给后台,后台在访问数据库取出分页数据再转换为json格式传递给前台,前台再解析显示到表格中。在此写出我在做的过程中遇到的问题,可以让其他人…

c 应用程序mysql_MySQL C 语言应用程序接口开发教程
从数据库中取回数据在这个实例中我们从表中取回数据。步骤:(1)创建连接(2)执行查询(3)获取结果集(4)提取所有可用的记录(5)释放结果集实例程序打印 writers 表中所有的记录(姓名)。#include #include int main(int argc, char * argv[]){MYSQL * conn;MYSQL_RES * r…

GreenPlum学习笔记:基础知识
一、介绍 GreenPlum分布式数据仓库,大规模并行计算技术。 无共享/MPP核心架构Greenplum数据库软件将数据平均分布到系统的所有节点服务器上,所以节点存储每张表或表分区的部分行,所有数据加载和查询都是自动在各个节点服务器上并行运行&…

java 套接字关联的通道_Java 通道教程 – NIO 2.0
# Java 通道教程 – NIO 2.0> 原文: [https://howtodoinjava.com/java7/nio/java-nio-2-0-channels/](https://howtodoinjava.com/java7/nio/java-nio-2-0-channels/)通道是继[**缓冲区**](//howtodoinjava.com/java-7/nio/java-nio-2-0-working-with-buffers/ &…

虚拟机ubuntu14.04系统设置静态ip
ubuntu14.04 设置静态ip vim /etc/network/interfaces 原来只有 auto lo iface lo inet loopback 修改成如下: auto lo iface lo inet loopbackauto eth0 iface eth0 inet static #静态ip address 192.168.1.6 #要设置的ip gateway 192.168.1.1 #这…

高职信息安全比赛攻防思路_30.LNGZ2020-30:2020年辽宁省职业院校技能大赛(高职组)“信息安全管理与评估”赛项规程...
12020年辽宁省职业院校技能大赛(高职组)信息安全管理与评估赛项规程一、赛项名称赛项编号:LNGZ2020-30赛项名称:信息安全管理与评估英文名称:Information Security Management and Evaluation赛项组别:高职组赛项归属:…

oracle rac对心跳要求_关于心跳网络引起的Oracle RAC的节点驱逐(不是实例驱逐)...
关于心跳网络引起的Oracle RAC的节点驱逐(不是实例驱逐)问:假设如下场景:4个节点rac,心跳线走的是千m网络交换机,若是该千M网络交换机断电,我想知道crs的驱逐节点的算法是怎么样的?Oracle 大连 GCS 答复&am…

php 字符串数组转数组对象_php怎么将数组转成对象?
php将数组转成对象的方法:1、使用数据类型转换,在数组变量前添加“(Object)”来将数组转成对象。2、先使用json_encode()函数将数组转换为json字符串;然后使用json_decode()函数将json字符串转换成对象。php将数组转成对象有时候数组要转为对…

如何在Win7电脑上增加新磁盘分区?
我们在重装好系统Win7系统后有时会碰到需要新建磁盘分区的情况,这时我们再重装系统进行磁盘分区就有些过于麻烦了,其实我们可以利用Win7系统自身的磁盘管理功能来新建一个磁盘分区。下面好系统重装助手就来介绍一下好系统Win7系统电脑磁盘新建分区的方法…