diff --git a/cli/cli.go b/cli/cli.go index 447475e..c4e895b 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -1,6 +1,7 @@ package cli import "os" +import "io" import "fmt" import "sort" import "errors" @@ -11,6 +12,8 @@ import "strconv" // possible to create multiple Cli's to have so many sub-commands in one // program. type Cli struct { + Logger + // Description describes the application. Description string @@ -49,12 +52,13 @@ func New (description string, flags ...*Flag) *Cli { } // AddSub adds a sub-command to this command. It automatically fills out the -// sub-command's Super field. +// sub-command's Super field, and alters the prefix of its Logger to match. func (this *Cli) AddSub (name string, sub *Cli) { super := this.Super if super != "" { super += " " } super += name - sub.Super = super + sub.Super = super + sub.Prefix = os.Args[0] + " " + super this.Sub[name] = sub } @@ -67,7 +71,7 @@ func (this *Cli) AddSub (name string, sub *Cli) { // command that did the parsing. func (this *Cli) Parse (args []string) (*Cli, error) { // try to find a sub-command - if this.Sub != nil && len(args) > 0 { + if this.Sub != nil && len(args) > 1 { if sub, ok := this.Sub[args[1]]; ok { return sub.Parse(args[1:]) } @@ -153,7 +157,7 @@ func (this *Cli) Parse (args []string) (*Cli, error) { func (this *Cli) ParseOrExit (args []string) *Cli { command, err := this.Parse(args) if err != nil { - fmt.Fprintf(os.Stderr, "%s: %v\n\n", os.Args[0], err) + fmt.Fprintf(this, "%s: %v\n\n", os.Args[0], err) this.Usage() os.Exit(2) } @@ -183,25 +187,25 @@ func (this *Cli) LongFlag (long string) (*Flag, bool) { // Usage prints out usage/help information. func (this *Cli) Usage () { // syntax - fmt.Fprint(os.Stderr, "Usage:") - fmt.Fprint(os.Stderr, " ", os.Args[0]) + fmt.Fprint(this, "Usage:") + fmt.Fprint(this, " ", os.Args[0]) if this.Super != "" { - fmt.Fprint(os.Stderr, " ", this.Super) + fmt.Fprint(this, " ", this.Super) } hasSubCommands := this.Sub != nil && len(this.Sub) > 0 if hasSubCommands { - fmt.Fprint(os.Stderr, " [COMMAND]") + fmt.Fprint(this, " [COMMAND]") } if this.Syntax == "" { - fmt.Fprint(os.Stderr, " [OPTION]...") + fmt.Fprint(this, " [OPTION]...") } else { - fmt.Fprint(os.Stderr, " ", this.Syntax) + fmt.Fprint(this, " ", this.Syntax) } - fmt.Fprint(os.Stderr, "\n\n") + fmt.Fprint(this, "\n\n") // description if this.Description != "" { - fmt.Fprintf(os.Stderr, "%s\n\n", this.Description) + fmt.Fprintf(this, "%s\n\n", this.Description) } // fit the longest flag @@ -228,12 +232,12 @@ func (this *Cli) Usage () { shortLong += "--" + flag.Long } - fmt.Fprintf(os.Stderr, format, shortLong, flag.Help) + fmt.Fprintf(this, format, shortLong, flag.Help) } // commands if hasSubCommands { - fmt.Fprint(os.Stderr, "\nCommands:\n\n") + fmt.Fprint(this, "\nCommands:\n\n") names := sortMapKeys(this.Sub) // fit the longest command @@ -246,11 +250,65 @@ func (this *Cli) Usage () { format := fmt.Sprint("\t%-", longest + 2, "s%s\n") for _, name := range names { - fmt.Fprintf(os.Stderr, format, name, this.Sub[name].Description) + fmt.Fprintf(this, format, name, this.Sub[name].Description) } } } +// Logger prints messages to an output writer. +type Logger struct { + // Writer specifies the writer to output to. If it is nil, the Logger + // will output to os.Stderr. If you wish to silence the output, set it + // to io.Discard. + io.Writer + + // Debug determines whether or not debug messages will be logged. + Debug bool + + // Prefix is printed before all messages. If this is an empty string, + // os.Args[0] will be used. + Prefix string +} + +// Println logs a normal message. +func (this *Logger) Println (v ...any) { + fmt.Fprintf(this, "%s: %s", this.prefix(), fmt.Sprintln(v...)) +} + +// Errorln logs an error message. +func (this *Logger) Errorln (v ...any) { + fmt.Fprintf(this, "%s: %s", this.prefix(), fmt.Sprintln(v...)) +} + +// Warnln logs a warning. +func (this *Logger) Warnln (v ...any) { + fmt.Fprintf(this, "%s: warning: %s", this.prefix(), fmt.Sprintln(v...)) +} + +// Debugln logs debugging information. It will only print the message if the +// Debug field is set to true. +func (this *Logger) Debugln (v ...any) { + if !this.Debug { return } + fmt.Fprintf(this, "%s: debug: %s", this.prefix(), fmt.Sprintln(v...)) +} + +func (this *Logger) prefix () string { + if this.Prefix == "" { + return os.Args[0] + } else { + return this.Prefix + } +} + +// Write writes to the Logger's writer. If it is nil, it writes to os.Stderr. +func (this *Logger) Write (data []byte) (n int, err error) { + if this.Writer == nil { + return os.Stderr.Write(data) + } else { + return this.Writer.Write(data) + } +} + // Flag is a command line option. type Flag struct { Short rune // The short form of the flag (-l) diff --git a/cmd/fsplc/main.go b/cmd/fsplc/main.go index 9e6b942..c677dcf 100644 --- a/cmd/fsplc/main.go +++ b/cmd/fsplc/main.go @@ -1,6 +1,7 @@ package main import "os" +import "io" import "path/filepath" import "git.tebibyte.media/fspl/fspl/cli" import "git.tebibyte.media/fspl/fspl/entity" @@ -11,7 +12,7 @@ func main () { // instantiate the compiler // FIXME: perhaps we want different paths on Windows? comp := new(compiler.Compiler) - comp.Stderr = os.Stderr + comp.Writer = os.Stderr comp.Resolver = compiler.NewResolver ( "/usr/local/src/fspl", "/usr/src/fspl", @@ -68,7 +69,7 @@ func main () { comp.Optimization = optimization.Value comp.Format = format.Value comp.Debug = debug.Value != "" - comp.Quiet = quiet.Value != "" + if quiet.Value != "" { comp.Writer = io.Discard } err = comp.CompileUnit(entity.Address(application.Args[0])) if err != nil { diff --git a/compiler/compiler.go b/compiler/compiler.go index 4634e2b..d2185e3 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -1,6 +1,5 @@ package compiler -import "io" import "os" import "fmt" import "sort" @@ -8,6 +7,7 @@ import "errors" import "os/exec" import "path/filepath" import "github.com/google/uuid" +import "git.tebibyte.media/fspl/fspl/cli" import "git.tebibyte.media/fspl/fspl/llvm" import "git.tebibyte.media/fspl/fspl/lexer" import "git.tebibyte.media/fspl/fspl/entity" @@ -19,12 +19,11 @@ import "git.tebibyte.media/fspl/fspl/generator/native" type Compiler struct { Resolver + cli.Logger + Output string Optimization string Format string - Stderr io.Writer - Debug bool - Quiet bool } func (this *Compiler) CompileUnit (address entity.Address) error { @@ -353,25 +352,6 @@ func (this *Compiler) FindBackend (filetype string) (string, []string, error) { "or 'clang' are accessable from your PATH") } -func (this *Compiler) Errorln (v ...any) { - if this.Quiet { return } - if this.Stderr == nil { return } - fmt.Fprintf(this.Stderr, "%s: %s", os.Args[0], fmt.Sprintln(v...)) -} - -func (this *Compiler) Warnln (v ...any) { - if this.Quiet { return } - if this.Stderr == nil { return } - fmt.Fprintf(this.Stderr, "%s: warning: %s", os.Args[0], fmt.Sprintln(v...)) -} - -func (this *Compiler) Debugln (v ...any) { - if this.Quiet { return } - if !this.Debug { return } - if this.Stderr == nil { return } - fmt.Fprintf(this.Stderr, "%s: debug: %s", os.Args[0], fmt.Sprintln(v...)) -} - func sortMapKeys[T any] (unsorted map[string] T) []string { keys := make([]string, len(unsorted)) index := 0