shithub: mc

ref: b0b7605bb866cccae5ea15cb9b8b18524ecc84d6
dir: /lib/std/optparse.myr/

View raw version
use "alloc"
use "die"
use "extremum"
use "fmt"
use "strbuf"
use "option"
use "sleq"
use "slpush"
use "syswrap-ss"
use "syswrap"
use "types"
use "utf"

pkg std =
	type optdef = struct
		argdesc	: byte[:]	/* the description for the usage */
		minargs	: std.size	/* the minimum number of positional args */
		maxargs	: std.size	/* the maximum number of positional args (0 = unlimited) */
		noargs	: std.bool	/* whether we accept args at all */
		opts	: optdesc[:]	/* the description of the options */
	;;

	type optdesc = struct
		opt	: char
		arg	: byte[:]
		desc	: byte[:]
		dest	: std.option(byte[:]#)
		optional	: bool
	;;

	type optparsed = struct
		opts	: (char, byte[:])[:]
		args	: byte[:][:]
		prog	: byte[:]
	;;

	const optparse	: (optargs : byte[:][:], def : optdef# -> optparsed)
	const optusage	: (prog : byte[:], def : optdef# -> void)
;;

type optctx = struct
	/* public variables */
	args	: byte[:][:]

	/* data passed in */
	optdef	: optdef#
	optargs	: byte[:][:]

	/* state */
	argidx	: size
	curarg	: byte[:]
	optdone	: bool	/* if we've seen '--', everything's an arg */
	finished	: bool	/* if we've processed all the optargs */
;;


const optparse = {args, def
	var ctx : optctx
	var parsed

	parsed = [
		.opts=[][:],
		.args=[][:],
		.prog=args[0]
	]
	optinit(&ctx, args, def)
	while !optdone(&ctx)
		slpush(&parsed.opts, optnext(&ctx))
	;;
	if ctx.args.len < def.minargs
		put("error: expected at least {} args, got {}\n", def.minargs, ctx.args.len)
		optusage(ctx.optargs[0], ctx.optdef)
		exit(1)
	;;
	if def.maxargs > 0 && ctx.args.len < def.minargs
		put("error: expected at most {} args, got {}\n", def.minargs, ctx.args.len)
		optusage(ctx.optargs[0], ctx.optdef)
		exit(1)
	;;
	if def.noargs && ctx.args.len != 0
		put("error: expected no args, got {}\n", ctx.args.len)
		optusage(ctx.optargs[0], ctx.optdef)
		exit(1)
	;;
	parsed.args = ctx.args
	-> parsed
}

const optinit = {ctx, args, def
	ctx# = [
		.optargs = args,
		.optdef = def,
		.optdone = false,
		.finished = false,
		.argidx = 0,
		.curarg = [][:],
		.args = [][:],
	]

	next(ctx)
	-> ctx
}

const optnext = {ctx
	var c
	var arg

	(c, ctx.curarg) = strstep(ctx.curarg)

	match optinfo(ctx, c)
	| `None:
		if c == 'h' || c == '?'
			optusage(ctx.optargs[0], ctx.optdef)
			exit(0)
		else
			fatal("unexpected argument '{}'\n", c)
		;;
	| `Some (true, needed, dest):
		/* -arg => '-a' 'rg' */
		if ctx.curarg.len > 0
			arg = ctx.curarg
			ctx.curarg = ctx.curarg[ctx.curarg.len:]
			next(ctx)
		/* '-a rg' => '-a' 'rg' */
		elif ctx.argidx < (ctx.optargs.len - 1)
			arg = ctx.optargs[ctx.argidx + 1]
			ctx.argidx++
			next(ctx)
		elif needed
			put("Expected argument for {}\n", c)
			exit(1)
		;;
		match dest
		| `std.Some d:	d# = arg
		| `std.None:	/* nothing */
		;;
	| `Some (false, _, _):
		arg = ""
		if ctx.curarg.len == 0
			next(ctx)
		;;
	;;


	-> (c, arg)
}

const optdone = {ctx
	-> ctx.curarg.len == 0 && ctx.finished
}

const optinfo = {ctx, opt
	for o in ctx.optdef.opts
		if o.opt == opt
			-> `Some (o.arg.len != 0, !o.optional, o.dest)
		;;
	;;
	-> `None
}

const next = {ctx
	var i

	for i = ctx.argidx + 1; i < ctx.optargs.len; i++
		if !ctx.optdone && decode(ctx.optargs[i]) == '-'
			if sleq(ctx.optargs[i], "--")
				ctx.optdone = true
			else
				goto foundopt
			;;
		else
			slpush(&ctx.args, ctx.optargs[i])
		;;
	;;
:finishedopt
	ctx.finished = true
	-> false
:foundopt
	ctx.argidx = i
	ctx.curarg = ctx.optargs[i][1:]
	-> true
}

const optusage = {prog, def
	var sb, s

	sb = mksb()
	std.sbfmt(sb, "usage: {} [-h?", prog)
	for o in def.opts
		if o.arg.len == 0
			std.sbfmt(sb, "{}", o.opt)
		;;
	;;
	std.sbfmt(sb, "] ")
	for o in def.opts
		if o.arg.len != 0
			std.sbfmt(sb, "[-{} {}] ", o.opt, o.arg)
		;;
	;;
	std.sbfmt(sb, "{}\n", def.argdesc)
	std.sbfmt(sb, "\t-h\tprint this help message\n")
	std.sbfmt(sb, "\t-?\tprint this help message\n")
	for o in def.opts
		std.sbfmt(sb, "\t-{}{}{}\t{}\n", o.opt, sep(o.arg), o.arg, o.desc)
	;;
	s = sbfin(sb)
	write(1, s)
	slfree(s)
}

const sep = {s
	if s.len > 0
		-> " "
	else
		-> ""
	;;
}