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 ### Command line options
<!-- doc/usage.txt start -->
```terminal ```terminal
Usage: izapple [file] Usage: izapple2 [file]
file file
path to image to use on the boot device path to image to use on the boot device
-charrom string -charrom string
@ -200,12 +201,14 @@ Usage: izapple [file]
force all letters to be uppercased (no need for caps lock!) force all letters to be uppercased (no need for caps lock!)
-model string -model string
set base model (default "2enh") set base model (default "2enh")
-mods string
comma separated list of mods applied to the board, available mods are 'shift', 'four-colors
-nsc string -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 -profile
generate profile trace to analyse with pprof generate profile trace to analyse with pprof
-ramworks string -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 -rgb
emulate the RGB modes of the 80col RGB card for DHGR emulate the RGB modes of the 80col RGB card for DHGR
-rom string -rom string
@ -215,7 +218,7 @@ Usage: izapple [file]
-s0 string -s0 string
slot 0 configuration. (default "language") slot 0 configuration. (default "language")
-s1 string -s1 string
slot 1 configuration. (default "parallel") slot 1 configuration. (default "empty")
-s2 string -s2 string
slot 2 configuration. (default "vidhd") slot 2 configuration. (default "vidhd")
-s3 string -s3 string
@ -233,11 +236,49 @@ Usage: izapple [file]
-trace string -trace string
trace CPU execution with one or more comma separated tracers (default "none") 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 pre-configured models are:
The available cards are: brainboard, diskii, memexp, mouse, swyftcard, inout, smartport, thunderclock, fujinet, videx, vidhd, diskiiseq, fastchip, language, softswitchlogger, parallel, saturn. 2: Apple ][
The available tracers are: ucsd, cpu, ss, ssreg, panicSS, mos, mosfull, mli. 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 ## Building from source

View File

@ -14,7 +14,12 @@ type apple2Tester struct {
} }
func makeApple2Tester(model string, overrides *configuration) (*apple2Tester, error) { 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 { if err != nil {
return nil, err return nil, err
} }

View File

@ -4,7 +4,6 @@ import (
"embed" "embed"
"flag" "flag"
"fmt" "fmt"
"os"
"strings" "strings"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
@ -83,11 +82,11 @@ func (c *configuration) set(key string, value string) {
c.data[key] = value c.data[key] = value
} }
func initConfigurationModels() (*configurationModels, error) { func loadConfigurationModelsAndDefault() (*configurationModels, *configuration, error) {
models := configurationModels{} models := &configurationModels{}
dir, err := configurationFiles.ReadDir("configs") dir, err := configurationFiles.ReadDir("configs")
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
models.preconfiguredConfigs = make(map[string]*configuration) 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) { if file.Type().IsRegular() && strings.HasSuffix(strings.ToLower(file.Name()), configSuffix) {
content, err := configurationFiles.ReadFile("configs/" + file.Name()) content, err := configurationFiles.ReadFile("configs/" + file.Name())
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
lines := strings.Split(string(content), "\n") lines := strings.Split(string(content), "\n")
config := newConfiguration() config := newConfiguration()
@ -106,7 +105,7 @@ func initConfigurationModels() (*configurationModels, error) {
} }
colonPos := strings.Index(line, ":") colonPos := strings.Index(line, ":")
if colonPos < 0 { 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]) key := strings.TrimSpace(line[:colonPos])
value := strings.TrimSpace(line[colonPos+1:]) value := strings.TrimSpace(line[colonPos+1:])
@ -117,23 +116,13 @@ func initConfigurationModels() (*configurationModels, error) {
} }
} }
// Check validity of base configuration defaultConfig, err := models.get(defaultConfiguration)
/* base, ok := configs.preconfiguredConfigs[baseConfigurationName] if err != nil {
if !ok { return nil, nil, err
return nil, fmt.Errorf("base configuration %s.cfg not found", baseConfigurationName) }
} defaultConfig.set(confModel, defaultConfiguration)
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)
}
*/
// Todo check that all configs have valid keys return models, defaultConfig, nil
return &models, nil
} }
func mergeConfigs(base *configuration, addition *configuration) *configuration { func mergeConfigs(base *configuration, addition *configuration) *configuration {
@ -147,7 +136,7 @@ func mergeConfigs(base *configuration, addition *configuration) *configuration {
return result return result
} }
func (c *configurationModels) getFromModel(name string) (*configuration, error) { func (c *configurationModels) get(name string) (*configuration, error) {
name = strings.TrimSpace(name) name = strings.TrimSpace(name)
config, ok := c.preconfiguredConfigs[name] config, ok := c.preconfiguredConfigs[name]
if !ok { if !ok {
@ -159,7 +148,7 @@ func (c *configurationModels) getFromModel(name string) (*configuration, error)
return config, nil return config, nil
} }
parent, err := c.getFromModel(parentName) parent, err := c.get(parentName)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -179,13 +168,8 @@ func (c *configurationModels) availableModels() []string {
return models return models
} }
func getConfigurationFromModel(model string, overrides *configuration) (*configuration, error) { func (c *configurationModels) getWithOverrides(model string, overrides *configuration) (*configuration, error) {
configurationModels, err := initConfigurationModels() configValues, err := c.get(model)
if err != nil {
return nil, err
}
configValues, err := configurationModels.getFromModel(model)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -196,12 +180,7 @@ func getConfigurationFromModel(model string, overrides *configuration) (*configu
return configValues, nil return configValues, nil
} }
func getConfigurationFromCommandLine() (*configuration, string, error) { func setupFlags(models *configurationModels, configuration *configuration) error {
configurationModels, err := initConfigurationModels()
if err != nil {
return nil, "", err
}
paramDescription := map[string]string{ paramDescription := map[string]string{
confModel: "set base model", confModel: "set base model",
confRom: "main rom file", confRom: "main rom file",
@ -228,16 +207,10 @@ func getConfigurationFromCommandLine() (*configuration, string, error) {
boolParams := []string{confProfile, confForceCaps, confRgb, confRomx} 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 { for name, description := range paramDescription {
defaultValue, ok := configuration.getHas(name) defaultValue, ok := configuration.getHas(name)
if !ok { 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) { if slices.Contains(boolParams, name) {
flag.Bool(name, defaultValue == "true", description) flag.Bool(name, defaultValue == "true", description)
@ -248,14 +221,14 @@ func getConfigurationFromCommandLine() (*configuration, string, error) {
flag.Usage = func() { flag.Usage = func() {
out := flag.CommandLine.Output() 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, " file\n")
fmt.Fprintf(out, " path to image to use on the boot device\n") fmt.Fprintf(out, " path to image to use on the boot device\n")
flag.PrintDefaults() flag.PrintDefaults()
fmt.Fprintf(out, "\nThe available pre configured models are:\n") fmt.Fprintf(out, "\nThe available pre-configured models are:\n")
for _, model := range configurationModels.availableModels() { for _, model := range models.availableModels() {
config, _ := configurationModels.getFromModel(model) config, _ := models.get(model)
fmt.Fprintf(out, " %s: %s\n", model, config.get(confName)) 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() flag.Parse()
modelFlag := flag.Lookup(confModel) modelFlag := flag.Lookup(confModel)
if modelFlag != nil && strings.TrimSpace(modelFlag.Value.String()) != defaultConfiguration { if modelFlag != nil && strings.TrimSpace(modelFlag.Value.String()) != defaultConfiguration {
// Replace the model // Replace the model
configuration, err = configurationModels.getFromModel(modelFlag.Value.String()) configuration, err = models.get(modelFlag.Value.String())
if err != nil { if err != nil {
return nil, "", err return nil, "", err
} }
@ -287,5 +271,7 @@ func getConfigurationFromCommandLine() (*configuration, string, error) {
configuration.set(f.Name, f.Value.String()) 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 package izapple2
import ( import (
"flag"
"os"
"strings"
"testing" "testing"
) )
func TestConfigurationModel(t *testing.T) { func TestConfigurationModel(t *testing.T) {
t.Run("test that the default model exists", func(t *testing.T) { t.Run("test that the default model exists", func(t *testing.T) {
models, err := initConfigurationModels() _, _, err := loadConfigurationModelsAndDefault()
if err != nil {
t.Fatal(err)
}
_, err = models.getFromModel(defaultConfiguration)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
}) })
t.Run("test preconfigured models are complete", func(t *testing.T) { t.Run("test preconfigured models are complete", func(t *testing.T) {
models, err := initConfigurationModels() models, _, err := loadConfigurationModelsAndDefault()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -30,7 +29,7 @@ func TestConfigurationModel(t *testing.T) {
} }
availabledModels := models.availableModels() availabledModels := models.availableModels()
for _, modelName := range availabledModels { for _, modelName := range availabledModels {
model, err := models.getFromModel(modelName) model, err := models.get(modelName)
if err != nil { if err != nil {
t.Error(err) 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