1# flag - command line flags.2#3# A flag is declared before parsing and read after it, from the Value of what4# the declaration returned:5#6# flag = import("flag")7#8# port = flag.Int("port", 8080, "the port to listen on")9# quiet = flag.Bool("q", false, "say nothing")10# rest = flag.Parse()11#12# if failed(rest) { println(rest); exit(2) }13# println("port {port.Value}, files {rest}")14#15# The forms understood are -name value, -name=value and, for a boolean, -name16# on its own. Two dashes work like one, and a bare -- ends the flags: what17# follows is returned as it is, dashes included. -h and --help print the18# usage and exit.19#20# A program that parses something other than its own command line, a test for21# one, builds its own set with New and calls set.Parse(args).2223os = import("os")24strconv = import("strconv")25strings = import("strings")2627# New returns an empty set of flags named after the program it parses for.28New = fn(name) {29 set = new()30 set.Name = name31 set.flags = {} # name -> the object handed back at declaration32 set.order = [] # the names, in the order they were declared3334 # define adds a flag and returns the object its value lands in.35 set.define = fn(kind, name, value, usage) {36 f = new()37 f.Name = name38 f.Kind = kind39 f.Value = value40 f.Default = value41 f.Usage = usage4243 set.flags[name] = f44 set.order = append(set.order, name)45 return f46 }4748 set.String = fn(name, value, usage) { set.define("string", name, string(value), usage) }49 set.Int = fn(name, value, usage) { set.define("int", name, int(value, 64), usage) }50 set.Float = fn(name, value, usage) { set.define("float", name, float(value), usage) }51 set.Bool = fn(name, value, usage) { set.define("bool", name, value == true, usage) }5253 # set1 stores the text of one flag, converted to the kind it was declared54 # with.55 set.set1 = fn(f, text) {56 if f.Kind == "string" {57 f.Value = text58 return null59 }6061 if f.Kind == "bool" {62 if text == "true" || text == "1" {63 f.Value = true64 return null65 }66 if text == "false" || text == "0" {67 f.Value = false68 return null69 }70 return error("flag: -{f.Name} wants true or false, got {text}")71 }7273 if f.Kind == "int" {74 if failed(n = strconv.Atoi(text)) {75 return error("flag: -{f.Name} wants a number, got {text}")76 }77 f.Value = n78 return null79 }8081 if failed(x = strconv.ParseFloat(text)) {82 return error("flag: -{f.Name} wants a number, got {text}")83 }84 f.Value = x85 return null86 }8788 # Usage returns the lines describing the flags, the way the help prints89 # them.90 set.Usage = fn() {91 out = "usage: {set.Name} [flags] [arguments]\n"92 for i = 0; i < len(set.order); ++i {93 f = set.flags[set.order[i]]94 out = out + " -{f.Name}"95 if f.Kind != "bool" {96 out = out + " {f.Kind}"97 }98 out = out + "\n"99 if f.Usage != null && f.Usage != "" {100 out = out + " \t{f.Usage}"101 if f.Kind == "string" {102 out = out + " (default \"{f.Default}\")\n"103 } else {104 out = out + " (default {f.Default})\n"105 }106 }107 }108 return out109 }110111 # Lookup returns the flag named name, or null.112 set.Lookup = fn(name) {113 f = set.flags[name]114 if f == null {115 return null116 }117 return f118 }119120 # Parse reads args, which are the arguments without the program name, and121 # returns the ones left over after the flags. An unknown flag or a value122 # of the wrong kind gives an error.123 set.Parse = fn(args) {124 rest = []125 for i = 0; i < len(args); ++i {126 arg = string(args[i])127128 # Everything after -- is an argument, and so is anything that129 # doesn't start with a dash. A lone - is a name for stdin.130 if arg == "--" {131 for j = i + 1; j < len(args); ++j {132 rest = append(rest, string(args[j]))133 }134 return rest135 }136 if len(arg) < 2 || arg[0] != "-" {137 rest = append(rest, arg)138 continue139 }140141 name = slice(arg, 1, len(arg))142 if name[0] == "-" {143 name = slice(name, 1, len(name))144 }145146 # -name=value carries its value with it.147 text = null148 cut = strings.Cut(name, "=")149 if cut.Found {150 name = cut.Before151 text = cut.After152 }153154 if name == "h" || name == "help" {155 print(set.Usage())156 exit(0)157 }158159 f = set.flags[name]160 if f == null {161 return error("flag: -{name} is not a flag of {set.Name}")162 }163164 if text == null {165 # A boolean on its own is true. Anything else takes the next166 # argument, whatever it looks like.167 if f.Kind == "bool" {168 f.Value = true169 continue170 }171 if i + 1 >= len(args) {172 return error("flag: -{name} needs a value")173 }174 ++i175 text = string(args[i])176 }177178 if failed(err = set.set1(f, text)) {179 return err180 }181 }182 return rest183 }184185 # Reset puts every flag back to the value it was declared with, which is186 # what a test parsing twice with the same set needs.187 set.Reset = fn() {188 for i = 0; i < len(set.order); ++i {189 f = set.flags[set.order[i]]190 f.Value = f.Default191 }192 return null193 }194195 return set196}197198# The set behind the functions of the module, which parses the command line of199# the program itself.200CommandLine = New(if len(os.Args) > 0 { os.Args[0] } else { "program" })201202String = fn(name, value, usage) { CommandLine.String(name, value, usage) }203Int = fn(name, value, usage) { CommandLine.Int(name, value, usage) }204Float = fn(name, value, usage) { CommandLine.Float(name, value, usage) }205Bool = fn(name, value, usage) { CommandLine.Bool(name, value, usage) }206Lookup = fn(name) { CommandLine.Lookup(name) }207Usage = fn() { CommandLine.Usage() }208209# Parse reads the command line of the program, past its name, and returns the210# arguments that are not flags.211Parse = fn() { CommandLine.Parse(slice(os.Args, 1, len(os.Args))) }