Created a new argument parsing system
This commit is contained in:
parent
2bc5eb1f49
commit
b4a886b126
|
@ -0,0 +1,200 @@
|
|||
package main
|
||||
|
||||
import "os"
|
||||
import "fmt"
|
||||
import "errors"
|
||||
import "strings"
|
||||
import "strconv"
|
||||
|
||||
type Cli struct {
|
||||
// Description describes the application.
|
||||
Description string
|
||||
|
||||
// The syntax description of the command.
|
||||
Syntax string
|
||||
|
||||
// Flag is a set of flags to parse.
|
||||
Flags []*Flag
|
||||
|
||||
// Args is a list of leftover arguments that were not parsed.
|
||||
Args []string
|
||||
}
|
||||
|
||||
func NewCli (description string, flags ...*Flag) *Cli {
|
||||
return &Cli {
|
||||
Description: description,
|
||||
Flags: flags,
|
||||
}
|
||||
}
|
||||
|
||||
func (this *Cli) Parse (args []string) error {
|
||||
// strip out program name
|
||||
if len(args) > 0 { args = args[1:] }
|
||||
|
||||
next := func () string {
|
||||
arg := args[0]
|
||||
args = args[1:]
|
||||
return arg
|
||||
}
|
||||
|
||||
processFlag := func (flag *Flag, allowInput bool) error {
|
||||
if flag.Validate == nil {
|
||||
flag.Value = "true"
|
||||
} else {
|
||||
if len(args) < 1 || !allowInput {
|
||||
return errors.New(
|
||||
flag.String() +
|
||||
" requires a value")
|
||||
}
|
||||
flag.Value = next()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// process args one by one
|
||||
for ; len(args) > 0; next() {
|
||||
arg := args[0]
|
||||
|
||||
switch {
|
||||
case arg == "--":
|
||||
// halt parsing
|
||||
this.Args = append(this.Args, args[1:]...)
|
||||
return nil
|
||||
|
||||
case strings.HasPrefix(arg, "--"):
|
||||
// long flag
|
||||
flag, ok := this.LongFlag(arg[2:])
|
||||
if !ok {
|
||||
return errors.New("unknown flag: " + arg)
|
||||
}
|
||||
err := processFlag(flag, true)
|
||||
if err != nil { return err }
|
||||
|
||||
case strings.HasPrefix(arg, "-") && len(arg) > 1:
|
||||
// one or more short flags
|
||||
arg := arg[1:]
|
||||
for _, part := range arg {
|
||||
flag, ok := this.ShortFlag(part)
|
||||
if !ok {
|
||||
return errors.New (
|
||||
"unknown flag: -" +
|
||||
string(part))
|
||||
}
|
||||
err := processFlag(flag, len(arg) == 1)
|
||||
if err != nil { return err }
|
||||
}
|
||||
|
||||
default:
|
||||
// not a flag
|
||||
this.Args = append(this.Args, arg)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *Cli) ParseOrExit (args []string) {
|
||||
err := this.Parse(args)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%s: %v\n\n", os.Args[0], err)
|
||||
this.Usage()
|
||||
os.Exit(2)
|
||||
}
|
||||
}
|
||||
|
||||
func (this *Cli) ShortFlag (short rune) (*Flag, bool) {
|
||||
if short == 0 { return nil, false }
|
||||
for _, flag := range this.Flags {
|
||||
if flag.Short == short { return flag, true }
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (this *Cli) LongFlag (long string) (*Flag, bool) {
|
||||
if long == "" { return nil, false }
|
||||
for _, flag := range this.Flags {
|
||||
if flag.Long == long { return flag, true }
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (this *Cli) Usage () {
|
||||
fmt.Fprintf(os.Stderr, "Usage: %s ", os.Args[0])
|
||||
if this.Syntax == "" {
|
||||
fmt.Fprint(os.Stderr, "[OPTION]...")
|
||||
} else {
|
||||
fmt.Fprint(os.Stderr, this.Syntax)
|
||||
}
|
||||
fmt.Fprint(os.Stderr, "\n\n")
|
||||
if this.Description != "" {
|
||||
fmt.Fprintf(os.Stderr, "%s\n\n", this.Description)
|
||||
}
|
||||
|
||||
longest := 0
|
||||
for _, flag := range this.Flags {
|
||||
longest = max(longest, len(flag.Long))
|
||||
}
|
||||
format := fmt.Sprint("\t%-", longest + 8, "s%s\n")
|
||||
|
||||
for _, flag := range this.Flags {
|
||||
shortLong := "-" + string(flag.Short)
|
||||
if flag.Short != 0 && flag.Long != "" {
|
||||
shortLong += ", "
|
||||
}
|
||||
shortLong += "--" + flag.Long
|
||||
|
||||
fmt.Fprintf(os.Stderr, format, shortLong, flag.Help)
|
||||
}
|
||||
}
|
||||
|
||||
type Flag struct {
|
||||
Short rune // The short form of the flag (-l)
|
||||
Long string // Long form of the flag (--long-form)
|
||||
Help string // Help text to display by the flag
|
||||
|
||||
// Validate is an optional function that is called when an input is
|
||||
// passed to the flag. It checks whether or not the input is valid, and
|
||||
// returns an error if it isn't. If this function is nil, the flag is
|
||||
// assumed to have no input.
|
||||
Validate func (string) error
|
||||
|
||||
// Value contains the input given to the flag, and is filled out when
|
||||
// the flags are parsed. If this is a non-input flag (if Validate is
|
||||
// nil), this will be set to true. If the flag was not specified, Value
|
||||
// will be unchanged.
|
||||
Value string
|
||||
}
|
||||
|
||||
func (this *Flag) String () string {
|
||||
if this.Long == "" {
|
||||
return fmt.Sprintf("-%c", this.Short)
|
||||
} else {
|
||||
return fmt.Sprintf("--%s", this.Long)
|
||||
}
|
||||
}
|
||||
|
||||
func NewFlag(short rune, long string, help string) *Flag {
|
||||
return &Flag {
|
||||
Short: short,
|
||||
Long: long,
|
||||
Help: help,
|
||||
}
|
||||
}
|
||||
|
||||
func NewInputFlag(short rune, long string, help string, defaul string, validate func (string) error) *Flag {
|
||||
return &Flag {
|
||||
Short: short,
|
||||
Long: long,
|
||||
Help: help,
|
||||
Value: defaul,
|
||||
Validate: validate,
|
||||
}
|
||||
}
|
||||
|
||||
func ValString (value string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValInt (value string) error {
|
||||
_, err := strconv.Atoi(value)
|
||||
return err
|
||||
}
|
|
@ -14,7 +14,7 @@ import "git.tebibyte.media/sashakoshka/fspl/generator/native"
|
|||
|
||||
type Compiler struct {
|
||||
Output string
|
||||
Optimization int
|
||||
Optimization string
|
||||
}
|
||||
|
||||
func (this *Compiler) Compile (inputs []string) error {
|
||||
|
@ -73,7 +73,7 @@ func (this *Compiler) CompileModule (module *llvm.Module, filetype string) error
|
|||
"-",
|
||||
fmt.Sprintf("-filetype=%s", filetype),
|
||||
"-o", this.Output,
|
||||
fmt.Sprintf("-O=%d", this.Optimization),
|
||||
fmt.Sprintf("-O=%s", this.Optimization),
|
||||
}
|
||||
} else {
|
||||
commandName = "clang"
|
||||
|
|
|
@ -2,31 +2,40 @@ package main
|
|||
|
||||
import "os"
|
||||
import "fmt"
|
||||
import "flag"
|
||||
import "errors"
|
||||
import "strings"
|
||||
import "os/exec"
|
||||
import "path/filepath"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/llvm"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/lexer"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/parser"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/analyzer"
|
||||
import ferrors "git.tebibyte.media/sashakoshka/fspl/errors"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/generator/native"
|
||||
|
||||
func main () {
|
||||
compiler := new(Compiler)
|
||||
help := NewFlag (
|
||||
'h', "help",
|
||||
"Display usage information and exit")
|
||||
output := NewInputFlag (
|
||||
'o', "output",
|
||||
"Output filename", "",
|
||||
ValString)
|
||||
optimization := NewInputFlag (
|
||||
'O', "optimization",
|
||||
"Optimization level (0-3)", "0",
|
||||
func (value string) error {
|
||||
switch value {
|
||||
case "0", "1", "2", "3": return nil
|
||||
default: return errors.New("optimization level must be 0-3")
|
||||
}
|
||||
})
|
||||
|
||||
flag.StringVar(&compiler.Output, "o", "", "Output filename")
|
||||
flag.IntVar(&compiler.Optimization, "O", 0, "Optimization level (0-3)")
|
||||
flag.Parse()
|
||||
|
||||
if compiler.Optimization < 0 || compiler.Optimization > 3 {
|
||||
flag.Usage()
|
||||
cli := NewCli("Compile FSPL source files", help, output, optimization)
|
||||
cli.Syntax = "[OPTION]... [FILE]..."
|
||||
cli.ParseOrExit(os.Args)
|
||||
if help.Value != "" {
|
||||
cli.Usage()
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
compiler := new(Compiler)
|
||||
compiler.Output = output.Value
|
||||
compiler.Optimization = optimization.Value
|
||||
|
||||
err := compiler.Compile(flag.Args())
|
||||
err := compiler.Compile(cli.Args)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%s: %v\n", os.Args[0], ferrors.Format(err))
|
||||
os.Exit(1)
|
||||
|
|
Loading…
Reference in New Issue