: error c2062: 意外的类型“int”_Go 命令行解析 flag 包之扩展新类型

news/2024/4/19 2:22:47

d454f329d6b6b5b8b685210eb25ec755.png

上篇文章 说到,除布尔类型 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。它用于指定版本信息的来源,允许值的范围是 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() 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.DurationdurationValueFlag

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

实现新类型

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

新类型定义如下:

type stringEnumValue struct {options []stringp   *string
}

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

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

func newStringEnumValue(val string, p *string, options []string) *StringEnumValue {*option = valreturn &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) = 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。


欢迎关注我的微信公众号。

3b49886d1a09fd30333984499a9e4dbd.png

http://www.ppmy.cn/news/610717.html

相关文章

Clang:LLVM的C语言家族前端

Clang:LLVM的C语言家族前端 Clang项目为LLVM 项目的C语言家族(C,C ,Objective C / C ,OpenCL,CUDA和RenderScript)中的语言提供了语言前端和工具基础结构。提供了GCC兼容的编译器驱动程序&#…

LNMP一键安装的卸载

如果是lnmp一键安装的 进入安装包目录 [rootwww home]# cd lnmp0.9-full [rootwww lnmp0.9-full]# ls 然后 [rootwww lnmp0.9-full]# ./uninstall.sh LNMPV0.8 for CentOS/RadHat Linux VPS Written by Licess A tool to auto-compile & install NginxMySQLPHP on Li…

Android Shape 的使用

学而时习,温故而知新。 今天复习shape 画各种常见类型的背景图 使用: 当在 java 代码R.drawable.文件的名称 当在布局中时 android:background“drawable/文件的名字” 位置在 res下面的drawable 里面 1 先看下shape可以指定几种类型 li…

Vue.js 源码目录设计(二)

Vue.js 的源码都在 src 目录下,其目录结构如下。 src ├── compiler # 编译相关 ├── core # 核心代码 ├── platforms # 不同平台的支持 ├── server # 服务端渲染 ├── sfc # .vue 文件解析 ├── sha…

elasticdump安装_elasticsearch导出、导入工具-elasticdump

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

php 正则中文匹配

汉字一定注意是gbk还是utf8编码 UTF-8匹配:在javascript中,要判定字符串是中文是很简朴的。比如:var str "php编程";if (/^[\u4e00-\u9fa5]$/.test(str)) {alert("该字符串全部是中文");} else{alert("该字符串不全部是中文&…

LLDB调试器

LLDB调试器 这是LLDB文档! LLDB是下一代高性能调试器。它是作为一组可重用的组件构建的,这些组件可充分利用大型LLVM Project中的现有库,例如Clang表达式解析器和LLVM反汇编程序。 LLDB是macOS上Xcode中的默认调试器,并支持在台式…

Android layer-list 使用

在了解layer-list 之前需要知道shape 如果不清楚可以看shape 介绍 shape 画的背景图 都是单一的一个,有时候遇到二个的,这个时候就需要使用layer-list layer-list 里面的各个item 都是重叠的,我们可以使用left ,right &#xff…

C# 内存回收

开发完成之后发现自己写的程序内存占用太高,找到如下解决方案 使用了一个timer每2s调用一次ClearMemory() #region 内存回收[DllImport("kernel32.dll", EntryPoint "SetProcessWorkingSetSize")]public static extern …

dom vue 加载完 执行_前端面试题——Vue

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

centos7下没有iptables进行安装或更新

从centos7开始使用linux,之前版本的没有深入了解过,今天要开放个端口,需要有防火墙的相关操作,从网上查资料都是编辑/etc/sysconfig目录下面的iptables文件,可我进入这个文件之后,并没有发现这iptables文件…

RecyclerView 点击Item 改变文字颜色以及所在的背景色

为了防止看错内容,下面讲解要实现的内容 方法有很多中,这里使用notifyDataSetChanged 来刷新内容个人感觉更为简单点 首先可以先定义一个位置,然后点击item的时候把positon 赋值给自己定义位置,然后在绑定数据的地方修改文字颜色…

“ compiler-rt”运行时runtime库

“ compiler-rt”运行时runtime库 编译器-rt项目包括: • Builtins-一个简单的库,提供了代码生成和其他运行时runtime组件所需的特定于目标的低级接口。例如,当为32位目标进行编译时,将双精度数转换为64位无符号整数将编译为对“ …

MySQL开启远程连接权限

1、登陆mysql数据库 mysql -u root -p 查看user表 mysql> use mysql; Database changed mysql> select host,user,password from user; --------------------------------------------------------------- | host | user | password …

Android 侧滑栏 (DrawerLayout)

DrawerLayout 实现侧滑栏非常简单 支持左滑动以及右滑动 默认滑动出来侧滑栏DrawerLayout 需要引入support:appcompat 库,一般创建项目的时候自带的有,这个库就不用管了 DrawerLayout 需要使用 android.support.v4.widget.DrawerLayout 作为父类布局 …

PMP 的 10 个常见问题

文章目录 PMP 的 10 个常见问题1、问题清单2、问题说明 PMP 的 10 个常见问题 1、问题清单 什么是项目范围管理?什么是创新管理?什么是风险管理?什么是 WBS(工作分解结构)?什么是项目网络图?什么…

二叉树线索化示意图_103-线索化二叉树思路图解

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

Android include 标签使用

include 标签就是在一个布局中引入另一个布局&#xff0c; 好处呢&#xff0c;可能就是有多个界面某个部位的内容相同单独写了一个布局&#xff0c;在使用的时候使用include 还有可能界面布局在一个布局太长了&#xff0c;就多写了几个布局 在布局中的引用 <includelayou…

Git基本命令和GitFlow工作流

本篇博客讲解了git的一些基本的团队协作命令&#xff0c;和GitFlow工作流指南 git 团队协作的一些命令 1.开分支 git branch 新分支名 例如&#xff0c;在master分支下&#xff0c;新开一个开发分支&#xff1a; git branch dev 2.切换到新分支 git checkout 分支名 例如&am…

unity人物旋转移动代码_Unity3D研究院之脚本实现模型的平移与旋转(六)

123 说&#xff1a;雨松大大&#xff0c;有个问题想请教一下&#xff0c;我用UNET构建了个小场景&#xff0c;在电脑上可以客户端可以连接到服务器&#xff0c;Windows和Linux都可以&#xff0c;发布到安卓缺连不了&#xff0c;这是问什么呢说&#xff1a;求教一下&#xff0c;…