diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..61a9a60
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,9 @@
+{
+ "filewatcher.commands": [
+ {
+ "match": "\\.go",
+ "cmd": "echo '${file} changed'",
+ "event": "onFileChange",
+ },
+ ]
+}
diff --git a/README.md b/README.md
index 21fa12b..c79cf24 100644
--- a/README.md
+++ b/README.md
@@ -28,11 +28,34 @@ diskii's major disadvantage is that it mostly doesn't exist yet.
It rhymes with “whiskey”.
-Discussion/support is in
-[#apple2 on the retrocomputing Slack](https://retrocomputing.slack.com/messages/apple2/)
-(invites [here](https://retrocomputing.herokuapp.com)).
+Discussion/support is on the
+[apple2infinitum Slack](https://apple2infinitum.slack.com/)
+(invites [here](http://apple2.gs:3000/)).
-### Goals
+# Examples
+
+Get a listing of files on a DOS 3.3 disk image:
+```
+diskii ls dos33master.dsk
+```
+
+… or a ProDOS disk image:
+```
+diskii ls ProDOS_2_4_2.po
+```
+
+… or a Super-Mon disk image:
+```
+diskii ls Super-Mon-2.0.dsk
+```
+
+Reorder the sectors in a disk image:
+```
+diskii reorder ProDOS_2_4_2.dsk ProDOS_2_4_2.po
+```
+
+
+# Goals
Eventually, it aims to be a comprehensive disk image manipulation
tool, but for now only some parts work.
@@ -47,8 +70,8 @@ Current disk operations supported:
| ---------------- | -------- | ------ | ------------------ |
| basic structures | ✓ | ✓ | ✓ |
| ls | ✓ | ✓ | ✓ |
-| dump | ✓ | ✗ | ✓ |
-| put | ✗ | ✗ | ✓ |
+| dump | ✗ | ✗ | ✗ |
+| put | ✗ | ✗ | ✗ |
| dumptext | ✗ | ✗ | ✗ |
| delete | ✗ | ✗ | ✗ |
| rename | ✗ | ✗ | ✗ |
@@ -59,7 +82,7 @@ Current disk operations supported:
| init | ✗ | ✗ | ✗ |
| defrag | ✗ | ✗ | ✗ |
-### Installing/updating
+# Installing/updating
Assuming you have Go installed, run `go get -u github.com/zellyn/diskii`
You can also download automatically-built binaries from the
@@ -68,25 +91,24 @@ page](https://github.com/zellyn/diskii/releases/latest). If you
need binaries for a different architecture, please send a pull
request or open an issue.
-### Short-term TODOs/roadmap/easy ways to contribute
+# Short-term TODOs/roadmap/easy ways to contribute
My rough TODO list (apart from anything marked (✗) in the disk
operations matrix is listed below. Anything that an actual user needs
will be likely to get priority.
+- [ ] Make `put` accept load address for appropriate filetypes.
+- [ ] Implement `GetFile` for prodos
- [x] Build per-platform binaries for Linux, MacOS, Windows.
- [x] Implement `GetFile` for DOS 3.3
- [ ] Add and implement the `-l` flag for `ls`
- [x] Add `Delete` to the `disk.Operator` interface
- - [x] Implement it for Super-Mon
+ - [ ] Implement it for Super-Mon
- [ ] Implement it for DOS 3.3
-- [ ] Make 13-sector DOS disks work
-- [ ] Read/write nybble formats
-- [ ] Read/write gzipped files
-- [ ] Add basic ProDOS structures
-- [ ] Add ProDOS support
+- [ ] Add ProDOS support for all commands
+- [x] Make `filetypes` command use a tabwriter to write as a table
-### Related tools
+# Related tools
- http://a2ciderpress.com/ - the great grandaddy of them all. Windows only, unless you Wine
- http://retrocomputingaustralia.com/rca-downloads/ Michael Mulhern's MacOS package of CiderPress
@@ -107,3 +129,48 @@ will be likely to get priority.
- https://github.com/slotek/apple2-disk-util - ruby
- https://github.com/slotek/dsk2nib - C
- https://github.com/robmcmullen/atrcopy - dos3.3, python
+
+# Notes
+
+## Disk formats
+
+- `.do`
+- `.po`
+- `.dsk` - could be DO or PO. When in doubt, assume DO.
+
+| Physical Sectors | DOS 3.2 Logical | DOS 3.3 Logical | ProDOS/Pascal Logical | CP/M Logical |
+|------------------|-----------------|-----------------|-----------------------|------------- |
+| 0 | 0 | 0 | 0.0 | 0.0 |
+| 1 | 1 | 7 | 4.0 | 2.3 |
+| 2 | 2 | E | 0.1 | 1.2 |
+| 3 | 3 | 6 | 4.1 | 0.1 |
+| 4 | 4 | D | 1.0 | 3.0 |
+| 5 | 5 | 5 | 5.0 | 1.3 |
+| 6 | 6 | C | 1.1 | 0.2 |
+| 7 | 7 | 4 | 5.1 | 3.1 |
+| 8 | 8 | B | 2.0 | 2.0 |
+| 9 | 9 | 3 | 6.0 | 0.3 |
+| A | A | A | 2.1 | 3.2 |
+| B | B | 2 | 6.1 | 2.1 |
+| C | C | 9 | 3.0 | 1.0 |
+| D | | 1 | 7.0 | 3.3 |
+| E | | 8 | 3.1 | 2.2 |
+| F | | F | 7.1 | 1.1 |
+
+_Note: DOS 3.2 rearranged the physical sectors on disk to achieve interleaving._
+### RWTS - DOS
+
+Sector mapping:
+http://www.textfiles.com/apple/ANATOMY/rwts.s.txt and search for INTRLEAV
+
+Mapping from specified sector to physical sector:
+
+`00 0D 0B 09 07 05 03 01 0E 0C 0A 08 06 04 02 0F`
+
+So if you write to "T0S1" with DOS RWTS, it ends up in physical sector 0D.
+
+## Commandline examples for thinking about how it should work
+
+diskii ls dos33.dsk
+diskii --order=do ls dos33.dsk
+diskii --order=do --system=nakedos ls nakedos.dsk
diff --git a/lib/basic/applesoft/applesoft.go b/basic/applesoft/applesoft.go
similarity index 100%
rename from lib/basic/applesoft/applesoft.go
rename to basic/applesoft/applesoft.go
diff --git a/lib/basic/applesoft/applesoft_test.go b/basic/applesoft/applesoft_test.go
similarity index 98%
rename from lib/basic/applesoft/applesoft_test.go
rename to basic/applesoft/applesoft_test.go
index b6abbf7..3de9e0e 100644
--- a/lib/basic/applesoft/applesoft_test.go
+++ b/basic/applesoft/applesoft_test.go
@@ -5,7 +5,7 @@ package applesoft
import (
"testing"
- "github.com/zellyn/diskii/lib/basic"
+ "github.com/zellyn/diskii/basic"
)
// helloBinary is a simple basic program used for testing. Listing
diff --git a/lib/basic/basic.go b/basic/basic.go
similarity index 100%
rename from lib/basic/basic.go
rename to basic/basic.go
diff --git a/lib/basic/integer/integer.go b/basic/integer/integer.go
similarity index 100%
rename from lib/basic/integer/integer.go
rename to basic/integer/integer.go
diff --git a/lib/basic/integer/integer_test.go b/basic/integer/integer_test.go
similarity index 98%
rename from lib/basic/integer/integer_test.go
rename to basic/integer/integer_test.go
index 0581056..37c3f3b 100644
--- a/lib/basic/integer/integer_test.go
+++ b/basic/integer/integer_test.go
@@ -5,7 +5,7 @@ package integer
import (
"testing"
- "github.com/zellyn/diskii/lib/basic"
+ "github.com/zellyn/diskii/basic"
)
// helloBinary is a simple basic program used for testing. Listing
diff --git a/build b/build
new file mode 100755
index 0000000..414934a
--- /dev/null
+++ b/build
@@ -0,0 +1,57 @@
+#!/usr/bin/env bash
+set -euo pipefail
+export ACME="$HOME/gh/acme/ACME_Lib"
+ACME_BIN="$HOME/gh/acme/acme"
+
+
+$ACME_BIN -o writetest.o -r writetest.lst writetest.asm
+
+# cp data/disks/dos33mst.dsk writetest.dsk
+# diskii put -f writetest.dsk writetest writetest.o
+
+# Also run mame? (set MAMEDIR and MAMEBIN to your local variant)
+[[ -z "${MAMEDIR-}" ]] && MAMEDIR="/Users/zellyn/Library/Application Support/Ample"
+[[ -z "${MAMEBIN-}" ]] && MAMEBIN="/Applications/Ample.app/Contents/MacOS/mame64"
+
+# Write audit.o into an OpenEmulator config?
+[[ -z "${TMPLS-}" ]] && TMPLS=~/gh/OpenEmulator-OSX/modules/libemulation/res/templates
+
+DSK=$(realpath ./audit.dsk)
+
+case "${1-none}" in
+ "2ee")
+ # mame64 apple2ee -skip_gameinfo -nosamples -window -resolution 1120x840 -flop1 /Users/zellyn/gh/a2audit/audit/audit.dsk
+ (cd "$MAMEDIR"; "$MAMEBIN" apple2ee -window -flop1 "$DSK" -skip_gameinfo)
+ ;;
+ "2e")
+ (cd "$MAMEDIR"; "$MAMEBIN" apple2e -window -flop1 "$DSK" -skip_gameinfo)
+ ;;
+ "2p")
+ (cd "$MAMEDIR"; "$MAMEBIN" apple2p -window -flop1 "$DSK" -skip_gameinfo)
+ ;;
+ "2")
+ (cd "$MAMEDIR"; "$MAMEBIN" apple2 -window -flop1 "$DSK" -skip_gameinfo)
+ ;;
+ "2ee-d")
+ (cd "$MAMEDIR"; "$MAMEBIN" apple2ee -window -flop1 "$DSK" -skip_gameinfo -debug)
+ ;;
+ "2e-d")
+ (cd "$MAMEDIR"; "$MAMEBIN" apple2e -window -flop1 "$DSK" -skip_gameinfo -debug)
+ ;;
+ "2p-d")
+ (cd "$MAMEDIR"; "$MAMEBIN" apple2p -window -flop1 "$DSK" -skip_gameinfo -debug)
+ ;;
+ "2-d")
+ (cd "$MAMEDIR"; "$MAMEBIN" apple2 -window -flop1 "$DSK" -skip_gameinfo -debug)
+ ;;
+ "oe")
+ (head -c 24576 /dev/zero; cat audit.o; head -c 65536 /dev/zero) | head -c 65536 > $TMPLS/Apple\ II/Apple\ IIe-test.emulation/appleIIe.mainRam.bin
+ sed -e 's|||' $TMPLS/Apple\ II/Apple\ IIe.xml > $TMPLS/Apple\ II/Apple\ IIe-test.emulation/info.xml
+ ;;
+ "none")
+ ;;
+ *)
+ echo Options: 2ee, 2e, 2p, 2, 2ee-d, 2e-d, 2p-d, 2-d
+esac
+
+true # Signal success (since we had a bunch of conditionals that can return false status).
diff --git a/cmd/applesoft.go b/cmd/applesoft.go
index 94f4d9c..5cfb25c 100644
--- a/cmd/applesoft.go
+++ b/cmd/applesoft.go
@@ -3,87 +3,41 @@
package cmd
import (
- "fmt"
"os"
- "github.com/spf13/cobra"
- "github.com/zellyn/diskii/lib/basic"
- "github.com/zellyn/diskii/lib/basic/applesoft"
- "github.com/zellyn/diskii/lib/helpers"
+ "github.com/zellyn/diskii/basic"
+ "github.com/zellyn/diskii/basic/applesoft"
+ "github.com/zellyn/diskii/helpers"
+ "github.com/zellyn/diskii/types"
)
-// applesoftCmd represents the applesoft command
-var applesoftCmd = &cobra.Command{
- Use: "applesoft",
- Short: "work with applesoft programs",
- Long: `diskii applesoft contains the subcommands useful for working
- with Applesoft programs.`,
+type ApplesoftCmd struct {
+ Decode DecodeCmd `kong:"cmd,help='Convert a binary Applesoft program to a text LISTing.'"`
}
-func init() {
- RootCmd.AddCommand(applesoftCmd)
+type DecodeCmd struct {
+ Filename string `kong:"arg,default='-',type='existingfile',help='Binary Applesoft file to read, or “-” for stdin.'"`
- // Here you will define your flags and configuration settings.
-
- // Cobra supports Persistent Flags which will work for this command
- // and all subcommands, e.g.:
- // applesoftCmd.PersistentFlags().String("foo", "", "A help for foo")
-
- // Cobra supports local flags which will only run when this command
- // is called directly, e.g.:
- // applesoftCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
+ Location uint16 `kong:"type='anybaseuint16',default='0x801',help='Starting program location in memory.'"`
+ Raw bool `kong:"short='r',help='Print raw control codes (no escaping)'"`
}
-// ----- applesoft decode command -------------------------------------------
-
-var location uint16 // flag for starting location in memory
-var rawControlCodes bool // flag for whether to skip escaping control codes
-
-// decodeCmd represents the decode command
-var decodeCmd = &cobra.Command{
- Use: "decode filename",
- Short: "convert a binary applesoft program to a LISTing",
- Long: `
-decode converts a binary Applesoft program to a text LISTing.
-
-Examples:
-decode filename # read filename
-decode - # read stdin`,
- Run: func(cmd *cobra.Command, args []string) {
- if err := runDecode(args); err != nil {
- fmt.Fprintln(os.Stderr, err.Error())
- os.Exit(-1)
- }
- },
+func (d DecodeCmd) Help() string {
+ return `Examples:
+ # Dump the contents of HELLO and then decode it.
+ diskii dump dos33master.dsk HELLO | diskii applesoft decode -`
}
-func init() {
- applesoftCmd.AddCommand(decodeCmd)
-
- // Here you will define your flags and configuration settings.
-
- // Cobra supports Persistent Flags which will work for this command
- // and all subcommands, e.g.:
- // decodeCmd.PersistentFlags().String("foo", "", "A help for foo")
-
- decodeCmd.Flags().Uint16VarP(&location, "location", "l", 0x801, "Starting program location in memory")
- decodeCmd.Flags().BoolVarP(&rawControlCodes, "raw", "r", false, "Print raw control codes (no escaping)")
-}
-
-// runDecode performs the actual decode logic.
-func runDecode(args []string) error {
- if len(args) != 1 {
- return fmt.Errorf("decode expects one argument: the filename (or - for stdin)")
- }
- contents, err := helpers.FileContentsOrStdIn(args[0])
+func (d *DecodeCmd) Run(globals *types.Globals) error {
+ contents, err := helpers.FileContentsOrStdIn(d.Filename)
if err != nil {
return err
}
- listing, err := applesoft.Decode(contents, location)
+ listing, err := applesoft.Decode(contents, d.Location)
if err != nil {
return err
}
- if rawControlCodes {
+ if d.Raw {
os.Stdout.WriteString(listing.String())
} else {
os.Stdout.WriteString(basic.ChevronControlCodes(listing.String()))
diff --git a/cmd/catalog.go b/cmd/catalog.go
index 62eeecd..3156d16 100644
--- a/cmd/catalog.go
+++ b/cmd/catalog.go
@@ -6,59 +6,39 @@ import (
"fmt"
"os"
- "github.com/spf13/cobra"
- "github.com/zellyn/diskii/lib/disk"
+ "github.com/zellyn/diskii/disk"
+ "github.com/zellyn/diskii/types"
)
-var shortnames bool // flag for whether to print short filenames
-var debug bool
+type LsCmd struct {
+ Order types.DiskOrder `kong:"default='auto',enum='auto,do,po',help='Logical-to-physical sector order.'"`
+ System string `kong:"default='auto',enum='auto,dos3',help='DOS system used for image.'"`
-// catalogCmd represents the cat command, used to catalog a disk or
-// directory.
-var catalogCmd = &cobra.Command{
- Use: "catalog",
- Aliases: []string{"cat", "ls"},
- Short: "print a list of files",
- Long: `Catalog a disk or subdirectory.`,
- Run: func(cmd *cobra.Command, args []string) {
- if err := runCat(args); err != nil {
- fmt.Fprintln(os.Stderr, err.Error())
- os.Exit(-1)
- }
- },
-}
-
-func init() {
- RootCmd.AddCommand(catalogCmd)
- catalogCmd.Flags().BoolVarP(&shortnames, "shortnames", "s", false, "whether to print short filenames (only makes a difference on Super-Mon disks)")
- catalogCmd.Flags().BoolVarP(&debug, "debug", "d", false, "pring debug information")
+ ShortNames bool `kong:"short='s',help='Whether to print short filenames (only makes a difference on Super-Mon disks).'"`
+ Image *os.File `kong:"arg,required,help='Disk/device image to read.'"`
+ Directory string `kong:"arg,optional,help='Directory to list (ProDOS only).'"`
}
-// runCat performs the actual catalog logic.
-func runCat(args []string) error {
- if len(args) < 1 || len(args) > 2 {
- return fmt.Errorf("cat expects a disk image filename, and an optional subdirectory")
- }
- op, err := disk.Open(args[0])
+func (l *LsCmd) Run(globals *types.Globals) error {
+ op, order, err := disk.OpenFile(l.Image, l.Order, l.System, globals.DiskOperatorFactories, globals.Debug)
if err != nil {
- return err
+ return fmt.Errorf("%w: %s", err, l.Image.Name())
}
- if debug {
- fmt.Printf("Got disk of type %q with underlying sector/block order %q.\n", op.Name(), op.Order())
+ if globals.Debug {
+ fmt.Fprintf(os.Stderr, "Opened disk with order %q, system %q\n", order, op.Name())
}
- subdir := ""
- if len(args) == 2 {
+
+ if l.Directory != "" {
if !op.HasSubdirs() {
return fmt.Errorf("Disks of type %q cannot have subdirectories", op.Name())
}
- subdir = args[1]
}
- fds, err := op.Catalog(subdir)
+ fds, err := op.Catalog(l.Directory)
if err != nil {
return err
}
for _, fd := range fds {
- if !shortnames && fd.Fullname != "" {
+ if !l.ShortNames && fd.Fullname != "" {
fmt.Println(fd.Fullname)
} else {
fmt.Println(fd.Name)
diff --git a/cmd/delete.go b/cmd/delete.go
index d7977fa..3ad470e 100644
--- a/cmd/delete.go
+++ b/cmd/delete.go
@@ -4,61 +4,38 @@ package cmd
import (
"fmt"
- "os"
- "github.com/spf13/cobra"
- "github.com/zellyn/diskii/lib/disk"
+ "github.com/zellyn/diskii/disk"
+ "github.com/zellyn/diskii/types"
)
-var missingok bool // flag for whether to consider deleting a nonexistent file okay
+type DeleteCmd struct {
+ Order types.DiskOrder `kong:"default='auto',enum='auto,do,po',help='Logical-to-physical sector order.'"`
+ System string `kong:"default='auto',enum='auto,dos3',help='DOS system used for image.'"`
+ MissingOk bool `kong:"short='f',help='Overwrite existing file?'"`
-// deleteCmd represents the delete command, used to delete a file.
-var deleteCmd = &cobra.Command{
- Use: "delete",
- Short: "delete a file",
- Long: `Delete a file.
-
-delete disk-image.dsk HELLO
-`,
- Run: func(cmd *cobra.Command, args []string) {
- if err := runDelete(args); err != nil {
- fmt.Fprintln(os.Stderr, err.Error())
- os.Exit(-1)
- }
- },
+ DiskImage string `kong:"arg,required,type='existingfile',help='Disk image to modify.'"`
+ Filename string `kong:"arg,required,help='Filename to use on disk.'"`
}
-func init() {
- RootCmd.AddCommand(deleteCmd)
- deleteCmd.Flags().BoolVarP(&missingok, "missingok", "f", false, "if true, don't consider deleting a nonexistent file an error")
+func (d DeleteCmd) Help() string {
+ return `Examples:
+ # Delete file GREMLINS on disk image games.dsk.
+ diskii rm games.dsk GREMLINS`
}
-// runDelete performs the actual delete logic.
-func runDelete(args []string) error {
- if len(args) != 2 {
- return fmt.Errorf("delete expects a disk image filename, and a filename")
- }
- op, err := disk.Open(args[0])
- if err != nil {
- return err
- }
- deleted, err := op.Delete(args[1])
+func (d *DeleteCmd) Run(globals *types.Globals) error {
+ op, order, err := disk.OpenFilename(d.DiskImage, d.Order, d.System, globals.DiskOperatorFactories, globals.Debug)
if err != nil {
return err
}
- if !deleted && !missingok {
- return fmt.Errorf("file %q not found", args[1])
- }
- f, err := os.Create(args[0])
- if err != nil {
- return err
- }
- _, err = op.Write(f)
+
+ deleted, err := op.Delete(d.Filename)
if err != nil {
return err
}
- if err = f.Close(); err != nil {
- return err
+ if !deleted && !d.MissingOk {
+ return fmt.Errorf("file %q not found (use -f to prevent this being an error)", d.Filename)
}
- return nil
+ return disk.WriteBack(d.DiskImage, op, order, true)
}
diff --git a/cmd/dump.go b/cmd/dump.go
index fce649a..d53884e 100644
--- a/cmd/dump.go
+++ b/cmd/dump.go
@@ -3,48 +3,39 @@
package cmd
import (
- "fmt"
"os"
- "github.com/spf13/cobra"
- "github.com/zellyn/diskii/lib/disk"
+ "github.com/zellyn/diskii/disk"
+ "github.com/zellyn/diskii/types"
)
-// dumpCmd represents the dump command, used to dump the raw contents
-// of a file.
-var dumpCmd = &cobra.Command{
- Use: "dump",
- Short: "dump the raw contents of a file",
- Long: `Dump the raw contents of a file.
-
-dump disk-image.dsk HELLO
-`,
- Run: func(cmd *cobra.Command, args []string) {
- if err := runDump(args); err != nil {
- fmt.Fprintln(os.Stderr, err.Error())
- os.Exit(-1)
- }
- },
+type DumpCmd struct {
+ Order types.DiskOrder `kong:"default='auto',enum='auto,do,po',help='Logical-to-physical sector order.'"`
+ System string `kong:"default='auto',enum='auto,dos3',help='DOS system used for image.'"`
+
+ DiskImage string `kong:"arg,required,type='existingfile',help='Disk image to modify.'"`
+ Filename string `kong:"arg,required,help='Filename to use on disk.'"`
}
-func init() {
- RootCmd.AddCommand(dumpCmd)
+func (d DumpCmd) Help() string {
+ return `Examples:
+ # Dump file GREMLINS on disk image games.dsk.
+ diskii dump games.dsk GREMLINS`
}
-// runDump performs the actual dump logic.
-func runDump(args []string) error {
- if len(args) != 2 {
- return fmt.Errorf("dump expects a disk image filename, and a filename")
+func (d *DumpCmd) Run(globals *types.Globals) error {
+ op, _, err := disk.OpenFilename(d.DiskImage, d.Order, d.System, globals.DiskOperatorFactories, globals.Debug)
+ if err != nil {
+ return err
}
- op, err := disk.Open(args[0])
+
+ file, err := op.GetFile(d.Filename)
if err != nil {
return err
}
- file, err := op.GetFile(args[1])
+ _, err = os.Stdout.Write(file.Data)
if err != nil {
return err
}
- // TODO(zellyn): allow writing to files
- os.Stdout.Write(file.Data)
return nil
}
diff --git a/cmd/filetypes.go b/cmd/filetypes.go
index 5138217..cbcc469 100644
--- a/cmd/filetypes.go
+++ b/cmd/filetypes.go
@@ -5,39 +5,22 @@ package cmd
import (
"fmt"
"os"
+ "text/tabwriter"
- "github.com/spf13/cobra"
- "github.com/zellyn/diskii/lib/disk"
+ "github.com/zellyn/diskii/types"
)
-var all bool // flag for whether to show all filetypes
-
-// filetypesCmd represents the filetypes command, used to display
-// valid filetypes recognized by diskii.
-var filetypesCmd = &cobra.Command{
- Use: "filetypes",
- Short: "print a list of filetypes",
- Long: `Print a list of filetypes understood by diskii`,
- Run: func(cmd *cobra.Command, args []string) {
- if err := runFiletypes(args); err != nil {
- fmt.Fprintln(os.Stderr, err.Error())
- os.Exit(-1)
- }
- },
-}
-
-func init() {
- RootCmd.AddCommand(filetypesCmd)
- filetypesCmd.Flags().BoolVarP(&all, "all", "a", false, "display all types, including SOS types and reserved ranges")
+type FiletypesCmd struct {
+ All bool `kong:"help='Display all types, including SOS types and reserved ranges.'"`
}
-// runFiletypes performs the actual listing of filetypes.
-func runFiletypes(args []string) error {
- if len(args) != 0 {
- return fmt.Errorf("filetypes expects no arguments")
- }
- for _, typ := range disk.FiletypeNames(all) {
- fmt.Println(typ)
+func (f *FiletypesCmd) Run(globals *types.Globals) error {
+ w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
+ fmt.Fprintln(w, "Description\tName\tThree-letter Name\tOne-letter Name")
+ fmt.Fprintln(w, "-----------\t----\t-----------------\t---------------")
+ for _, typ := range types.FiletypeInfos(f.All) {
+ fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", typ.Desc, typ.Name, typ.ThreeLetter, typ.OneLetter)
}
+ w.Flush()
return nil
}
diff --git a/cmd/nakedos.go b/cmd/nakedos.go
index 7fb1ed8..bf6e8a6 100644
--- a/cmd/nakedos.go
+++ b/cmd/nakedos.go
@@ -4,87 +4,76 @@ package cmd
import (
"fmt"
- "os"
- "github.com/spf13/cobra"
- "github.com/zellyn/diskii/lib/disk"
- "github.com/zellyn/diskii/lib/supermon"
+ "github.com/zellyn/diskii/disk"
+ "github.com/zellyn/diskii/supermon"
+ "github.com/zellyn/diskii/types"
)
-// nakedosCmd represents the nakedos command
-var nakedosCmd = &cobra.Command{
- Use: "nakedos",
- Short: "work with NakedOS disks",
- Long: `diskii nakedos contains the subcommands useful for working
-with NakedOS (and Super-Mon) disks`,
- Aliases: []string{"supermon"},
+const helloName = "FHELLO" // filename to use (if Super-Mon)
+
+type NakedOSCmd struct {
+ Mkhello MkHelloCmd `kong:"cmd,help='Create an FHELLO program that loads and runs another file.'"`
}
-func init() {
- RootCmd.AddCommand(nakedosCmd)
+// Help shows extended help on NakedOS/Super-Mon.
+func (n NakedOSCmd) Help() string {
+ return `NakedOS and Super-Mon were created by the amazing Martin Haye. For more information see:
+ Source/docs: https://bitbucket.org/martin.haye/super-mon/
+ Presentation: https://www.kansasfest.org/2012/08/2010-haye-nakedos/`
}
-// ----- mkhello command ----------------------------------------------------
+type MkHelloCmd struct {
+ Order types.DiskOrder `kong:"default='auto',enum='auto,do,po',help='Logical-to-physical sector order.'"`
-var address uint16 // flag for address to load at
-var start uint16 // flag for address to start execution at
-const helloName = "FHELLO" // filename to use (if Super-Mon)
+ DiskImage string `kong:"arg,required,type='existingfile',help='Disk image to modify.'"`
+ Filename string `kong:"arg,required,help='Name of NakedOS file to load.'"`
-// mkhelloCmd represents the mkhello command
-var mkhelloCmd = &cobra.Command{
- Use: "mkhello filename",
- Short: "create an FHELLO program that loads and runs another file",
- Long: `
-mkhello creates file DF01:FHELLO that loads and runs another program at a specific address.
-
-Examples:
-mkhello test.dsk FDEMO # load and run FDEMO at the default address, then jump to the start of the loaded code.
-mkhello test.dsk --address 0x2000 --start 0x2100 DF06 # load and run file DF06 at address 0x2000, and jump to 0x2100.`,
- Run: func(cmd *cobra.Command, args []string) {
- if err := runMkhello(args); err != nil {
- fmt.Fprintln(os.Stderr, err.Error())
- os.Exit(-1)
- }
- },
+ Address uint16 `kong:"type='anybaseuint16',default='0x6000',help='Address to load the code at.'"`
+ Start uint16 `kong:"type='anybaseuint16',default='0xFFFF',help='Address to jump to. Defaults to 0xFFFF, which means “same as address flag”'"`
}
-func init() {
- nakedosCmd.AddCommand(mkhelloCmd)
-
- // Here you will define your flags and configuration settings.
+func (m MkHelloCmd) Help() string {
+ return `This command creates a very short DF01:FHELLO program that simply loads another program of your choice.
+
+Examples:
+ # Load and run FDEMO at the default address, then jump to the start of the loaded code.
+ mkhello test.dsk FDEMO
- mkhelloCmd.Flags().Uint16VarP(&address, "address", "a", 0x6000, "memory location to load code at")
- mkhelloCmd.Flags().Uint16VarP(&start, "start", "s", 0x6000, "memory location to jump to")
+ # Load and run file DF06 at address 0x2000, and jump to 0x2100.
+ mkhello test.dsk --address 0x2000 --start 0x2100 DF06`
}
-// runMkhello performs the actual mkhello logic.
-func runMkhello(args []string) error {
- if len(args) != 2 {
- return fmt.Errorf("usage: diskii mkhello ")
+func (m *MkHelloCmd) Run(globals *types.Globals) error {
+ if m.Start == 0xFFFF {
+ m.Start = m.Address
}
- if address%256 != 0 {
- return fmt.Errorf("address %d (%04X) not on a page boundary", address, address)
+
+ if m.Address%256 != 0 {
+ return fmt.Errorf("address %d (%04X) not on a page boundary", m.Address, m.Address)
}
- if start < address {
- return fmt.Errorf("start address %d (%04X) < load address %d (%04X)", start, start, address, address)
+ if m.Start < m.Address {
+ return fmt.Errorf("start address %d (%04X) < load address %d (%04X)", m.Start, m.Start, m.Address, m.Address)
}
- op, err := disk.Open(args[0])
+
+ op, order, err := disk.OpenFilename(m.DiskImage, m.Order, "auto", globals.DiskOperatorFactories, globals.Debug)
if err != nil {
return err
}
+
if op.Name() != "nakedos" {
return fmt.Errorf("mkhello only works on disks of type %q; got %q", "nakedos", op.Name())
}
nakOp, ok := op.(supermon.Operator)
if !ok {
- return fmt.Errorf("internal error: cannot cast to expected supermon.Operator type")
+ return fmt.Errorf("internal error: cannot cast to expected supermon.Operator type (got %T)", op)
}
- addr, symbolAddr, _, err := nakOp.ST.FilesForCompoundName(args[1])
+ addr, symbolAddr, _, err := nakOp.ST.FilesForCompoundName(m.Filename)
if err != nil {
return err
}
if addr == 0 && symbolAddr == 0 {
- return fmt.Errorf("cannot parse %q as valid filename", args[1])
+ return fmt.Errorf("cannot parse %q as valid filename", m.Filename)
}
toLoad := addr
if addr == 0 {
@@ -94,32 +83,24 @@ func runMkhello(args []string) error {
0x20, 0x40, 0x03, // JSR NAKEDOS
0x6D, 0x01, 0xDC, // ADC NKRDFILE
0x2C, toLoad, 0xDF, // BIT ${file number to load}
- 0x2C, 0x00, byte(address >> 8), // BIT ${target page}
- 0xD8, // CLD
- 0x4C, byte(start), byte(start >> 8), // JMP ${target page}
+ 0x2C, 0x00, byte(m.Address >> 8), // BIT ${target page}
+ 0xD8, // CLD
+ 0x4C, byte(m.Start), byte(m.Start >> 8), // JMP ${target page}
}
- fileInfo := disk.FileInfo{
- Descriptor: disk.Descriptor{
+ fileInfo := types.FileInfo{
+ Descriptor: types.Descriptor{
Name: fmt.Sprintf("DF01:%s", helloName),
Length: len(contents),
- Type: disk.FiletypeBinary,
+ Type: types.FiletypeBinary,
},
Data: contents,
}
+ _ = fileInfo
+
_, err = op.PutFile(fileInfo, true)
if err != nil {
return err
}
- f, err := os.Create(args[0])
- if err != nil {
- return err
- }
- _, err = op.Write(f)
- if err != nil {
- return err
- }
- if err = f.Close(); err != nil {
- return err
- }
- return nil
+
+ return disk.WriteBack(m.DiskImage, op, order, true)
}
diff --git a/cmd/put.go b/cmd/put.go
index f057273..9143911 100644
--- a/cmd/put.go
+++ b/cmd/put.go
@@ -4,80 +4,61 @@ package cmd
import (
"fmt"
- "os"
- "github.com/spf13/cobra"
- "github.com/zellyn/diskii/lib/disk"
- "github.com/zellyn/diskii/lib/helpers"
+ "github.com/zellyn/diskii/disk"
+ "github.com/zellyn/diskii/helpers"
+ "github.com/zellyn/diskii/types"
)
-var filetypeName string // flag for file type
-var overwrite bool // flag for whether to overwrite
+type PutCmd struct {
+ Order types.DiskOrder `kong:"default='auto',enum='auto,do,po',help='Logical-to-physical sector order.'"`
+ System string `kong:"default='auto',enum='auto,dos3',help='DOS system used for image.'"`
+ FiletypeName string `kong:"default='B',help='Type of file (“diskii filetypes” to list).'"`
+ Overwrite bool `kong:"short='f',help='Overwrite existing file?'"`
-// putCmd represents the put command, used to put the raw contents
-// of a file.
-var putCmd = &cobra.Command{
- Use: "put",
- Short: "put the raw contents of a file",
- Long: `Put the raw contents of a file.
-
-put disk-image.dsk HELLO
-`,
- Run: func(cmd *cobra.Command, args []string) {
- if err := runPut(args); err != nil {
- fmt.Fprintln(os.Stderr, err.Error())
- os.Exit(-1)
- }
- },
+ DiskImage string `kong:"arg,required,type='existingfile',help='Disk image to modify.'"`
+ TargetFilename string `kong:"arg,required,help='Filename to use on disk.'"`
+ SourceFilename string `kong:"arg,required,type='existingfile',help='Name of file containing data to put.'"`
}
-func init() {
- RootCmd.AddCommand(putCmd)
- putCmd.Flags().StringVarP(&filetypeName, "type", "t", "B", "Type of file (`diskii filetypes` to list)")
- putCmd.Flags().BoolVarP(&overwrite, "overwrite", "f", false, "whether to overwrite existing files")
+func (p PutCmd) Help() string {
+ return `Examples:
+ # Put file gremlins.o onto disk image games.dsk, using the filename GREMLINS.
+ diskii put games.dsk GREMLINS gremlins.o`
}
-// runPut performs the actual put logic.
-func runPut(args []string) error {
- if len(args) != 3 {
- return fmt.Errorf("usage: put