diff --git a/Cargo.toml b/Cargo.toml index 0fc6b0f..9e16629 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "a2kit" -version = "2.6.1" +version = "2.7.0" edition = "2021" readme = "README.md" license = "MIT" diff --git a/completions/_a2kit b/completions/_a2kit index 3ea1825..2e6b43b 100644 --- a/completions/_a2kit +++ b/completions/_a2kit @@ -271,6 +271,22 @@ _arguments "${_arguments_options[@]}" \ '--help[Print help]' \ && ret=0 ;; +(stat) +_arguments "${_arguments_options[@]}" \ +'-d+[path to disk image]:PATH:_files' \ +'--dimg+[path to disk image]:PATH:_files' \ +'-h[Print help]' \ +'--help[Print help]' \ +&& ret=0 +;; +(geometry) +_arguments "${_arguments_options[@]}" \ +'-d+[path to disk image]:PATH:_files' \ +'--dimg+[path to disk image]:PATH:_files' \ +'-h[Print help]' \ +'--help[Print help]' \ +&& ret=0 +;; (tokenize) _arguments "${_arguments_options[@]}" \ '-a+[address of tokenized code (Applesoft only)]:ADDRESS: ' \ @@ -403,6 +419,14 @@ _arguments "${_arguments_options[@]}" \ _arguments "${_arguments_options[@]}" \ && ret=0 ;; +(stat) +_arguments "${_arguments_options[@]}" \ +&& ret=0 +;; +(geometry) +_arguments "${_arguments_options[@]}" \ +&& ret=0 +;; (tokenize) _arguments "${_arguments_options[@]}" \ && ret=0 @@ -456,6 +480,8 @@ _a2kit_commands() { 'dir:write disk image catalog to stdout' \ 'ls:write disk image catalog to stdout' \ 'tree:write directory tree as a JSON string to stdout' \ +'stat:write FS statistics as a JSON string to stdout' \ +'geometry:write disk geometry as a JSON string to stdout' \ 'tokenize:read from stdin, tokenize, write to stdout' \ 'tok:read from stdin, tokenize, write to stdout' \ 'detokenize:read from stdin, detokenize, write to stdout' \ @@ -494,6 +520,16 @@ _a2kit__help__detokenize_commands() { local commands; commands=() _describe -t commands 'a2kit help detokenize commands' commands "$@" } +(( $+functions[_a2kit__geometry_commands] )) || +_a2kit__geometry_commands() { + local commands; commands=() + _describe -t commands 'a2kit geometry commands' commands "$@" +} +(( $+functions[_a2kit__help__geometry_commands] )) || +_a2kit__help__geometry_commands() { + local commands; commands=() + _describe -t commands 'a2kit help geometry commands' commands "$@" +} (( $+functions[_a2kit__get_commands] )) || _a2kit__get_commands() { local commands; commands=() @@ -523,6 +559,8 @@ _a2kit__help_commands() { 'put:read from stdin, write to local or disk image' \ 'catalog:write disk image catalog to stdout' \ 'tree:write directory tree as a JSON string to stdout' \ +'stat:write FS statistics as a JSON string to stdout' \ +'geometry:write disk geometry as a JSON string to stdout' \ 'tokenize:read from stdin, tokenize, write to stdout' \ 'detokenize:read from stdin, detokenize, write to stdout' \ 'help:Print this message or the help of the given subcommand(s)' \ @@ -624,6 +662,16 @@ _a2kit__retype_commands() { local commands; commands=() _describe -t commands 'a2kit retype commands' commands "$@" } +(( $+functions[_a2kit__help__stat_commands] )) || +_a2kit__help__stat_commands() { + local commands; commands=() + _describe -t commands 'a2kit help stat commands' commands "$@" +} +(( $+functions[_a2kit__stat_commands] )) || +_a2kit__stat_commands() { + local commands; commands=() + _describe -t commands 'a2kit stat commands' commands "$@" +} (( $+functions[_a2kit__help__tokenize_commands] )) || _a2kit__help__tokenize_commands() { local commands; commands=() diff --git a/completions/_a2kit.ps1 b/completions/_a2kit.ps1 index 1bed899..b2cef7a 100644 --- a/completions/_a2kit.ps1 +++ b/completions/_a2kit.ps1 @@ -41,6 +41,8 @@ Register-ArgumentCompleter -Native -CommandName 'a2kit' -ScriptBlock { [CompletionResult]::new('put', 'put', [CompletionResultType]::ParameterValue, 'read from stdin, write to local or disk image') [CompletionResult]::new('catalog', 'catalog', [CompletionResultType]::ParameterValue, 'write disk image catalog to stdout') [CompletionResult]::new('tree', 'tree', [CompletionResultType]::ParameterValue, 'write directory tree as a JSON string to stdout') + [CompletionResult]::new('stat', 'stat', [CompletionResultType]::ParameterValue, 'write FS statistics as a JSON string to stdout') + [CompletionResult]::new('geometry', 'geometry', [CompletionResultType]::ParameterValue, 'write disk geometry as a JSON string to stdout') [CompletionResult]::new('tokenize', 'tokenize', [CompletionResultType]::ParameterValue, 'read from stdin, tokenize, write to stdout') [CompletionResult]::new('detokenize', 'detokenize', [CompletionResultType]::ParameterValue, 'read from stdin, detokenize, write to stdout') [CompletionResult]::new('help', 'help', [CompletionResultType]::ParameterValue, 'Print this message or the help of the given subcommand(s)') @@ -223,6 +225,20 @@ Register-ArgumentCompleter -Native -CommandName 'a2kit' -ScriptBlock { [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help') break } + 'a2kit;stat' { + [CompletionResult]::new('-d', 'd', [CompletionResultType]::ParameterName, 'path to disk image') + [CompletionResult]::new('--dimg', 'dimg', [CompletionResultType]::ParameterName, 'path to disk image') + [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help') + [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help') + break + } + 'a2kit;geometry' { + [CompletionResult]::new('-d', 'd', [CompletionResultType]::ParameterName, 'path to disk image') + [CompletionResult]::new('--dimg', 'dimg', [CompletionResultType]::ParameterName, 'path to disk image') + [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help') + [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help') + break + } 'a2kit;tokenize' { [CompletionResult]::new('-a', 'a', [CompletionResultType]::ParameterName, 'address of tokenized code (Applesoft only)') [CompletionResult]::new('--addr', 'addr', [CompletionResultType]::ParameterName, 'address of tokenized code (Applesoft only)') @@ -256,6 +272,8 @@ Register-ArgumentCompleter -Native -CommandName 'a2kit' -ScriptBlock { [CompletionResult]::new('put', 'put', [CompletionResultType]::ParameterValue, 'read from stdin, write to local or disk image') [CompletionResult]::new('catalog', 'catalog', [CompletionResultType]::ParameterValue, 'write disk image catalog to stdout') [CompletionResult]::new('tree', 'tree', [CompletionResultType]::ParameterValue, 'write directory tree as a JSON string to stdout') + [CompletionResult]::new('stat', 'stat', [CompletionResultType]::ParameterValue, 'write FS statistics as a JSON string to stdout') + [CompletionResult]::new('geometry', 'geometry', [CompletionResultType]::ParameterValue, 'write disk geometry as a JSON string to stdout') [CompletionResult]::new('tokenize', 'tokenize', [CompletionResultType]::ParameterValue, 'read from stdin, tokenize, write to stdout') [CompletionResult]::new('detokenize', 'detokenize', [CompletionResultType]::ParameterValue, 'read from stdin, detokenize, write to stdout') [CompletionResult]::new('help', 'help', [CompletionResultType]::ParameterValue, 'Print this message or the help of the given subcommand(s)') @@ -309,6 +327,12 @@ Register-ArgumentCompleter -Native -CommandName 'a2kit' -ScriptBlock { 'a2kit;help;tree' { break } + 'a2kit;help;stat' { + break + } + 'a2kit;help;geometry' { + break + } 'a2kit;help;tokenize' { break } diff --git a/completions/a2kit.bash b/completions/a2kit.bash index 93b8ed1..ec3aeb1 100644 --- a/completions/a2kit.bash +++ b/completions/a2kit.bash @@ -36,6 +36,9 @@ _a2kit() { a2kit,era) cmd="a2kit__delete" ;; + a2kit,geometry) + cmd="a2kit__geometry" + ;; a2kit,get) cmd="a2kit__get" ;; @@ -72,6 +75,9 @@ _a2kit() { a2kit,retype) cmd="a2kit__retype" ;; + a2kit,stat) + cmd="a2kit__stat" + ;; a2kit,tok) cmd="a2kit__tokenize" ;; @@ -99,6 +105,9 @@ _a2kit() { a2kit__help,detokenize) cmd="a2kit__help__detokenize" ;; + a2kit__help,geometry) + cmd="a2kit__help__geometry" + ;; a2kit__help,get) cmd="a2kit__help__get" ;; @@ -132,6 +141,9 @@ _a2kit() { a2kit__help,retype) cmd="a2kit__help__retype" ;; + a2kit__help,stat) + cmd="a2kit__help__stat" + ;; a2kit__help,tokenize) cmd="a2kit__help__tokenize" ;; @@ -154,7 +166,7 @@ _a2kit() { case "${cmd}" in a2kit) - opts="-h -V --help --version mkdsk mkdir delete protect unprotect lock unlock rename retype verify minify renumber get put catalog tree tokenize detokenize help" + opts="-h -V --help --version mkdsk mkdir delete protect unprotect lock unlock rename retype verify minify renumber get put catalog tree stat geometry tokenize detokenize help" if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 @@ -249,6 +261,28 @@ _a2kit() { COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 ;; + a2kit__geometry) + opts="-d -h --dimg --help" + if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then + COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) + return 0 + fi + case "${prev}" in + --dimg) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; + -d) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; + *) + COMPREPLY=() + ;; + esac + COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) + return 0 + ;; a2kit__get) opts="-f -t -d -l -h --file --type --dimg --len --trunc --help" if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then @@ -296,7 +330,7 @@ _a2kit() { return 0 ;; a2kit__help) - opts="mkdsk mkdir delete protect unprotect lock unlock rename retype verify minify renumber get put catalog tree tokenize detokenize help" + opts="mkdsk mkdir delete protect unprotect lock unlock rename retype verify minify renumber get put catalog tree stat geometry tokenize detokenize help" if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 @@ -351,6 +385,20 @@ _a2kit() { COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 ;; + a2kit__help__geometry) + opts="" + if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then + COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) + return 0 + fi + case "${prev}" in + *) + COMPREPLY=() + ;; + esac + COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) + return 0 + ;; a2kit__help__get) opts="" if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then @@ -505,6 +553,20 @@ _a2kit() { COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 ;; + a2kit__help__stat) + opts="" + if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then + COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) + return 0 + fi + case "${prev}" in + *) + COMPREPLY=() + ;; + esac + COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) + return 0 + ;; a2kit__help__tokenize) opts="" if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then @@ -949,6 +1011,28 @@ _a2kit() { COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 ;; + a2kit__stat) + opts="-d -h --dimg --help" + if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then + COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) + return 0 + fi + case "${prev}" in + --dimg) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; + -d) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; + *) + COMPREPLY=() + ;; + esac + COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) + return 0 + ;; a2kit__tokenize) opts="-a -t -h --addr --type --help" if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then diff --git a/completions/a2kit.elv b/completions/a2kit.elv index 22a953f..e0b074c 100644 --- a/completions/a2kit.elv +++ b/completions/a2kit.elv @@ -38,6 +38,8 @@ set edit:completion:arg-completer[a2kit] = {|@words| cand put 'read from stdin, write to local or disk image' cand catalog 'write disk image catalog to stdout' cand tree 'write directory tree as a JSON string to stdout' + cand stat 'write FS statistics as a JSON string to stdout' + cand geometry 'write disk geometry as a JSON string to stdout' cand tokenize 'read from stdin, tokenize, write to stdout' cand detokenize 'read from stdin, detokenize, write to stdout' cand help 'Print this message or the help of the given subcommand(s)' @@ -203,6 +205,18 @@ set edit:completion:arg-completer[a2kit] = {|@words| cand -h 'Print help' cand --help 'Print help' } + &'a2kit;stat'= { + cand -d 'path to disk image' + cand --dimg 'path to disk image' + cand -h 'Print help' + cand --help 'Print help' + } + &'a2kit;geometry'= { + cand -d 'path to disk image' + cand --dimg 'path to disk image' + cand -h 'Print help' + cand --help 'Print help' + } &'a2kit;tokenize'= { cand -a 'address of tokenized code (Applesoft only)' cand --addr 'address of tokenized code (Applesoft only)' @@ -234,6 +248,8 @@ set edit:completion:arg-completer[a2kit] = {|@words| cand put 'read from stdin, write to local or disk image' cand catalog 'write disk image catalog to stdout' cand tree 'write directory tree as a JSON string to stdout' + cand stat 'write FS statistics as a JSON string to stdout' + cand geometry 'write disk geometry as a JSON string to stdout' cand tokenize 'read from stdin, tokenize, write to stdout' cand detokenize 'read from stdin, detokenize, write to stdout' cand help 'Print this message or the help of the given subcommand(s)' @@ -270,6 +286,10 @@ set edit:completion:arg-completer[a2kit] = {|@words| } &'a2kit;help;tree'= { } + &'a2kit;help;stat'= { + } + &'a2kit;help;geometry'= { + } &'a2kit;help;tokenize'= { } &'a2kit;help;detokenize'= { diff --git a/completions/a2kit.fish b/completions/a2kit.fish index feb477e..ae1103a 100644 --- a/completions/a2kit.fish +++ b/completions/a2kit.fish @@ -16,6 +16,8 @@ complete -c a2kit -n "__fish_use_subcommand" -f -a "get" -d 'read from stdin, lo complete -c a2kit -n "__fish_use_subcommand" -f -a "put" -d 'read from stdin, write to local or disk image' complete -c a2kit -n "__fish_use_subcommand" -f -a "catalog" -d 'write disk image catalog to stdout' complete -c a2kit -n "__fish_use_subcommand" -f -a "tree" -d 'write directory tree as a JSON string to stdout' +complete -c a2kit -n "__fish_use_subcommand" -f -a "stat" -d 'write FS statistics as a JSON string to stdout' +complete -c a2kit -n "__fish_use_subcommand" -f -a "geometry" -d 'write disk geometry as a JSON string to stdout' complete -c a2kit -n "__fish_use_subcommand" -f -a "tokenize" -d 'read from stdin, tokenize, write to stdout' complete -c a2kit -n "__fish_use_subcommand" -f -a "detokenize" -d 'read from stdin, detokenize, write to stdout' complete -c a2kit -n "__fish_use_subcommand" -f -a "help" -d 'Print this message or the help of the given subcommand(s)' @@ -87,27 +89,33 @@ complete -c a2kit -n "__fish_seen_subcommand_from catalog" -s h -l help -d 'Prin complete -c a2kit -n "__fish_seen_subcommand_from tree" -s d -l dimg -d 'path to disk image' -r -F complete -c a2kit -n "__fish_seen_subcommand_from tree" -l meta -d 'include metadata' complete -c a2kit -n "__fish_seen_subcommand_from tree" -s h -l help -d 'Print help' +complete -c a2kit -n "__fish_seen_subcommand_from stat" -s d -l dimg -d 'path to disk image' -r -F +complete -c a2kit -n "__fish_seen_subcommand_from stat" -s h -l help -d 'Print help' +complete -c a2kit -n "__fish_seen_subcommand_from geometry" -s d -l dimg -d 'path to disk image' -r -F +complete -c a2kit -n "__fish_seen_subcommand_from geometry" -s h -l help -d 'Print help' complete -c a2kit -n "__fish_seen_subcommand_from tokenize" -s a -l addr -d 'address of tokenized code (Applesoft only)' -r complete -c a2kit -n "__fish_seen_subcommand_from tokenize" -s t -l type -d 'type of the file' -r -f -a "{atxt '',itxt '',mtxt ''}" complete -c a2kit -n "__fish_seen_subcommand_from tokenize" -s h -l help -d 'Print help' complete -c a2kit -n "__fish_seen_subcommand_from detokenize" -s t -l type -d 'type of the file' -r -f -a "{atok '',itok '',mtok ''}" complete -c a2kit -n "__fish_seen_subcommand_from detokenize" -s h -l help -d 'Print help' -complete -c a2kit -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from mkdsk; and not __fish_seen_subcommand_from mkdir; and not __fish_seen_subcommand_from delete; and not __fish_seen_subcommand_from protect; and not __fish_seen_subcommand_from unprotect; and not __fish_seen_subcommand_from lock; and not __fish_seen_subcommand_from unlock; and not __fish_seen_subcommand_from rename; and not __fish_seen_subcommand_from retype; and not __fish_seen_subcommand_from verify; and not __fish_seen_subcommand_from minify; and not __fish_seen_subcommand_from renumber; and not __fish_seen_subcommand_from get; and not __fish_seen_subcommand_from put; and not __fish_seen_subcommand_from catalog; and not __fish_seen_subcommand_from tree; and not __fish_seen_subcommand_from tokenize; and not __fish_seen_subcommand_from detokenize; and not __fish_seen_subcommand_from help" -f -a "mkdsk" -d 'write a blank disk image to the given path' -complete -c a2kit -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from mkdsk; and not __fish_seen_subcommand_from mkdir; and not __fish_seen_subcommand_from delete; and not __fish_seen_subcommand_from protect; and not __fish_seen_subcommand_from unprotect; and not __fish_seen_subcommand_from lock; and not __fish_seen_subcommand_from unlock; and not __fish_seen_subcommand_from rename; and not __fish_seen_subcommand_from retype; and not __fish_seen_subcommand_from verify; and not __fish_seen_subcommand_from minify; and not __fish_seen_subcommand_from renumber; and not __fish_seen_subcommand_from get; and not __fish_seen_subcommand_from put; and not __fish_seen_subcommand_from catalog; and not __fish_seen_subcommand_from tree; and not __fish_seen_subcommand_from tokenize; and not __fish_seen_subcommand_from detokenize; and not __fish_seen_subcommand_from help" -f -a "mkdir" -d 'create a new directory inside a disk image' -complete -c a2kit -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from mkdsk; and not __fish_seen_subcommand_from mkdir; and not __fish_seen_subcommand_from delete; and not __fish_seen_subcommand_from protect; and not __fish_seen_subcommand_from unprotect; and not __fish_seen_subcommand_from lock; and not __fish_seen_subcommand_from unlock; and not __fish_seen_subcommand_from rename; and not __fish_seen_subcommand_from retype; and not __fish_seen_subcommand_from verify; and not __fish_seen_subcommand_from minify; and not __fish_seen_subcommand_from renumber; and not __fish_seen_subcommand_from get; and not __fish_seen_subcommand_from put; and not __fish_seen_subcommand_from catalog; and not __fish_seen_subcommand_from tree; and not __fish_seen_subcommand_from tokenize; and not __fish_seen_subcommand_from detokenize; and not __fish_seen_subcommand_from help" -f -a "delete" -d 'delete a file or directory inside a disk image' -complete -c a2kit -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from mkdsk; and not __fish_seen_subcommand_from mkdir; and not __fish_seen_subcommand_from delete; and not __fish_seen_subcommand_from protect; and not __fish_seen_subcommand_from unprotect; and not __fish_seen_subcommand_from lock; and not __fish_seen_subcommand_from unlock; and not __fish_seen_subcommand_from rename; and not __fish_seen_subcommand_from retype; and not __fish_seen_subcommand_from verify; and not __fish_seen_subcommand_from minify; and not __fish_seen_subcommand_from renumber; and not __fish_seen_subcommand_from get; and not __fish_seen_subcommand_from put; and not __fish_seen_subcommand_from catalog; and not __fish_seen_subcommand_from tree; and not __fish_seen_subcommand_from tokenize; and not __fish_seen_subcommand_from detokenize; and not __fish_seen_subcommand_from help" -f -a "protect" -d 'password protect a disk or file' -complete -c a2kit -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from mkdsk; and not __fish_seen_subcommand_from mkdir; and not __fish_seen_subcommand_from delete; and not __fish_seen_subcommand_from protect; and not __fish_seen_subcommand_from unprotect; and not __fish_seen_subcommand_from lock; and not __fish_seen_subcommand_from unlock; and not __fish_seen_subcommand_from rename; and not __fish_seen_subcommand_from retype; and not __fish_seen_subcommand_from verify; and not __fish_seen_subcommand_from minify; and not __fish_seen_subcommand_from renumber; and not __fish_seen_subcommand_from get; and not __fish_seen_subcommand_from put; and not __fish_seen_subcommand_from catalog; and not __fish_seen_subcommand_from tree; and not __fish_seen_subcommand_from tokenize; and not __fish_seen_subcommand_from detokenize; and not __fish_seen_subcommand_from help" -f -a "unprotect" -d 'remove password protection from a disk or file' -complete -c a2kit -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from mkdsk; and not __fish_seen_subcommand_from mkdir; and not __fish_seen_subcommand_from delete; and not __fish_seen_subcommand_from protect; and not __fish_seen_subcommand_from unprotect; and not __fish_seen_subcommand_from lock; and not __fish_seen_subcommand_from unlock; and not __fish_seen_subcommand_from rename; and not __fish_seen_subcommand_from retype; and not __fish_seen_subcommand_from verify; and not __fish_seen_subcommand_from minify; and not __fish_seen_subcommand_from renumber; and not __fish_seen_subcommand_from get; and not __fish_seen_subcommand_from put; and not __fish_seen_subcommand_from catalog; and not __fish_seen_subcommand_from tree; and not __fish_seen_subcommand_from tokenize; and not __fish_seen_subcommand_from detokenize; and not __fish_seen_subcommand_from help" -f -a "lock" -d 'write protect a file or directory inside a disk image' -complete -c a2kit -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from mkdsk; and not __fish_seen_subcommand_from mkdir; and not __fish_seen_subcommand_from delete; and not __fish_seen_subcommand_from protect; and not __fish_seen_subcommand_from unprotect; and not __fish_seen_subcommand_from lock; and not __fish_seen_subcommand_from unlock; and not __fish_seen_subcommand_from rename; and not __fish_seen_subcommand_from retype; and not __fish_seen_subcommand_from verify; and not __fish_seen_subcommand_from minify; and not __fish_seen_subcommand_from renumber; and not __fish_seen_subcommand_from get; and not __fish_seen_subcommand_from put; and not __fish_seen_subcommand_from catalog; and not __fish_seen_subcommand_from tree; and not __fish_seen_subcommand_from tokenize; and not __fish_seen_subcommand_from detokenize; and not __fish_seen_subcommand_from help" -f -a "unlock" -d 'remove write protection from a file or directory inside a disk image' -complete -c a2kit -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from mkdsk; and not __fish_seen_subcommand_from mkdir; and not __fish_seen_subcommand_from delete; and not __fish_seen_subcommand_from protect; and not __fish_seen_subcommand_from unprotect; and not __fish_seen_subcommand_from lock; and not __fish_seen_subcommand_from unlock; and not __fish_seen_subcommand_from rename; and not __fish_seen_subcommand_from retype; and not __fish_seen_subcommand_from verify; and not __fish_seen_subcommand_from minify; and not __fish_seen_subcommand_from renumber; and not __fish_seen_subcommand_from get; and not __fish_seen_subcommand_from put; and not __fish_seen_subcommand_from catalog; and not __fish_seen_subcommand_from tree; and not __fish_seen_subcommand_from tokenize; and not __fish_seen_subcommand_from detokenize; and not __fish_seen_subcommand_from help" -f -a "rename" -d 'rename a file or directory inside a disk image' -complete -c a2kit -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from mkdsk; and not __fish_seen_subcommand_from mkdir; and not __fish_seen_subcommand_from delete; and not __fish_seen_subcommand_from protect; and not __fish_seen_subcommand_from unprotect; and not __fish_seen_subcommand_from lock; and not __fish_seen_subcommand_from unlock; and not __fish_seen_subcommand_from rename; and not __fish_seen_subcommand_from retype; and not __fish_seen_subcommand_from verify; and not __fish_seen_subcommand_from minify; and not __fish_seen_subcommand_from renumber; and not __fish_seen_subcommand_from get; and not __fish_seen_subcommand_from put; and not __fish_seen_subcommand_from catalog; and not __fish_seen_subcommand_from tree; and not __fish_seen_subcommand_from tokenize; and not __fish_seen_subcommand_from detokenize; and not __fish_seen_subcommand_from help" -f -a "retype" -d 'change file type inside a disk image' -complete -c a2kit -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from mkdsk; and not __fish_seen_subcommand_from mkdir; and not __fish_seen_subcommand_from delete; and not __fish_seen_subcommand_from protect; and not __fish_seen_subcommand_from unprotect; and not __fish_seen_subcommand_from lock; and not __fish_seen_subcommand_from unlock; and not __fish_seen_subcommand_from rename; and not __fish_seen_subcommand_from retype; and not __fish_seen_subcommand_from verify; and not __fish_seen_subcommand_from minify; and not __fish_seen_subcommand_from renumber; and not __fish_seen_subcommand_from get; and not __fish_seen_subcommand_from put; and not __fish_seen_subcommand_from catalog; and not __fish_seen_subcommand_from tree; and not __fish_seen_subcommand_from tokenize; and not __fish_seen_subcommand_from detokenize; and not __fish_seen_subcommand_from help" -f -a "verify" -d 'read from stdin and error check' -complete -c a2kit -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from mkdsk; and not __fish_seen_subcommand_from mkdir; and not __fish_seen_subcommand_from delete; and not __fish_seen_subcommand_from protect; and not __fish_seen_subcommand_from unprotect; and not __fish_seen_subcommand_from lock; and not __fish_seen_subcommand_from unlock; and not __fish_seen_subcommand_from rename; and not __fish_seen_subcommand_from retype; and not __fish_seen_subcommand_from verify; and not __fish_seen_subcommand_from minify; and not __fish_seen_subcommand_from renumber; and not __fish_seen_subcommand_from get; and not __fish_seen_subcommand_from put; and not __fish_seen_subcommand_from catalog; and not __fish_seen_subcommand_from tree; and not __fish_seen_subcommand_from tokenize; and not __fish_seen_subcommand_from detokenize; and not __fish_seen_subcommand_from help" -f -a "minify" -d 'reduce program size' -complete -c a2kit -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from mkdsk; and not __fish_seen_subcommand_from mkdir; and not __fish_seen_subcommand_from delete; and not __fish_seen_subcommand_from protect; and not __fish_seen_subcommand_from unprotect; and not __fish_seen_subcommand_from lock; and not __fish_seen_subcommand_from unlock; and not __fish_seen_subcommand_from rename; and not __fish_seen_subcommand_from retype; and not __fish_seen_subcommand_from verify; and not __fish_seen_subcommand_from minify; and not __fish_seen_subcommand_from renumber; and not __fish_seen_subcommand_from get; and not __fish_seen_subcommand_from put; and not __fish_seen_subcommand_from catalog; and not __fish_seen_subcommand_from tree; and not __fish_seen_subcommand_from tokenize; and not __fish_seen_subcommand_from detokenize; and not __fish_seen_subcommand_from help" -f -a "renumber" -d 'renumber BASIC program lines' -complete -c a2kit -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from mkdsk; and not __fish_seen_subcommand_from mkdir; and not __fish_seen_subcommand_from delete; and not __fish_seen_subcommand_from protect; and not __fish_seen_subcommand_from unprotect; and not __fish_seen_subcommand_from lock; and not __fish_seen_subcommand_from unlock; and not __fish_seen_subcommand_from rename; and not __fish_seen_subcommand_from retype; and not __fish_seen_subcommand_from verify; and not __fish_seen_subcommand_from minify; and not __fish_seen_subcommand_from renumber; and not __fish_seen_subcommand_from get; and not __fish_seen_subcommand_from put; and not __fish_seen_subcommand_from catalog; and not __fish_seen_subcommand_from tree; and not __fish_seen_subcommand_from tokenize; and not __fish_seen_subcommand_from detokenize; and not __fish_seen_subcommand_from help" -f -a "get" -d 'read from stdin, local, or disk image, write to stdout' -complete -c a2kit -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from mkdsk; and not __fish_seen_subcommand_from mkdir; and not __fish_seen_subcommand_from delete; and not __fish_seen_subcommand_from protect; and not __fish_seen_subcommand_from unprotect; and not __fish_seen_subcommand_from lock; and not __fish_seen_subcommand_from unlock; and not __fish_seen_subcommand_from rename; and not __fish_seen_subcommand_from retype; and not __fish_seen_subcommand_from verify; and not __fish_seen_subcommand_from minify; and not __fish_seen_subcommand_from renumber; and not __fish_seen_subcommand_from get; and not __fish_seen_subcommand_from put; and not __fish_seen_subcommand_from catalog; and not __fish_seen_subcommand_from tree; and not __fish_seen_subcommand_from tokenize; and not __fish_seen_subcommand_from detokenize; and not __fish_seen_subcommand_from help" -f -a "put" -d 'read from stdin, write to local or disk image' -complete -c a2kit -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from mkdsk; and not __fish_seen_subcommand_from mkdir; and not __fish_seen_subcommand_from delete; and not __fish_seen_subcommand_from protect; and not __fish_seen_subcommand_from unprotect; and not __fish_seen_subcommand_from lock; and not __fish_seen_subcommand_from unlock; and not __fish_seen_subcommand_from rename; and not __fish_seen_subcommand_from retype; and not __fish_seen_subcommand_from verify; and not __fish_seen_subcommand_from minify; and not __fish_seen_subcommand_from renumber; and not __fish_seen_subcommand_from get; and not __fish_seen_subcommand_from put; and not __fish_seen_subcommand_from catalog; and not __fish_seen_subcommand_from tree; and not __fish_seen_subcommand_from tokenize; and not __fish_seen_subcommand_from detokenize; and not __fish_seen_subcommand_from help" -f -a "catalog" -d 'write disk image catalog to stdout' -complete -c a2kit -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from mkdsk; and not __fish_seen_subcommand_from mkdir; and not __fish_seen_subcommand_from delete; and not __fish_seen_subcommand_from protect; and not __fish_seen_subcommand_from unprotect; and not __fish_seen_subcommand_from lock; and not __fish_seen_subcommand_from unlock; and not __fish_seen_subcommand_from rename; and not __fish_seen_subcommand_from retype; and not __fish_seen_subcommand_from verify; and not __fish_seen_subcommand_from minify; and not __fish_seen_subcommand_from renumber; and not __fish_seen_subcommand_from get; and not __fish_seen_subcommand_from put; and not __fish_seen_subcommand_from catalog; and not __fish_seen_subcommand_from tree; and not __fish_seen_subcommand_from tokenize; and not __fish_seen_subcommand_from detokenize; and not __fish_seen_subcommand_from help" -f -a "tree" -d 'write directory tree as a JSON string to stdout' -complete -c a2kit -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from mkdsk; and not __fish_seen_subcommand_from mkdir; and not __fish_seen_subcommand_from delete; and not __fish_seen_subcommand_from protect; and not __fish_seen_subcommand_from unprotect; and not __fish_seen_subcommand_from lock; and not __fish_seen_subcommand_from unlock; and not __fish_seen_subcommand_from rename; and not __fish_seen_subcommand_from retype; and not __fish_seen_subcommand_from verify; and not __fish_seen_subcommand_from minify; and not __fish_seen_subcommand_from renumber; and not __fish_seen_subcommand_from get; and not __fish_seen_subcommand_from put; and not __fish_seen_subcommand_from catalog; and not __fish_seen_subcommand_from tree; and not __fish_seen_subcommand_from tokenize; and not __fish_seen_subcommand_from detokenize; and not __fish_seen_subcommand_from help" -f -a "tokenize" -d 'read from stdin, tokenize, write to stdout' -complete -c a2kit -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from mkdsk; and not __fish_seen_subcommand_from mkdir; and not __fish_seen_subcommand_from delete; and not __fish_seen_subcommand_from protect; and not __fish_seen_subcommand_from unprotect; and not __fish_seen_subcommand_from lock; and not __fish_seen_subcommand_from unlock; and not __fish_seen_subcommand_from rename; and not __fish_seen_subcommand_from retype; and not __fish_seen_subcommand_from verify; and not __fish_seen_subcommand_from minify; and not __fish_seen_subcommand_from renumber; and not __fish_seen_subcommand_from get; and not __fish_seen_subcommand_from put; and not __fish_seen_subcommand_from catalog; and not __fish_seen_subcommand_from tree; and not __fish_seen_subcommand_from tokenize; and not __fish_seen_subcommand_from detokenize; and not __fish_seen_subcommand_from help" -f -a "detokenize" -d 'read from stdin, detokenize, write to stdout' -complete -c a2kit -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from mkdsk; and not __fish_seen_subcommand_from mkdir; and not __fish_seen_subcommand_from delete; and not __fish_seen_subcommand_from protect; and not __fish_seen_subcommand_from unprotect; and not __fish_seen_subcommand_from lock; and not __fish_seen_subcommand_from unlock; and not __fish_seen_subcommand_from rename; and not __fish_seen_subcommand_from retype; and not __fish_seen_subcommand_from verify; and not __fish_seen_subcommand_from minify; and not __fish_seen_subcommand_from renumber; and not __fish_seen_subcommand_from get; and not __fish_seen_subcommand_from put; and not __fish_seen_subcommand_from catalog; and not __fish_seen_subcommand_from tree; and not __fish_seen_subcommand_from tokenize; and not __fish_seen_subcommand_from detokenize; and not __fish_seen_subcommand_from help" -f -a "help" -d 'Print this message or the help of the given subcommand(s)' +complete -c a2kit -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from mkdsk; and not __fish_seen_subcommand_from mkdir; and not __fish_seen_subcommand_from delete; and not __fish_seen_subcommand_from protect; and not __fish_seen_subcommand_from unprotect; and not __fish_seen_subcommand_from lock; and not __fish_seen_subcommand_from unlock; and not __fish_seen_subcommand_from rename; and not __fish_seen_subcommand_from retype; and not __fish_seen_subcommand_from verify; and not __fish_seen_subcommand_from minify; and not __fish_seen_subcommand_from renumber; and not __fish_seen_subcommand_from get; and not __fish_seen_subcommand_from put; and not __fish_seen_subcommand_from catalog; and not __fish_seen_subcommand_from tree; and not __fish_seen_subcommand_from stat; and not __fish_seen_subcommand_from geometry; and not __fish_seen_subcommand_from tokenize; and not __fish_seen_subcommand_from detokenize; and not __fish_seen_subcommand_from help" -f -a "mkdsk" -d 'write a blank disk image to the given path' +complete -c a2kit -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from mkdsk; and not __fish_seen_subcommand_from mkdir; and not __fish_seen_subcommand_from delete; and not __fish_seen_subcommand_from protect; and not __fish_seen_subcommand_from unprotect; and not __fish_seen_subcommand_from lock; and not __fish_seen_subcommand_from unlock; and not __fish_seen_subcommand_from rename; and not __fish_seen_subcommand_from retype; and not __fish_seen_subcommand_from verify; and not __fish_seen_subcommand_from minify; and not __fish_seen_subcommand_from renumber; and not __fish_seen_subcommand_from get; and not __fish_seen_subcommand_from put; and not __fish_seen_subcommand_from catalog; and not __fish_seen_subcommand_from tree; and not __fish_seen_subcommand_from stat; and not __fish_seen_subcommand_from geometry; and not __fish_seen_subcommand_from tokenize; and not __fish_seen_subcommand_from detokenize; and not __fish_seen_subcommand_from help" -f -a "mkdir" -d 'create a new directory inside a disk image' +complete -c a2kit -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from mkdsk; and not __fish_seen_subcommand_from mkdir; and not __fish_seen_subcommand_from delete; and not __fish_seen_subcommand_from protect; and not __fish_seen_subcommand_from unprotect; and not __fish_seen_subcommand_from lock; and not __fish_seen_subcommand_from unlock; and not __fish_seen_subcommand_from rename; and not __fish_seen_subcommand_from retype; and not __fish_seen_subcommand_from verify; and not __fish_seen_subcommand_from minify; and not __fish_seen_subcommand_from renumber; and not __fish_seen_subcommand_from get; and not __fish_seen_subcommand_from put; and not __fish_seen_subcommand_from catalog; and not __fish_seen_subcommand_from tree; and not __fish_seen_subcommand_from stat; and not __fish_seen_subcommand_from geometry; and not __fish_seen_subcommand_from tokenize; and not __fish_seen_subcommand_from detokenize; and not __fish_seen_subcommand_from help" -f -a "delete" -d 'delete a file or directory inside a disk image' +complete -c a2kit -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from mkdsk; and not __fish_seen_subcommand_from mkdir; and not __fish_seen_subcommand_from delete; and not __fish_seen_subcommand_from protect; and not __fish_seen_subcommand_from unprotect; and not __fish_seen_subcommand_from lock; and not __fish_seen_subcommand_from unlock; and not __fish_seen_subcommand_from rename; and not __fish_seen_subcommand_from retype; and not __fish_seen_subcommand_from verify; and not __fish_seen_subcommand_from minify; and not __fish_seen_subcommand_from renumber; and not __fish_seen_subcommand_from get; and not __fish_seen_subcommand_from put; and not __fish_seen_subcommand_from catalog; and not __fish_seen_subcommand_from tree; and not __fish_seen_subcommand_from stat; and not __fish_seen_subcommand_from geometry; and not __fish_seen_subcommand_from tokenize; and not __fish_seen_subcommand_from detokenize; and not __fish_seen_subcommand_from help" -f -a "protect" -d 'password protect a disk or file' +complete -c a2kit -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from mkdsk; and not __fish_seen_subcommand_from mkdir; and not __fish_seen_subcommand_from delete; and not __fish_seen_subcommand_from protect; and not __fish_seen_subcommand_from unprotect; and not __fish_seen_subcommand_from lock; and not __fish_seen_subcommand_from unlock; and not __fish_seen_subcommand_from rename; and not __fish_seen_subcommand_from retype; and not __fish_seen_subcommand_from verify; and not __fish_seen_subcommand_from minify; and not __fish_seen_subcommand_from renumber; and not __fish_seen_subcommand_from get; and not __fish_seen_subcommand_from put; and not __fish_seen_subcommand_from catalog; and not __fish_seen_subcommand_from tree; and not __fish_seen_subcommand_from stat; and not __fish_seen_subcommand_from geometry; and not __fish_seen_subcommand_from tokenize; and not __fish_seen_subcommand_from detokenize; and not __fish_seen_subcommand_from help" -f -a "unprotect" -d 'remove password protection from a disk or file' +complete -c a2kit -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from mkdsk; and not __fish_seen_subcommand_from mkdir; and not __fish_seen_subcommand_from delete; and not __fish_seen_subcommand_from protect; and not __fish_seen_subcommand_from unprotect; and not __fish_seen_subcommand_from lock; and not __fish_seen_subcommand_from unlock; and not __fish_seen_subcommand_from rename; and not __fish_seen_subcommand_from retype; and not __fish_seen_subcommand_from verify; and not __fish_seen_subcommand_from minify; and not __fish_seen_subcommand_from renumber; and not __fish_seen_subcommand_from get; and not __fish_seen_subcommand_from put; and not __fish_seen_subcommand_from catalog; and not __fish_seen_subcommand_from tree; and not __fish_seen_subcommand_from stat; and not __fish_seen_subcommand_from geometry; and not __fish_seen_subcommand_from tokenize; and not __fish_seen_subcommand_from detokenize; and not __fish_seen_subcommand_from help" -f -a "lock" -d 'write protect a file or directory inside a disk image' +complete -c a2kit -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from mkdsk; and not __fish_seen_subcommand_from mkdir; and not __fish_seen_subcommand_from delete; and not __fish_seen_subcommand_from protect; and not __fish_seen_subcommand_from unprotect; and not __fish_seen_subcommand_from lock; and not __fish_seen_subcommand_from unlock; and not __fish_seen_subcommand_from rename; and not __fish_seen_subcommand_from retype; and not __fish_seen_subcommand_from verify; and not __fish_seen_subcommand_from minify; and not __fish_seen_subcommand_from renumber; and not __fish_seen_subcommand_from get; and not __fish_seen_subcommand_from put; and not __fish_seen_subcommand_from catalog; and not __fish_seen_subcommand_from tree; and not __fish_seen_subcommand_from stat; and not __fish_seen_subcommand_from geometry; and not __fish_seen_subcommand_from tokenize; and not __fish_seen_subcommand_from detokenize; and not __fish_seen_subcommand_from help" -f -a "unlock" -d 'remove write protection from a file or directory inside a disk image' +complete -c a2kit -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from mkdsk; and not __fish_seen_subcommand_from mkdir; and not __fish_seen_subcommand_from delete; and not __fish_seen_subcommand_from protect; and not __fish_seen_subcommand_from unprotect; and not __fish_seen_subcommand_from lock; and not __fish_seen_subcommand_from unlock; and not __fish_seen_subcommand_from rename; and not __fish_seen_subcommand_from retype; and not __fish_seen_subcommand_from verify; and not __fish_seen_subcommand_from minify; and not __fish_seen_subcommand_from renumber; and not __fish_seen_subcommand_from get; and not __fish_seen_subcommand_from put; and not __fish_seen_subcommand_from catalog; and not __fish_seen_subcommand_from tree; and not __fish_seen_subcommand_from stat; and not __fish_seen_subcommand_from geometry; and not __fish_seen_subcommand_from tokenize; and not __fish_seen_subcommand_from detokenize; and not __fish_seen_subcommand_from help" -f -a "rename" -d 'rename a file or directory inside a disk image' +complete -c a2kit -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from mkdsk; and not __fish_seen_subcommand_from mkdir; and not __fish_seen_subcommand_from delete; and not __fish_seen_subcommand_from protect; and not __fish_seen_subcommand_from unprotect; and not __fish_seen_subcommand_from lock; and not __fish_seen_subcommand_from unlock; and not __fish_seen_subcommand_from rename; and not __fish_seen_subcommand_from retype; and not __fish_seen_subcommand_from verify; and not __fish_seen_subcommand_from minify; and not __fish_seen_subcommand_from renumber; and not __fish_seen_subcommand_from get; and not __fish_seen_subcommand_from put; and not __fish_seen_subcommand_from catalog; and not __fish_seen_subcommand_from tree; and not __fish_seen_subcommand_from stat; and not __fish_seen_subcommand_from geometry; and not __fish_seen_subcommand_from tokenize; and not __fish_seen_subcommand_from detokenize; and not __fish_seen_subcommand_from help" -f -a "retype" -d 'change file type inside a disk image' +complete -c a2kit -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from mkdsk; and not __fish_seen_subcommand_from mkdir; and not __fish_seen_subcommand_from delete; and not __fish_seen_subcommand_from protect; and not __fish_seen_subcommand_from unprotect; and not __fish_seen_subcommand_from lock; and not __fish_seen_subcommand_from unlock; and not __fish_seen_subcommand_from rename; and not __fish_seen_subcommand_from retype; and not __fish_seen_subcommand_from verify; and not __fish_seen_subcommand_from minify; and not __fish_seen_subcommand_from renumber; and not __fish_seen_subcommand_from get; and not __fish_seen_subcommand_from put; and not __fish_seen_subcommand_from catalog; and not __fish_seen_subcommand_from tree; and not __fish_seen_subcommand_from stat; and not __fish_seen_subcommand_from geometry; and not __fish_seen_subcommand_from tokenize; and not __fish_seen_subcommand_from detokenize; and not __fish_seen_subcommand_from help" -f -a "verify" -d 'read from stdin and error check' +complete -c a2kit -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from mkdsk; and not __fish_seen_subcommand_from mkdir; and not __fish_seen_subcommand_from delete; and not __fish_seen_subcommand_from protect; and not __fish_seen_subcommand_from unprotect; and not __fish_seen_subcommand_from lock; and not __fish_seen_subcommand_from unlock; and not __fish_seen_subcommand_from rename; and not __fish_seen_subcommand_from retype; and not __fish_seen_subcommand_from verify; and not __fish_seen_subcommand_from minify; and not __fish_seen_subcommand_from renumber; and not __fish_seen_subcommand_from get; and not __fish_seen_subcommand_from put; and not __fish_seen_subcommand_from catalog; and not __fish_seen_subcommand_from tree; and not __fish_seen_subcommand_from stat; and not __fish_seen_subcommand_from geometry; and not __fish_seen_subcommand_from tokenize; and not __fish_seen_subcommand_from detokenize; and not __fish_seen_subcommand_from help" -f -a "minify" -d 'reduce program size' +complete -c a2kit -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from mkdsk; and not __fish_seen_subcommand_from mkdir; and not __fish_seen_subcommand_from delete; and not __fish_seen_subcommand_from protect; and not __fish_seen_subcommand_from unprotect; and not __fish_seen_subcommand_from lock; and not __fish_seen_subcommand_from unlock; and not __fish_seen_subcommand_from rename; and not __fish_seen_subcommand_from retype; and not __fish_seen_subcommand_from verify; and not __fish_seen_subcommand_from minify; and not __fish_seen_subcommand_from renumber; and not __fish_seen_subcommand_from get; and not __fish_seen_subcommand_from put; and not __fish_seen_subcommand_from catalog; and not __fish_seen_subcommand_from tree; and not __fish_seen_subcommand_from stat; and not __fish_seen_subcommand_from geometry; and not __fish_seen_subcommand_from tokenize; and not __fish_seen_subcommand_from detokenize; and not __fish_seen_subcommand_from help" -f -a "renumber" -d 'renumber BASIC program lines' +complete -c a2kit -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from mkdsk; and not __fish_seen_subcommand_from mkdir; and not __fish_seen_subcommand_from delete; and not __fish_seen_subcommand_from protect; and not __fish_seen_subcommand_from unprotect; and not __fish_seen_subcommand_from lock; and not __fish_seen_subcommand_from unlock; and not __fish_seen_subcommand_from rename; and not __fish_seen_subcommand_from retype; and not __fish_seen_subcommand_from verify; and not __fish_seen_subcommand_from minify; and not __fish_seen_subcommand_from renumber; and not __fish_seen_subcommand_from get; and not __fish_seen_subcommand_from put; and not __fish_seen_subcommand_from catalog; and not __fish_seen_subcommand_from tree; and not __fish_seen_subcommand_from stat; and not __fish_seen_subcommand_from geometry; and not __fish_seen_subcommand_from tokenize; and not __fish_seen_subcommand_from detokenize; and not __fish_seen_subcommand_from help" -f -a "get" -d 'read from stdin, local, or disk image, write to stdout' +complete -c a2kit -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from mkdsk; and not __fish_seen_subcommand_from mkdir; and not __fish_seen_subcommand_from delete; and not __fish_seen_subcommand_from protect; and not __fish_seen_subcommand_from unprotect; and not __fish_seen_subcommand_from lock; and not __fish_seen_subcommand_from unlock; and not __fish_seen_subcommand_from rename; and not __fish_seen_subcommand_from retype; and not __fish_seen_subcommand_from verify; and not __fish_seen_subcommand_from minify; and not __fish_seen_subcommand_from renumber; and not __fish_seen_subcommand_from get; and not __fish_seen_subcommand_from put; and not __fish_seen_subcommand_from catalog; and not __fish_seen_subcommand_from tree; and not __fish_seen_subcommand_from stat; and not __fish_seen_subcommand_from geometry; and not __fish_seen_subcommand_from tokenize; and not __fish_seen_subcommand_from detokenize; and not __fish_seen_subcommand_from help" -f -a "put" -d 'read from stdin, write to local or disk image' +complete -c a2kit -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from mkdsk; and not __fish_seen_subcommand_from mkdir; and not __fish_seen_subcommand_from delete; and not __fish_seen_subcommand_from protect; and not __fish_seen_subcommand_from unprotect; and not __fish_seen_subcommand_from lock; and not __fish_seen_subcommand_from unlock; and not __fish_seen_subcommand_from rename; and not __fish_seen_subcommand_from retype; and not __fish_seen_subcommand_from verify; and not __fish_seen_subcommand_from minify; and not __fish_seen_subcommand_from renumber; and not __fish_seen_subcommand_from get; and not __fish_seen_subcommand_from put; and not __fish_seen_subcommand_from catalog; and not __fish_seen_subcommand_from tree; and not __fish_seen_subcommand_from stat; and not __fish_seen_subcommand_from geometry; and not __fish_seen_subcommand_from tokenize; and not __fish_seen_subcommand_from detokenize; and not __fish_seen_subcommand_from help" -f -a "catalog" -d 'write disk image catalog to stdout' +complete -c a2kit -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from mkdsk; and not __fish_seen_subcommand_from mkdir; and not __fish_seen_subcommand_from delete; and not __fish_seen_subcommand_from protect; and not __fish_seen_subcommand_from unprotect; and not __fish_seen_subcommand_from lock; and not __fish_seen_subcommand_from unlock; and not __fish_seen_subcommand_from rename; and not __fish_seen_subcommand_from retype; and not __fish_seen_subcommand_from verify; and not __fish_seen_subcommand_from minify; and not __fish_seen_subcommand_from renumber; and not __fish_seen_subcommand_from get; and not __fish_seen_subcommand_from put; and not __fish_seen_subcommand_from catalog; and not __fish_seen_subcommand_from tree; and not __fish_seen_subcommand_from stat; and not __fish_seen_subcommand_from geometry; and not __fish_seen_subcommand_from tokenize; and not __fish_seen_subcommand_from detokenize; and not __fish_seen_subcommand_from help" -f -a "tree" -d 'write directory tree as a JSON string to stdout' +complete -c a2kit -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from mkdsk; and not __fish_seen_subcommand_from mkdir; and not __fish_seen_subcommand_from delete; and not __fish_seen_subcommand_from protect; and not __fish_seen_subcommand_from unprotect; and not __fish_seen_subcommand_from lock; and not __fish_seen_subcommand_from unlock; and not __fish_seen_subcommand_from rename; and not __fish_seen_subcommand_from retype; and not __fish_seen_subcommand_from verify; and not __fish_seen_subcommand_from minify; and not __fish_seen_subcommand_from renumber; and not __fish_seen_subcommand_from get; and not __fish_seen_subcommand_from put; and not __fish_seen_subcommand_from catalog; and not __fish_seen_subcommand_from tree; and not __fish_seen_subcommand_from stat; and not __fish_seen_subcommand_from geometry; and not __fish_seen_subcommand_from tokenize; and not __fish_seen_subcommand_from detokenize; and not __fish_seen_subcommand_from help" -f -a "stat" -d 'write FS statistics as a JSON string to stdout' +complete -c a2kit -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from mkdsk; and not __fish_seen_subcommand_from mkdir; and not __fish_seen_subcommand_from delete; and not __fish_seen_subcommand_from protect; and not __fish_seen_subcommand_from unprotect; and not __fish_seen_subcommand_from lock; and not __fish_seen_subcommand_from unlock; and not __fish_seen_subcommand_from rename; and not __fish_seen_subcommand_from retype; and not __fish_seen_subcommand_from verify; and not __fish_seen_subcommand_from minify; and not __fish_seen_subcommand_from renumber; and not __fish_seen_subcommand_from get; and not __fish_seen_subcommand_from put; and not __fish_seen_subcommand_from catalog; and not __fish_seen_subcommand_from tree; and not __fish_seen_subcommand_from stat; and not __fish_seen_subcommand_from geometry; and not __fish_seen_subcommand_from tokenize; and not __fish_seen_subcommand_from detokenize; and not __fish_seen_subcommand_from help" -f -a "geometry" -d 'write disk geometry as a JSON string to stdout' +complete -c a2kit -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from mkdsk; and not __fish_seen_subcommand_from mkdir; and not __fish_seen_subcommand_from delete; and not __fish_seen_subcommand_from protect; and not __fish_seen_subcommand_from unprotect; and not __fish_seen_subcommand_from lock; and not __fish_seen_subcommand_from unlock; and not __fish_seen_subcommand_from rename; and not __fish_seen_subcommand_from retype; and not __fish_seen_subcommand_from verify; and not __fish_seen_subcommand_from minify; and not __fish_seen_subcommand_from renumber; and not __fish_seen_subcommand_from get; and not __fish_seen_subcommand_from put; and not __fish_seen_subcommand_from catalog; and not __fish_seen_subcommand_from tree; and not __fish_seen_subcommand_from stat; and not __fish_seen_subcommand_from geometry; and not __fish_seen_subcommand_from tokenize; and not __fish_seen_subcommand_from detokenize; and not __fish_seen_subcommand_from help" -f -a "tokenize" -d 'read from stdin, tokenize, write to stdout' +complete -c a2kit -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from mkdsk; and not __fish_seen_subcommand_from mkdir; and not __fish_seen_subcommand_from delete; and not __fish_seen_subcommand_from protect; and not __fish_seen_subcommand_from unprotect; and not __fish_seen_subcommand_from lock; and not __fish_seen_subcommand_from unlock; and not __fish_seen_subcommand_from rename; and not __fish_seen_subcommand_from retype; and not __fish_seen_subcommand_from verify; and not __fish_seen_subcommand_from minify; and not __fish_seen_subcommand_from renumber; and not __fish_seen_subcommand_from get; and not __fish_seen_subcommand_from put; and not __fish_seen_subcommand_from catalog; and not __fish_seen_subcommand_from tree; and not __fish_seen_subcommand_from stat; and not __fish_seen_subcommand_from geometry; and not __fish_seen_subcommand_from tokenize; and not __fish_seen_subcommand_from detokenize; and not __fish_seen_subcommand_from help" -f -a "detokenize" -d 'read from stdin, detokenize, write to stdout' +complete -c a2kit -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from mkdsk; and not __fish_seen_subcommand_from mkdir; and not __fish_seen_subcommand_from delete; and not __fish_seen_subcommand_from protect; and not __fish_seen_subcommand_from unprotect; and not __fish_seen_subcommand_from lock; and not __fish_seen_subcommand_from unlock; and not __fish_seen_subcommand_from rename; and not __fish_seen_subcommand_from retype; and not __fish_seen_subcommand_from verify; and not __fish_seen_subcommand_from minify; and not __fish_seen_subcommand_from renumber; and not __fish_seen_subcommand_from get; and not __fish_seen_subcommand_from put; and not __fish_seen_subcommand_from catalog; and not __fish_seen_subcommand_from tree; and not __fish_seen_subcommand_from stat; and not __fish_seen_subcommand_from geometry; and not __fish_seen_subcommand_from tokenize; and not __fish_seen_subcommand_from detokenize; and not __fish_seen_subcommand_from help" -f -a "help" -d 'Print this message or the help of the given subcommand(s)' diff --git a/src/bios/bpb.rs b/src/bios/bpb.rs index 6862c50..7094acd 100644 --- a/src/bios/bpb.rs +++ b/src/bios/bpb.rs @@ -69,6 +69,31 @@ pub struct BPBFoundation { pub tot_sec_32: [u8;4], } +impl BPBFoundation { + fn to_json(&self,indent: u16) -> String { + let mut ans = json::JsonValue::new_object(); + let mut bpb = json::JsonValue::new_object(); + bpb["bytes_per_sec"] = json::JsonValue::String(hex::encode_upper(&self.bytes_per_sec)); + bpb["sec_per_clus"] = json::JsonValue::String(hex::encode_upper(&vec![self.sec_per_clus])); + bpb["reserved_sectors"] = json::JsonValue::String(hex::encode_upper(&self.reserved_sectors)); + bpb["num_fats"] = json::JsonValue::String(hex::encode_upper(&vec![self.num_fats])); + bpb["root_ent_cnt"] = json::JsonValue::String(hex::encode_upper(&self.root_ent_cnt)); + bpb["tot_sec_16"] = json::JsonValue::String(hex::encode_upper(&self.tot_sec_16)); + bpb["media"] = json::JsonValue::String(hex::encode_upper(&vec![self.media])); + bpb["fat_size_16"] = json::JsonValue::String(hex::encode_upper(&self.fat_size_16)); + bpb["sec_per_trk"] = json::JsonValue::String(hex::encode_upper(&self.sec_per_trk)); + bpb["num_heads"] = json::JsonValue::String(hex::encode_upper(&self.num_heads)); + bpb["hidd_sec"] = json::JsonValue::String(hex::encode_upper(&self.hidd_sec)); + bpb["tot_sec_32"] = json::JsonValue::String(hex::encode_upper(&self.tot_sec_32)); + ans["bpb"] = bpb; + if indent==0 { + return json::stringify(ans); + } else { + return json::stringify_pretty(ans,indent); + } + } +} + /// Introduced with Windows 95, appears starting at byte 36 of the boot sector. #[derive(DiskStruct)] pub struct BPBExtension32 { @@ -478,6 +503,9 @@ impl BootSector { self.tail.vol_id = id; self.tail.vol_lab = label; } + pub fn to_json(&self,indent: u16) -> String { + self.foundation.to_json(indent) + } } const SSSD_8: BPBFoundation = BPBFoundation { diff --git a/src/bios/dpb.rs b/src/bios/dpb.rs index a2b47df..46c88c6 100644 --- a/src/bios/dpb.rs +++ b/src/bios/dpb.rs @@ -50,6 +50,31 @@ pub struct DiskParameterBlock { pub reserved_track_capacity: usize } +impl DiskParameterBlock { + pub fn to_json(&self,indent: u16) -> String { + let mut ans = json::JsonValue::new_object(); + let mut dpb = json::JsonValue::new_object(); + dpb["spt"] = json::JsonValue::String(hex::encode_upper(&u16::to_le_bytes(self.spt))); + dpb["bsh"] = json::JsonValue::String(hex::encode_upper(&vec![self.bsh])); + dpb["blm"] = json::JsonValue::String(hex::encode_upper(&vec![self.blm])); + dpb["exm"] = json::JsonValue::String(hex::encode_upper(&vec![self.exm])); + dpb["dsm"] = json::JsonValue::String(hex::encode_upper(&u16::to_le_bytes(self.dsm))); + dpb["drm"] = json::JsonValue::String(hex::encode_upper(&u16::to_le_bytes(self.drm))); + dpb["al0"] = json::JsonValue::String(hex::encode_upper(&vec![self.al0])); + dpb["al1"] = json::JsonValue::String(hex::encode_upper(&vec![self.al1])); + dpb["cks"] = json::JsonValue::String(hex::encode_upper(&u16::to_le_bytes(self.cks))); + dpb["off"] = json::JsonValue::String(hex::encode_upper(&u16::to_le_bytes(self.off))); + dpb["psh"] = json::JsonValue::String(hex::encode_upper(&vec![self.psh])); + dpb["phm"] = json::JsonValue::String(hex::encode_upper(&vec![self.phm])); + ans["dpb"] = dpb; + if indent==0 { + return json::stringify(ans); + } else { + return json::stringify_pretty(ans,indent); + } + } +} + pub const A2_525: DiskParameterBlock = DiskParameterBlock { spt: 32, bsh: 3, diff --git a/src/cli.rs b/src/cli.rs index 222a60d..21a318d 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -306,6 +306,26 @@ Detokenize from image: `a2kit get -f prog -t atok -d myimg.dsk | a2kit detokeniz .about("write directory tree as a JSON string to stdout") .after_help(IN_HELP), ); + main_cmd = main_cmd.subcommand( + Command::new("stat") + .arg( + arg!(-d --dimg "path to disk image") + .value_hint(ValueHint::FilePath) + .required(false), + ) + .about("write FS statistics as a JSON string to stdout") + .after_help(IN_HELP), + ); + main_cmd = main_cmd.subcommand( + Command::new("geometry") + .arg( + arg!(-d --dimg "path to disk image") + .value_hint(ValueHint::FilePath) + .required(false), + ) + .about("write disk geometry as a JSON string to stdout") + .after_help(IN_HELP), + ); main_cmd = main_cmd.subcommand( Command::new("tokenize") .arg( diff --git a/src/fs/cpm/directory.rs b/src/fs/cpm/directory.rs index 5b7b7e6..b9cc424 100644 --- a/src/fs/cpm/directory.rs +++ b/src/fs/cpm/directory.rs @@ -785,6 +785,20 @@ impl Directory { } Ok(ans) } + /// Collect users, this assumes we have established a valid CP/M directory + pub fn get_users(&self) -> Vec { + let mut ans = Vec::new(); + for i in 0..self.num_entries() { + let xtype = self.get_type(&Ptr::ExtentEntry(i)); + if let Some(fx) = self.get_entry::(&Ptr::ExtentEntry(i)) { + if !ans.contains(&fx.user) { + ans.push(fx.user); + } + } + } + ans.sort(); + ans + } /// Sort the files based on the order of appearance in the directory. /// Panics if there is a file with an empty entry list. pub fn sort_on_entry_index(&self,files: &BTreeMap) -> BTreeMap { diff --git a/src/fs/cpm/display.rs b/src/fs/cpm/display.rs index 548ad2f..49ee07f 100644 --- a/src/fs/cpm/display.rs +++ b/src/fs/cpm/display.rs @@ -644,5 +644,8 @@ pub fn tree(dir: &directory::Directory,dpb: &DiskParameterBlock,include_meta: bo } } } - Ok(json::stringify_pretty(tree, 4)) + match dpb.user_blocks() > 2048 && include_meta { + true => Ok(json::stringify_pretty(tree, 1)), + false => Ok(json::stringify_pretty(tree, 2)) + } } \ No newline at end of file diff --git a/src/fs/cpm/mod.rs b/src/fs/cpm/mod.rs index 007f3bf..d39eac0 100644 --- a/src/fs/cpm/mod.rs +++ b/src/fs/cpm/mod.rs @@ -565,6 +565,29 @@ impl super::DiskFS for Disk { fn new_fimg(&self,chunk_len: usize) -> super::FileImage { Disk::new_fimg(chunk_len) } + fn stat(&mut self) -> Result { + let dir = &self.get_directory(); + Ok(super::Stat { + fs_name: "cpm".to_string(), + label: match dir.find_label() { + Some(label) => { + let (mut res,typ) = label.get_split_string(); + if typ.len()>0 { + res += "."; + res += &typ; + } + res + }, + None => "".to_string() + }, + users: dir.get_users().iter().map(|u| u.to_string()).collect(), + block_size: self.dpb.block_size(), + block_beg: 0, + block_end: self.dpb.user_blocks(), + free_blocks: self.num_free_blocks(dir) as usize, + raw: self.dpb.to_json(0) + }) + } fn catalog_to_stdout(&mut self, opt: &str) -> STDRESULT { let dir = self.get_directory(); match opt { diff --git a/src/fs/dos3x/mod.rs b/src/fs/dos3x/mod.rs index 52ff67f..6a9f5ef 100644 --- a/src/fs/dos3x/mod.rs +++ b/src/fs/dos3x/mod.rs @@ -686,6 +686,19 @@ impl super::DiskFS for Disk { fn new_fimg(&self,chunk_len: usize) -> super::FileImage { Disk::new_fimg(chunk_len) } + fn stat(&mut self) -> Result { + let vtoc = &self.get_vtoc_constants()?; + Ok(super::Stat { + fs_name: "a2 dos".to_string(), + label: vtoc.vol.to_string(), + users: Vec::new(), + block_size: 256, + block_beg: 0, + block_end: vtoc.sectors as usize * vtoc.tracks as usize, + free_blocks: self.num_free_sectors()?, + raw: "".to_string() + }) + } fn catalog_to_stdout(&mut self, _path: &str) -> STDRESULT { let vconst = self.get_vtoc_constants()?; let typ_map: HashMap = HashMap::from([(0," T"),(1," I"),(2," A"),(4," B"),(128,"*T"),(129,"*I"),(130,"*A"),(132,"*B")]); @@ -761,7 +774,7 @@ impl super::DiskFS for Disk { } ts = [dir.next_track,dir.next_sector]; if ts == [0,0] { - return Ok(json::stringify_pretty(tree, 4)); + return Ok(json::stringify_pretty(tree, 2)); } } error!("the disk image directory seems to be damaged"); @@ -925,14 +938,16 @@ impl super::DiskFS for Disk { } } fn read_block(&mut self,num: &str) -> Result<(u16,Vec),DYNERR> { - let vtoc = self.get_vtoc_ref()?; + let vconst = self.get_vtoc_constants()?; + let sec_cnt = vconst.sectors as usize; + let bytes_per_sector = u16::from_le_bytes(vconst.bytes) as usize; match usize::from_str(num) { Ok(sector) => { - if sector > vtoc.tracks as usize*vtoc.sectors as usize { + if sector > vconst.tracks as usize*sec_cnt { return Err(Box::new(Error::Range)); } - let mut buf: Vec = vec![0;256]; - self.read_sector(&mut buf,[(sector/16) as u8,(sector%16) as u8],0)?; + let mut buf: Vec = vec![0;bytes_per_sector]; + self.read_sector(&mut buf,[(sector/sec_cnt) as u8,(sector%sec_cnt) as u8],0)?; Ok((0,buf)) }, Err(e) => Err(Box::new(e)) @@ -940,13 +955,14 @@ impl super::DiskFS for Disk { } fn write_block(&mut self,num: &str,dat: &[u8]) -> Result { let vconst = self.get_vtoc_constants()?; + let sec_cnt = vconst.sectors as usize; let bytes_per_sector = u16::from_le_bytes(vconst.bytes); match usize::from_str(num) { Ok(sector) => { - if dat.len()>bytes_per_sector as usize || sector > vconst.tracks as usize*vconst.sectors as usize { + if dat.len()>bytes_per_sector as usize || sector > vconst.tracks as usize*sec_cnt { return Err(Box::new(Error::Range)); } - self.zap_sector(&dat,[(sector/16) as u8,(sector%16) as u8],0,bytes_per_sector)?; + self.zap_sector(&dat,[(sector/sec_cnt) as u8,(sector%sec_cnt) as u8],0,bytes_per_sector)?; Ok(dat.len()) }, Err(e) => Err(Box::new(e)) diff --git a/src/fs/fat/mod.rs b/src/fs/fat/mod.rs index 0698643..78b8ca7 100644 --- a/src/fs/fat/mod.rs +++ b/src/fs/fat/mod.rs @@ -922,6 +922,19 @@ impl super::DiskFS for Disk { fn new_fimg(&self,chunk_len: usize) -> super::FileImage { Disk::new_fimg(chunk_len,true) } + fn stat(&mut self) -> Result { + let (vol_lab,_) = self.get_root_dir()?; + Ok(super::Stat { + fs_name: "fat".to_string(), + label: vol_lab, + users: Vec::new(), + block_size: self.boot_sector.block_size() as usize, + block_beg: 2, + block_end: 2 + self.boot_sector.cluster_count_usable() as usize, + free_blocks: self.num_free_blocks()?, + raw: self.boot_sector.to_json(0) + }) + } fn catalog_to_stdout(&mut self, path_and_options: &str) -> STDRESULT { let items: Vec<&str> = path_and_options.split_whitespace().collect(); let (path,opt) = match items.len() { @@ -960,7 +973,10 @@ impl super::DiskFS for Disk { tree["files"] = self.tree_node(&dir,include_meta)?; tree["label"] = json::JsonValue::new_object(); tree["label"]["name"] = json::JsonValue::String(vol); - Ok(json::stringify_pretty(tree, 4)) + match self.boot_sector.cluster_count_usable() > 2048 && include_meta { + true => Ok(json::stringify_pretty(tree, 1)), + false => Ok(json::stringify_pretty(tree, 2)) + } } fn create(&mut self,path: &str) -> STDRESULT { let (name,mut loc) = self.prepare_to_write(path)?; @@ -1167,7 +1183,7 @@ impl super::DiskFS for Disk { match usize::from_str(num) { Ok(block) => { let mut buf: Vec = vec![0;self.boot_sector.block_size() as usize]; - if block >= 2 + self.boot_sector.cluster_count_usable() as usize { + if block < 2 || block >= 2 + self.boot_sector.cluster_count_usable() as usize { return Err(Box::new(Error::SectorNotFound)); } self.read_block(&mut buf,block,0)?; @@ -1179,9 +1195,12 @@ impl super::DiskFS for Disk { fn write_block(&mut self, num: &str, dat: &[u8]) -> Result { match usize::from_str(num) { Ok(block) => { - if dat.len() > self.boot_sector.block_size() as usize || block >= 2 + self.boot_sector.cluster_count_usable() as usize { + if block < 2 || block >= 2 + self.boot_sector.cluster_count_usable() as usize { return Err(Box::new(Error::SectorNotFound)); } + if dat.len() > self.boot_sector.block_size() as usize { + return Err(Box::new(Error::WriteFault)); + } self.zap_block(dat,block,0)?; Ok(dat.len()) }, diff --git a/src/fs/mod.rs b/src/fs/mod.rs index c6415d8..e8b1743 100644 --- a/src/fs/mod.rs +++ b/src/fs/mod.rs @@ -573,11 +573,25 @@ impl fmt::Display for Records { } } +pub struct Stat { + fs_name: String, + label: String, + users: Vec, + block_size: usize, + block_beg: usize, + block_end: usize, + free_blocks: usize, + /// raw params should be a JSON string or nothing + raw: String +} + /// Abstract file system interface. Presumed to own an underlying DiskImage. /// Provides BASIC-like high level commands, block operations, and file image operations. pub trait DiskFS { /// Create an empty file image appropriate for this file system fn new_fimg(&self,chunk_len: usize) -> FileImage; + /// Stat the file system + fn stat(&mut self) -> Result; /// List all the files on disk to standard output, mirrors `CATALOG` fn catalog_to_stdout(&mut self, path: &str) -> STDRESULT; /// Get the file system tree as a JSON string @@ -646,4 +660,27 @@ pub trait DiskFS { fn compare(&mut self,path: &std::path::Path,ignore: &HashMap>); /// Mutably borrow the underlying disk image fn get_img(&mut self) -> &mut Box; +} + +impl Stat { + pub fn to_json(&self,indent: u16) -> String { + let mut ans = json::JsonValue::new_object(); + ans["fs_name"] = json::JsonValue::String(self.fs_name.clone()); + ans["label"] = json::JsonValue::String(self.label.clone()); + ans["users"] = json::JsonValue::Array(self.users.iter().map(|s| json::JsonValue::String(s.clone())).collect()); + ans["block_size"] = json::JsonValue::Number(self.block_size.into()); + ans["block_beg"] = json::JsonValue::Number(self.block_beg.into()); + ans["block_end"] = json::JsonValue::Number(self.block_end.into()); + ans["free_blocks"] = json::JsonValue::Number(self.free_blocks.into()); + if let Ok(obj) = json::parse(&self.raw) { + ans["raw"] = obj; + } else { + ans["raw"] = json::JsonValue::Null; + } + if indent > 0 { + return json::stringify_pretty(ans, indent); + } else { + return json::stringify(ans); + } + } } \ No newline at end of file diff --git a/src/fs/pascal/mod.rs b/src/fs/pascal/mod.rs index 530acfa..8faac0a 100644 --- a/src/fs/pascal/mod.rs +++ b/src/fs/pascal/mod.rs @@ -523,6 +523,20 @@ impl super::DiskFS for Disk { fn new_fimg(&self,chunk_len: usize) -> super::FileImage { Disk::new_fimg(chunk_len) } + fn stat(&mut self) -> Result { + let dir = self.get_directory()?; + let free_block_tuple = self.num_free_blocks()?; + Ok(super::Stat { + fs_name: "a2 pascal".to_string(), + label: vol_name_to_string(dir.header.name,dir.header.name_len), + users: Vec::new(), + block_size: BLOCK_SIZE, + block_beg: 0, + block_end: dir.total_blocks(), + free_blocks: free_block_tuple.0 as usize, + raw: "".to_string() + }) + } fn catalog_to_stdout(&mut self, _path: &str) -> STDRESULT { let typ_map: HashMap = HashMap::from(TYPE_MAP_DISP); let dir = self.get_directory()?; @@ -588,7 +602,7 @@ impl super::DiskFS for Disk { } } } - Ok(json::stringify_pretty(tree, 4)) + Ok(json::stringify_pretty(tree, 2)) } fn create(&mut self,_path: &str) -> STDRESULT { error!("pascal implementation does not support operation"); diff --git a/src/fs/prodos/mod.rs b/src/fs/prodos/mod.rs index bbf3256..286ef3e 100644 --- a/src/fs/prodos/mod.rs +++ b/src/fs/prodos/mod.rs @@ -893,6 +893,19 @@ impl super::DiskFS for Disk { fn new_fimg(&self,chunk_len: usize) -> super::FileImage { Disk::new_fimg(chunk_len) } + fn stat(&mut self) -> Result { + let vheader = self.get_vol_header()?; + Ok(super::Stat { + fs_name: "prodos".to_string(), + label: vheader.name(), + users: Vec::new(), + block_size: BLOCK_SIZE, + block_beg: 0, + block_end: self.total_blocks, + free_blocks: self.num_free_blocks()? as usize, + raw: "".to_string() + }) + } fn catalog_to_stdout(&mut self, path: &str) -> STDRESULT { let b = self.find_dir_key_block(path)?; let mut dir = self.get_directory(b as usize)?; @@ -933,7 +946,10 @@ impl super::DiskFS for Disk { tree["files"] = self.tree_node(dir_block,include_meta)?; tree["label"] = json::JsonValue::new_object(); tree["label"]["name"] = json::JsonValue::String(vhdr.name()); - Ok(json::stringify_pretty(tree, 4)) + match vhdr.total_blocks() > 1600 && include_meta { + true => Ok(json::stringify_pretty(tree, 1)), + false => Ok(json::stringify_pretty(tree, 2)) + } } fn create(&mut self,path: &str) -> STDRESULT { match self.prepare_to_write(path) { diff --git a/src/img/disk35.rs b/src/img/disk35.rs index 78f0e30..6fe74ef 100644 --- a/src/img/disk35.rs +++ b/src/img/disk35.rs @@ -559,6 +559,26 @@ impl super::TrackBits for TrackBits { } return ans; } + fn chs_map(&mut self,bits: &[u8]) -> Result,NibbleError> { + let mut secs_found: Vec = Vec::new(); + self.reset(); + let mut ans: Vec<[usize;3]> = Vec::new(); + for _try in 0..32 { + if self.find_byte_pattern(bits,&self.adr_fmt.prolog.clone(),&self.adr_fmt.prolog_mask.clone(),None).is_some() { + let (mod_cyl,sector,side,_format,_chksum) = self.decode_addr(bits)?; + let cyl = mod_cyl as usize + 64 * (side as usize & 0x01); + let head = side as usize >> 5; + if secs_found.contains(§or) { + return Ok(ans) + } + ans.push([cyl,head,sector as usize]); + secs_found.push(sector); + } else { + return Err(NibbleError::BitPatternNotFound); + } + } + return Ok(ans); + } } /// create the inverse to the encoding table diff --git a/src/img/disk525.rs b/src/img/disk525.rs index 3d63a5e..c4aa180 100644 --- a/src/img/disk525.rs +++ b/src/img/disk525.rs @@ -592,6 +592,24 @@ impl super::TrackBits for TrackBits { } return ans; } + fn chs_map(&mut self,bits: &[u8]) -> Result,NibbleError> { + let mut secs_found: Vec = Vec::new(); + self.reset(); + let mut ans: Vec<[usize;3]> = Vec::new(); + for _try in 0..32 { + if self.find_byte_pattern(bits,&self.adr_fmt.prolog.clone(),&self.adr_fmt.prolog_mask.clone(),None).is_some() { + let (_vol,track,sector,_chksum) = self.decode_addr(bits); + if secs_found.contains(§or) { + return Ok(ans) + } + ans.push([track as usize,0,sector as usize]); + secs_found.push(sector); + } else { + return Err(NibbleError::BitPatternNotFound); + } + } + return Ok(ans); + } } fn invert_53() -> [u8;256] { diff --git a/src/img/dot2mg.rs b/src/img/dot2mg.rs index bacf404..095b43d 100644 --- a/src/img/dot2mg.rs +++ b/src/img/dot2mg.rs @@ -135,6 +135,9 @@ impl img::DiskImage for Dot2mg { fn track_count(&self) -> usize { self.raw_img.track_count() } + fn num_heads(&self) -> usize { + self.raw_img.num_heads() + } fn byte_capacity(&self) -> usize { self.raw_img.byte_capacity() } @@ -298,6 +301,9 @@ impl img::DiskImage for Dot2mg { fn set_track_buf(&mut self,cyl: usize,head: usize,dat: &[u8]) -> STDRESULT { self.raw_img.set_track_buf(cyl, head, dat) } + fn get_track_solution(&mut self,trk: usize) -> Result,DYNERR> { + self.raw_img.get_track_solution(trk) + } fn get_track_nibbles(&mut self,cyl: usize,head: usize) -> Result,DYNERR> { self.raw_img.get_track_nibbles(cyl, head) } diff --git a/src/img/dsk_d13.rs b/src/img/dsk_d13.rs index 9c04911..172cdb5 100644 --- a/src/img/dsk_d13.rs +++ b/src/img/dsk_d13.rs @@ -40,6 +40,9 @@ impl img::DiskImage for D13 { fn track_count(&self) -> usize { return self.tracks as usize; } + fn num_heads(&self) -> usize { + 1 + } fn byte_capacity(&self) -> usize { return self.data.len(); } @@ -116,6 +119,20 @@ impl img::DiskImage for D13 { error!("D13 images have no track bits"); return Err(Box::new(img::Error::ImageTypeMismatch)); } + fn get_track_solution(&mut self,trk: usize) -> Result,DYNERR> { + let [c,h] = self.track_2_ch(trk); + let mut chs_map: Vec<[usize;3]> = Vec::new(); + for i in 0..13 { + chs_map.push([c,h,crate::bios::skew::DOS32_PHYSICAL[i]]); + } + return Ok(Some(img::TrackSolution { + cylinder: c, + head: h, + flux_code: img::FluxCode::GCR, + nib_code: img::NibbleCode::N53, + chs_map + })); + } fn get_track_nibbles(&mut self,_cyl: usize,_head: usize) -> Result,DYNERR> { error!("D13 images have no track bits"); return Err(Box::new(img::Error::ImageTypeMismatch)); diff --git a/src/img/dsk_do.rs b/src/img/dsk_do.rs index 4aad4d4..a9179bc 100644 --- a/src/img/dsk_do.rs +++ b/src/img/dsk_do.rs @@ -51,6 +51,9 @@ impl img::DiskImage for DO { fn track_count(&self) -> usize { return self.tracks as usize; } + fn num_heads(&self) -> usize { + 1 + } fn byte_capacity(&self) -> usize { return self.data.len(); } @@ -187,6 +190,20 @@ impl img::DiskImage for DO { error!("DO images have no track bits"); return Err(Box::new(img::Error::ImageTypeMismatch)); } + fn get_track_solution(&mut self,trk: usize) -> Result,DYNERR> { + let [c,h] = self.track_2_ch(trk); + let mut chs_map: Vec<[usize;3]> = Vec::new(); + for i in 0..16 { + chs_map.push([c,h,i]); + } + return Ok(Some(img::TrackSolution { + cylinder: c, + head: h, + flux_code: img::FluxCode::GCR, + nib_code: img::NibbleCode::N62, + chs_map + })); + } fn get_track_nibbles(&mut self,_cyl: usize,_head: usize) -> Result,DYNERR> { error!("DO images have no track bits"); return Err(Box::new(img::Error::ImageTypeMismatch)); diff --git a/src/img/dsk_img.rs b/src/img/dsk_img.rs index c63a17b..f5bab6a 100644 --- a/src/img/dsk_img.rs +++ b/src/img/dsk_img.rs @@ -55,6 +55,9 @@ impl img::DiskImage for Img { fn track_count(&self) -> usize { return self.cylinders * self.heads; } + fn num_heads(&self) -> usize { + return self.heads; + } fn byte_capacity(&self) -> usize { return self.data.len(); } @@ -101,7 +104,7 @@ impl img::DiskImage for Img { } } fn read_sector(&mut self,cyl: usize,head: usize,sec: usize) -> Result,DYNERR> { - let track = cyl*self.heads + head; + let track = self.ch_2_track([cyl, head]); trace!("reading {}/{}/{}",cyl,head,sec); if track>=self.track_count() || sec<1 || sec>self.sectors as usize { error!("track/sector range should be 0-{}/1-{}",self.track_count()-1,self.sectors); @@ -111,7 +114,7 @@ impl img::DiskImage for Img { Ok(self.data[offset..offset+self.sec_size].to_vec()) } fn write_sector(&mut self,cyl: usize,head: usize,sec: usize,dat: &[u8]) -> STDRESULT { - let track = cyl*self.heads + head; + let track = self.ch_2_track([cyl, head]); trace!("writing {}/{}/{}",cyl,head,sec); if track>=self.track_count() || sec<1 || sec>self.sectors as usize { error!("track/sector range should be 0-{}/1-{}",self.track_count()-1,self.sectors); @@ -184,6 +187,26 @@ impl img::DiskImage for Img { error!("IMG images have no track bits"); return Err(Box::new(img::Error::ImageTypeMismatch)); } + fn get_track_solution(&mut self,trk: usize) -> Result,DYNERR> { + let [cylinder,head] = self.track_2_ch(trk); + let mut chs_map: Vec<[usize;3]> = Vec::new(); + for i in 0..self.sectors { + chs_map.push([cylinder,head,i]); + } + let flux_code = match self.kind { + img::DiskKind::D35(l) => l.flux_code[0], + img::DiskKind::D525(l) => l.flux_code[0], + img::DiskKind::D8(l) => l.flux_code[0], + _ => img::FluxCode::None + }; + return Ok(Some(img::TrackSolution { + cylinder, + head, + flux_code, + nib_code: img::NibbleCode::None, + chs_map + })); + } fn get_track_nibbles(&mut self,_cyl: usize,_head: usize) -> Result,DYNERR> { error!("IMG images have no track bits"); return Err(Box::new(img::Error::ImageTypeMismatch)); diff --git a/src/img/dsk_po.rs b/src/img/dsk_po.rs index 1b202ae..b57c272 100644 --- a/src/img/dsk_po.rs +++ b/src/img/dsk_po.rs @@ -51,6 +51,9 @@ impl img::DiskImage for PO { fn track_count(&self) -> usize { return self.blocks as usize/8; } + fn num_heads(&self) -> usize { + 1 + } fn byte_capacity(&self) -> usize { return self.data.len(); } @@ -115,6 +118,9 @@ impl img::DiskImage for PO { error!("PO images have no track bits"); return Err(Box::new(img::Error::ImageTypeMismatch)); } + fn get_track_solution(&mut self,_trk: usize) -> Result,DYNERR> { + return Ok(None); + } fn get_track_nibbles(&mut self,_cyl: usize,_head: usize) -> Result,DYNERR> { error!("PO images have no track bits"); return Err(Box::new(img::Error::ImageTypeMismatch)); diff --git a/src/img/imd.rs b/src/img/imd.rs index 8e5f8a6..55e83b5 100644 --- a/src/img/imd.rs +++ b/src/img/imd.rs @@ -19,6 +19,7 @@ use crate::fs::Block; use crate::img::names::*; use crate::{STDRESULT,DYNERR,putString}; +#[derive(FromPrimitive)] pub enum Mode { Fm500Kbps = 0, Fm300Kbps = 1, @@ -363,9 +364,6 @@ impl Imd { tracks } } - pub fn num_heads(&self) -> usize { - self.heads - } fn get_track_mut(&mut self,cyl: usize,head: usize) -> Result<&mut Track,img::Error> { for trk in &mut self.tracks { if trk.cylinder as usize==cyl && (trk.head & HEAD_MASK) as usize==head { @@ -411,6 +409,20 @@ impl img::DiskImage for Imd { fn track_count(&self) -> usize { self.tracks.len() } + fn num_heads(&self) -> usize { + self.heads + } + fn track_2_ch(&self,track: usize) -> [usize;2] { + [self.tracks[track].cylinder as usize,self.tracks[track].head as usize] + } + fn ch_2_track(&self,ch: [usize;2]) -> usize { + for i in 0..self.tracks.len() { + if self.tracks[i].cylinder as usize==ch[0] && self.tracks[i].head as usize==ch[1] { + return i + } + } + panic!("cylinder {}, head {} does not exist",ch[0],ch[1]); + } fn byte_capacity(&self) -> usize { let mut ans = 0; for trk in &self.tracks { @@ -670,6 +682,27 @@ impl img::DiskImage for Imd { error!("IMD images have no track bits"); return Err(Box::new(img::Error::ImageTypeMismatch)); } + fn get_track_solution(&mut self,trk: usize) -> Result,DYNERR> { + let trk_obj = &self.tracks[trk]; + let flux_code = match Mode::from_u8(trk_obj.mode) { + Some(Mode::Fm250Kbps) | Some(Mode::Fm300Kbps) | Some(Mode::Fm500Kbps) => img::FluxCode::FM, + Some(Mode::Mfm250Kbps) | Some(Mode::Mfm300Kbps) | Some(Mode::Mfm500Kbps) => img::FluxCode::MFM, + None => img::FluxCode::None + }; + let mut chs_map: Vec<[usize;3]> = Vec::new(); + for i in 0..trk_obj.sectors as usize { + let c = match trk_obj.cylinder_map.len()>i { true=>trk_obj.cylinder_map[i] as usize, false=>trk_obj.cylinder as usize }; + let h = match trk_obj.head_map.len()>i { true=>trk_obj.head_map[i] as usize, false=>trk_obj.head as usize }; + chs_map.push([c,h,trk_obj.sector_map[i] as usize]); + } + Ok(Some(img::TrackSolution { + cylinder: trk_obj.cylinder as usize, + head: trk_obj.head as usize, + flux_code, + nib_code: img::NibbleCode::None, + chs_map + })) + } fn get_track_nibbles(&mut self,_cyl: usize,_head: usize) -> Result,DYNERR> { error!("IMD images have no track bits"); return Err(Box::new(img::Error::ImageTypeMismatch)); diff --git a/src/img/mod.rs b/src/img/mod.rs index f83af5f..7ad94f3 100644 --- a/src/img/mod.rs +++ b/src/img/mod.rs @@ -124,6 +124,14 @@ pub struct BlockLayout { block_count: usize } +pub struct TrackSolution { + cylinder: usize, + head: usize, + flux_code: FluxCode, + nib_code: NibbleCode, + chs_map: Vec<[usize;3]> +} + #[derive(PartialEq,Eq,Clone,Copy)] pub struct TrackLayout { cylinders: [usize;5], @@ -234,6 +242,28 @@ impl fmt::Display for DiskKind { } } +impl fmt::Display for NibbleCode { + fn fmt(&self,f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + NibbleCode::N44 => write!(f,"4&4"), + NibbleCode::N53 => write!(f,"5&3"), + NibbleCode::N62 => write!(f,"6&2"), + NibbleCode::None => write!(f,"none") + } + } +} + +impl fmt::Display for FluxCode { + fn fmt(&self,f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + FluxCode::FM => write!(f,"FM"), + FluxCode::MFM => write!(f,"MFM"), + FluxCode::GCR => write!(f,"GCR"), + FluxCode::None => write!(f,"none") + } + } +} + /// Given a command line argument return a likely disk kind the user may want impl FromStr for DiskKind { type Err = Error; @@ -323,6 +353,9 @@ pub trait TrackBits { fn read_sector(&mut self,bits: &[u8],track: u8,sector: u8) -> Result,NibbleError>; /// Get aligned track nibbles; n.b. head position will move. fn to_nibbles(&mut self,bits: &[u8]) -> Vec; + /// Get CHS addresses in time order, or return an error. Head position will move. + /// This can also be used to determine if the assumed encoding is valid. + fn chs_map(&mut self,bits: &[u8]) -> Result,NibbleError>; } /// The main trait for working with any kind of disk image. @@ -331,6 +364,13 @@ pub trait TrackBits { /// track of the head position or other status indicators. pub trait DiskImage { fn track_count(&self) -> usize; + fn num_heads(&self) -> usize; + fn track_2_ch(&self,track: usize) -> [usize;2] { + [track/self.num_heads(),track%self.num_heads()] + } + fn ch_2_track(&self,ch: [usize;2]) -> usize { + ch[0]*self.num_heads() + ch[1] + } /// N.b. this means bytes, not nibbles, e.g., a nibble buffer will be larger fn byte_capacity(&self) -> usize; fn what_am_i(&self) -> DiskImageType; @@ -351,6 +391,11 @@ pub trait DiskImage { fn get_track_buf(&mut self,cyl: usize,head: usize) -> Result,DYNERR>; /// Set the track buffer using another track buffer, the sizes must match fn set_track_buf(&mut self,cyl: usize,head: usize,dat: &[u8]) -> STDRESULT; + /// Given a track index, get the physical CH, CHS map, flux and nibble codes for the track. + /// Implement this at a low level, making as few assumptions as possible. + /// The expense of this operation can vary widely depending on the image type. + /// No solution is not an error, i.e., we can return Ok(None). + fn get_track_solution(&mut self,track: usize) -> Result,DYNERR>; /// Get the track bytes as aligned nibbles; for user inspection fn get_track_nibbles(&mut self,cyl: usize,head: usize) -> Result,DYNERR>; /// Write the track to a string suitable for display, input should be pre-aligned nibbles, e.g. from `get_track_nibbles` @@ -377,6 +422,52 @@ pub trait DiskImage { fn put_metadata(&mut self,key_path: &Vec, _val: &json::JsonValue) -> STDRESULT { meta::test_metadata(key_path,self.what_am_i()) } + /// Write the disk geometry, including all track solutions, into a JSON string + fn export_geometry(&mut self,indent: u16) -> Result { + let mut solved_track_count = 0; + let mut root = json::JsonValue::new_object(); + root["package"] = json::JsonValue::String(package_string(&self.kind())); + let mut trk_ary = json::JsonValue::new_array(); + for trk in 0..self.track_count() { + match self.get_track_solution(trk) { + Ok(Some(sol)) => { + solved_track_count += 1; + let mut trk_obj = json::JsonValue::new_object(); + trk_obj["cylinder"] = json::JsonValue::Number(sol.cylinder.into()); + trk_obj["head"] = json::JsonValue::Number(sol.head.into()); + trk_obj["flux_code"] = match sol.flux_code { + FluxCode::None => json::JsonValue::Null, + f => json::JsonValue::String(f.to_string()) + }; + trk_obj["nibble_code"] = match sol.nib_code { + NibbleCode::None => json::JsonValue::Null, + n => json::JsonValue::String(n.to_string()) + }; + trk_obj["chs_map"] = json::JsonValue::new_array(); + for chs in sol.chs_map { + let mut chs_json = json::JsonValue::new_array(); + chs_json.push(chs[0])?; + chs_json.push(chs[1])?; + chs_json.push(chs[2])?; + trk_obj["chs_map"].push(chs_json)?; + } + trk_ary.push(trk_obj)?; + }, + Ok(None) => trk_ary.push(json::JsonValue::Null)?, + Err(e) => return Err(e) + } + } + if solved_track_count==0 { + root["tracks"] = json::JsonValue::Null; + } else { + root["tracks"] = trk_ary; + } + if indent==0 { + Ok(json::stringify(root)) + } else { + Ok(json::stringify_pretty(root, indent)) + } + } } /// Test a buffer for a size match to DOS-oriented track and sector counts. @@ -405,3 +496,15 @@ pub fn quantize_block(src: &[u8],quantum: usize) -> Vec { return padded; } +/// Package designation for geometry JSON (e.g., "3.5", "5.25", ...) +pub fn package_string(kind: &DiskKind) -> String { + match kind { + DiskKind::D3(_) => "3".to_string(), + DiskKind::D35(_) => "3.5".to_string(), + DiskKind::D525(_) => "5.25".to_string(), + DiskKind::D8(_) => "8".to_string(), + DiskKind::LogicalBlocks(_) => "logical".to_string(), + DiskKind::LogicalSectors(_) => "logical".to_string(), + DiskKind::Unknown => "unknown".to_string() + } +} diff --git a/src/img/nib.rs b/src/img/nib.rs index 5e45c3a..bb12c6f 100644 --- a/src/img/nib.rs +++ b/src/img/nib.rs @@ -115,6 +115,9 @@ impl img::DiskImage for Nib { fn track_count(&self) -> usize { self.tracks } + fn num_heads(&self) -> usize { + 1 + } fn byte_capacity(&self) -> usize { match self.kind { img::names::A2_DOS32_KIND => self.tracks*13*256, @@ -149,22 +152,36 @@ impl img::DiskImage for Nib { fn from_bytes(buf: &Vec) -> Option where Self: Sized { match buf.len() { l if l==35*TRACK_BYTE_CAPACITY_NIB => { - Some(Self { + let mut disk = Self { kind: img::names::A2_DOS33_KIND, tracks: 35, trk_cap: TRACK_BYTE_CAPACITY_NIB, data: buf.clone(), head_coords: HeadCoords { track: usize::MAX, bit_ptr: usize::MAX } - }) + }; + if let Ok(Some(_sol)) = disk.get_track_solution(0) { + debug!("setting disk kind to {}",disk.kind); + return Some(disk); + } else { + debug!("Looks like NIB, but could not solve track 0"); + return None; + } }, l if l==35*TRACK_BYTE_CAPACITY_NB2 => { - Some(Self { + let mut disk = Self { kind: img::names::A2_DOS33_KIND, tracks: 35, trk_cap: TRACK_BYTE_CAPACITY_NB2, data: buf.clone(), head_coords: HeadCoords { track: usize::MAX, bit_ptr: usize::MAX } - }) + }; + if let Ok(Some(_sol)) = disk.get_track_solution(0) { + debug!("setting disk kind to {}",disk.kind); + return Some(disk); + } else { + debug!("Looks like NB2, but could not solve track 0"); + return None; + } } _ => { debug!("Buffer size {} fails to match nib or nb2",buf.len()); @@ -189,6 +206,32 @@ impl img::DiskImage for Nib { bits.copy_from_slice(dat); Ok(()) } + fn get_track_solution(&mut self,track: usize) -> Result,DYNERR> { + let [cylinder,head] = self.track_2_ch(track); + self.kind = img::names::A2_DOS32_KIND; + let mut reader = self.new_rw_obj(track as u8); + if let Ok(chs_map) = reader.chs_map(self.get_trk_bits_ref(track as u8)) { + return Ok(Some(img::TrackSolution { + cylinder, + head, + flux_code: img::FluxCode::GCR, + nib_code: img::NibbleCode::N53, + chs_map + })); + } + self.kind = img::names::A2_DOS33_KIND; + reader = self.new_rw_obj(track as u8); + if let Ok(chs_map) = reader.chs_map(self.get_trk_bits_ref(track as u8)) { + return Ok(Some(img::TrackSolution { + cylinder, + head, + flux_code: img::FluxCode::GCR, + nib_code: img::NibbleCode::N62, + chs_map + })); + } + return Err(Box::new(img::Error::UnknownDiskKind)); + } fn get_track_nibbles(&mut self,cyl: usize,head: usize) -> Result,DYNERR> { let track_num = super::woz::cyl_head_to_track(self, cyl, head)?; let mut reader = self.new_rw_obj(track_num as u8); diff --git a/src/img/td0.rs b/src/img/td0.rs index a0e229f..2d82cf5 100644 --- a/src/img/td0.rs +++ b/src/img/td0.rs @@ -542,9 +542,6 @@ impl Td0 { end: 0xff } } - pub fn num_heads(&self) -> usize { - self.heads - } fn get_track_mut(&mut self,cyl: usize,head: usize) -> Result<&mut Track,img::Error> { for trk in &mut self.tracks { if trk.header.cylinder as usize==cyl && (trk.header.head & HEAD_MASK) as usize==head { @@ -605,6 +602,20 @@ impl img::DiskImage for Td0 { fn track_count(&self) -> usize { self.tracks.len() } + fn num_heads(&self) -> usize { + self.heads + } + fn track_2_ch(&self,track: usize) -> [usize;2] { + [self.tracks[track].header.cylinder as usize,self.tracks[track].header.head as usize] + } + fn ch_2_track(&self,ch: [usize;2]) -> usize { + for i in 0..self.tracks.len() { + if self.tracks[i].header.cylinder as usize==ch[0] && self.tracks[i].header.head as usize==ch[1] { + return i + } + } + panic!("cylinder {}, head {} does not exist",ch[0],ch[1]); + } fn byte_capacity(&self) -> usize { let mut ans = 0; for trk in &self.tracks { @@ -917,6 +928,24 @@ impl img::DiskImage for Td0 { error!("TD0 images have no track bits"); return Err(Box::new(img::Error::ImageTypeMismatch)); } + fn get_track_solution(&mut self,trk: usize) -> Result,DYNERR> { + let trk_obj = &self.tracks[trk]; + let flux_code = match trk_obj.header.head > 127 || self.header.data_rate > 127 { + true => img::FluxCode::FM, + false => img::FluxCode::MFM + }; + let mut chs_map: Vec<[usize;3]> = Vec::new(); + for sec in &trk_obj.sectors { + chs_map.push([sec.header.cylinder as usize,sec.header.head as usize,sec.header.id as usize]); + } + Ok(Some(img::TrackSolution { + cylinder: trk_obj.header.cylinder as usize, + head: trk_obj.header.head as usize, + flux_code, + nib_code: img::NibbleCode::None, + chs_map + })) + } fn get_track_nibbles(&mut self,_cyl: usize,_head: usize) -> Result,DYNERR> { error!("TD0 images have no track bits"); return Err(Box::new(img::Error::ImageTypeMismatch)); diff --git a/src/img/woz1.rs b/src/img/woz1.rs index 20fc242..4a503c2 100644 --- a/src/img/woz1.rs +++ b/src/img/woz1.rs @@ -353,6 +353,9 @@ impl img::DiskImage for Woz1 { _ => panic!("disk type not supported") } } + fn num_heads(&self) -> usize { + 1 + } fn byte_capacity(&self) -> usize { match self.info.disk_type { 1 => self.track_count()*16*256, @@ -408,15 +411,15 @@ impl img::DiskImage for Woz1 { } ptr = next; } - if u32::from_le_bytes(ans.info.id)>0 && u32::from_le_bytes(ans.tmap.id)>0 && u32::from_le_bytes(ans.trks.id)>0 { - // TODO: can we figure if this is a 13 sector disk at this point? - ans.kind = match ans.info.disk_type { - 1 => img::names::A2_DOS33_KIND, - _ => panic!("WOZ v1 encountered unexpected disk type in INFO chunk") - }; - debug!("setting disk kind to {}",ans.kind); + if u32::from_le_bytes(ans.info.id)>0 && u32::from_le_bytes(ans.tmap.id)>0 && u32::from_le_bytes(ans.trks.id)>0 && ans.info.disk_type==1 { + if let Ok(Some(_sol)) = ans.get_track_solution(0) { + debug!("setting disk kind to {}",ans.kind); + } else { + warn!("could not solve track 0, continuing with {}",ans.kind); + } return Some(ans); } + warn!("WOZ v1 sanity checks failed, refusing"); return None; } fn to_bytes(&mut self) -> Vec { @@ -449,6 +452,32 @@ impl img::DiskImage for Woz1 { bits.copy_from_slice(dat); Ok(()) } + fn get_track_solution(&mut self,track: usize) -> Result,DYNERR> { + let [cylinder,head] = self.track_2_ch(track); + self.kind = img::names::A2_DOS32_KIND; + let mut reader = self.new_rw_obj(track as u8); + if let Ok(chs_map) = reader.chs_map(self.get_trk_bits_ref(track as u8)) { + return Ok(Some(img::TrackSolution { + cylinder, + head, + flux_code: img::FluxCode::GCR, + nib_code: img::NibbleCode::N53, + chs_map + })); + } + self.kind = img::names::A2_DOS33_KIND; + reader = self.new_rw_obj(track as u8); + if let Ok(chs_map) = reader.chs_map(self.get_trk_bits_ref(track as u8)) { + return Ok(Some(img::TrackSolution { + cylinder, + head, + flux_code: img::FluxCode::GCR, + nib_code: img::NibbleCode::N62, + chs_map + })); + } + return Err(Box::new(img::Error::UnknownDiskKind)); + } fn get_track_nibbles(&mut self,cyl: usize,head: usize) -> Result,DYNERR> { let track_num = super::woz::cyl_head_to_track(self, cyl, head)?; let mut reader = self.new_rw_obj(track_num as u8); diff --git a/src/img/woz2.rs b/src/img/woz2.rs index 8d4d636..5e4deb5 100644 --- a/src/img/woz2.rs +++ b/src/img/woz2.rs @@ -663,6 +663,9 @@ impl img::DiskImage for Woz2 { _ => panic!("disk type not supported") } } + fn num_heads(&self) -> usize { + self.info.disk_sides as usize + } fn byte_capacity(&self) -> usize { match self.info.disk_type { 1 => self.track_count()*16*256, @@ -744,9 +747,14 @@ impl img::DiskImage for Woz2 { (2,_,2) => img::names::A2_800_KIND, _ => img::DiskKind::Unknown }; - debug!("setting disk kind to {}",ans.kind); + if let Ok(Some(_sol)) = ans.get_track_solution(0) { + debug!("setting disk kind to {}",ans.kind); + } else { + warn!("Could not solve track 0, continuing with {}",ans.kind); + } return Some(ans); } + debug!("WOZ v2 sanity checks failed, refusing"); return None; } fn to_bytes(&mut self) -> Vec { @@ -785,6 +793,52 @@ impl img::DiskImage for Woz2 { bits.copy_from_slice(dat); Ok(()) } + fn get_track_solution(&mut self,track: usize) -> Result,DYNERR> { + let [cylinder,head] = self.track_2_ch(track); + if self.info.disk_type==2 { + self.kind = match self.info.disk_sides { + 1 => img::names::A2_400_KIND, + 2 => img::names::A2_800_KIND, + _ => return Err(Box::new(img::Error::UnknownImageType)) + }; + let mut reader = self.new_rw_obj(track as u8); + if let Ok(chs_map) = reader.chs_map(self.get_trk_bits_ref(track as u8)) { + return Ok(Some(img::TrackSolution { + cylinder, + head, + flux_code: img::FluxCode::GCR, + nib_code: img::NibbleCode::N62, + chs_map + })); + } + return Ok(None); + } else if self.info.disk_type==1 { + self.kind = img::names::A2_DOS32_KIND; + let mut reader = self.new_rw_obj(track as u8); + if let Ok(chs_map) = reader.chs_map(self.get_trk_bits_ref(track as u8)) { + return Ok(Some(img::TrackSolution { + cylinder, + head, + flux_code: img::FluxCode::GCR, + nib_code: img::NibbleCode::N53, + chs_map + })); + } + self.kind = img::names::A2_DOS33_KIND; + reader = self.new_rw_obj(track as u8); + if let Ok(chs_map) = reader.chs_map(self.get_trk_bits_ref(track as u8)) { + return Ok(Some(img::TrackSolution { + cylinder, + head, + flux_code: img::FluxCode::GCR, + nib_code: img::NibbleCode::N62, + chs_map + })); + } + return Ok(None); + } + return Err(Box::new(img::Error::UnknownImageType)); + } fn get_track_nibbles(&mut self,cyl: usize,head: usize) -> Result,DYNERR> { let track_num = super::woz::cyl_head_to_track(self, cyl, head)?; let mut reader = self.new_rw_obj(track_num as u8); diff --git a/src/lib.rs b/src/lib.rs index 65ea603..def04b1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -283,10 +283,20 @@ pub fn create_img_from_bytestream(disk_img_data: &Vec,maybe_ext: Option<&str return Ok(Box::new(img)); } } + // For DO we need to run the FS heuristics to distinguish from PO, + // in case the extension hint is missing or vague. if img::dsk_do::file_extensions().contains(&ext) || ext=="" { if let Some(img) = img::dsk_do::DO::from_bytes(disk_img_data) { info!("Possible DO image"); - return Ok(Box::new(img)); + if ext=="do" { + return Ok(Box::new(img)); + } + // TODO: run the FS heuristics without taking ownership + if try_img(Box::new(img)).is_some() { + let copy = img::dsk_do::DO::from_bytes(disk_img_data).unwrap(); + return Ok(Box::new(copy)); + } + debug!("reject DO based on FS heuristics") } } if img::dsk_po::file_extensions().contains(&ext) || ext=="" { diff --git a/src/main.rs b/src/main.rs index 9ea2640..34bfffb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -61,6 +61,31 @@ fn main() -> Result<(),Box> }; } + // Output the FS stats as a JSON string + + if let Some(cmd) = matches.subcommand_matches("stat") { + return match a2kit::create_fs_from_file_or_stdin(cmd.get_one::("dimg")) { + Ok(mut dimg) => { + let stats = dimg.stat()?; + println!("{}",stats.to_json(2)); + Ok(()) + }, + Err(e) => Err(e) + }; + } + + // Output the disk geometry as a JSON string + + if let Some(cmd) = matches.subcommand_matches("geometry") { + return match a2kit::create_img_from_file_or_stdin(cmd.get_one::("dimg")) { + Ok(mut dimg) => { + println!("{}",dimg.export_geometry(2)?); + Ok(()) + }, + Err(e) => Err(e) + }; + } + // Verify if let Some(cmd) = matches.subcommand_matches("verify") { diff --git a/tests/msdos_test.rs b/tests/msdos_test.rs index cc5331f..721525d 100644 --- a/tests/msdos_test.rs +++ b/tests/msdos_test.rs @@ -1,4 +1,4 @@ -// test of prodos disk image module +// test of FAT file system use std::path::Path; use std::fmt::Write; use a2kit::fs::{fat,DiskFS,Block};