τau / flag /

flag.tau

source
/Users/niconex/Documents/tau/stdlib/flag/flag.tau
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))) }