Easier command line not having to fully setup the slots.

This commit is contained in:
Iván Izaguirre
2026-01-19 19:09:40 +01:00
parent dcb4e3c923
commit 5b43c5173a
4 changed files with 408 additions and 66 deletions
+1
View File
@@ -14,6 +14,7 @@ frontend/a2fyne/a2fyne
frontend/headless/headless
frontend/*/snapshot.gif
frontend/*/snapshot.png
izapple2headless
printer.out
.DS_STORE
.scannerwork
+187 -5
View File
@@ -286,10 +286,172 @@ func setupFlags(models *configurationModels, configuration *configuration) error
return nil
}
func getConfigurationFromCommandLine() (*configuration, []string, error) {
var diskAliases = map[string]string{
"dos33": "<internal>/dos33.dsk",
"prodos": "<internal>/ProDOS_2_4_3.po",
"cpm": "<internal>/cpm_2.20B_56K.po",
"cardcat": "<internal>/Card Cat 1.7.dsk",
}
func applyDiskAliases(filename string) string {
if alias, ok := diskAliases[filename]; ok {
return alias
}
return filename
}
// classifyFile determines if a file is a diskette or block device
// Returns true if the file is a diskette, false if it's a block device
func classifyFile(filename string) bool {
filename = applyDiskAliases(filename)
_, err := LoadDiskette(filename)
return err == nil
}
// processPositionalFilenames handles filenames passed as positional arguments
// and configures slots s6, s5, s7 based on the file types
func processPositionalFilenames(config *configuration, filenames []string) error {
if len(filenames) == 0 {
return nil
}
diskettes := []string{}
blockDevices := []string{}
for _, filename := range filenames {
filename = applyDiskAliases(filename)
if classifyFile(filename) {
diskettes = append(diskettes, filename)
} else {
blockDevices = append(blockDevices, filename)
}
}
// Configure diskette slots (s6 and s5)
if len(diskettes) == 1 {
config.set(confS6, fmt.Sprintf("diskii,disk1=\"%s\"", diskettes[0]))
} else if len(diskettes) >= 2 {
config.set(confS6, fmt.Sprintf("diskii,disk1=\"%s\",disk2=\"%s\"", diskettes[0], diskettes[1]))
}
if len(diskettes) == 3 {
config.set(confS5, fmt.Sprintf("diskii,disk1=\"%s\"", diskettes[2]))
} else if len(diskettes) >= 4 {
config.set(confS5, fmt.Sprintf("diskii,disk1=\"%s\",disk2=\"%s\"", diskettes[2], diskettes[3]))
}
if len(diskettes) > 4 {
return fmt.Errorf("up to 4 diskettes can be loaded, %v found", len(diskettes))
}
// Configure block device slots (s7 and s5)
if len(blockDevices) > 8 {
return fmt.Errorf("up to 8 block devices can be loaded, %v found", len(blockDevices))
}
if len(blockDevices) > 0 {
config.set(confS7, fmt.Sprintf("smartport,image1=\"%s\"", blockDevices[0]))
if len(blockDevices) > 1 {
smartportConfig := "smartport"
for i, filename := range blockDevices {
if i == 0 {
continue
}
smartportConfig += fmt.Sprintf(",image%v=\"%s\"", i+1, filename)
}
config.set(confS5, smartportConfig)
}
}
return nil
}
// expandSlotConfiguration expands a partial slot configuration into a full one
// If the configuration is just filenames, it detects the file type and creates
// the appropriate card configuration (diskii for diskettes, smartport for block devices)
func expandSlotConfiguration(configString string) (string, error) {
if configString == "" {
return "", nil
}
// Split by comma to get parts (but respect quotes)
parts := splitConfigurationString(configString, ',')
if len(parts) == 0 {
return configString, nil
}
// Check if first part is a card name or a filename
firstPart := strings.TrimSpace(parts[0])
// Skip expansion for special values
if firstPart == noCardName {
return configString, nil
}
// If it contains '=' it's already a parameter, so it's a full config
if strings.Contains(firstPart, "=") {
return configString, nil
}
// Check if first part is a known card name
_, isCard := getCardFactory()[strings.ToLower(firstPart)]
if isCard {
// Already a full configuration
return configString, nil
}
// It's a partial configuration - just filenames
// Detect the file types and build the appropriate configuration
diskettes := []string{}
blockDevices := []string{}
for _, part := range parts {
filename := strings.TrimSpace(part)
if filename == "" {
continue
}
// Apply disk aliases
filename = applyDiskAliases(filename)
// Try to load as diskette
_, err := LoadDiskette(filename)
if err == nil {
diskettes = append(diskettes, part) // Keep original part (may have quotes)
} else {
blockDevices = append(blockDevices, part)
}
}
// Build the configuration based on what we found
if len(diskettes) > 0 && len(blockDevices) == 0 {
// All diskettes - create diskii configuration
config := "diskii"
for i, disk := range diskettes {
diskNum := i + 1
if diskNum > 2 {
return "", fmt.Errorf("diskii card supports maximum 2 disks, got %d", len(diskettes))
}
config += fmt.Sprintf(",disk%d=%s", diskNum, disk)
}
return config, nil
} else if len(blockDevices) > 0 && len(diskettes) == 0 {
// All block devices - create smartport configuration
config := "smartport"
for i, device := range blockDevices {
imageNum := i + 1
config += fmt.Sprintf(",image%d=%s", imageNum, device)
}
return config, nil
} else if len(diskettes) > 0 && len(blockDevices) > 0 {
return "", fmt.Errorf("cannot mix diskettes and block devices in the same slot configuration")
}
// No valid files found
return configString, nil
}
func getConfigurationFromCommandLine() (*configuration, error) {
models, configuration, err := loadConfigurationModelsAndDefault()
if err != nil {
return nil, nil, err
return nil, err
}
setupFlags(models, configuration)
@@ -301,7 +463,7 @@ func getConfigurationFromCommandLine() (*configuration, []string, error) {
// Replace the model
configuration, err = models.get(modelFlag.Value.String())
if err != nil {
return nil, nil, err
return nil, err
}
}
@@ -309,9 +471,29 @@ func getConfigurationFromCommandLine() (*configuration, []string, error) {
configuration.set(f.Name, f.Value.String())
})
filenames := flag.Args()
// Expand partial slot configurations (e.g., "-s4 disk.dsk" -> "-s4 diskii,disk1=disk.dsk")
slotParams := []string{confS0, confS1, confS2, confS3, confS4, confS5, confS6, confS7}
for _, slotParam := range slotParams {
if configuration.has(slotParam) {
slotConfig := configuration.get(slotParam)
expandedConfig, err := expandSlotConfiguration(slotConfig)
if err != nil {
return nil, fmt.Errorf("error expanding slot configuration for %s: %w", slotParam, err)
}
if expandedConfig != slotConfig {
configuration.set(slotParam, expandedConfig)
}
}
}
return configuration, filenames, nil
// Process positional filenames (e.g., "program disk.dsk")
filenames := flag.Args()
err = processPositionalFilenames(configuration, filenames)
if err != nil {
return nil, err
}
return configuration, nil
}
func (c *configuration) dump() {
+219
View File
@@ -74,3 +74,222 @@ If it is correct, execute \"go run update_readme.go\" in the doc folder.`)
}
})
}
func TestExpandSlotConfiguration(t *testing.T) {
t.Run("test empty configuration", func(t *testing.T) {
result, err := expandSlotConfiguration("")
if err != nil {
t.Error(err)
}
if result != "" {
t.Errorf("expected empty string, got %s", result)
}
})
t.Run("test special value 'empty'", func(t *testing.T) {
result, err := expandSlotConfiguration("empty")
if err != nil {
t.Error(err)
}
if result != "empty" {
t.Errorf("expected 'empty', got %s", result)
}
})
t.Run("test full configuration with card name", func(t *testing.T) {
input := "diskii,disk1=\"test.dsk\""
result, err := expandSlotConfiguration(input)
if err != nil {
t.Error(err)
}
if result != input {
t.Errorf("expected %s, got %s", input, result)
}
})
t.Run("test configuration with parameters", func(t *testing.T) {
input := "trace=true"
result, err := expandSlotConfiguration(input)
if err != nil {
t.Error(err)
}
if result != input {
t.Errorf("expected %s, got %s", input, result)
}
})
t.Run("test single diskette file expansion", func(t *testing.T) {
result, err := expandSlotConfiguration("resources/dos33.dsk")
if err != nil {
t.Error(err)
}
expected := "diskii,disk1=resources/dos33.dsk"
if result != expected {
t.Errorf("expected %s, got %s", expected, result)
}
})
t.Run("test multiple diskette files expansion", func(t *testing.T) {
result, err := expandSlotConfiguration("resources/dos33.dsk,resources/audit.dsk")
if err != nil {
t.Error(err)
}
expected := "diskii,disk1=resources/dos33.dsk,disk2=resources/audit.dsk"
if result != expected {
t.Errorf("expected %s, got %s", expected, result)
}
})
t.Run("test too many diskettes error", func(t *testing.T) {
_, err := expandSlotConfiguration("resources/dos33.dsk,resources/audit.dsk,resources/dos33.dsk")
if err == nil {
t.Error("expected error for more than 2 diskettes")
}
})
t.Run("test disk alias expansion", func(t *testing.T) {
result, err := expandSlotConfiguration("dos33")
if err != nil {
t.Error(err)
}
expected := "diskii,disk1=dos33"
if result != expected {
t.Errorf("expected %s, got %s", expected, result)
}
})
}
func TestProcessPositionalFilenames(t *testing.T) {
t.Run("test empty filenames", func(t *testing.T) {
config := newConfiguration()
err := processPositionalFilenames(config, []string{})
if err != nil {
t.Error(err)
}
})
t.Run("test single diskette", func(t *testing.T) {
config := newConfiguration()
err := processPositionalFilenames(config, []string{"resources/dos33.dsk"})
if err != nil {
t.Error(err)
}
s6 := config.get(confS6)
expected := "diskii,disk1=\"resources/dos33.dsk\""
if s6 != expected {
t.Errorf("expected s6=%s, got %s", expected, s6)
}
})
t.Run("test two diskettes", func(t *testing.T) {
config := newConfiguration()
err := processPositionalFilenames(config, []string{"resources/dos33.dsk", "resources/audit.dsk"})
if err != nil {
t.Error(err)
}
s6 := config.get(confS6)
expected := "diskii,disk1=\"resources/dos33.dsk\",disk2=\"resources/audit.dsk\""
if s6 != expected {
t.Errorf("expected s6=%s, got %s", expected, s6)
}
})
t.Run("test three diskettes", func(t *testing.T) {
config := newConfiguration()
err := processPositionalFilenames(config, []string{
"resources/dos33.dsk",
"resources/audit.dsk",
"resources/dos33.dsk",
})
if err != nil {
t.Error(err)
}
s6 := config.get(confS6)
s5 := config.get(confS5)
expectedS6 := "diskii,disk1=\"resources/dos33.dsk\",disk2=\"resources/audit.dsk\""
expectedS5 := "diskii,disk1=\"resources/dos33.dsk\""
if s6 != expectedS6 {
t.Errorf("expected s6=%s, got %s", expectedS6, s6)
}
if s5 != expectedS5 {
t.Errorf("expected s5=%s, got %s", expectedS5, s5)
}
})
t.Run("test four diskettes", func(t *testing.T) {
config := newConfiguration()
err := processPositionalFilenames(config, []string{
"resources/dos33.dsk",
"resources/audit.dsk",
"resources/dos33.dsk",
"resources/audit.dsk",
})
if err != nil {
t.Error(err)
}
s6 := config.get(confS6)
s5 := config.get(confS5)
expectedS6 := "diskii,disk1=\"resources/dos33.dsk\",disk2=\"resources/audit.dsk\""
expectedS5 := "diskii,disk1=\"resources/dos33.dsk\",disk2=\"resources/audit.dsk\""
if s6 != expectedS6 {
t.Errorf("expected s6=%s, got %s", expectedS6, s6)
}
if s5 != expectedS5 {
t.Errorf("expected s5=%s, got %s", expectedS5, s5)
}
})
t.Run("test too many diskettes error", func(t *testing.T) {
config := newConfiguration()
err := processPositionalFilenames(config, []string{
"resources/dos33.dsk",
"resources/dos33.dsk",
"resources/dos33.dsk",
"resources/dos33.dsk",
"resources/dos33.dsk",
})
if err == nil {
t.Error("expected error for more than 4 diskettes")
}
})
t.Run("test block device", func(t *testing.T) {
config := newConfiguration()
// Use a file that won't be detected as a diskette
err := processPositionalFilenames(config, []string{"resources/ProDOS_2_4_3.po"})
if err != nil {
t.Error(err)
}
// ProDOS .po files are detected as diskettes, so this will set s6
s6 := config.get(confS6)
if !strings.Contains(s6, "diskii") {
t.Errorf("expected diskii configuration, got %s", s6)
}
})
}
func TestApplyDiskAliases(t *testing.T) {
t.Run("test dos33 alias", func(t *testing.T) {
result := applyDiskAliases("dos33")
expected := "<internal>/dos33.dsk"
if result != expected {
t.Errorf("expected %s, got %s", expected, result)
}
})
t.Run("test prodos alias", func(t *testing.T) {
result := applyDiskAliases("prodos")
expected := "<internal>/ProDOS_2_4_3.po"
if result != expected {
t.Errorf("expected %s, got %s", expected, result)
}
})
t.Run("test non-alias filename", func(t *testing.T) {
input := "test.dsk"
result := applyDiskAliases(input)
if result != input {
t.Errorf("expected %s, got %s", input, result)
}
})
}
+1 -61
View File
@@ -239,74 +239,14 @@ func loadMultiPageRom(a *Apple2, filenames []string) error {
return nil
}
var diskAliases = map[string]string{
"dos33": "<internal>/dos33.dsk",
"prodos": "<internal>/ProDOS_2_4_3.po",
"cpm": "<internal>/cpm_2.20B_56K.po",
"cardcat": "<internal>/Card Cat 1.7.dsk",
}
func applyDiskAliases(filename string) string {
if alias, ok := diskAliases[filename]; ok {
return alias
}
return filename
}
// CreateConfiguredApple is a device independent main. Video, keyboard and speaker won't be defined
func CreateConfiguredApple() (*Apple2, error) {
// Get configuration from defaults and the command line
configuration, filenames, err := getConfigurationFromCommandLine()
configuration, err := getConfigurationFromCommandLine()
if err != nil {
return nil, err
}
if len(filenames) > 0 {
diskettes := []string{}
blockDevices := []string{}
for _, filename := range filenames {
filename = applyDiskAliases(filename)
_, err := LoadDiskette(filename)
isDiskette := err == nil
if isDiskette {
diskettes = append(diskettes, filename)
} else {
blockDevices = append(blockDevices, filename)
}
}
if len(diskettes) == 1 {
configuration.set(confS6, fmt.Sprintf("diskii,disk1=\"%s\"", diskettes[0]))
} else if len(diskettes) >= 2 {
configuration.set(confS6, fmt.Sprintf("diskii,disk1=\"%s\",disk2=\"%s\"", diskettes[0], diskettes[1]))
}
if len(diskettes) == 3 {
configuration.set(confS5, fmt.Sprintf("diskii,disk1=\"%s\"", diskettes[2]))
} else if len(diskettes) >= 4 {
configuration.set(confS5, fmt.Sprintf("diskii,disk1=\"%s\",disk2=\"%s\"", diskettes[2], diskettes[3]))
}
if len(diskettes) > 4 {
return nil, fmt.Errorf("up to 4 diskettes can be loaded, %v found", len(diskettes))
}
if len(blockDevices) > 8 {
return nil, fmt.Errorf("up to 8 block devices can be loaded, %v found", len(blockDevices))
}
if len(blockDevices) > 0 {
configuration.set(confS7, fmt.Sprintf("smartport,image1=\"%s\"", blockDevices[0]))
if len(blockDevices) > 1 {
smartportConfig := "smartport"
for i, filename := range blockDevices {
if i == 0 {
continue
}
smartportConfig += fmt.Sprintf(",image%v=\"%s\"", i+1, filename)
}
configuration.set(confS5, smartportConfig)
}
}
}
a, err := configure(configuration)
if err != nil {
return nil, err