[sduoj] create command line application

2021SC@SDUSC

introduction

As an oj system, it can not judge the submission results of only one language, and it may even support all common language types. However, different languages require different compile and run commands. If the task of judging the language type is entrusted to the problem solver, we may need to modify the source code of the problem solver when adding a new supporting language or removing an outdated language, which is undoubtedly not conducive to the maintenance of the system.

At this time, we need a bridge that can connect the problem solver and compilers of different languages to meet our needs.

package main

import (
	"flag"
	"fmt"
)

func main() {
	var language string
	flag.StringVar(&language, "language", "go", "programing language")
	flag.Parse()
	switch language {
	case "go":
		fmt.Println("Compile run Golang file")
	case "java":
		fmt.Println("Compile run Java file")
	case "cpp":
		fmt.Println("Compile run C++file")
	default:
		fmt.Println("Language type not recognized!")
	}
}

Source code analysis

flag.StringVar

The flag package provides a series of functional interfaces for parsing command line parameters. The StringVar function registers a string type flag with the specified name, default value and usage information, and saves the flag value to the variable pointed to by p.

func StringVar(p *string, name string, value string, usage string) {
	CommandLine.Var(newStringValue(value, p), name, usage)
}

CommandLine in the function is the default command line flag set, which is used to parse os.Args

var CommandLine = NewFlagSet(os.Args[0], ExitOnError)

The NewFlagSet initializes and returns a FlagSet structure.

func NewFlagSet(name string, errorHandling ErrorHandling) *FlagSet {
	f := &FlagSet{
		name:          name,
		errorHandling: errorHandling,
	}
	f.Usage = f.defaultUsage
	return f
}

FlagSet represents a collection of registered flags. The Usage function in the structure will be called when the flag has an error.

type FlagSet struct {
	Usage func()

	name          string
	parsed        bool
	actual        map[string]*Flag
	formal        map[string]*Flag
	args          []string // arguments after flags
	errorHandling ErrorHandling
	output        io.Writer // nil means stderr; use Output() accessor
}

flag.Parse

Parse is called at the end of the registration of all command line parameters. Its function is to parse and bind command line parameters. This function calls the parse method of CommandLine we mentioned earlier.

func Parse() {
	CommandLine.Parse(os.Args[1:])
}

FlagSet.Parse

This method parses the flag from the parameter list, which is a further encapsulation of the parsing method. It handles the special situations that may be encountered in the parsing process, and leaves the parsing logic to FlagSet.parseOne.

func (f *FlagSet) Parse(arguments []string) error {
	f.parsed = true
	f.args = arguments
	for {
		seen, err := f.parseOne()
		if seen {
			continue
		}
		if err == nil {
			break
		}
		switch f.errorHandling {
		case ContinueOnError:
			return err
		case ExitOnError:
			if err == ErrHelp {
				os.Exit(0)
			}
			os.Exit(2)
		case PanicOnError:
			panic(err)
		}
	}
	return nil
}

FlagSet.parseOne

parseOne parses a flag.

func (f *FlagSet) parseOne() (bool, error) {
	...
}

This method will detect the length of the command line parameter. If the length is 0, it will return resolution failure.

if len(f.args) == 0 {
	return false, nil
}

Next, it checks the format of the command line arguments. Command line parameters support three command line syntax: - Flag: (only Boolean types are supported), - flag x (only non Boolean types are supported), - flag=x (both supported). According to these syntax formats, we can filter by detecting the parameter length and whether the first parameter is "-".

s := f.args[0]
if len(s) < 2 || s[0] != '-' {
	return false, nil
}

In practical application, we find that it is also possible to use the format of -- flag=x, because when it finds that the second character in the parameter is also "-", name will intercept from the third character.

numMinuses := 1
if s[1] == '-' {
	numMinuses++
	if len(s) == 2 { // "--" terminates the flags
		f.args = f.args[1:]
		return false, nil
	}
}
name := s[numMinuses:]

Then we should check the format of name. If the format is wrong, an error will be returned.

if len(name) == 0 || name[0] == '-' || name[0] == '=' {
	return false, f.failf("bad flag syntax: %s", s)
}

The above is part of the format detection of f.args[0]. For a command-line application, it does not necessarily have only one parameter. We can detect the next parameter the next time we call the parseOne method in the following ways.

f.args = f.args[1:]

Parameters include name and value, which are connected with "=". The following code parses the original name. After finding "=", assign the value after "=" to value, assign the value before "=" to name, and set hasValue to true.

f.args = f.args[1:]
hasValue := false
value := ""
for i := 1; i < len(name); i++ { // equals cannot be first
	if name[i] == '=' {
		value = name[i+1:]
		hasValue = true
		name = name[0:i]
		break
	}
}

When setting the parameter value, it will judge whether the value is Boolean or not.

if fv, ok := flag.Value.(boolFlag); ok && fv.IsBoolFlag() {
	...
} else {
	...
}

If the parameter value is Boolean, the following judgment will be made.

If value is not empty, set fv to value; If value is empty, fv is set to true.

if hasValue {
	if err := fv.Set(value); err != nil {
		return false, f.failf("invalid boolean value %q for -%s: %v", value, name, err)
	}
} else {
	if err := fv.Set("true"); err != nil {
		return false, f.failf("invalid boolean flag %s: %v", name, err)
	}
}

If the parameter value is not Boolean, the following judgment will be made.

If hasValue is false, we will try to find the parameter following the parameter as the value (that is, - language=java and - language java are considered the same). If we can't find it, an error will be returned.

We try to set the parameter value to the value just obtained. If there is an error, the error will be returned.

// It must have a value, which might be the next argument.
if !hasValue && len(f.args) > 0 {
	// value is the next arg
	hasValue = true
	value, f.args = f.args[0], f.args[1:]
}
if !hasValue {
	return false, f.failf("flag needs an argument: -%s", name)
}
if err := flag.Value.Set(value); err != nil {
	return false, f.failf("invalid value %q for flag -%s: %v", value, name, err)
}

Keywords: Go Operating System source code cmd

Added by mattmate on Thu, 07 Oct 2021 18:12:39 +0300