diff --git a/README.md b/README.md index d6bbea0..16ac0de 100644 --- a/README.md +++ b/README.md @@ -188,8 +188,9 @@ Only valid on SDL mode ### Command line options + ```terminal -Usage: izapple [file] +Usage: izapple2 [file] file path to image to use on the boot device -charrom string @@ -200,12 +201,14 @@ Usage: izapple [file] force all letters to be uppercased (no need for caps lock!) -model string set base model (default "2enh") + -mods string + comma separated list of mods applied to the board, available mods are 'shift', 'four-colors -nsc string - add a DS1216 No-Slot-Clock on the main ROM (use 'main') or a slot ROM (default "none") + add a DS1216 No-Slot-Clock on the main ROM (use 'main') or a slot ROM (default "main") -profile generate profile trace to analyse with pprof -ramworks string - memory to use with RAMWorks card, max is 16384 (default "none") + memory to use with RAMWorks card, max is 16384 (default "8192") -rgb emulate the RGB modes of the 80col RGB card for DHGR -rom string @@ -215,7 +218,7 @@ Usage: izapple [file] -s0 string slot 0 configuration. (default "language") -s1 string - slot 1 configuration. (default "parallel") + slot 1 configuration. (default "empty") -s2 string slot 2 configuration. (default "vidhd") -s3 string @@ -233,11 +236,49 @@ Usage: izapple [file] -trace string trace CPU execution with one or more comma separated tracers (default "none") -The available pre configured models are: swyft, 2e, 2enh, 2plus, base64a. -The available cards are: brainboard, diskii, memexp, mouse, swyftcard, inout, smartport, thunderclock, fujinet, videx, vidhd, diskiiseq, fastchip, language, softswitchlogger, parallel, saturn. -The available tracers are: ucsd, cpu, ss, ssreg, panicSS, mos, mosfull, mli. +The available pre-configured models are: + 2: Apple ][ + 2e: Apple IIe + 2enh: Apple //e + 2plus: Apple ][+ + base64a: Base 64A + swyft: swyft + +The available cards are: + brainboard: Firmware card. It has two ROM banks + brainboard2: Firmware card. It has up to four ROM banks + dan2sd: Apple II Peripheral Card that Interfaces to a ATMEGA328P for SD card storage + diskii: Disk II interface card + diskiiseq: Disk II interface card emulating the Woz state machine + fastchip: Accelerator card for Apple IIe (limited support) + fujinet: SmartPort interface card hosting the Fujinet + inout: Card to test I/O + language: Language card with 16 extra KB for the Apple ][ and ][+ + memexp: Memory expansion card + mouse: Mouse card implementation, does not emulate a real card, only the firmware behaviour + multirom: Multiple Image ROM card + parallel: Card to dump to a file what would be printed to a parallel printer + saturn: RAM card with 128Kb, it's like 8 language cards + smartport: SmartPort interface card + softswitchlogger: Card to log softswitch accesses + swyftcard: Card with the ROM needed to run the Swyftcard word processing system + thunderclock: Clock card + videx: Videx compatible 80 columns card + vidhd: Firmware signature of the VidHD card to trick Total Replay to use the SHR mode + +The available tracers are: + cpm65: Trace CPM65 BDOS calls + cpu: Trace CPU execution + mli: Trace ProDOS MLI calls + mos: Trace MOS calls with Applecorn skipping terminal IO + mosfull: Trace MOS calls with Applecorn + panicSS: Panic on unimplemented softswitches + ss: Trace sotfswiches calls + ssreg: Trace sotfswiches registrations + ucsd: Trace UCSD system calls ``` + ## Building from source diff --git a/apple2Tester.go b/apple2Tester.go index 1d9e83c..2131b3c 100644 --- a/apple2Tester.go +++ b/apple2Tester.go @@ -14,7 +14,12 @@ type apple2Tester struct { } func makeApple2Tester(model string, overrides *configuration) (*apple2Tester, error) { - config, err := getConfigurationFromModel(model, overrides) + models, _, err := loadConfigurationModelsAndDefault() + if err != nil { + return nil, err + } + + config, err := models.getWithOverrides(model, overrides) if err != nil { return nil, err } diff --git a/configuration.go b/configuration.go index 9086e79..779f110 100644 --- a/configuration.go +++ b/configuration.go @@ -4,7 +4,6 @@ import ( "embed" "flag" "fmt" - "os" "strings" "golang.org/x/exp/slices" @@ -83,11 +82,11 @@ func (c *configuration) set(key string, value string) { c.data[key] = value } -func initConfigurationModels() (*configurationModels, error) { - models := configurationModels{} +func loadConfigurationModelsAndDefault() (*configurationModels, *configuration, error) { + models := &configurationModels{} dir, err := configurationFiles.ReadDir("configs") if err != nil { - return nil, err + return nil, nil, err } models.preconfiguredConfigs = make(map[string]*configuration) @@ -95,7 +94,7 @@ func initConfigurationModels() (*configurationModels, error) { if file.Type().IsRegular() && strings.HasSuffix(strings.ToLower(file.Name()), configSuffix) { content, err := configurationFiles.ReadFile("configs/" + file.Name()) if err != nil { - return nil, err + return nil, nil, err } lines := strings.Split(string(content), "\n") config := newConfiguration() @@ -106,7 +105,7 @@ func initConfigurationModels() (*configurationModels, error) { } colonPos := strings.Index(line, ":") if colonPos < 0 { - return nil, fmt.Errorf("invalid configuration in %s:%d", file.Name(), iLine) + return nil, nil, fmt.Errorf("invalid configuration in %s:%d", file.Name(), iLine) } key := strings.TrimSpace(line[:colonPos]) value := strings.TrimSpace(line[colonPos+1:]) @@ -117,23 +116,13 @@ func initConfigurationModels() (*configurationModels, error) { } } - // Check validity of base configuration - /* base, ok := configs.preconfiguredConfigs[baseConfigurationName] - if !ok { - return nil, fmt.Errorf("base configuration %s.cfg not found", baseConfigurationName) - } - model, ok := base[argModel] - if !ok { - return nil, fmt.Errorf("model not found in base configuration %s.cfg", baseConfigurationName) - } - if _, ok := configs.preconfiguredConfigs[model]; !ok { - return nil, fmt.Errorf("model %s not found and used in base configuration %s.cfg", model, baseConfigurationName) - } - */ + defaultConfig, err := models.get(defaultConfiguration) + if err != nil { + return nil, nil, err + } + defaultConfig.set(confModel, defaultConfiguration) - // Todo check that all configs have valid keys - - return &models, nil + return models, defaultConfig, nil } func mergeConfigs(base *configuration, addition *configuration) *configuration { @@ -147,7 +136,7 @@ func mergeConfigs(base *configuration, addition *configuration) *configuration { return result } -func (c *configurationModels) getFromModel(name string) (*configuration, error) { +func (c *configurationModels) get(name string) (*configuration, error) { name = strings.TrimSpace(name) config, ok := c.preconfiguredConfigs[name] if !ok { @@ -159,7 +148,7 @@ func (c *configurationModels) getFromModel(name string) (*configuration, error) return config, nil } - parent, err := c.getFromModel(parentName) + parent, err := c.get(parentName) if err != nil { return nil, err } @@ -179,13 +168,8 @@ func (c *configurationModels) availableModels() []string { return models } -func getConfigurationFromModel(model string, overrides *configuration) (*configuration, error) { - configurationModels, err := initConfigurationModels() - if err != nil { - return nil, err - } - - configValues, err := configurationModels.getFromModel(model) +func (c *configurationModels) getWithOverrides(model string, overrides *configuration) (*configuration, error) { + configValues, err := c.get(model) if err != nil { return nil, err } @@ -196,12 +180,7 @@ func getConfigurationFromModel(model string, overrides *configuration) (*configu return configValues, nil } -func getConfigurationFromCommandLine() (*configuration, string, error) { - configurationModels, err := initConfigurationModels() - if err != nil { - return nil, "", err - } - +func setupFlags(models *configurationModels, configuration *configuration) error { paramDescription := map[string]string{ confModel: "set base model", confRom: "main rom file", @@ -228,16 +207,10 @@ func getConfigurationFromCommandLine() (*configuration, string, error) { boolParams := []string{confProfile, confForceCaps, confRgb, confRomx} - configuration, err := configurationModels.getFromModel(defaultConfiguration) - if err != nil { - return nil, "", err - } - configuration.set(confModel, defaultConfiguration) - for name, description := range paramDescription { defaultValue, ok := configuration.getHas(name) if !ok { - return nil, "", fmt.Errorf("default value not found for %s", name) + return fmt.Errorf("default value not found for %s", name) } if slices.Contains(boolParams, name) { flag.Bool(name, defaultValue == "true", description) @@ -248,14 +221,14 @@ func getConfigurationFromCommandLine() (*configuration, string, error) { flag.Usage = func() { out := flag.CommandLine.Output() - fmt.Fprintf(out, "Usage: %s [file]\n", os.Args[0]) + fmt.Fprintf(out, "Usage: %s [file]\n", flag.CommandLine.Name()) fmt.Fprintf(out, " file\n") fmt.Fprintf(out, " path to image to use on the boot device\n") flag.PrintDefaults() - fmt.Fprintf(out, "\nThe available pre configured models are:\n") - for _, model := range configurationModels.availableModels() { - config, _ := configurationModels.getFromModel(model) + fmt.Fprintf(out, "\nThe available pre-configured models are:\n") + for _, model := range models.availableModels() { + config, _ := models.get(model) fmt.Fprintf(out, " %s: %s\n", model, config.get(confName)) } @@ -272,12 +245,23 @@ func getConfigurationFromCommandLine() (*configuration, string, error) { } } + return nil +} + +func getConfigurationFromCommandLine() (*configuration, string, error) { + models, configuration, err := loadConfigurationModelsAndDefault() + if err != nil { + return nil, "", err + } + + setupFlags(models, configuration) + flag.Parse() modelFlag := flag.Lookup(confModel) if modelFlag != nil && strings.TrimSpace(modelFlag.Value.String()) != defaultConfiguration { // Replace the model - configuration, err = configurationModels.getFromModel(modelFlag.Value.String()) + configuration, err = models.get(modelFlag.Value.String()) if err != nil { return nil, "", err } @@ -287,5 +271,7 @@ func getConfigurationFromCommandLine() (*configuration, string, error) { configuration.set(f.Name, f.Value.String()) }) - return configuration, flag.Arg(0), nil + filename := flag.Arg(0) + + return configuration, filename, nil } diff --git a/configuration_test.go b/configuration_test.go index 5affd51..de9e900 100644 --- a/configuration_test.go +++ b/configuration_test.go @@ -1,24 +1,23 @@ package izapple2 import ( + "flag" + "os" + "strings" "testing" ) func TestConfigurationModel(t *testing.T) { t.Run("test that the default model exists", func(t *testing.T) { - models, err := initConfigurationModels() - if err != nil { - t.Fatal(err) - } - _, err = models.getFromModel(defaultConfiguration) + _, _, err := loadConfigurationModelsAndDefault() if err != nil { t.Error(err) } }) t.Run("test preconfigured models are complete", func(t *testing.T) { - models, err := initConfigurationModels() + models, _, err := loadConfigurationModelsAndDefault() if err != nil { t.Fatal(err) } @@ -30,7 +29,7 @@ func TestConfigurationModel(t *testing.T) { } availabledModels := models.availableModels() for _, modelName := range availabledModels { - model, err := models.getFromModel(modelName) + model, err := models.get(modelName) if err != nil { t.Error(err) } @@ -43,3 +42,35 @@ func TestConfigurationModel(t *testing.T) { } }) } + +func TestCommandLineHelp(t *testing.T) { + t.Run("test command line help", func(t *testing.T) { + models, configuration, err := loadConfigurationModelsAndDefault() + if err != nil { + t.Fatal(err) + } + + prevFlags := flag.CommandLine + flag.CommandLine = flag.NewFlagSet("izapple2", flag.ExitOnError) + + setupFlags(models, configuration) + + buffer := strings.Builder{} + flag.CommandLine.SetOutput(&buffer) + flag.Usage() + usage := buffer.String() + + flag.CommandLine = prevFlags + + prevous, err := os.ReadFile("doc/usage.txt") + if err != nil { + t.Fatal(err) + } + + if usage != string(prevous) { + os.WriteFile("doc/usage_new.txt", []byte(usage), 0644) + t.Errorf(`Usage has changed, check doc/usage_new.txt for the new version. +If it is correct, execute \"go run update_readme.go\" in the doc folder.`) + } + }) +} diff --git a/doc/update_readme.go b/doc/update_readme.go new file mode 100644 index 0000000..b529d8b --- /dev/null +++ b/doc/update_readme.go @@ -0,0 +1,56 @@ +package main + +import ( + "fmt" + "io/ioutil" + "log" + "os" + "strings" +) + +func main() { + // Define the file paths + readmePath := "../README.md" + usagePath := "usage.txt" + newUsagePath := "usage_new.txt" + + err := os.Rename(newUsagePath, usagePath) + if err != nil { + log.Fatal(err) + } + + // Read the contents of the usage file + usageBytes, err := ioutil.ReadFile(usagePath) + if err != nil { + log.Fatal(err) + } + + // Convert the usage bytes to string + usage := string(usageBytes) + + // Read the contents of the readme file + readmeBytes, err := ioutil.ReadFile(readmePath) + if err != nil { + log.Fatal(err) + } + + // Convert the readme bytes to string + readme := string(readmeBytes) + + // Find the start and end markers + startMarker := "" + endMarker := "" + startIndex := strings.Index(readme, startMarker) + endIndex := strings.Index(readme, endMarker) + + // Replace the lines between start and end markers with the usage + newReadme := readme[:startIndex+len(startMarker)] + "\n```terminal\n" + usage + "\n```\n" + readme[endIndex:] + + // Write the updated readme back to the file + err = ioutil.WriteFile(readmePath, []byte(newReadme), os.ModePerm) + if err != nil { + log.Fatal(err) + } + + fmt.Println("README.md updated successfully!") +} diff --git a/doc/usage.txt b/doc/usage.txt new file mode 100644 index 0000000..8920073 --- /dev/null +++ b/doc/usage.txt @@ -0,0 +1,86 @@ +Usage: izapple2 [file] + file + path to image to use on the boot device + -charrom string + rom file for the character generator (default "/Apple IIe Video Enhanced.bin") + -cpu string + cpu type, can be '6502' or '65c02' (default "65c02") + -forceCaps + force all letters to be uppercased (no need for caps lock!) + -model string + set base model (default "2enh") + -mods string + comma separated list of mods applied to the board, available mods are 'shift', 'four-colors + -nsc string + add a DS1216 No-Slot-Clock on the main ROM (use 'main') or a slot ROM (default "main") + -profile + generate profile trace to analyse with pprof + -ramworks string + memory to use with RAMWorks card, max is 16384 (default "8192") + -rgb + emulate the RGB modes of the 80col RGB card for DHGR + -rom string + main rom file (default "/Apple2e_Enhanced.rom") + -romx + emulate a RomX + -s0 string + slot 0 configuration. (default "language") + -s1 string + slot 1 configuration. (default "empty") + -s2 string + slot 2 configuration. (default "vidhd") + -s3 string + slot 3 configuration. (default "fastchip") + -s4 string + slot 4 configuration. (default "mouse") + -s5 string + slot 5 configuration. (default "empty") + -s6 string + slot 6 configuration. (default "diskii,disk1=/dos33.dsk") + -s7 string + slot 7 configuration. (default "empty") + -speed string + cpu speed in Mhz, can be 'ntsc', 'pal', 'full' or a decimal nunmber (default "ntsc") + -trace string + trace CPU execution with one or more comma separated tracers (default "none") + +The available pre-configured models are: + 2: Apple ][ + 2e: Apple IIe + 2enh: Apple //e + 2plus: Apple ][+ + base64a: Base 64A + swyft: swyft + +The available cards are: + brainboard: Firmware card. It has two ROM banks + brainboard2: Firmware card. It has up to four ROM banks + dan2sd: Apple II Peripheral Card that Interfaces to a ATMEGA328P for SD card storage + diskii: Disk II interface card + diskiiseq: Disk II interface card emulating the Woz state machine + fastchip: Accelerator card for Apple IIe (limited support) + fujinet: SmartPort interface card hosting the Fujinet + inout: Card to test I/O + language: Language card with 16 extra KB for the Apple ][ and ][+ + memexp: Memory expansion card + mouse: Mouse card implementation, does not emulate a real card, only the firmware behaviour + multirom: Multiple Image ROM card + parallel: Card to dump to a file what would be printed to a parallel printer + saturn: RAM card with 128Kb, it's like 8 language cards + smartport: SmartPort interface card + softswitchlogger: Card to log softswitch accesses + swyftcard: Card with the ROM needed to run the Swyftcard word processing system + thunderclock: Clock card + videx: Videx compatible 80 columns card + vidhd: Firmware signature of the VidHD card to trick Total Replay to use the SHR mode + +The available tracers are: + cpm65: Trace CPM65 BDOS calls + cpu: Trace CPU execution + mli: Trace ProDOS MLI calls + mos: Trace MOS calls with Applecorn skipping terminal IO + mosfull: Trace MOS calls with Applecorn + panicSS: Panic on unimplemented softswitches + ss: Trace sotfswiches calls + ssreg: Trace sotfswiches registrations + ucsd: Trace UCSD system calls