mirror of
https://github.com/JetSetIlly/Gopher2600.git
synced 2024-06-02 03:58:02 -04:00
improved gopher2600.go structure
removed modalflags package. modalflags was added very early in the project. it's neater and simpler to implement command-line modes with the standard flag package directly improved log message for unsupported terminal type remove rand.Seed(time) on startup. rand.Seed() is a deprecated function
This commit is contained in:
parent
8addbe5251
commit
85adbca367
|
@ -24,59 +24,27 @@ package debugger
|
|||
// library, which is where this requirement originates.
|
||||
type CommandLineOptions struct {
|
||||
// common to debugger and play modes
|
||||
Log *bool
|
||||
Spec *string
|
||||
FpsCap *bool
|
||||
Multiload *int
|
||||
Mapping *string
|
||||
Left *string
|
||||
Right *string
|
||||
Profile *string
|
||||
ELF *string
|
||||
Log bool
|
||||
Spec string
|
||||
FpsCap bool
|
||||
Multiload int
|
||||
Mapping string
|
||||
Left string
|
||||
Right string
|
||||
Profile string
|
||||
ELF string
|
||||
|
||||
// playmode only
|
||||
ComparisonROM *string
|
||||
ComparisonPrefs *string
|
||||
Record *bool
|
||||
PlaybackCheckROM *bool
|
||||
PatchFile *string
|
||||
Wav *bool
|
||||
NoEject *bool
|
||||
Macro *string
|
||||
ComparisonROM string
|
||||
ComparisonPrefs string
|
||||
Record bool
|
||||
PlaybackCheckROM bool
|
||||
PatchFile string
|
||||
Wav bool
|
||||
NoEject bool
|
||||
Macro string
|
||||
|
||||
// debugger only
|
||||
InitScript *string
|
||||
TermType *string
|
||||
}
|
||||
|
||||
// NewCommandLineOptions creates a minimum instance of CommandLineOptions such
|
||||
// that it is safe to dereference the fields in all situations.
|
||||
//
|
||||
// The values of these fields are shared by type and will be the default values
|
||||
// for that type. ie. a bool is false, an int is zero, etc. Care should be
|
||||
// taken therefore to replace the instance with the result from the modalflag
|
||||
// (or flag) package.
|
||||
func NewCommandLineOptions() CommandLineOptions {
|
||||
var b bool
|
||||
var s string
|
||||
var i int
|
||||
return CommandLineOptions{
|
||||
Log: &b,
|
||||
Spec: &s,
|
||||
FpsCap: &b,
|
||||
Multiload: &i,
|
||||
Mapping: &s,
|
||||
Left: &s,
|
||||
Right: &s,
|
||||
Profile: &s,
|
||||
ELF: &s,
|
||||
ComparisonROM: &s,
|
||||
ComparisonPrefs: &s,
|
||||
Record: &b,
|
||||
PatchFile: &s,
|
||||
Wav: &b,
|
||||
InitScript: &s,
|
||||
TermType: &s,
|
||||
NoEject: &b,
|
||||
}
|
||||
InitScript string
|
||||
TermType string
|
||||
}
|
||||
|
|
|
@ -341,7 +341,7 @@ func NewDebugger(opts CommandLineOptions, create CreateUserInterface) (*Debugger
|
|||
|
||||
// creat a new television. this will be used during the initialisation of
|
||||
// the VCS and not referred to directly again
|
||||
tv, err := television.NewTelevision(*opts.Spec)
|
||||
tv, err := television.NewTelevision(opts.Spec)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("debugger: %w", err)
|
||||
}
|
||||
|
@ -440,7 +440,7 @@ func NewDebugger(opts CommandLineOptions, create CreateUserInterface) (*Debugger
|
|||
dbg.vcs.RIOT.Ports.AttachPlugMonitor(dbg)
|
||||
|
||||
// set fps cap
|
||||
dbg.vcs.TV.SetFPSCap(*opts.FpsCap)
|
||||
dbg.vcs.TV.SetFPSCap(opts.FpsCap)
|
||||
|
||||
// initialise terminal
|
||||
err = dbg.term.Initialise()
|
||||
|
@ -677,7 +677,7 @@ func (dbg *Debugger) StartInDebugMode(filename string) error {
|
|||
if filename == "" {
|
||||
cartload = cartridgeloader.Loader{}
|
||||
} else {
|
||||
cartload, err = cartridgeloader.NewLoader(filename, *dbg.opts.Mapping)
|
||||
cartload, err = cartridgeloader.NewLoader(filename, dbg.opts.Mapping)
|
||||
if err != nil {
|
||||
return fmt.Errorf("debugger: %w", err)
|
||||
}
|
||||
|
@ -688,7 +688,7 @@ func (dbg *Debugger) StartInDebugMode(filename string) error {
|
|||
return fmt.Errorf("debugger: %w", err)
|
||||
}
|
||||
|
||||
err = dbg.insertPeripheralsOnStartup(*dbg.opts.Left, *dbg.opts.Right)
|
||||
err = dbg.insertPeripheralsOnStartup(dbg.opts.Left, dbg.opts.Right)
|
||||
if err != nil {
|
||||
return fmt.Errorf("debugger: %w", err)
|
||||
}
|
||||
|
@ -699,8 +699,8 @@ func (dbg *Debugger) StartInDebugMode(filename string) error {
|
|||
}
|
||||
|
||||
// intialisation script because we're in debugger mode
|
||||
if *dbg.opts.InitScript != "" {
|
||||
scr, err := script.RescribeScript(*dbg.opts.InitScript)
|
||||
if dbg.opts.InitScript != "" {
|
||||
scr, err := script.RescribeScript(dbg.opts.InitScript)
|
||||
if err == nil {
|
||||
dbg.term.Silence(true)
|
||||
err = dbg.inputLoop(scr, false)
|
||||
|
@ -751,7 +751,7 @@ func (dbg *Debugger) StartInPlayMode(filename string) error {
|
|||
if filename == "" {
|
||||
cartload = cartridgeloader.Loader{}
|
||||
} else {
|
||||
cartload, err = cartridgeloader.NewLoader(filename, *dbg.opts.Mapping)
|
||||
cartload, err = cartridgeloader.NewLoader(filename, dbg.opts.Mapping)
|
||||
if err != nil {
|
||||
return fmt.Errorf("debugger: %w", err)
|
||||
}
|
||||
|
@ -768,22 +768,22 @@ func (dbg *Debugger) StartInPlayMode(filename string) error {
|
|||
return fmt.Errorf("debugger: %w", err)
|
||||
}
|
||||
|
||||
err = dbg.insertPeripheralsOnStartup(*dbg.opts.Left, *dbg.opts.Right)
|
||||
err = dbg.insertPeripheralsOnStartup(dbg.opts.Left, dbg.opts.Right)
|
||||
if err != nil {
|
||||
return fmt.Errorf("debugger: %w", err)
|
||||
}
|
||||
|
||||
// apply patch if requested. note that this will be in addition to any
|
||||
// patches applied during setup.AttachCartridge
|
||||
if *dbg.opts.PatchFile != "" {
|
||||
_, err := patch.CartridgeMemory(dbg.vcs.Mem.Cart, *dbg.opts.PatchFile)
|
||||
if dbg.opts.PatchFile != "" {
|
||||
_, err := patch.CartridgeMemory(dbg.vcs.Mem.Cart, dbg.opts.PatchFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("debugger: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// record wav file
|
||||
if *dbg.opts.Wav {
|
||||
if dbg.opts.Wav {
|
||||
fn := unique.Filename("audio", cartload.ShortName())
|
||||
ww, err := wavwriter.NewWavWriter(fn)
|
||||
if err != nil {
|
||||
|
@ -793,25 +793,25 @@ func (dbg *Debugger) StartInPlayMode(filename string) error {
|
|||
}
|
||||
|
||||
// record gameplay
|
||||
if *dbg.opts.Record {
|
||||
if dbg.opts.Record {
|
||||
dbg.startRecording(cartload.ShortName())
|
||||
}
|
||||
} else {
|
||||
if *dbg.opts.Record {
|
||||
if dbg.opts.Record {
|
||||
return fmt.Errorf("debugger: cannot make a new recording using a playback file")
|
||||
}
|
||||
|
||||
dbg.startPlayback(filename)
|
||||
}
|
||||
|
||||
if *dbg.opts.Macro != "" {
|
||||
dbg.macro, err = macro.NewMacro(*dbg.opts.Macro, dbg, dbg.vcs.Input, dbg.vcs.TV, dbg.gui)
|
||||
if dbg.opts.Macro != "" {
|
||||
dbg.macro, err = macro.NewMacro(dbg.opts.Macro, dbg, dbg.vcs.Input, dbg.vcs.TV, dbg.gui)
|
||||
if err != nil {
|
||||
return fmt.Errorf("debugger: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
err = dbg.startComparison(*dbg.opts.ComparisonROM, *dbg.opts.ComparisonPrefs)
|
||||
err = dbg.startComparison(dbg.opts.ComparisonROM, dbg.opts.ComparisonPrefs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("debugger: %w", err)
|
||||
}
|
||||
|
@ -1046,9 +1046,9 @@ func (dbg *Debugger) attachCartridge(cartload cartridgeloader.Loader) (e error)
|
|||
if _, ok := cart.(*supercharger.Supercharger); ok {
|
||||
switch event {
|
||||
case notifications.NotifySuperchargerLoadStarted:
|
||||
if *dbg.opts.Multiload >= 0 {
|
||||
logger.Logf("debugger", "forcing supercharger multiload (%#02x)", uint8(*dbg.opts.Multiload))
|
||||
dbg.vcs.Mem.Poke(supercharger.MutliloadByteAddress, uint8(*dbg.opts.Multiload))
|
||||
if dbg.opts.Multiload >= 0 {
|
||||
logger.Logf("debugger", "forcing supercharger multiload (%#02x)", uint8(dbg.opts.Multiload))
|
||||
dbg.vcs.Mem.Poke(supercharger.MutliloadByteAddress, uint8(dbg.opts.Multiload))
|
||||
}
|
||||
|
||||
case notifications.NotifySuperchargerFastloadEnded:
|
||||
|
@ -1141,7 +1141,7 @@ func (dbg *Debugger) attachCartridge(cartload cartridgeloader.Loader) (e error)
|
|||
}
|
||||
|
||||
// check for cartridge ejection. if the NoEject option is set then return error
|
||||
if *dbg.opts.NoEject && dbg.vcs.Mem.Cart.IsEjected() {
|
||||
if dbg.opts.NoEject && dbg.vcs.Mem.Cart.IsEjected() {
|
||||
// if there is an error left over from the AttachCartridge() call
|
||||
// above, return that rather than "cartridge ejected"
|
||||
if err != nil {
|
||||
|
@ -1160,7 +1160,7 @@ func (dbg *Debugger) attachCartridge(cartload cartridgeloader.Loader) (e error)
|
|||
}
|
||||
|
||||
dbg.CoProcDisasm.AttachCartridge(dbg)
|
||||
err = dbg.CoProcDev.AttachCartridge(dbg, cartload.Filename, *dbg.opts.ELF)
|
||||
err = dbg.CoProcDev.AttachCartridge(dbg, cartload.Filename, dbg.opts.ELF)
|
||||
if err != nil {
|
||||
logger.Logf("debugger", err.Error())
|
||||
if errors.Is(err, coproc_dwarf.UnsupportedDWARF) {
|
||||
|
@ -1235,7 +1235,7 @@ func (dbg *Debugger) endRecording() {
|
|||
}
|
||||
|
||||
func (dbg *Debugger) startPlayback(filename string) error {
|
||||
plb, err := recorder.NewPlayback(filename, *dbg.opts.PlaybackCheckROM)
|
||||
plb, err := recorder.NewPlayback(filename, dbg.opts.PlaybackCheckROM)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -1353,7 +1353,7 @@ func (dbg *Debugger) hotload() (e error) {
|
|||
}
|
||||
|
||||
dbg.CoProcDisasm.AttachCartridge(dbg)
|
||||
dbg.CoProcDev.AttachCartridge(dbg, cartload.Filename, *dbg.opts.ELF)
|
||||
dbg.CoProcDev.AttachCartridge(dbg, cartload.Filename, dbg.opts.ELF)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -147,7 +147,9 @@ func TestDebugger_withNonExistantInitScript(t *testing.T) {
|
|||
return &mockGUI{}, trm, nil
|
||||
}
|
||||
|
||||
dbg, err := debugger.NewDebugger(debugger.NewCommandLineOptions(), create)
|
||||
var opts debugger.CommandLineOptions
|
||||
|
||||
dbg, err := debugger.NewDebugger(opts, create)
|
||||
if err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
|
@ -170,7 +172,9 @@ func TestDebugger(t *testing.T) {
|
|||
return &mockGUI{}, trm, nil
|
||||
}
|
||||
|
||||
dbg, err := debugger.NewDebugger(debugger.NewCommandLineOptions(), create)
|
||||
var opts debugger.CommandLineOptions
|
||||
|
||||
dbg, err := debugger.NewDebugger(opts, create)
|
||||
if err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ func (dsm *Disassembly) Write(output io.Writer, attr ColumnAttr) error {
|
|||
// WriteBank writes the disassembly of the selected bank to io.Writer.
|
||||
func (dsm *Disassembly) WriteBank(output io.Writer, attr ColumnAttr, bank int) error {
|
||||
if bank >= len(dsm.disasmEntries.Entries) {
|
||||
return nil
|
||||
return fmt.Errorf("no bank %d in cartridge", bank)
|
||||
}
|
||||
|
||||
ct := 0
|
||||
|
|
603
gopher2600.go
603
gopher2600.go
|
@ -17,9 +17,9 @@ package main
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
|
@ -37,7 +37,6 @@ import (
|
|||
"github.com/jetsetilly/gopher2600/gui/sdlimgui"
|
||||
"github.com/jetsetilly/gopher2600/hardware/riot/ports"
|
||||
"github.com/jetsetilly/gopher2600/logger"
|
||||
"github.com/jetsetilly/gopher2600/modalflag"
|
||||
"github.com/jetsetilly/gopher2600/performance"
|
||||
"github.com/jetsetilly/gopher2600/recorder"
|
||||
"github.com/jetsetilly/gopher2600/regression"
|
||||
|
@ -118,7 +117,7 @@ func main() {
|
|||
|
||||
// launch program as a go routine. further communication is through
|
||||
// the mainSync instance
|
||||
go launch(sync)
|
||||
go launch(sync, os.Args[1:])
|
||||
|
||||
// if there is no GUI then we should sleep so that the select channel loop
|
||||
// doesn't go beserk
|
||||
|
@ -211,57 +210,42 @@ func main() {
|
|||
|
||||
// launch is called from main() as a goroutine. uses mainSync instance to
|
||||
// indicate gui creation and to quit.
|
||||
func launch(sync *mainSync) {
|
||||
func launch(sync *mainSync, args []string) {
|
||||
logger.Log("runtime", fmt.Sprintf("number of cores being used: %d", runtime.NumCPU()))
|
||||
|
||||
// we generate random numbers in some places. seed the generator with the
|
||||
// current time
|
||||
rand.Seed(int64(time.Now().Nanosecond()))
|
||||
// get mode from command line
|
||||
var mode string
|
||||
|
||||
md := &modalflag.Modes{Output: os.Stdout}
|
||||
md.NewArgs(os.Args[1:])
|
||||
md.NewMode()
|
||||
md.AddSubModes("RUN", "PLAY", "DEBUG", "DISASM", "PERFORMANCE", "REGRESS", "VERSION")
|
||||
|
||||
p, err := md.Parse()
|
||||
switch p {
|
||||
case modalflag.ParseHelp:
|
||||
sync.state <- stateRequest{req: reqQuit}
|
||||
return
|
||||
|
||||
case modalflag.ParseError:
|
||||
fmt.Printf("* error: %v\n", err)
|
||||
sync.state <- stateRequest{req: reqQuit, args: 10}
|
||||
return
|
||||
if len(args) > 0 {
|
||||
mode = strings.ToUpper(args[0])
|
||||
}
|
||||
|
||||
switch md.Mode() {
|
||||
var err error
|
||||
|
||||
switch mode {
|
||||
default:
|
||||
mode = "RUN"
|
||||
err = emulate(mode, sync, args)
|
||||
case "RUN":
|
||||
fallthrough
|
||||
|
||||
case "PLAY":
|
||||
err = emulate(govern.ModePlay, md, sync)
|
||||
|
||||
fallthrough
|
||||
case "DEBUG":
|
||||
err = emulate(govern.ModeDebugger, md, sync)
|
||||
|
||||
err = emulate(mode, sync, args[1:])
|
||||
case "DISASM":
|
||||
err = disasm(md)
|
||||
|
||||
err = disasm(mode, args[1:])
|
||||
case "PERFORMANCE":
|
||||
err = perform(md, sync)
|
||||
|
||||
err = perform(mode, sync, args[1:])
|
||||
case "REGRESS":
|
||||
err = regress(md, sync)
|
||||
|
||||
err = regress(mode, args[1:])
|
||||
case "VERSION":
|
||||
err = showVersion(md)
|
||||
err = showVersion(mode, args[1:])
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
// swallow power off error messages. send quit signal with return value of 20 instead
|
||||
if !errors.Is(err, ports.PowerOff) {
|
||||
fmt.Printf("* error in %s mode: %s\n", md.String(), err)
|
||||
fmt.Printf("* error in %s mode: %s\n", mode, err)
|
||||
sync.state <- stateRequest{req: reqQuit, args: 20}
|
||||
return
|
||||
}
|
||||
|
@ -274,74 +258,80 @@ const defaultInitScript = "debuggerInit"
|
|||
|
||||
// emulate is the main emulation launch function, shared by play and debug
|
||||
// modes. the other modes initialise and run the emulation differently.
|
||||
func emulate(emulationMode govern.Mode, md *modalflag.Modes, sync *mainSync) error {
|
||||
// start new commandline mode. to this we'll add the command line arguments
|
||||
// that are specific to the emulation mode (it's unfortunate that mode is
|
||||
// used to describe two separate concepts but they really have nothing to
|
||||
// do with one another).
|
||||
md.NewMode()
|
||||
|
||||
// prepare the path to the initialisation script used by the debugger. we
|
||||
// can name the file in the defaultInitScript const declaration but the
|
||||
// construction of the path is platform sensitive so we must do it here
|
||||
defInitScript, err := resources.JoinPath(defaultInitScript)
|
||||
if err != nil {
|
||||
return err
|
||||
func emulate(mode string, sync *mainSync, args []string) error {
|
||||
var emulationMode govern.Mode
|
||||
switch mode {
|
||||
case "PLAY":
|
||||
emulationMode = govern.ModePlay
|
||||
case "RUN":
|
||||
emulationMode = govern.ModePlay
|
||||
case "DEBUG":
|
||||
emulationMode = govern.ModeDebugger
|
||||
default:
|
||||
panic(fmt.Errorf("unknown emulation mode: %s", mode))
|
||||
}
|
||||
|
||||
// new CommandLineOptions instance. this type collates the individual
|
||||
// options that can be set by the command line
|
||||
opts := debugger.NewCommandLineOptions()
|
||||
// opts collates the individual options that can be set by the command line
|
||||
var opts debugger.CommandLineOptions
|
||||
|
||||
// arguments common to both play and debugging modes
|
||||
opts.Log = md.AddBool("log", false, "echo debugging log to stdout")
|
||||
opts.Spec = md.AddString("tv", "AUTO", "television specification: AUTO, NTSC, PAL, PAL60, SECAM")
|
||||
opts.FpsCap = md.AddBool("fpscap", true, "cap FPS to emulated match TV")
|
||||
opts.Multiload = md.AddInt("multiload", -1, "force multiload byte (supercharger only; 0 to 255)")
|
||||
opts.Mapping = md.AddString("mapping", "AUTO", "force use of cartridge mapping")
|
||||
opts.Left = md.AddString("left", "AUTO", "left player port: AUTO, STICK, PADDLE, KEYPAD, GAMEPAD")
|
||||
opts.Right = md.AddString("right", "AUTO", "right player port: AUTO, STICK, PADDLE, KEYPAD, GAMEPAD, SAVEKEY, ATARIVOX")
|
||||
opts.Profile = md.AddString("profile", "none", "run performance check with profiling: command separated CPU, MEM, TRACE or ALL")
|
||||
opts.ELF = md.AddString("elf", "", "path to corresponding ELF file for binary. only valid for some coprocessor supporting ROMs.")
|
||||
flgs := flag.NewFlagSet(mode, flag.ExitOnError)
|
||||
flgs.BoolVar(&opts.Log, "log", false, "echo debugging log to stdout")
|
||||
flgs.StringVar(&opts.Spec, "tv", "AUTO", "televsion specifcation: AUTO, NTSC, PAL, PAL60, SECAM")
|
||||
flgs.BoolVar(&opts.FpsCap, "fpscap", true, "cap FPS to emulation TV")
|
||||
flgs.IntVar(&opts.Multiload, "multiload", -1, "force multiload byte (supercharger only; 0 to 255")
|
||||
flgs.StringVar(&opts.Mapping, "mapping", "AUTO", "force cartridge mapper selection")
|
||||
flgs.StringVar(&opts.Left, "left", "AUTO", "left player port: AUTO, STICK, PADDLE, KEYPAD, GAMEPAD")
|
||||
flgs.StringVar(&opts.Right, "right", "AUTO", "left player port: AUTO, STICK, PADDLE, KEYPAD, GAMEPAD")
|
||||
flgs.StringVar(&opts.Profile, "profile", "none", "run performance check with profiling: CPU, MEM, TRACE, ALL (comma sep)")
|
||||
flgs.StringVar(&opts.ELF, "elf", "", "path to ELF file. only valid for some coproc supporting ROMs")
|
||||
|
||||
// playmode specific arguments
|
||||
if emulationMode == govern.ModePlay {
|
||||
opts.ComparisonROM = md.AddString("comparisonROM", "", "ROM to run in parallel for comparison")
|
||||
opts.ComparisonPrefs = md.AddString("comparisonPrefs", "", "preferences for comparison emulation")
|
||||
opts.Record = md.AddBool("record", false, "record user input to a file")
|
||||
opts.PlaybackCheckROM = md.AddBool("playbackCheckROM", true, "whether to check ROM hash on playback")
|
||||
opts.PatchFile = md.AddString("patch", "", "patch to apply to main emulation (not playback files)")
|
||||
opts.Wav = md.AddBool("wav", false, "record audio to wav file")
|
||||
opts.NoEject = md.AddBool("noeject", false, "a cartridge must be attached at all times. emulator will quit if not")
|
||||
opts.Macro = md.AddString("macro", "", "macro file to be run on trigger")
|
||||
flgs.StringVar(&opts.ComparisonROM, "comparisonROM", "", "ROM to run in parallel for comparison")
|
||||
flgs.StringVar(&opts.ComparisonPrefs, "comparisonPrefs", "", "preferences for comparison emulation")
|
||||
flgs.BoolVar(&opts.Record, "record", false, "record user input to playback file")
|
||||
flgs.BoolVar(&opts.PlaybackCheckROM, "playbackCheckROM", true, "check ROM hashes on playback")
|
||||
flgs.StringVar(&opts.PatchFile, "patch", "", "path to apply to emulation (not playback files")
|
||||
flgs.BoolVar(&opts.Wav, "wav", false, "record audio to wav file")
|
||||
flgs.BoolVar(&opts.NoEject, "noeject", false, "emulator will not quit is noeject is true")
|
||||
flgs.StringVar(&opts.Macro, "macro", "", "macro file to be run on trigger")
|
||||
}
|
||||
|
||||
// debugger specific arguments
|
||||
if emulationMode == govern.ModeDebugger {
|
||||
opts.InitScript = md.AddString("initscript", defInitScript, "script to run on debugger start")
|
||||
opts.TermType = md.AddString("term", "IMGUI", "terminal type to use in debug mode: IMGUI, COLOR, PLAIN")
|
||||
// prepare the path to the initialisation script used by the debugger. we
|
||||
// can name the file in the defaultInitScript const declaration but the
|
||||
// construction of the path is platform sensitive so we must do it here
|
||||
defInitScript, err := resources.JoinPath(defaultInitScript)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
flgs.StringVar(&opts.InitScript, "initscript", defInitScript, "script to run on debugger start")
|
||||
flgs.StringVar(&opts.TermType, "term", "IMGUI", "terminal type: IMGUI, COLOR, PLAIN")
|
||||
} else {
|
||||
// non debugger emulation is always of type IMGUI
|
||||
tt := "IMGUI"
|
||||
opts.TermType = &tt
|
||||
opts.TermType = "IMGUI"
|
||||
}
|
||||
|
||||
// parse arguments
|
||||
p, err := md.Parse()
|
||||
if err != nil || p != modalflag.ParseContinue {
|
||||
// parse args and get copy of remaining arguments
|
||||
err := flgs.Parse(args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
args = flgs.Args()
|
||||
|
||||
// check remaining arguments. if there are any outstanding arguments to
|
||||
// process then the user has made a mistake
|
||||
if len(md.RemainingArgs()) > 1 {
|
||||
return fmt.Errorf("too many arguments for %s mode", md)
|
||||
if len(args) > 1 {
|
||||
return fmt.Errorf("too many arguments")
|
||||
}
|
||||
|
||||
// turn logging on by setting the echo function. events are still logged
|
||||
// and available via the debugger but will not be "echoed" to the terminal,
|
||||
// unless this option is on
|
||||
if *opts.Log {
|
||||
if opts.Log {
|
||||
logger.SetEcho(os.Stdout, true)
|
||||
} else {
|
||||
logger.SetEcho(nil, false)
|
||||
|
@ -362,7 +352,7 @@ func emulate(emulationMode govern.Mode, md *modalflag.Modes, sync *mainSync) err
|
|||
var scr gui.GUI
|
||||
|
||||
// create GUI as appropriate
|
||||
if *opts.TermType == "IMGUI" {
|
||||
if opts.TermType == "IMGUI" {
|
||||
sync.state <- stateRequest{req: reqCreateGUI,
|
||||
args: guiCreate(func() (guiControl, error) {
|
||||
return sdlimgui.NewSdlImgui(e)
|
||||
|
@ -390,10 +380,11 @@ func emulate(emulationMode govern.Mode, md *modalflag.Modes, sync *mainSync) err
|
|||
// if the GUI does not supply a terminal then use a color or plain terminal
|
||||
// as a fallback
|
||||
if term == nil {
|
||||
switch strings.ToUpper(*opts.TermType) {
|
||||
switch strings.ToUpper(opts.TermType) {
|
||||
default:
|
||||
fmt.Printf("! unknown terminal type (%s) defaulting to plain\n", *opts.TermType)
|
||||
fallthrough
|
||||
logger.Logf("terminal", "unknown terminal: %s", opts.TermType)
|
||||
logger.Logf("terminal", "defaulting to plain")
|
||||
term = &plainterm.PlainTerminal{}
|
||||
case "PLAIN":
|
||||
term = &plainterm.PlainTerminal{}
|
||||
case "COLOR":
|
||||
|
@ -410,15 +401,20 @@ func emulate(emulationMode govern.Mode, md *modalflag.Modes, sync *mainSync) err
|
|||
// set up a launch function. this function is called either directly or via
|
||||
// a call to performance.RunProfiler()
|
||||
dbgLaunch := func() error {
|
||||
var romFile string
|
||||
if len(args) != 0 {
|
||||
romFile = args[0]
|
||||
}
|
||||
|
||||
switch emulationMode {
|
||||
case govern.ModeDebugger:
|
||||
err := dbg.StartInDebugMode(md.GetArg(0))
|
||||
err := dbg.StartInDebugMode(romFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case govern.ModePlay:
|
||||
err := dbg.StartInPlayMode(md.GetArg(0))
|
||||
err := dbg.StartInPlayMode(romFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -429,7 +425,7 @@ func emulate(emulationMode govern.Mode, md *modalflag.Modes, sync *mainSync) err
|
|||
|
||||
// check for profiling option and either run the launch function (prepared
|
||||
// above) via the performance.RunProfiler() function or directly
|
||||
prf, err := performance.ParseProfileString(*opts.Profile)
|
||||
prf, err := performance.ParseProfileString(opts.Profile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -461,29 +457,34 @@ func emulate(emulationMode govern.Mode, md *modalflag.Modes, sync *mainSync) err
|
|||
return nil
|
||||
}
|
||||
|
||||
func disasm(md *modalflag.Modes) error {
|
||||
md.NewMode()
|
||||
func disasm(mode string, args []string) error {
|
||||
var mapping string
|
||||
var bytecode bool
|
||||
var bank int
|
||||
|
||||
mapping := md.AddString("mapping", "AUTO", "force use of cartridge mapping")
|
||||
bytecode := md.AddBool("bytecode", false, "include bytecode in disassembly")
|
||||
bank := md.AddInt("bank", -1, "show disassembly for a specific bank")
|
||||
flgs := flag.NewFlagSet(mode, flag.ExitOnError)
|
||||
flgs.StringVar(&mapping, "mapping", "AUTO", "force cartridge mapper selection")
|
||||
flgs.BoolVar(&bytecode, "bytecode", false, "including bytecode in disassembly")
|
||||
flgs.IntVar(&bank, "bank", -1, "show disassembly for a specific bank")
|
||||
|
||||
p, err := md.Parse()
|
||||
if err != nil || p != modalflag.ParseContinue {
|
||||
// parse args and get copy of remaining arguments
|
||||
err := flgs.Parse(args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
args = flgs.Args()
|
||||
|
||||
switch len(md.RemainingArgs()) {
|
||||
switch len(args) {
|
||||
case 0:
|
||||
return fmt.Errorf("2600 cartridge required for %s mode", md)
|
||||
return fmt.Errorf("2600 cartridge required")
|
||||
case 1:
|
||||
attr := disassembly.ColumnAttr{
|
||||
ByteCode: *bytecode,
|
||||
ByteCode: bytecode,
|
||||
Label: true,
|
||||
Cycles: true,
|
||||
}
|
||||
|
||||
cartload, err := cartridgeloader.NewLoader(md.GetArg(0), *mapping)
|
||||
cartload, err := cartridgeloader.NewLoader(args[0], mapping)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -494,68 +495,76 @@ func disasm(md *modalflag.Modes) error {
|
|||
// print what disassembly output we do have
|
||||
if dsm != nil {
|
||||
// ignore any further errors
|
||||
_ = dsm.Write(md.Output, attr)
|
||||
_ = dsm.Write(os.Stdout, attr)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// output entire disassembly or just a specific bank
|
||||
if *bank < 0 {
|
||||
err = dsm.Write(md.Output, attr)
|
||||
if bank < 0 {
|
||||
err = dsm.Write(os.Stdout, attr)
|
||||
} else {
|
||||
err = dsm.WriteBank(md.Output, attr, *bank)
|
||||
err = dsm.WriteBank(os.Stdout, attr, bank)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("too many arguments for %s mode", md)
|
||||
return fmt.Errorf("too many arguments")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func perform(md *modalflag.Modes, sync *mainSync) error {
|
||||
md.NewMode()
|
||||
func perform(mode string, sync *mainSync, args []string) error {
|
||||
var mapping string
|
||||
var spec string
|
||||
var uncapped bool
|
||||
var duration string
|
||||
var profile string
|
||||
var log bool
|
||||
|
||||
mapping := md.AddString("mapping", "AUTO", "force use of cartridge mapping")
|
||||
spec := md.AddString("tv", "AUTO", "television specification: NTSC, PAL, PAL60, SECAM")
|
||||
uncapped := md.AddBool("uncapped", true, "run perfomance with no FPS cap")
|
||||
duration := md.AddString("duration", "5s", "run duration (note: there is a 2s overhead)")
|
||||
profile := md.AddString("profile", "NONE", "run performance check with profiling: command separated CPU, MEM, TRACE or ALL")
|
||||
log := md.AddBool("log", false, "echo debugging log to stdout")
|
||||
flgs := flag.NewFlagSet(mode, flag.ExitOnError)
|
||||
flgs.StringVar(&mapping, "mapping", "AUTO", "form cartridge mapper selection")
|
||||
flgs.StringVar(&spec, "spec", "AUTO", "television specification: NTSC, PAL, PAL60, SECAM")
|
||||
flgs.BoolVar(&uncapped, "uncapped", true, "run performance no FPS cap")
|
||||
flgs.StringVar(&duration, "duration", "5s", "run duation (with an additional 2s overhead)")
|
||||
flgs.StringVar(&profile, "profile", "none", "run performance check with profiling: CPU, MEM, TRACE, ALL (comma sep)")
|
||||
flgs.BoolVar(&log, "log", false, "echo debugging log to stdout")
|
||||
|
||||
p, err := md.Parse()
|
||||
if err != nil || p != modalflag.ParseContinue {
|
||||
// parse args and get copy of remaining arguments
|
||||
err := flgs.Parse(args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
args = flgs.Args()
|
||||
|
||||
// set debugging log echo
|
||||
if *log {
|
||||
if log {
|
||||
logger.SetEcho(os.Stdout, true)
|
||||
} else {
|
||||
logger.SetEcho(nil, false)
|
||||
}
|
||||
|
||||
switch len(md.RemainingArgs()) {
|
||||
switch len(args) {
|
||||
case 0:
|
||||
return fmt.Errorf("2600 cartridge required for %s mode", md)
|
||||
return fmt.Errorf("2600 cartridge required")
|
||||
case 1:
|
||||
cartload, err := cartridgeloader.NewLoader(md.GetArg(0), *mapping)
|
||||
cartload, err := cartridgeloader.NewLoader(args[0], mapping)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cartload.Close()
|
||||
|
||||
// check for profiling options
|
||||
p, err := performance.ParseProfileString(*profile)
|
||||
p, err := performance.ParseProfileString(profile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// run performance check
|
||||
err = performance.Check(md.Output, p, cartload, *spec, *uncapped, *duration)
|
||||
err = performance.Check(os.Stdout, p, cartload, spec, uncapped, duration)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -564,147 +573,145 @@ func perform(md *modalflag.Modes, sync *mainSync) error {
|
|||
// changes to the performance window impacting the play mode
|
||||
|
||||
default:
|
||||
return fmt.Errorf("too many arguments for %s mode", md)
|
||||
return fmt.Errorf("too many arguments")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func regress(md *modalflag.Modes, sync *mainSync) error {
|
||||
md.NewMode()
|
||||
md.AddSubModes("RUN", "LIST", "DELETE", "ADD", "REDUX", "CLEANUP")
|
||||
func regress(mode string, args []string) error {
|
||||
var subMode string
|
||||
|
||||
p, err := md.Parse()
|
||||
if err != nil || p != modalflag.ParseContinue {
|
||||
if len(args) > 0 {
|
||||
subMode = strings.ToUpper(args[0])
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
switch subMode {
|
||||
default:
|
||||
err = regressRun(fmt.Sprintf("%s %s", mode, "RUN"), args)
|
||||
case "RUN":
|
||||
err = regressRun(fmt.Sprintf("%s %s", mode, subMode), args[1:])
|
||||
case "LIST":
|
||||
err = regressList(fmt.Sprintf("%s %s", mode, subMode), args[1:])
|
||||
case "DELETE":
|
||||
err = regressDelete(fmt.Sprintf("%s %s", mode, subMode), args[1:])
|
||||
case "ADD":
|
||||
err = regressAdd(fmt.Sprintf("%s %s", mode, subMode), args[1:])
|
||||
case "REDUX":
|
||||
err = regressRedux(fmt.Sprintf("%s %s", mode, subMode), args[1:])
|
||||
case "CLEANUP":
|
||||
err = regressCleanup(fmt.Sprintf("%s %s", mode, subMode), args[1:])
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch md.Mode() {
|
||||
case "RUN":
|
||||
md.NewMode()
|
||||
return nil
|
||||
}
|
||||
|
||||
// no additional arguments
|
||||
verbose := md.AddBool("verbose", false, "output more detail (eg. error messages)")
|
||||
func regressRun(mode string, args []string) error {
|
||||
var verbose bool
|
||||
|
||||
p, err := md.Parse()
|
||||
if err != nil || p != modalflag.ParseContinue {
|
||||
return err
|
||||
}
|
||||
flgs := flag.NewFlagSet(mode, flag.ExitOnError)
|
||||
flgs.BoolVar(&verbose, "v", false, "output more detail")
|
||||
|
||||
err = regression.RegressRun(md.Output, *verbose, md.RemainingArgs())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// parse args and get copy of remaining arguments
|
||||
err := flgs.Parse(args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
args = flgs.Args()
|
||||
|
||||
case "LIST":
|
||||
md.NewMode()
|
||||
|
||||
// no additional arguments
|
||||
|
||||
p, err := md.Parse()
|
||||
if err != nil || p != modalflag.ParseContinue {
|
||||
return err
|
||||
}
|
||||
|
||||
switch len(md.RemainingArgs()) {
|
||||
case 0:
|
||||
err := regression.RegressList(md.Output)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("no additional arguments required for %s mode", md)
|
||||
}
|
||||
|
||||
case "DELETE":
|
||||
md.NewMode()
|
||||
|
||||
answerYes := md.AddBool("yes", false, "answer yes to confirmation")
|
||||
|
||||
p, err := md.Parse()
|
||||
if err != nil || p != modalflag.ParseContinue {
|
||||
return err
|
||||
}
|
||||
|
||||
switch len(md.RemainingArgs()) {
|
||||
case 0:
|
||||
return fmt.Errorf("database key required for %s mode", md)
|
||||
case 1:
|
||||
|
||||
// use stdin for confirmation unless "yes" flag has been sent
|
||||
var confirmation io.Reader
|
||||
if *answerYes {
|
||||
confirmation = &yesReader{}
|
||||
} else {
|
||||
confirmation = os.Stdin
|
||||
}
|
||||
|
||||
err := regression.RegressDelete(md.Output, confirmation, md.GetArg(0))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("only one entry can be deleted at at time")
|
||||
}
|
||||
|
||||
case "ADD":
|
||||
return regressAdd(md)
|
||||
|
||||
case "REDUX":
|
||||
md.NewMode()
|
||||
|
||||
answerYes := md.AddBool("yes", false, "always answer yes to confirmation")
|
||||
|
||||
p, err := md.Parse()
|
||||
if err != nil || p != modalflag.ParseContinue {
|
||||
return err
|
||||
}
|
||||
|
||||
var confirmation io.Reader
|
||||
if *answerYes {
|
||||
confirmation = &yesReader{}
|
||||
} else {
|
||||
confirmation = os.Stdin
|
||||
}
|
||||
|
||||
return regression.RegressRedux(md.Output, confirmation)
|
||||
|
||||
case "CLEANUP":
|
||||
md.NewMode()
|
||||
|
||||
answerYes := md.AddBool("yes", false, "always answer yes to confirmation")
|
||||
|
||||
p, err := md.Parse()
|
||||
if err != nil || p != modalflag.ParseContinue {
|
||||
return err
|
||||
}
|
||||
|
||||
var confirmation io.Reader
|
||||
if *answerYes {
|
||||
confirmation = &yesReader{}
|
||||
} else {
|
||||
confirmation = os.Stdin
|
||||
}
|
||||
|
||||
return regression.RegressCleanup(md.Output, confirmation)
|
||||
err = regression.RegressRun(os.Stdout, verbose, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func regressAdd(md *modalflag.Modes) error {
|
||||
md.NewMode()
|
||||
func regressList(mode string, args []string) error {
|
||||
flgs := flag.NewFlagSet(mode, flag.ExitOnError)
|
||||
|
||||
mode := md.AddString("mode", "", "type of regression entry")
|
||||
notes := md.AddString("notes", "", "additional annotation for the database")
|
||||
mapping := md.AddString("mapping", "AUTO", "force use of cartridge mapping [non-playback]")
|
||||
spec := md.AddString("tv", "AUTO", "television specification: NTSC, PAL, PAL60, SECAM [non-playback]")
|
||||
numframes := md.AddInt("frames", 10, "number of frames to run [non-playback]")
|
||||
state := md.AddString("state", "", "record emulator state at every CPU step [non-playback]")
|
||||
log := md.AddBool("log", false, "echo debugging log to stdout")
|
||||
// parse args and get copy of remaining arguments
|
||||
err := flgs.Parse(args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
args = flgs.Args()
|
||||
|
||||
md.AdditionalHelp(
|
||||
`The regression test to be added can be the path to a cartridge file or a previously
|
||||
err = regression.RegressList(os.Stdout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func regressDelete(mode string, args []string) error {
|
||||
var yes bool
|
||||
|
||||
flgs := flag.NewFlagSet(mode, flag.ExitOnError)
|
||||
flgs.BoolVar(&yes, "yes", false, "answer yes to confirmation request")
|
||||
|
||||
// parse args and get copy of remaining arguments
|
||||
err := flgs.Parse(args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
args = flgs.Args()
|
||||
|
||||
switch len(args) {
|
||||
case 0:
|
||||
return fmt.Errorf("database key required")
|
||||
case 1:
|
||||
|
||||
// use stdin for confirmation unless "yes" flag has been sent
|
||||
var confirmation io.Reader
|
||||
if yes {
|
||||
confirmation = &yesReader{}
|
||||
} else {
|
||||
confirmation = os.Stdin
|
||||
}
|
||||
|
||||
err := regression.RegressDelete(os.Stdout, confirmation, args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("only one entry can be deleted at at time")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func regressAdd(mode string, args []string) error {
|
||||
var regressMode string
|
||||
var notes string
|
||||
var mapping string
|
||||
var spec string
|
||||
var numFrames int
|
||||
var state string
|
||||
var log bool
|
||||
|
||||
flgs := flag.NewFlagSet(mode, flag.ContinueOnError)
|
||||
flgs.StringVar(®ressMode, "mode", "", "type of regression entry")
|
||||
flgs.StringVar(¬es, "notes", "", "additional annotation for the entry")
|
||||
flgs.StringVar(&mapping, "mapping", "AUTO", "form cartridge mapper selection")
|
||||
flgs.StringVar(&spec, "spec", "AUTO", "television specification: NTSC, PAL, PAL60, SECAM")
|
||||
flgs.IntVar(&numFrames, "frames", 10, "number of frames to run (ignored if mode is 'playback'")
|
||||
flgs.StringVar(&state, "state", "", "record emulator state at every CPU step (ignored if mode is 'playback'")
|
||||
flgs.BoolVar(&log, "log", false, "echo debugging log to stdout")
|
||||
|
||||
// parse args and get copy of remaining arguments
|
||||
err := flgs.Parse(args)
|
||||
if err != nil {
|
||||
if err == flag.ErrHelp {
|
||||
fmt.Println()
|
||||
fmt.Println(`The regression test to be added can be the path to a cartridge file or a previously
|
||||
recorded playback file. For playback files, the flags marked [non-playback] do not make
|
||||
sense and will be ignored.
|
||||
|
||||
|
@ -716,84 +723,78 @@ with the default VIDEO mode.
|
|||
|
||||
The -log flag intructs the program to echo the log to the console. Do not confuse this
|
||||
with the LOG mode. Note that asking for log output will suppress regression progress meters.`)
|
||||
|
||||
p, err := md.Parse()
|
||||
if err != nil || p != modalflag.ParseContinue {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
args = flgs.Args()
|
||||
|
||||
// set debugging log echo
|
||||
if *log {
|
||||
if log {
|
||||
logger.SetEcho(os.Stdout, true)
|
||||
md.Output = &nopWriter{}
|
||||
} else {
|
||||
logger.SetEcho(nil, false)
|
||||
}
|
||||
|
||||
switch len(md.RemainingArgs()) {
|
||||
switch len(args) {
|
||||
case 0:
|
||||
return fmt.Errorf("2600 cartridge or playback file required for %s mode", md)
|
||||
return fmt.Errorf("2600 cartridge or playback file required")
|
||||
case 1:
|
||||
var reg regression.Regressor
|
||||
var regressor regression.Regressor
|
||||
|
||||
if *mode == "" {
|
||||
if err := recorder.IsPlaybackFile(md.GetArg(0)); err == nil {
|
||||
*mode = "PLAYBACK"
|
||||
if regressMode == "" {
|
||||
if err := recorder.IsPlaybackFile(args[0]); err == nil {
|
||||
regressMode = "PLAYBACK"
|
||||
} else if !errors.Is(err, recorder.NotAPlaybackFile) {
|
||||
return err
|
||||
} else {
|
||||
*mode = "VIDEO"
|
||||
regressMode = "VIDEO"
|
||||
}
|
||||
}
|
||||
|
||||
switch strings.ToUpper(*mode) {
|
||||
switch strings.ToUpper(regressMode) {
|
||||
case "VIDEO":
|
||||
cartload, err := cartridgeloader.NewLoader(md.GetArg(0), *mapping)
|
||||
cartload, err := cartridgeloader.NewLoader(args[0], mapping)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cartload.Close()
|
||||
|
||||
statetype, err := regression.NewStateType(*state)
|
||||
statetype, err := regression.NewStateType(state)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
reg = ®ression.VideoRegression{
|
||||
regressor = ®ression.VideoRegression{
|
||||
CartLoad: cartload,
|
||||
TVtype: strings.ToUpper(*spec),
|
||||
NumFrames: *numframes,
|
||||
TVtype: strings.ToUpper(spec),
|
||||
NumFrames: numFrames,
|
||||
State: statetype,
|
||||
Notes: *notes,
|
||||
Notes: notes,
|
||||
}
|
||||
case "PLAYBACK":
|
||||
// check and warn if unneeded arguments have been specified
|
||||
md.Visit(func(flg string) {
|
||||
if flg == "frames" {
|
||||
fmt.Printf("! ignored %s flag when adding playback entry\n", flg)
|
||||
}
|
||||
})
|
||||
|
||||
reg = ®ression.PlaybackRegression{
|
||||
Script: md.GetArg(0),
|
||||
Notes: *notes,
|
||||
regressor = ®ression.PlaybackRegression{
|
||||
Script: args[0],
|
||||
Notes: notes,
|
||||
}
|
||||
case "LOG":
|
||||
cartload, err := cartridgeloader.NewLoader(md.GetArg(0), *mapping)
|
||||
cartload, err := cartridgeloader.NewLoader(args[0], mapping)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cartload.Close()
|
||||
|
||||
reg = ®ression.LogRegression{
|
||||
regressor = ®ression.LogRegression{
|
||||
CartLoad: cartload,
|
||||
TVtype: strings.ToUpper(*spec),
|
||||
NumFrames: *numframes,
|
||||
Notes: *notes,
|
||||
TVtype: strings.ToUpper(spec),
|
||||
NumFrames: numFrames,
|
||||
Notes: notes,
|
||||
}
|
||||
}
|
||||
|
||||
err := regression.RegressAdd(md.Output, reg)
|
||||
err := regression.RegressAdd(os.Stdout, regressor)
|
||||
if err != nil {
|
||||
// using carriage return (without newline) at beginning of error
|
||||
// message because we want to overwrite the last output from
|
||||
|
@ -807,19 +808,61 @@ with the LOG mode. Note that asking for log output will suppress regression prog
|
|||
return nil
|
||||
}
|
||||
|
||||
func showVersion(md *modalflag.Modes) error {
|
||||
md.NewMode()
|
||||
revision := md.AddBool("revision", false,
|
||||
"display revision information from version control system (if available)")
|
||||
func regressRedux(mode string, args []string) error {
|
||||
var yes bool
|
||||
|
||||
p, err := md.Parse()
|
||||
if err != nil || p != modalflag.ParseContinue {
|
||||
flgs := flag.NewFlagSet(mode, flag.ExitOnError)
|
||||
flgs.BoolVar(&yes, "yes", false, "answer yes to confirmation request")
|
||||
|
||||
// parse args and get copy of remaining arguments
|
||||
err := flgs.Parse(args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
args = flgs.Args()
|
||||
|
||||
var confirmation io.Reader
|
||||
if yes {
|
||||
confirmation = &yesReader{}
|
||||
} else {
|
||||
confirmation = os.Stdin
|
||||
}
|
||||
|
||||
return regression.RegressRedux(os.Stdout, confirmation)
|
||||
}
|
||||
|
||||
func regressCleanup(mode string, args []string) error {
|
||||
var yes bool
|
||||
|
||||
flgs := flag.NewFlagSet(mode, flag.ExitOnError)
|
||||
flgs.BoolVar(&yes, "yes", false, "answer yes to confirmation request")
|
||||
|
||||
// parse args and get copy of remaining arguments
|
||||
err := flgs.Parse(args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
args = flgs.Args()
|
||||
|
||||
var confirmation io.Reader
|
||||
if yes {
|
||||
confirmation = &yesReader{}
|
||||
} else {
|
||||
confirmation = os.Stdin
|
||||
}
|
||||
|
||||
return regression.RegressCleanup(os.Stdout, confirmation)
|
||||
}
|
||||
|
||||
func showVersion(mode string, args []string) error {
|
||||
var revision bool
|
||||
|
||||
flgs := flag.NewFlagSet(mode, flag.ExitOnError)
|
||||
flgs.BoolVar(&revision, "v", false, "display revision information (if available")
|
||||
flgs.Parse(args)
|
||||
|
||||
fmt.Println(version.Version)
|
||||
|
||||
if *revision {
|
||||
if revision {
|
||||
fmt.Println(version.Revision)
|
||||
}
|
||||
|
||||
|
|
135
modalflag/doc.go
135
modalflag/doc.go
|
@ -1,135 +0,0 @@
|
|||
// This file is part of Gopher2600.
|
||||
//
|
||||
// Gopher2600 is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Gopher2600 is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Gopher2600. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// Package modalflag is a wrapper for the flag package in the Go standard
|
||||
// library. It provides a convenient method of handling program modes (and
|
||||
// sub-modes) and allows different flags for each mode.
|
||||
//
|
||||
// At it's simplest it can be used as a replacement for the flag package, with
|
||||
// some differences. Whereas, with flag.FlagSet you call Parse() with the array of
|
||||
// strings as the only argument, with modalflag you first NewArgs() with the
|
||||
// array of arguments and then Parse() with no arguments. For example (note
|
||||
// that no error handling of the Parse() function is shown here):
|
||||
//
|
||||
// md = Modes{Output: os.Stdout}
|
||||
// md.NewArgs(os.Args[1:])
|
||||
// _, _ = md.Parse()
|
||||
//
|
||||
// The reason for his difference is to allow effective parsing of modes and
|
||||
// sub-modes. We'll come to program modes in a short while.
|
||||
//
|
||||
// In the above example, once the arguments have been parsed, non-flag arguments
|
||||
// can be retrieved with the RemainingArgs() or GetArg() function. For example,
|
||||
// handling exactly one argument:
|
||||
//
|
||||
// switch len(md.RemainingArgs()) {
|
||||
// case 0:
|
||||
// return fmt.Errorf("argument required")
|
||||
// case 1:
|
||||
// Process(md.GetArg(0))
|
||||
// default:
|
||||
// return fmt.Errorf("too many arguments")
|
||||
// }
|
||||
//
|
||||
// Adding flags is similar to the flag package. Adding a boolean flag:
|
||||
//
|
||||
// verbose := md.AddBool("verbose", false, "print additional log messages")
|
||||
//
|
||||
// These flag functions return a pointer to a variable of the specified type. The
|
||||
// initial value of these variables if the default value, the second argument in
|
||||
// the function call above. The Parse() function will set these values
|
||||
// appropriately according what the user has requested, for example:
|
||||
//
|
||||
// if *verbose {
|
||||
// fmt.Println(additionalLogMessage)
|
||||
// }
|
||||
//
|
||||
// The most important difference between the standard flag package and the
|
||||
// modalflag package is the ability of the latter to handle "modes". In this
|
||||
// context, a mode is a special command line argument that when specified, puts
|
||||
// the program into a different mode of operation. The best and most relevant
|
||||
// example I can think of is the go command. The go command has many different
|
||||
// modes: build, doc, get, test, etc. Each of these modes are different enough
|
||||
// to require a different set of flags and expected arguments.
|
||||
//
|
||||
// The modalflag package handles sub-modes with the AddSubModes() function.
|
||||
// This function takes any number of string arguments, each one the name of a
|
||||
// mode.
|
||||
//
|
||||
// md.AddSubModes("run", "test", "debug")
|
||||
//
|
||||
// For simplicity, all sub-mode comparisons are case insensitive.
|
||||
//
|
||||
// Subsequent calls to Parse() will then process flags in the normal way but
|
||||
// unlike the regular flag.Parse() function will check to see if the first
|
||||
// argument after the flags is one of these modes. If it is, then the
|
||||
// RemainingArgs() function will return all the arguments after the flags AND
|
||||
// the mode selector.
|
||||
//
|
||||
// So, for example:
|
||||
//
|
||||
// md.Parse()
|
||||
// switch md.Mode() {
|
||||
// case "RUN":
|
||||
// runMode(*verbose)
|
||||
// default:
|
||||
// fmt.Printf("%s not yet implemented", md.Mode())
|
||||
// }
|
||||
//
|
||||
// Now that we've decided on what mode we're in, we can again call Parse() to
|
||||
// process the remaining arguments. This example shows how we can handle return
|
||||
// state and errors from the Parse() function:
|
||||
//
|
||||
// func runMode(verbose bool) {
|
||||
// md.NewMode()
|
||||
// md.AddDuration("runtime", time.ParseDuration("10s"), "max run time")
|
||||
// p, err := md.Parse
|
||||
// switch p {
|
||||
// case ParseError:
|
||||
// fmt.Println(err)
|
||||
// return
|
||||
// case ParseHelp:
|
||||
// return
|
||||
// }
|
||||
// doRun(md.RemainingArguments)
|
||||
// }
|
||||
//
|
||||
// This second call to Parse() will check for any additional flags and any
|
||||
// further sub-modes (none in this example).
|
||||
//
|
||||
// We can chain modes together as deep as we want. For example, the "test" mode
|
||||
// added above could be divided into several different modes:
|
||||
//
|
||||
// md = Modes{Output: os.Stdout}
|
||||
// md.NewArgs(os.Args[1:])
|
||||
// md.AddSubModes("run", "test", "debug")
|
||||
// _, _ = md.Parse()
|
||||
// switch md.Mode() {
|
||||
// case "TEST":
|
||||
// md.NewMode()
|
||||
// md.AddSubModes("A", "B", "C")
|
||||
// _, _ = md.Parse()
|
||||
// switch md.Mode() {
|
||||
// case "A":
|
||||
// testA()
|
||||
// case "B":
|
||||
// testB()
|
||||
// case "C":
|
||||
// testC()
|
||||
// }
|
||||
// default:
|
||||
// fmt.Printf("%s not yet implemented", md.Mode())
|
||||
// }
|
||||
package modalflag
|
|
@ -1,93 +0,0 @@
|
|||
// This file is part of Gopher2600.
|
||||
//
|
||||
// Gopher2600 is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Gopher2600 is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Gopher2600. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package modalflag
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// helpWriter is used to amend the default output from the flag package.
|
||||
type helpWriter struct {
|
||||
// the last []byte sent to the Write() function
|
||||
buffer []byte
|
||||
}
|
||||
|
||||
// Clear contents of output buffer.
|
||||
func (hw *helpWriter) Clear() {
|
||||
hw.buffer = []byte{}
|
||||
}
|
||||
|
||||
func (hw *helpWriter) Help(output io.Writer, banner string, subModes []string, additionalHelp string) {
|
||||
s := string(hw.buffer)
|
||||
helpLines := strings.Split(s, "\n")
|
||||
|
||||
// output "no help available" message if there is no flag information and no
|
||||
// sub-modes
|
||||
if s == "Usage:\n" && len(subModes) == 0 {
|
||||
if additionalHelp != "" {
|
||||
output.Write([]byte(additionalHelp))
|
||||
output.Write([]byte("\n"))
|
||||
return
|
||||
}
|
||||
|
||||
output.Write([]byte("No help available"))
|
||||
if banner != "" {
|
||||
output.Write([]byte(fmt.Sprintf(" for %s", banner)))
|
||||
}
|
||||
output.Write([]byte("\n"))
|
||||
return
|
||||
}
|
||||
|
||||
if banner != "" {
|
||||
// supplement default banner with additional string
|
||||
output.Write([]byte(fmt.Sprintf("%s for %s mode\n", helpLines[0], banner)))
|
||||
} else {
|
||||
// there is no banner so just print the default flag package banner
|
||||
output.Write([]byte(helpLines[0]))
|
||||
output.Write([]byte("\n"))
|
||||
}
|
||||
|
||||
// add help message produced by flag package
|
||||
if len(helpLines) > 1 {
|
||||
s := strings.Join(helpLines[1:], "\n")
|
||||
output.Write([]byte(s))
|
||||
}
|
||||
|
||||
// add sub-mode information
|
||||
if len(subModes) > 0 {
|
||||
// add an additional new line if we've already printed flag information
|
||||
if len(helpLines) > 2 {
|
||||
output.Write([]byte("\n"))
|
||||
}
|
||||
|
||||
output.Write([]byte(fmt.Sprintf(" available sub-modes: %s\n", strings.Join(subModes, ", "))))
|
||||
output.Write([]byte(fmt.Sprintf(" default: %s\n", subModes[0])))
|
||||
}
|
||||
|
||||
if additionalHelp != "" {
|
||||
output.Write([]byte("\n"))
|
||||
output.Write([]byte(additionalHelp))
|
||||
output.Write([]byte("\n"))
|
||||
}
|
||||
}
|
||||
|
||||
// Write buffers all output.
|
||||
func (hw *helpWriter) Write(p []byte) (n int, err error) {
|
||||
hw.buffer = append(hw.buffer, p...)
|
||||
return len(p), nil
|
||||
}
|
|
@ -1,296 +0,0 @@
|
|||
// This file is part of Gopher2600.
|
||||
//
|
||||
// Gopher2600 is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Gopher2600 is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Gopher2600. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package modalflag
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const modeSeparator = "/"
|
||||
|
||||
// Modes provides an easy way of handling command line arguments. The Output
|
||||
// field should be specified before calling Parse() or you will not see any
|
||||
// help messages.
|
||||
type Modes struct {
|
||||
// where to print output (help messages etc). defaults to os.Stdout
|
||||
Output io.Writer
|
||||
|
||||
// whether Parse() has been called recently
|
||||
parsed bool
|
||||
|
||||
// the underlying flag structure. this can be used directly as described by
|
||||
// the flag.FlagSet documentation. the only thing you shouldn't do is call
|
||||
// Parse() directly. Use the Parse() function of the parent Modes struct
|
||||
// instead.
|
||||
//
|
||||
// a new flagset is created on every call to NewArgs() and NewMode()
|
||||
flags *flag.FlagSet
|
||||
|
||||
// the argument list as specified by the NewArgs() function
|
||||
args []string
|
||||
argsIdx int
|
||||
|
||||
// the most recent list of sub-modes specified with the NewMode() function
|
||||
subModes []string
|
||||
|
||||
// path is the series of sub-modes that have been found during subsequent
|
||||
// calls to Parse()
|
||||
//
|
||||
// we never reset this variable
|
||||
path []string
|
||||
|
||||
// some modes will benefit from a verbose explanation. use
|
||||
additionalHelp string
|
||||
|
||||
minRemainingArgs int
|
||||
maxRemainingArgs int
|
||||
}
|
||||
|
||||
func (md *Modes) String() string {
|
||||
return md.Path()
|
||||
}
|
||||
|
||||
// Mode returns the last mode to be encountered.
|
||||
func (md *Modes) Mode() string {
|
||||
if len(md.path) == 0 {
|
||||
return ""
|
||||
}
|
||||
return md.path[len(md.path)-1]
|
||||
}
|
||||
|
||||
// Path returns a string all the modes encountered during parsing.
|
||||
func (md *Modes) Path() string {
|
||||
return strings.Join(md.path, modeSeparator)
|
||||
}
|
||||
|
||||
// NewArgs with a string of arguments (from the command line for example).
|
||||
func (md *Modes) NewArgs(args []string) {
|
||||
// initialise args
|
||||
md.args = args
|
||||
md.argsIdx = 0
|
||||
|
||||
// by definition, a newly initialised Modes struct begins with a new mode
|
||||
md.NewMode()
|
||||
}
|
||||
|
||||
// NewMode indicates that further arguments should be considered part of a new
|
||||
// mode.
|
||||
func (md *Modes) NewMode() {
|
||||
md.subModes = []string{}
|
||||
md.flags = flag.NewFlagSet("", flag.ContinueOnError)
|
||||
md.parsed = false
|
||||
}
|
||||
|
||||
// AdditionalHelp allows you to add extensive help text to be displayed in
|
||||
// addition to the regular help on available flags.
|
||||
func (md *Modes) AdditionalHelp(help string) {
|
||||
md.additionalHelp = help
|
||||
}
|
||||
|
||||
// MinMax specifies the minimum and maximum number of non-flags arguments that
|
||||
// are required.
|
||||
func (md *Modes) MinMax(min int, max int) {
|
||||
md.minRemainingArgs = min
|
||||
md.maxRemainingArgs = max
|
||||
}
|
||||
|
||||
// Parsed returns false if Parse() has not yet been called since either a call
|
||||
// to NewArgs() or NewMode(). Note that, a Modes struct is considered to be
|
||||
// Parsed() even if Parse() results in an error.
|
||||
func (md *Modes) Parsed() bool {
|
||||
return md.parsed
|
||||
}
|
||||
|
||||
// ParseResult is returned from the Parse() function.
|
||||
type ParseResult int
|
||||
|
||||
// a list of valid ParseResult values.
|
||||
const (
|
||||
// Continue with command line processing. How this result should be
|
||||
// interpreted depends on the context, which the caller of the Parse()
|
||||
// function knows best. However, generally we can say that if sub-modes
|
||||
// were specified in the preceding call to NewMode() then the Mode field
|
||||
// of the Modes struct should be checked.
|
||||
ParseContinue ParseResult = iota
|
||||
|
||||
// The wrong number of non-flag arguments have been specified.
|
||||
ParseTooFewArgs
|
||||
ParseTooManyArgs
|
||||
|
||||
// Help was requested and has been printed.
|
||||
ParseHelp
|
||||
|
||||
// an error has occurred and is returned as the second return value.
|
||||
ParseError
|
||||
)
|
||||
|
||||
// Parse the top level layer of arguments. Returns a value of ParseResult.
|
||||
// The idiomatic usage is as follows:
|
||||
//
|
||||
// p, err := md.Parse()
|
||||
// switch p {
|
||||
// case modalflag.ParseHelp:
|
||||
// // help message has already been printed
|
||||
// return
|
||||
// case modalflag.ParseError:
|
||||
// printError(err)
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// Help messages are handled automatically by the function. The return value
|
||||
// ParseHelp is to help you guide your program appropriately. The above pattern
|
||||
// suggests it should be treated similarly to an error and without the need to
|
||||
// display anything further to the user.
|
||||
//
|
||||
// Note that the Output field of the Modes struct *must* be specified in order
|
||||
// for any help messages to be visible. The most common and useful value of the
|
||||
// field is os.Stdout.
|
||||
func (md *Modes) Parse() (ParseResult, error) {
|
||||
// flag the parsed flag in all instances, even if we eventually return an
|
||||
// error
|
||||
md.parsed = true
|
||||
|
||||
// set output of flags.Parse() to an instance of helpWriter
|
||||
hw := &helpWriter{}
|
||||
md.flags.SetOutput(hw)
|
||||
|
||||
// parse arguments
|
||||
err := md.flags.Parse(md.args[md.argsIdx:])
|
||||
if err != nil {
|
||||
if err == flag.ErrHelp {
|
||||
hw.Help(md.Output, md.Path(), md.subModes, md.additionalHelp)
|
||||
hw.Clear()
|
||||
return ParseHelp, nil
|
||||
}
|
||||
|
||||
// flags have been set that are not recognised. if sub-modes and a
|
||||
// default mode have been defined, set selected mode to default mode
|
||||
// and continue. otherwise return error
|
||||
if len(md.subModes) > 0 {
|
||||
md.path = append(md.path, md.subModes[0])
|
||||
} else {
|
||||
return ParseError, err
|
||||
}
|
||||
} else if len(md.subModes) > 0 {
|
||||
arg := strings.ToUpper(md.flags.Arg(0))
|
||||
|
||||
// check to see if the single argument is in the list of modes,
|
||||
// starting off assuming it isn't
|
||||
mode := md.subModes[0]
|
||||
for i := range md.subModes {
|
||||
if md.subModes[i] == arg {
|
||||
// found matching sub-mode
|
||||
mode = arg
|
||||
md.argsIdx++
|
||||
break // for loop
|
||||
}
|
||||
}
|
||||
|
||||
// add mode (either one we've found or the default) and add it to
|
||||
// the path
|
||||
md.path = append(md.path, mode)
|
||||
}
|
||||
|
||||
r := md.RemainingArgs()
|
||||
if md.minRemainingArgs > 0 && len(r) < md.minRemainingArgs {
|
||||
return ParseTooFewArgs, nil
|
||||
}
|
||||
|
||||
if md.maxRemainingArgs > 0 && len(r) > md.maxRemainingArgs {
|
||||
return ParseTooManyArgs, nil
|
||||
}
|
||||
|
||||
return ParseContinue, nil
|
||||
}
|
||||
|
||||
// RemainingArgs after a call to Parse() ie. arguments that aren't flags or a
|
||||
// listed sub-mode.
|
||||
func (md *Modes) RemainingArgs() []string {
|
||||
return md.flags.Args()
|
||||
}
|
||||
|
||||
// GetArg returns the numbered argument that isn't a flag or listed sub-mode.
|
||||
func (md *Modes) GetArg(i int) string {
|
||||
return md.flags.Arg(i)
|
||||
}
|
||||
|
||||
// AddSubModes to list of submodes for next parse. The first sub-mode in the
|
||||
// list is considered to be the default sub-mode. If you need more control over
|
||||
// this, AddDefaultSubMode() can be used.
|
||||
//
|
||||
// Note that sub-mode comparisons are case insensitive.
|
||||
func (md *Modes) AddSubModes(submodes ...string) {
|
||||
md.subModes = append(md.subModes, submodes...)
|
||||
for i := range md.subModes {
|
||||
md.subModes[i] = strings.ToUpper(md.subModes[i])
|
||||
}
|
||||
}
|
||||
|
||||
// AddDefaultSubMode to list of sub-modes.
|
||||
func (md *Modes) AddDefaultSubMode(defSubMode string) {
|
||||
md.subModes = append([]string{defSubMode}, md.subModes...)
|
||||
}
|
||||
|
||||
// AddBool flag for next call to Parse().
|
||||
func (md *Modes) AddBool(name string, value bool, usage string) *bool {
|
||||
return md.flags.Bool(name, value, usage)
|
||||
}
|
||||
|
||||
// AddDuration flag for next call to Parse().
|
||||
func (md *Modes) AddDuration(name string, value time.Duration, usage string) *time.Duration {
|
||||
return md.flags.Duration(name, value, usage)
|
||||
}
|
||||
|
||||
// AddFloat64 flag for next call to Parse().
|
||||
func (md *Modes) AddFloat64(name string, value float64, usage string) *float64 {
|
||||
return md.flags.Float64(name, value, usage)
|
||||
}
|
||||
|
||||
// AddInt flag for next call to Parse().
|
||||
func (md *Modes) AddInt(name string, value int, usage string) *int {
|
||||
return md.flags.Int(name, value, usage)
|
||||
}
|
||||
|
||||
// AddInt64 flag for next call to Parse().
|
||||
func (md *Modes) AddInt64(name string, value int64, usage string) *int64 {
|
||||
return md.flags.Int64(name, value, usage)
|
||||
}
|
||||
|
||||
// AddString flag for next call to Parse().
|
||||
func (md *Modes) AddString(name string, value string, usage string) *string {
|
||||
return md.flags.String(name, value, usage)
|
||||
}
|
||||
|
||||
// AddUint flag for next call to Parse().
|
||||
func (md *Modes) AddUint(name string, value uint, usage string) *uint {
|
||||
return md.flags.Uint(name, value, usage)
|
||||
}
|
||||
|
||||
// AddUint64 flag for next call to Parse().
|
||||
func (md *Modes) AddUint64(name string, value uint64, usage string) *uint64 {
|
||||
return md.flags.Uint64(name, value, usage)
|
||||
}
|
||||
|
||||
// Visit visits the flags in lexicographical order, calling fn for each. It
|
||||
// visits only those flags that have been set.
|
||||
func (md *Modes) Visit(fn func(flag string)) {
|
||||
md.flags.Visit(func(f *flag.Flag) {
|
||||
fn(f.Name)
|
||||
})
|
||||
}
|
|
@ -1,158 +0,0 @@
|
|||
// This file is part of Gopher2600.
|
||||
//
|
||||
// Gopher2600 is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Gopher2600 is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Gopher2600. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package modalflag_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/jetsetilly/gopher2600/modalflag"
|
||||
"github.com/jetsetilly/gopher2600/test"
|
||||
)
|
||||
|
||||
func TestNoModesNoFlags(t *testing.T) {
|
||||
md := modalflag.Modes{Output: os.Stdout}
|
||||
md.NewArgs([]string{})
|
||||
|
||||
p, err := md.Parse()
|
||||
if p != modalflag.ParseContinue {
|
||||
t.Error("expected ParseContinue")
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("did not expect error: %s", err)
|
||||
}
|
||||
if md.Mode() != "" {
|
||||
t.Errorf("did not expect to see mode as result of Parse()")
|
||||
}
|
||||
if md.Path() != "" {
|
||||
t.Errorf("did not expect to see modes in mode path")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoModes(t *testing.T) {
|
||||
md := modalflag.Modes{Output: os.Stdout}
|
||||
md.NewArgs([]string{"-test", "1", "2"})
|
||||
testFlag := md.AddBool("test", false, "test flag")
|
||||
|
||||
if *testFlag != false {
|
||||
t.Error("expected *testFlag to be false before Parse()")
|
||||
}
|
||||
|
||||
p, err := md.Parse()
|
||||
if p != modalflag.ParseContinue {
|
||||
t.Error("expected ParseContinue")
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("did not expect error: %s", err)
|
||||
}
|
||||
if md.Mode() != "" {
|
||||
t.Errorf("did not expect to see mode as result of Parse()")
|
||||
}
|
||||
if md.Path() != "" {
|
||||
t.Errorf("did not expect to see modes in mode path")
|
||||
}
|
||||
|
||||
if *testFlag != true {
|
||||
t.Error("expected *testFlag to be true after Parse()")
|
||||
}
|
||||
|
||||
if len(md.RemainingArgs()) != 2 {
|
||||
t.Error("expected number of RemainingArgs() to be 2 after Parse()")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoHelpAvailable(t *testing.T) {
|
||||
tw := &test.Writer{}
|
||||
|
||||
md := modalflag.Modes{Output: tw}
|
||||
md.NewArgs([]string{"-help"})
|
||||
|
||||
p, _ := md.Parse()
|
||||
if p != modalflag.ParseHelp {
|
||||
t.Error("expected ParseHelp return value from Parse()")
|
||||
}
|
||||
|
||||
if !tw.Compare("No help available\n") {
|
||||
t.Error("unexpected help message (wanted 'No help available')")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHelpFlags(t *testing.T) {
|
||||
tw := &test.Writer{}
|
||||
|
||||
md := modalflag.Modes{Output: tw}
|
||||
md.NewArgs([]string{"-help"})
|
||||
md.AddBool("test", true, "test flag")
|
||||
|
||||
p, _ := md.Parse()
|
||||
if p != modalflag.ParseHelp {
|
||||
t.Error("expected ParseHelp return value from Parse()")
|
||||
}
|
||||
|
||||
expectedHelp := "Usage:\n" +
|
||||
" -test\n" +
|
||||
" test flag (default true)\n"
|
||||
|
||||
if !tw.Compare(expectedHelp) {
|
||||
t.Error("unexpected help message")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHelpModes(t *testing.T) {
|
||||
tw := &test.Writer{}
|
||||
|
||||
md := modalflag.Modes{Output: tw}
|
||||
md.NewArgs([]string{"-help"})
|
||||
md.AddSubModes("A", "B", "C")
|
||||
|
||||
p, _ := md.Parse()
|
||||
if p != modalflag.ParseHelp {
|
||||
t.Error("expected ParseHelp return value from Parse()")
|
||||
}
|
||||
|
||||
expectedHelp := "Usage:\n" +
|
||||
" available sub-modes: A, B, C\n" +
|
||||
" default: A\n"
|
||||
|
||||
if !tw.Compare(expectedHelp) {
|
||||
t.Error("unexpected help message")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHelpFlagsAndModes(t *testing.T) {
|
||||
tw := &test.Writer{}
|
||||
|
||||
md := modalflag.Modes{Output: tw}
|
||||
md.NewArgs([]string{"-help"})
|
||||
md.AddBool("test", true, "test flag")
|
||||
md.AddSubModes("A", "B", "C")
|
||||
|
||||
p, _ := md.Parse()
|
||||
if p != modalflag.ParseHelp {
|
||||
t.Error("expected ParseHelp return value from Parse()")
|
||||
}
|
||||
|
||||
expectedHelp := "Usage:\n" +
|
||||
" -test\n" +
|
||||
" test flag (default true)\n" +
|
||||
"\n" +
|
||||
" available sub-modes: A, B, C\n" +
|
||||
" default: A\n"
|
||||
|
||||
if !tw.Compare(expectedHelp) {
|
||||
t.Error("unexpected help message")
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue