Improve usage message and automate the inclusion in README.md

This commit is contained in:
Ivan Izaguirre 2024-02-12 20:45:52 +01:00
parent 9178372942
commit 51a7f17e5b
6 changed files with 270 additions and 65 deletions

View File

@ -188,8 +188,9 @@ Only valid on SDL mode
### Command line options
<!-- doc/usage.txt start -->
```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
```
<!-- doc/usage.txt end -->
## Building from source

View File

@ -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
}

View File

@ -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
}

View File

@ -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.`)
}
})
}

56
doc/update_readme.go Normal file
View File

@ -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 := "<!-- doc/usage.txt start -->"
endMarker := "<!-- doc/usage.txt end -->"
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!")
}

86
doc/usage.txt Normal file
View File

@ -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 "<internal>/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 "<internal>/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=<internal>/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