/opts.go

https://code.google.com/p/opts-go/ · Go · 409 lines · 291 code · 48 blank · 70 comment · 50 complexity · fa793b30b054dccdc54951798c2dcd01 MD5 · raw file

  1. // Copyright 2010 The Go Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. /*
  5. The opts package provides advanced GNU- and POSIX- style option parsing.
  6. */
  7. package opts
  8. import (
  9. "fmt"
  10. "os"
  11. "strings"
  12. )
  13. //
  14. // Exported variables
  15. //
  16. // The name with which this program was called
  17. var Xname = os.Args[0]
  18. // The list of optionless arguments provided
  19. var Args []string = make([]string, 0, len(os.Args)-1)
  20. // A description of the program, which may be multiline
  21. var Description string
  22. // A string with the usage of the program. usage: and the name of the program
  23. // are automatically prefixed.
  24. var Usage string = "[options]"
  25. //
  26. // Description of options
  27. //
  28. // The built-in types of options.
  29. const (
  30. FLAG = iota
  31. HALF
  32. SINGLE
  33. MULTI
  34. )
  35. // The built-in types of errors.
  36. const (
  37. UNKNOWNERR = iota // unknown option
  38. REQARGERR // a required argument was not present
  39. NOARGERR // an argument was present where none should have been
  40. )
  41. // Whether or not arguments are required
  42. const (
  43. NOARG = iota
  44. OPTARG
  45. REQARG
  46. )
  47. // Parsing is a callback used by Option implementations to report errors.
  48. type Parsing struct{}
  49. // Error prints the relevant error message and exits.
  50. func (Parsing) Error(err int, opt string) {
  51. switch err {
  52. case UNKNOWNERR:
  53. fmt.Fprintf(os.Stderr,
  54. "%s: %s: unknown option\n",
  55. Xname, opt)
  56. case REQARGERR:
  57. fmt.Fprintf(os.Stderr,
  58. "%s: %s: argument required\n",
  59. Xname, opt)
  60. case NOARGERR:
  61. fmt.Fprintf(os.Stderr,
  62. "%s: %s takes no argument\n",
  63. Xname, opt)
  64. }
  65. os.Exit(1)
  66. }
  67. // Option represents a conceptual option, which there may be multiple methods
  68. // of invoking.
  69. type Option interface {
  70. // Forms returns a slice with all forms of this option. Forms that
  71. // begin with a single dash are interpreted as short options, multiple
  72. // of which may be combined in one argument (-abcdef). Forms that begin
  73. // with two dashes are interpreted as long options, which must remain
  74. // whole.
  75. Forms() []string
  76. // Description returns the description of this option.
  77. Description() string
  78. // ArgName returns a descriptive name for the argument this option
  79. // takes, or an empty string if this option takes none.
  80. ArgName() string
  81. // Required NOARG, OPTARG, or REQARG
  82. Arg() int
  83. // Invoke is called when this option appears in the command line.
  84. // If the option expects an argument (as indicated by ArgName),
  85. // Invoke is guaranteed not to be called without one. Similarly, if
  86. // the option does not expect an argument, Invoke is guaranteed to be
  87. // called only with the first parameter being the empty string.
  88. Invoke(string, Parsing)
  89. }
  90. // A partial implementation of the Option interface that reflects what most
  91. // options share.
  92. type genopt struct {
  93. shortform string
  94. longform string
  95. description string
  96. }
  97. func (o genopt) Forms() []string {
  98. forms := make([]string, 0, 2)
  99. if len(o.shortform) > 0 {
  100. forms = forms[0:1]
  101. forms[0] = o.shortform
  102. }
  103. if len(o.longform) > 0 {
  104. forms = forms[0 : len(forms)+1]
  105. forms[len(forms)-1] = o.longform
  106. }
  107. return forms
  108. }
  109. func (o genopt) Description() string { return o.description }
  110. type flag struct {
  111. genopt
  112. dest *bool
  113. }
  114. func (flag) ArgName() string { return "" }
  115. func (o flag) Arg() int { return NOARG }
  116. func (o flag) Invoke(string, Parsing) {
  117. *o.dest = true
  118. }
  119. type half struct {
  120. genopt
  121. dest *string
  122. dflt string // the value if the option is not given
  123. givendflt string // the default value if the option is given
  124. }
  125. func (o half) ArgName() string { return o.givendflt }
  126. func (o half) Arg() int { return OPTARG }
  127. func (o half) Invoke(arg string, _ Parsing) {
  128. if arg == "" {
  129. *o.dest = o.givendflt
  130. } else {
  131. *o.dest = arg
  132. }
  133. }
  134. type single struct {
  135. genopt
  136. dest *string
  137. dflt string
  138. }
  139. func (o single) ArgName() string { return o.dflt }
  140. func (o single) Arg() int { return REQARG }
  141. func (o single) Invoke(arg string, _ Parsing) {
  142. *o.dest = arg
  143. }
  144. type multi struct {
  145. genopt
  146. valuedesc string
  147. dest []string
  148. }
  149. func (o multi) ArgName() string { return o.valuedesc }
  150. func (o multi) Arg() int { return REQARG }
  151. func (o multi) Invoke(arg string, _ Parsing) {
  152. o.dest = append(o.dest, arg)
  153. }
  154. // Stores an option of any kind
  155. type option struct {
  156. dflt string
  157. strdest *string
  158. strslicedest *[]string
  159. }
  160. // The registered options
  161. var options map[string]Option = map[string]Option{}
  162. // A plain list of options, for when we need to hit each only once
  163. var optionList []Option = make([]Option, 0, 1)
  164. // Adds - if there is none.
  165. func makeShort(s string) string {
  166. if len(s) >= 1 && s[0] != '-' {
  167. s = "-" + s
  168. }
  169. return s
  170. }
  171. // Adds -- if there is none.
  172. func makeLong(s string) string {
  173. s = "--" + strings.TrimLeft(s,"-")
  174. return s
  175. }
  176. // Add adds the given option.
  177. func Add(opt Option) {
  178. for _, form := range opt.Forms() {
  179. options[form] = opt
  180. }
  181. l := len(optionList)
  182. if len(optionList)+1 > cap(optionList) {
  183. old := optionList
  184. optionList = make([]Option, 2*(len(old)+1))
  185. copy(optionList, old)
  186. }
  187. optionList = optionList[0:l+1]
  188. optionList[len(optionList)-1]=opt
  189. }
  190. // Flag creates a new Flag-type option, and adds it, returning the destination.
  191. func Flag(sform string, lform string, desc string) *bool {
  192. dest := new(bool)
  193. o := flag{
  194. genopt: genopt{
  195. shortform: makeShort(sform),
  196. longform: makeLong(lform),
  197. description: desc,
  198. },
  199. dest: dest,
  200. }
  201. Add(o)
  202. return dest
  203. }
  204. // ShortFlag is like Flag, but no long form is used.
  205. func ShortFlag(sform string, desc string) *bool {
  206. return Flag(sform, "", desc)
  207. }
  208. // LongFlag is like Flag, but no short form is used.
  209. func LongFlag(lform string, desc string) *bool {
  210. return Flag("", lform, desc)
  211. }
  212. // Half creates a new Half-type option, and adds it, returning the destination.
  213. func Half(sform string, lform string, desc string, dflt string, gdflt string) *string {
  214. dest := &dflt
  215. o := half{
  216. genopt: genopt{
  217. shortform: makeShort(sform),
  218. longform: makeLong(lform),
  219. description: desc,
  220. },
  221. dest: dest,
  222. dflt: dflt,
  223. givendflt: gdflt,
  224. }
  225. Add(o)
  226. return dest
  227. }
  228. // ShortHalf is like Half, but no long form is used.
  229. func ShortHalf(sform string, desc string, dflt string, gdflt string) *string {
  230. return Half(sform, "", desc, dflt, gdflt)
  231. }
  232. // LongHalf is like Half, but no short form is used.
  233. func LongHalf(lform string, desc string, dflt string, gdflt string) *string {
  234. return Half("", lform, desc, dflt, gdflt)
  235. }
  236. // Single creates a new Single-type option, and adds it, returning the destination.
  237. func Single(sform string, lform string, desc string, dflt string) *string {
  238. dest := &dflt
  239. o := single{
  240. genopt: genopt{
  241. shortform: makeShort(sform),
  242. longform: makeLong(lform),
  243. description: desc,
  244. },
  245. dest: dest,
  246. dflt: dflt,
  247. }
  248. Add(o)
  249. return dest
  250. }
  251. // ShortSingle is like Single, but no long form is used.
  252. func ShortSingle(sform string, desc string, dflt string) *string {
  253. return Single(sform, "", desc, dflt)
  254. }
  255. // LongSingle is like Single, but no short form is used.
  256. func LongSingle(lform string, desc string, dflt string) *string {
  257. return Single("", lform, desc, dflt)
  258. }
  259. // Multi creates a new Multi-type option, and adds it, returning the destination.
  260. func Multi(sform string, lform string, desc string, valuedesc string) []string {
  261. o := multi{
  262. genopt: genopt{
  263. shortform: makeShort(sform),
  264. longform: makeLong(lform),
  265. description: desc,
  266. },
  267. dest: make([]string, 0, 4),
  268. valuedesc: valuedesc,
  269. }
  270. Add(o)
  271. return o.dest
  272. }
  273. // ShortMulti is like Multi, but no long form is used.
  274. func ShortMulti(sform string, desc string, valuedesc string) []string {
  275. return Multi(sform, "", desc, valuedesc)
  276. }
  277. // LongMulti is like Multi, but no short form is used.
  278. func LongMulti(lform string, desc string, valuedesc string) []string {
  279. return Multi("", lform, desc, valuedesc)
  280. }
  281. // True if the option list has been terminated by '-', false otherwise.
  282. var optsOver bool
  283. // Parse performs parsing of the command line, making complete information
  284. // available to the program.
  285. func Parse() {
  286. ParseArgs(os.Args)
  287. }
  288. // ParseArgs performs parsing of the given command line, making complete
  289. // information available to the program.
  290. //
  291. // This function was created specifically to enable unit testing - the proper
  292. // entry point for most programs is Parse.
  293. func ParseArgs(args []string) {
  294. addHelp()
  295. p := Parsing{}
  296. for i := 1; i < len(args); i++ {
  297. arg := args[i]
  298. if len(arg) > 0 && arg[0] == '-' && !optsOver {
  299. switch {
  300. case len(arg) == 1:
  301. // blank option - end option parsing
  302. optsOver = true
  303. case arg[1] == '-':
  304. // long option
  305. argparts := strings.SplitN(arg, "=", 2)
  306. var val string
  307. if len(argparts) == 2 {
  308. arg, val = argparts[0], argparts[1]
  309. }
  310. if option, ok := options[arg]; ok {
  311. switch {
  312. case val == "" && option.Arg() != REQARG:
  313. option.Invoke(val, p)
  314. case val != "" && option.Arg() != NOARG:
  315. option.Invoke(val, p)
  316. case val == "" && option.Arg() == REQARG:
  317. p.Error(REQARGERR, arg)
  318. case val != "" && option.Arg() == NOARG:
  319. p.Error(NOARGERR, arg)
  320. }
  321. } else {
  322. p.Error(UNKNOWNERR, arg)
  323. }
  324. default:
  325. // short option series
  326. for j, optChar := range arg[1:len(arg)] {
  327. opt := string(optChar)
  328. if option, ok := options["-"+opt]; ok {
  329. if option.ArgName() == "" {
  330. option.Invoke("", p)
  331. continue
  332. }
  333. // handle both -Oarg and -O arg
  334. if j != len(arg)-2 {
  335. val := arg[j+2 : len(arg)]
  336. option.Invoke(val, p)
  337. break
  338. }
  339. i++
  340. if i < len(args) {
  341. option.Invoke(args[i], p)
  342. } else if option.Arg() == REQARG {
  343. p.Error(REQARGERR, arg)
  344. } else {
  345. option.Invoke("", p)
  346. }
  347. } else {
  348. p.Error(UNKNOWNERR, "-"+opt)
  349. }
  350. }
  351. }
  352. } else {
  353. Args = Args[0 : len(Args)+1]
  354. Args[len(Args)-1] = arg
  355. }
  356. }
  357. if *printHelp {
  358. Help()
  359. os.Exit(0)
  360. }
  361. }