mirror of
https://github.com/zellyn/diskii.git
synced 2024-06-09 19:29:29 +00:00
more lints, removed woz
This commit is contained in:
parent
23c9b1edcf
commit
c2dd6362de
|
@ -1,3 +1,5 @@
|
||||||
|
issues:
|
||||||
|
exclude-use-default: false
|
||||||
linters:
|
linters:
|
||||||
enable:
|
enable:
|
||||||
- gocritic
|
- gocritic
|
||||||
|
|
|
@ -11,10 +11,12 @@ import (
|
||||||
"github.com/zellyn/diskii/types"
|
"github.com/zellyn/diskii/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ApplesoftCmd is the kong `applesoft` command.
|
||||||
type ApplesoftCmd struct {
|
type ApplesoftCmd struct {
|
||||||
Decode DecodeCmd `kong:"cmd,help='Convert a binary Applesoft program to a text LISTing.'"`
|
Decode DecodeCmd `kong:"cmd,help='Convert a binary Applesoft program to a text LISTing.'"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DecodeCmd is the kong `decode` command.
|
||||||
type DecodeCmd struct {
|
type DecodeCmd struct {
|
||||||
Filename string `kong:"arg,default='-',type='existingfile',help='Binary Applesoft file to read, or “-” for stdin.'"`
|
Filename string `kong:"arg,default='-',type='existingfile',help='Binary Applesoft file to read, or “-” for stdin.'"`
|
||||||
|
|
||||||
|
@ -22,12 +24,14 @@ type DecodeCmd struct {
|
||||||
Raw bool `kong:"short='r',help='Print raw control codes (no escaping)'"`
|
Raw bool `kong:"short='r',help='Print raw control codes (no escaping)'"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Help displays extended help and examples.
|
||||||
func (d DecodeCmd) Help() string {
|
func (d DecodeCmd) Help() string {
|
||||||
return `Examples:
|
return `Examples:
|
||||||
# Dump the contents of HELLO and then decode it.
|
# Dump the contents of HELLO and then decode it.
|
||||||
diskii dump dos33master.dsk HELLO | diskii applesoft decode -`
|
diskii dump dos33master.dsk HELLO | diskii applesoft decode -`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Run the decode command.
|
||||||
func (d *DecodeCmd) Run(globals *types.Globals) error {
|
func (d *DecodeCmd) Run(globals *types.Globals) error {
|
||||||
contents, err := helpers.FileContentsOrStdIn(d.Filename)
|
contents, err := helpers.FileContentsOrStdIn(d.Filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -38,9 +42,9 @@ func (d *DecodeCmd) Run(globals *types.Globals) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if d.Raw {
|
if d.Raw {
|
||||||
os.Stdout.WriteString(listing.String())
|
_, _ = os.Stdout.WriteString(listing.String())
|
||||||
} else {
|
} else {
|
||||||
os.Stdout.WriteString(basic.ChevronControlCodes(listing.String()))
|
_, _ = os.Stdout.WriteString(basic.ChevronControlCodes(listing.String()))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"github.com/zellyn/diskii/types"
|
"github.com/zellyn/diskii/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// LsCmd is the kong `ls` command.
|
||||||
type LsCmd struct {
|
type LsCmd struct {
|
||||||
Order types.DiskOrder `kong:"default='auto',enum='auto,do,po',help='Logical-to-physical sector order.'"`
|
Order types.DiskOrder `kong:"default='auto',enum='auto,do,po',help='Logical-to-physical sector order.'"`
|
||||||
System string `kong:"default='auto',enum='auto,dos3,prodos,nakedos',help='DOS system used for image.'"`
|
System string `kong:"default='auto',enum='auto,dos3,prodos,nakedos',help='DOS system used for image.'"`
|
||||||
|
@ -19,6 +20,7 @@ type LsCmd struct {
|
||||||
Directory string `kong:"arg,optional,help='Directory to list (ProDOS only).'"`
|
Directory string `kong:"arg,optional,help='Directory to list (ProDOS only).'"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Help displays extended help and examples.
|
||||||
func (l LsCmd) Help() string {
|
func (l LsCmd) Help() string {
|
||||||
return `Examples:
|
return `Examples:
|
||||||
# Simple ls of a disk image
|
# Simple ls of a disk image
|
||||||
|
@ -27,6 +29,7 @@ func (l LsCmd) Help() string {
|
||||||
diskii ls --order do --system nakedos Super-Mon-2.0.dsk`
|
diskii ls --order do --system nakedos Super-Mon-2.0.dsk`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Run the `ls` command.
|
||||||
func (l *LsCmd) Run(globals *types.Globals) error {
|
func (l *LsCmd) Run(globals *types.Globals) error {
|
||||||
op, order, err := disk.OpenFile(l.Image, l.Order, l.System, globals.DiskOperatorFactories, globals.Debug)
|
op, order, err := disk.OpenFile(l.Image, l.Order, l.System, globals.DiskOperatorFactories, globals.Debug)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"github.com/zellyn/diskii/types"
|
"github.com/zellyn/diskii/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// DeleteCmd is the kong `delete` command.
|
||||||
type DeleteCmd struct {
|
type DeleteCmd struct {
|
||||||
Order types.DiskOrder `kong:"default='auto',enum='auto,do,po',help='Logical-to-physical sector order.'"`
|
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.'"`
|
System string `kong:"default='auto',enum='auto,dos3',help='DOS system used for image.'"`
|
||||||
|
@ -18,12 +19,14 @@ type DeleteCmd struct {
|
||||||
Filename string `kong:"arg,required,help='Filename to use on disk.'"`
|
Filename string `kong:"arg,required,help='Filename to use on disk.'"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Help displays extended help and examples.
|
||||||
func (d DeleteCmd) Help() string {
|
func (d DeleteCmd) Help() string {
|
||||||
return `Examples:
|
return `Examples:
|
||||||
# Delete file GREMLINS on disk image games.dsk.
|
# Delete file GREMLINS on disk image games.dsk.
|
||||||
diskii rm games.dsk GREMLINS`
|
diskii rm games.dsk GREMLINS`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Run the `delete` command.
|
||||||
func (d *DeleteCmd) Run(globals *types.Globals) error {
|
func (d *DeleteCmd) Run(globals *types.Globals) error {
|
||||||
op, order, err := disk.OpenFilename(d.DiskImage, d.Order, d.System, globals.DiskOperatorFactories, globals.Debug)
|
op, order, err := disk.OpenFilename(d.DiskImage, d.Order, d.System, globals.DiskOperatorFactories, globals.Debug)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
2
cmd/doc.go
Normal file
2
cmd/doc.go
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
// Package cmd contains the actual command implementations.
|
||||||
|
package cmd
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"github.com/zellyn/diskii/types"
|
"github.com/zellyn/diskii/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// DumpCmd is the kong `dump` command.
|
||||||
type DumpCmd struct {
|
type DumpCmd struct {
|
||||||
Order types.DiskOrder `kong:"default='auto',enum='auto,do,po',help='Logical-to-physical sector order.'"`
|
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.'"`
|
System string `kong:"default='auto',enum='auto,dos3',help='DOS system used for image.'"`
|
||||||
|
@ -17,12 +18,14 @@ type DumpCmd struct {
|
||||||
Filename string `kong:"arg,required,help='Filename to use on disk.'"`
|
Filename string `kong:"arg,required,help='Filename to use on disk.'"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Help displays extended help and examples.
|
||||||
func (d DumpCmd) Help() string {
|
func (d DumpCmd) Help() string {
|
||||||
return `Examples:
|
return `Examples:
|
||||||
# Dump file GREMLINS on disk image games.dsk.
|
# Dump file GREMLINS on disk image games.dsk.
|
||||||
diskii dump games.dsk GREMLINS`
|
diskii dump games.dsk GREMLINS`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Run the `dump` command.
|
||||||
func (d *DumpCmd) Run(globals *types.Globals) error {
|
func (d *DumpCmd) Run(globals *types.Globals) error {
|
||||||
op, _, err := disk.OpenFilename(d.DiskImage, d.Order, d.System, globals.DiskOperatorFactories, globals.Debug)
|
op, _, err := disk.OpenFilename(d.DiskImage, d.Order, d.System, globals.DiskOperatorFactories, globals.Debug)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -10,10 +10,12 @@ import (
|
||||||
"github.com/zellyn/diskii/types"
|
"github.com/zellyn/diskii/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// FiletypesCmd is the kong `filetypes` command.
|
||||||
type FiletypesCmd struct {
|
type FiletypesCmd struct {
|
||||||
All bool `kong:"help='Display all types, including SOS types and reserved ranges.'"`
|
All bool `kong:"help='Display all types, including SOS types and reserved ranges.'"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Run the `filetypes` command.
|
||||||
func (f *FiletypesCmd) Run(globals *types.Globals) error {
|
func (f *FiletypesCmd) Run(globals *types.Globals) error {
|
||||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
|
w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
|
||||||
fmt.Fprintln(w, "Description\tName\tThree-letter Name\tOne-letter Name")
|
fmt.Fprintln(w, "Description\tName\tThree-letter Name\tOne-letter Name")
|
||||||
|
@ -21,6 +23,6 @@ func (f *FiletypesCmd) Run(globals *types.Globals) error {
|
||||||
for _, typ := range types.FiletypeInfos(f.All) {
|
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)
|
fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", typ.Desc, typ.Name, typ.ThreeLetter, typ.OneLetter)
|
||||||
}
|
}
|
||||||
w.Flush()
|
_ = w.Flush()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
|
|
||||||
const helloName = "FHELLO" // filename to use (if Super-Mon)
|
const helloName = "FHELLO" // filename to use (if Super-Mon)
|
||||||
|
|
||||||
|
// NakedOSCmd is the kong `nakedos` sub-command.
|
||||||
type NakedOSCmd struct {
|
type NakedOSCmd struct {
|
||||||
Mkhello MkHelloCmd `kong:"cmd,help='Create an FHELLO program that loads and runs another file.'"`
|
Mkhello MkHelloCmd `kong:"cmd,help='Create an FHELLO program that loads and runs another file.'"`
|
||||||
}
|
}
|
||||||
|
@ -23,6 +24,7 @@ func (n NakedOSCmd) Help() string {
|
||||||
Presentation: https://www.kansasfest.org/2012/08/2010-haye-nakedos/`
|
Presentation: https://www.kansasfest.org/2012/08/2010-haye-nakedos/`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MkHelloCmd is the kong `mkhello` command.
|
||||||
type MkHelloCmd struct {
|
type MkHelloCmd struct {
|
||||||
Order types.DiskOrder `kong:"default='auto',enum='auto,do,po',help='Logical-to-physical sector order.'"`
|
Order types.DiskOrder `kong:"default='auto',enum='auto,do,po',help='Logical-to-physical sector order.'"`
|
||||||
|
|
||||||
|
@ -33,6 +35,7 @@ type MkHelloCmd struct {
|
||||||
Start uint16 `kong:"type='anybaseuint16',default='0xFFFF',help='Address to jump to. Defaults to 0xFFFF, which means “same as address flag”'"`
|
Start uint16 `kong:"type='anybaseuint16',default='0xFFFF',help='Address to jump to. Defaults to 0xFFFF, which means “same as address flag”'"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Help displays extended help and examples.
|
||||||
func (m MkHelloCmd) Help() string {
|
func (m MkHelloCmd) Help() string {
|
||||||
return `This command creates a very short DF01:FHELLO program that simply loads another program of your choice.
|
return `This command creates a very short DF01:FHELLO program that simply loads another program of your choice.
|
||||||
|
|
||||||
|
@ -44,6 +47,7 @@ Examples:
|
||||||
mkhello test.dsk --address 0x2000 --start 0x2100 DF06`
|
mkhello test.dsk --address 0x2000 --start 0x2100 DF06`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Run the `mkhello` command.
|
||||||
func (m *MkHelloCmd) Run(globals *types.Globals) error {
|
func (m *MkHelloCmd) Run(globals *types.Globals) error {
|
||||||
if m.Start == 0xFFFF {
|
if m.Start == 0xFFFF {
|
||||||
m.Start = m.Address
|
m.Start = m.Address
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"github.com/zellyn/diskii/types"
|
"github.com/zellyn/diskii/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// PutCmd is the kong `put` command.
|
||||||
type PutCmd struct {
|
type PutCmd struct {
|
||||||
Order types.DiskOrder `kong:"default='auto',enum='auto,do,po',help='Logical-to-physical sector order.'"`
|
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.'"`
|
System string `kong:"default='auto',enum='auto,dos3',help='DOS system used for image.'"`
|
||||||
|
@ -21,12 +22,14 @@ type PutCmd struct {
|
||||||
SourceFilename string `kong:"arg,required,type='existingfile',help='Name of file containing data to put.'"`
|
SourceFilename string `kong:"arg,required,type='existingfile',help='Name of file containing data to put.'"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Help displays extended help and examples.
|
||||||
func (p PutCmd) Help() string {
|
func (p PutCmd) Help() string {
|
||||||
return `Examples:
|
return `Examples:
|
||||||
# Put file gremlins.o onto disk image games.dsk, using the filename GREMLINS.
|
# Put file gremlins.o onto disk image games.dsk, using the filename GREMLINS.
|
||||||
diskii put games.dsk GREMLINS gremlins.o`
|
diskii put games.dsk GREMLINS gremlins.o`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Run the `put` command.
|
||||||
func (p *PutCmd) Run(globals *types.Globals) error {
|
func (p *PutCmd) Run(globals *types.Globals) error {
|
||||||
if p.DiskImage == "-" {
|
if p.DiskImage == "-" {
|
||||||
if p.SourceFilename == "-" {
|
if p.SourceFilename == "-" {
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"github.com/zellyn/diskii/types"
|
"github.com/zellyn/diskii/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ReorderCmd is the kong `reorder` command.
|
||||||
type ReorderCmd struct {
|
type ReorderCmd struct {
|
||||||
Order types.DiskOrder `kong:"default='auto',enum='auto,do,po',help='Logical-to-physical sector order.'"`
|
Order types.DiskOrder `kong:"default='auto',enum='auto,do,po',help='Logical-to-physical sector order.'"`
|
||||||
NewOrder types.DiskOrder `kong:"default='auto',enum='auto,do,po',help='New Logical-to-physical sector order.'"`
|
NewOrder types.DiskOrder `kong:"default='auto',enum='auto,do,po',help='New Logical-to-physical sector order.'"`
|
||||||
|
@ -17,6 +18,7 @@ type ReorderCmd struct {
|
||||||
NewDiskImage string `kong:"arg,optional,type='path',help='Disk image to write, if different.'"`
|
NewDiskImage string `kong:"arg,optional,type='path',help='Disk image to write, if different.'"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Run the `reorder` command.
|
||||||
func (r *ReorderCmd) Run(globals *types.Globals) error {
|
func (r *ReorderCmd) Run(globals *types.Globals) error {
|
||||||
fromOrderName, toOrderName, err := getOrders(r.DiskImage, r.Order, r.NewDiskImage, r.NewOrder)
|
fromOrderName, toOrderName, err := getOrders(r.DiskImage, r.Order, r.NewDiskImage, r.NewOrder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"github.com/zellyn/diskii/types"
|
"github.com/zellyn/diskii/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// SDCmd is the kong `mksd` command.
|
||||||
type SDCmd struct {
|
type SDCmd struct {
|
||||||
Order types.DiskOrder `kong:"default='auto',enum='auto,do,po',help='Logical-to-physical sector order.'"`
|
Order types.DiskOrder `kong:"default='auto',enum='auto,do,po',help='Logical-to-physical sector order.'"`
|
||||||
|
|
||||||
|
@ -22,6 +23,7 @@ type SDCmd struct {
|
||||||
Start uint16 `kong:"type='anybaseuint16',default='0xFFFF',help='Address to jump to. Defaults to 0xFFFF, which means “same as address flag”'"`
|
Start uint16 `kong:"type='anybaseuint16',default='0xFFFF',help='Address to jump to. Defaults to 0xFFFF, which means “same as address flag”'"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Help displays extended help and examples.
|
||||||
func (s SDCmd) Help() string {
|
func (s SDCmd) Help() string {
|
||||||
return `
|
return `
|
||||||
See https://github.com/peterferrie/standard-delivery for details.
|
See https://github.com/peterferrie/standard-delivery for details.
|
||||||
|
@ -34,6 +36,7 @@ Examples:
|
||||||
diskii mksd test.dsk foo.o --address 0x2000 --start 0x2100`
|
diskii mksd test.dsk foo.o --address 0x2000 --start 0x2100`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Run the `mksd` command.
|
||||||
func (s *SDCmd) Run(globals *types.Globals) error {
|
func (s *SDCmd) Run(globals *types.Globals) error {
|
||||||
if s.Start == 0xFFFF {
|
if s.Start == 0xFFFF {
|
||||||
s.Start = s.Address
|
s.Start = s.Address
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
// Package data is a bunch of go:embed embedded files.
|
||||||
package data
|
package data
|
||||||
|
|
||||||
import _ "embed"
|
import _ "embed"
|
||||||
|
|
73
disk/dev.go
73
disk/dev.go
|
@ -1,73 +0,0 @@
|
||||||
// Copyright © 2017 Zellyn Hunter <zellyn@gmail.com>
|
|
||||||
|
|
||||||
// dev.go contains logic for reading ".po" disk images.
|
|
||||||
|
|
||||||
package disk
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A ProDOS block.
|
|
||||||
type Block [512]byte
|
|
||||||
|
|
||||||
// Dev represents a .po disk image.
|
|
||||||
type Dev struct {
|
|
||||||
data []byte // The actual data in the file
|
|
||||||
blocks uint16 // Number of blocks
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadDev loads a .po image from a file.
|
|
||||||
func LoadDev(filename string) (Dev, error) {
|
|
||||||
bb, err := ioutil.ReadFile(filename)
|
|
||||||
if err != nil {
|
|
||||||
return Dev{}, err
|
|
||||||
}
|
|
||||||
if len(bb)%512 != 0 {
|
|
||||||
return Dev{}, fmt.Errorf("expected file %q to contain a multiple of 512 bytes, but got %d", filename, len(bb))
|
|
||||||
}
|
|
||||||
return Dev{
|
|
||||||
data: bb,
|
|
||||||
blocks: uint16(len(bb) / 512),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Empty creates a .po image that is all zeros.
|
|
||||||
func EmptyDev(blocks uint16) Dev {
|
|
||||||
return Dev{
|
|
||||||
data: make([]byte, 512*int(blocks)),
|
|
||||||
blocks: blocks,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadBlock reads a single block from the device. It always returns
|
|
||||||
// 512 byes.
|
|
||||||
func (d Dev) ReadBlock(index uint16) (Block, error) {
|
|
||||||
var b Block
|
|
||||||
copy(b[:], d.data[int(index)*512:int(index+1)*512])
|
|
||||||
return b, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteBlock writes a single block to a device. It expects exactly
|
|
||||||
// 512 bytes.
|
|
||||||
func (d Dev) WriteBlock(index uint16, data Block) error {
|
|
||||||
copy(d.data[int(index)*512:int(index+1)*512], data[:])
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Blocks returns the number of blocks in the device.
|
|
||||||
func (d Dev) Blocks() uint16 {
|
|
||||||
return d.blocks
|
|
||||||
}
|
|
||||||
|
|
||||||
// Order returns the order of blocks on the device.
|
|
||||||
func (d Dev) Order() string {
|
|
||||||
return "prodos"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write writes the device contents to the given file.
|
|
||||||
func (d Dev) Write(w io.Writer) (n int, err error) {
|
|
||||||
return w.Write(d.data)
|
|
||||||
}
|
|
|
@ -65,3 +65,6 @@ type TrackSector struct {
|
||||||
Track byte
|
Track byte
|
||||||
Sector byte
|
Sector byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Block is a ProDOS block: 512 bytes.
|
||||||
|
type Block [512]byte
|
||||||
|
|
18
disk/open.go
18
disk/open.go
|
@ -5,6 +5,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/zellyn/diskii/helpers"
|
"github.com/zellyn/diskii/helpers"
|
||||||
|
@ -16,15 +17,15 @@ func OpenFilename(filename string, order types.DiskOrder, system string, operato
|
||||||
if filename == "-" {
|
if filename == "-" {
|
||||||
return OpenFile(os.Stdin, order, system, operatorFactories, debug)
|
return OpenFile(os.Stdin, order, system, operatorFactories, debug)
|
||||||
}
|
}
|
||||||
file, err := os.Open(filename)
|
file, err := os.Open(filepath.Clean(filename))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
return OpenFile(file, order, system, operatorFactories, debug)
|
return OpenFile(file, order, system, operatorFactories, debug)
|
||||||
}
|
}
|
||||||
|
|
||||||
// OpenImage attempts to open a disk or device image, using the provided ordering and system type.
|
// OpenFile attempts to open a disk or device image, using the provided ordering and system type.
|
||||||
// OpenImage will close the file.
|
// OpenFile will close the file.
|
||||||
func OpenFile(file *os.File, order types.DiskOrder, system string, operatorFactories []types.OperatorFactory, debug bool) (types.Operator, types.DiskOrder, error) {
|
func OpenFile(file *os.File, order types.DiskOrder, system string, operatorFactories []types.OperatorFactory, debug bool) (types.Operator, types.DiskOrder, error) {
|
||||||
bb, err := io.ReadAll(file)
|
bb, err := io.ReadAll(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -173,17 +174,6 @@ func Swizzle(diskimage []byte, order []int) ([]byte, error) {
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func UnSwizzle(diskimage []byte, order []int) ([]byte, error) {
|
|
||||||
if err := validateOrder(order); err != nil {
|
|
||||||
return nil, fmt.Errorf("called UnSwizzle with weird order: %w", err)
|
|
||||||
}
|
|
||||||
reverseOrder := make([]int, FloppySectors)
|
|
||||||
for index, mapping := range order {
|
|
||||||
reverseOrder[mapping] = index
|
|
||||||
}
|
|
||||||
return Swizzle(diskimage, reverseOrder)
|
|
||||||
}
|
|
||||||
|
|
||||||
// validateOrder validates that an order mapping is valid, and maps [0,15] onto
|
// validateOrder validates that an order mapping is valid, and maps [0,15] onto
|
||||||
// [0,15] without repeats.
|
// [0,15] without repeats.
|
||||||
func validateOrder(order []int) error {
|
func validateOrder(order []int) error {
|
||||||
|
|
|
@ -21,6 +21,7 @@ func FileContentsOrStdIn(s string) ([]byte, error) {
|
||||||
return os.ReadFile(s)
|
return os.ReadFile(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WriteOutput writes a byte slice to the given filename, using `-` for standard out.
|
||||||
func WriteOutput(filename string, contents []byte, force bool) error {
|
func WriteOutput(filename string, contents []byte, force bool) error {
|
||||||
if filename == "-" {
|
if filename == "-" {
|
||||||
_, err := os.Stdout.Write(contents)
|
_, err := os.Stdout.Write(contents)
|
||||||
|
|
|
@ -60,8 +60,10 @@ func (bp bitmapPart) ToBlock() (disk.Block, error) {
|
||||||
return bp.data, nil
|
return bp.data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VolumeBitMap represents a volume bitmap.
|
||||||
type VolumeBitMap []bitmapPart
|
type VolumeBitMap []bitmapPart
|
||||||
|
|
||||||
|
// NewVolumeBitMap returns a volume bitmap of the given size.
|
||||||
func NewVolumeBitMap(startBlock uint16, blocks uint16) VolumeBitMap {
|
func NewVolumeBitMap(startBlock uint16, blocks uint16) VolumeBitMap {
|
||||||
vbm := VolumeBitMap(make([]bitmapPart, (blocks+(512*8)-1)/(512*8)))
|
vbm := VolumeBitMap(make([]bitmapPart, (blocks+(512*8)-1)/(512*8)))
|
||||||
for i := range vbm {
|
for i := range vbm {
|
||||||
|
@ -73,10 +75,12 @@ func NewVolumeBitMap(startBlock uint16, blocks uint16) VolumeBitMap {
|
||||||
return vbm
|
return vbm
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MarkUsed marks the given block as used.
|
||||||
func (vbm VolumeBitMap) MarkUsed(block uint16) {
|
func (vbm VolumeBitMap) MarkUsed(block uint16) {
|
||||||
vbm.mark(block, false)
|
vbm.mark(block, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MarkUnused marks the given block as free.
|
||||||
func (vbm VolumeBitMap) MarkUnused(block uint16) {
|
func (vbm VolumeBitMap) MarkUnused(block uint16) {
|
||||||
vbm.mark(block, true)
|
vbm.mark(block, true)
|
||||||
}
|
}
|
||||||
|
@ -261,6 +265,7 @@ func (vdb VolumeDirectoryBlock) Validate() (errors []error) {
|
||||||
return errors
|
return errors
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VolumeDirectoryHeader represents a volume directory header.
|
||||||
type VolumeDirectoryHeader struct {
|
type VolumeDirectoryHeader struct {
|
||||||
TypeAndNameLength byte // Storage type (top four bits) and volume name length (lower four).
|
TypeAndNameLength byte // Storage type (top four bits) and volume name length (lower four).
|
||||||
VolumeName [15]byte // Volume name (actual length defined in TypeAndNameLength)
|
VolumeName [15]byte // Volume name (actual length defined in TypeAndNameLength)
|
||||||
|
@ -319,14 +324,20 @@ func (vdh VolumeDirectoryHeader) Validate() (errors []error) {
|
||||||
return errors
|
return errors
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Access represents a level of file access.
|
||||||
type Access byte
|
type Access byte
|
||||||
|
|
||||||
const (
|
const (
|
||||||
AccessReadable Access = 0x01
|
// AccessReadable denotes a file as readable.
|
||||||
AccessWritable Access = 0x02
|
AccessReadable Access = 0x01
|
||||||
|
// AccessWritable denotes a file as writable.
|
||||||
|
AccessWritable Access = 0x02
|
||||||
|
// AccessChangedSinceBackup is (I think) always true on real disks.
|
||||||
AccessChangedSinceBackup Access = 0x20
|
AccessChangedSinceBackup Access = 0x20
|
||||||
AccessRenamable Access = 0x40
|
// AccessRenamable denotes a file as renamable.
|
||||||
AccessDestroyable Access = 0x80
|
AccessRenamable Access = 0x40
|
||||||
|
// AccessDestroyable denotes a file as deletable.
|
||||||
|
AccessDestroyable Access = 0x80
|
||||||
)
|
)
|
||||||
|
|
||||||
// FileDescriptor is the entry in the volume directory for a file or
|
// FileDescriptor is the entry in the volume directory for a file or
|
||||||
|
@ -420,7 +431,7 @@ func (fd FileDescriptor) Validate() (errors []error) {
|
||||||
return errors
|
return errors
|
||||||
}
|
}
|
||||||
|
|
||||||
// An index block contains 256 16-bit block numbers, pointing to other
|
// IndexBlock is an index block, containing 256 16-bit block numbers, pointing to other
|
||||||
// blocks. The LSBs are stored in the first half, MSBs in the second.
|
// blocks. The LSBs are stored in the first half, MSBs in the second.
|
||||||
type IndexBlock disk.Block
|
type IndexBlock disk.Block
|
||||||
|
|
||||||
|
@ -537,6 +548,7 @@ func (sb SubdirectoryBlock) Validate() (errors []error) {
|
||||||
return errors
|
return errors
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SubdirectoryHeader represents a subdirectory header.
|
||||||
type SubdirectoryHeader struct {
|
type SubdirectoryHeader struct {
|
||||||
TypeAndNameLength byte // Storage type (top four bits) and subdirectory name length (lower four).
|
TypeAndNameLength byte // Storage type (top four bits) and subdirectory name length (lower four).
|
||||||
SubdirectoryName [15]byte // Subdirectory name (actual length defined in TypeAndNameLength)
|
SubdirectoryName [15]byte // Subdirectory name (actual length defined in TypeAndNameLength)
|
||||||
|
|
|
@ -4,6 +4,7 @@ package supermon
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -48,7 +49,7 @@ func loadSectorMap(filename string) (SectorMap, []byte, error) {
|
||||||
// getOperator gets a types.Operator for the given NakedOS disk, assumed to be
|
// getOperator gets a types.Operator for the given NakedOS disk, assumed to be
|
||||||
// in "do" order.
|
// in "do" order.
|
||||||
func getOperator(filename string) (types.Operator, error) {
|
func getOperator(filename string) (types.Operator, error) {
|
||||||
f, err := os.Open(filename)
|
f, err := os.Open(filepath.Clean(filename))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
14
types/ops.go
14
types/ops.go
|
@ -17,13 +17,19 @@ type Descriptor struct {
|
||||||
Type Filetype
|
Type Filetype
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DiskOrder specifies the logical disk ordering.
|
||||||
type DiskOrder string
|
type DiskOrder string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
DiskOrderDO = DiskOrder("do")
|
// DiskOrderDO is the DOS 3.3 logical ordering.
|
||||||
DiskOrderPO = DiskOrder("po")
|
DiskOrderDO = DiskOrder("do")
|
||||||
DiskOrderRaw = DiskOrder("raw")
|
// DiskOrderPO is the ProDOS logical ordering.
|
||||||
DiskOrderAuto = DiskOrder("auto")
|
DiskOrderPO = DiskOrder("po")
|
||||||
|
// DiskOrderRaw is the logical ordering that doesn't change anything.
|
||||||
|
DiskOrderRaw = DiskOrder("raw")
|
||||||
|
// DiskOrderAuto is the logical ordering that tells diskii to guess.
|
||||||
|
DiskOrderAuto = DiskOrder("auto")
|
||||||
|
// DiskOrderUnknown is usually an error condition, or a signal that guessing failed.
|
||||||
DiskOrderUnknown = DiskOrder("")
|
DiskOrderUnknown = DiskOrder("")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Package types holds various types that are needed all over the place. They're
|
||||||
|
// in their own package to avoid circular dependencies.
|
||||||
package types
|
package types
|
||||||
|
|
||||||
// Globals holds flags and configuration that are shared globally.
|
// Globals holds flags and configuration that are shared globally.
|
||||||
|
|
268
woz/woz.go
268
woz/woz.go
|
@ -1,268 +0,0 @@
|
||||||
package woz
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"fmt"
|
|
||||||
"hash"
|
|
||||||
"hash/crc32"
|
|
||||||
"io"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
const wozHeader = "WOZ1\xFF\n\r\n"
|
|
||||||
const TrackLength = 6656
|
|
||||||
|
|
||||||
type Woz struct {
|
|
||||||
Info Info
|
|
||||||
Unknowns []UnknownChunk
|
|
||||||
TMap [160]uint8
|
|
||||||
TRKS []TRK
|
|
||||||
Metadata Metadata
|
|
||||||
}
|
|
||||||
|
|
||||||
type UnknownChunk struct {
|
|
||||||
ID string
|
|
||||||
Data []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
type DiskType uint8
|
|
||||||
|
|
||||||
const (
|
|
||||||
DiskType525 DiskType = 1
|
|
||||||
DiskType35 DiskType = 2
|
|
||||||
)
|
|
||||||
|
|
||||||
type Info struct {
|
|
||||||
Version uint8
|
|
||||||
DiskType DiskType
|
|
||||||
WriteProtected bool
|
|
||||||
Synchronized bool
|
|
||||||
Cleaned bool
|
|
||||||
Creator string
|
|
||||||
}
|
|
||||||
|
|
||||||
type TRK struct {
|
|
||||||
BitStream [6646]uint8
|
|
||||||
BytesUsed uint16
|
|
||||||
BitCount uint16
|
|
||||||
SplicePoint uint16
|
|
||||||
SpliceNibble uint8
|
|
||||||
SpliceBitCount uint8
|
|
||||||
Reserved uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
type Metadata struct {
|
|
||||||
Keys []string
|
|
||||||
RawValues map[string]string
|
|
||||||
}
|
|
||||||
|
|
||||||
type decoder struct {
|
|
||||||
r io.Reader
|
|
||||||
woz *Woz
|
|
||||||
crc hash.Hash32
|
|
||||||
tmp [3 * 256]byte
|
|
||||||
crcVal uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// A FormatError reports that the input is not a valid woz file.
|
|
||||||
type FormatError string
|
|
||||||
|
|
||||||
func (e FormatError) Error() string { return "woz: invalid format: " + string(e) }
|
|
||||||
|
|
||||||
type CRCError struct {
|
|
||||||
Declared uint32
|
|
||||||
Computed uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e CRCError) Error() string {
|
|
||||||
return fmt.Sprintf("woz: failed checksum: declared=%d; computed=%d", e.Declared, e.Computed)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *decoder) info(format string, args ...interface{}) {
|
|
||||||
if !strings.HasSuffix(format, "\n") {
|
|
||||||
format += "\n"
|
|
||||||
}
|
|
||||||
fmt.Printf("INFO: "+format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *decoder) warn(format string, args ...interface{}) {
|
|
||||||
if !strings.HasSuffix(format, "\n") {
|
|
||||||
format += "\n"
|
|
||||||
}
|
|
||||||
fmt.Printf("WARN: "+format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *decoder) checkHeader() error {
|
|
||||||
_, err := io.ReadFull(d.r, d.tmp[:len(wozHeader)])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if string(d.tmp[:len(wozHeader)]) != wozHeader {
|
|
||||||
return FormatError("not a woz file")
|
|
||||||
}
|
|
||||||
return binary.Read(d.r, binary.LittleEndian, &d.crcVal)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *decoder) parseChunk() (done bool, err error) {
|
|
||||||
// Read the chunk type and length
|
|
||||||
n, err := io.ReadFull(d.r, d.tmp[:8])
|
|
||||||
if err != nil {
|
|
||||||
if n == 0 && err == io.EOF {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
length := binary.LittleEndian.Uint32(d.tmp[4:8])
|
|
||||||
d.crc.Write(d.tmp[:8])
|
|
||||||
switch string(d.tmp[:4]) {
|
|
||||||
case "INFO":
|
|
||||||
return false, d.parseINFO(length)
|
|
||||||
case "TMAP":
|
|
||||||
return false, d.parseTMAP(length)
|
|
||||||
case "TRKS":
|
|
||||||
return false, d.parseTRKS(length)
|
|
||||||
case "META":
|
|
||||||
return false, d.parseMETA(length)
|
|
||||||
default:
|
|
||||||
return false, d.parseUnknown(string(d.tmp[:4]), length)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *decoder) parseINFO(length uint32) error {
|
|
||||||
d.info("INFO chunk!\n")
|
|
||||||
if length != 60 {
|
|
||||||
d.warn("expected INFO chunk length of 60; got %d", length)
|
|
||||||
}
|
|
||||||
if _, err := io.ReadFull(d.r, d.tmp[:length]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
d.crc.Write(d.tmp[:length])
|
|
||||||
|
|
||||||
d.woz.Info.Version = d.tmp[0]
|
|
||||||
d.woz.Info.DiskType = DiskType(d.tmp[1])
|
|
||||||
d.woz.Info.WriteProtected = d.tmp[2] == 1
|
|
||||||
d.woz.Info.Synchronized = d.tmp[3] == 1
|
|
||||||
d.woz.Info.Cleaned = d.tmp[4] == 1
|
|
||||||
d.woz.Info.Creator = strings.TrimRight(string(d.tmp[5:37]), " ")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *decoder) parseTMAP(length uint32) error {
|
|
||||||
d.info("TMAP chunk!\n")
|
|
||||||
if length != 160 {
|
|
||||||
d.warn("expected TMAP chunk length of 160; got %d", length)
|
|
||||||
}
|
|
||||||
if _, err := io.ReadFull(d.r, d.woz.TMap[:]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
d.crc.Write(d.woz.TMap[:])
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *decoder) parseTRKS(length uint32) error {
|
|
||||||
d.info("TRKS chunk!\n")
|
|
||||||
if length%TrackLength != 0 {
|
|
||||||
return FormatError(fmt.Sprintf("expected TRKS chunk length to be a multiple of %d; got %d", TrackLength, length))
|
|
||||||
}
|
|
||||||
buf := make([]byte, length)
|
|
||||||
if _, err := io.ReadFull(d.r, buf); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
d.crc.Write(buf)
|
|
||||||
|
|
||||||
for offset := 0; offset < int(length); offset += TrackLength {
|
|
||||||
b := buf[offset : offset+TrackLength]
|
|
||||||
t := TRK{
|
|
||||||
BytesUsed: binary.LittleEndian.Uint16(b[6646:6648]),
|
|
||||||
BitCount: binary.LittleEndian.Uint16(b[6648:6650]),
|
|
||||||
SplicePoint: binary.LittleEndian.Uint16(b[6650:6652]),
|
|
||||||
SpliceNibble: b[6652],
|
|
||||||
SpliceBitCount: b[6653],
|
|
||||||
Reserved: binary.LittleEndian.Uint16(b[6654:6656]),
|
|
||||||
}
|
|
||||||
copy(t.BitStream[:], b)
|
|
||||||
d.woz.TRKS = append(d.woz.TRKS, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
// type TRK struct {
|
|
||||||
// Bitstream [6646]uint8
|
|
||||||
// BytesUsed uint16
|
|
||||||
// BitCount uint16
|
|
||||||
// SplicePoint uint16
|
|
||||||
// SpliceNibble uint8
|
|
||||||
// SpliceBitCount uint8
|
|
||||||
// Reserved uint16
|
|
||||||
// }
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *decoder) parseMETA(length uint32) error {
|
|
||||||
d.info("META chunk!\n")
|
|
||||||
buf := make([]byte, length)
|
|
||||||
if _, err := io.ReadFull(d.r, buf); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
d.crc.Write(buf)
|
|
||||||
rows := strings.Split(string(buf), "\n")
|
|
||||||
m := &d.woz.Metadata
|
|
||||||
m.RawValues = make(map[string]string, len(rows))
|
|
||||||
for _, row := range rows {
|
|
||||||
parts := strings.SplitN(row, "\t", 2)
|
|
||||||
if len(parts) == 0 {
|
|
||||||
return FormatError("empty metadata line")
|
|
||||||
}
|
|
||||||
if len(parts) == 1 {
|
|
||||||
return FormatError("strange metadata line with no tab: " + parts[0])
|
|
||||||
}
|
|
||||||
m.Keys = append(m.Keys, parts[0])
|
|
||||||
m.RawValues[parts[0]] = parts[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *decoder) parseUnknown(id string, length uint32) error {
|
|
||||||
d.info("unknown chunk type (%s): ignoring\n", id)
|
|
||||||
buf := make([]byte, length)
|
|
||||||
if _, err := io.ReadFull(d.r, buf); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
d.crc.Write(buf)
|
|
||||||
d.woz.Unknowns = append(d.woz.Unknowns, UnknownChunk{ID: id, Data: buf})
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decode reads a woz disk image from r and returns it as a *Woz.
|
|
||||||
func Decode(r io.Reader) (*Woz, error) {
|
|
||||||
d := &decoder{
|
|
||||||
r: r,
|
|
||||||
crc: crc32.NewIEEE(),
|
|
||||||
woz: &Woz{},
|
|
||||||
}
|
|
||||||
if err := d.checkHeader(); err != nil {
|
|
||||||
if err == io.EOF {
|
|
||||||
err = io.ErrUnexpectedEOF
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read all chunks.
|
|
||||||
for {
|
|
||||||
done, err := d.parseChunk()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if done {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check CRC.
|
|
||||||
if d.crcVal != d.crc.Sum32() {
|
|
||||||
return d.woz, CRCError{Declared: d.crcVal, Computed: d.crc.Sum32()}
|
|
||||||
}
|
|
||||||
|
|
||||||
return d.woz, nil
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
package woz_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/zellyn/diskii/data"
|
|
||||||
"github.com/zellyn/diskii/woz"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestBasicLoad(t *testing.T) {
|
|
||||||
wz, err := woz.Decode(bytes.NewReader(data.DOS33masterWOZ))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if len(wz.Unknowns) > 0 {
|
|
||||||
t.Fatalf("want 0 unknowns; got %d", len(wz.Unknowns))
|
|
||||||
}
|
|
||||||
// fmt.Printf("%#v\n", wz)
|
|
||||||
// t.Fatal("NOTHING")
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user