>> rewrite arg parisng to remove "commands"

> add strategy and directory flags
This commit is contained in:
Thomas Lindop 2025-05-13 17:48:19 +01:00
parent 50d7189654
commit c82804bf26
2 changed files with 75 additions and 66 deletions

View file

@ -7,8 +7,8 @@ Say you want to backup some of your files on to a memory stick. Most likely
that memory stick uses `FAT32` or `exFAT` as its file system, as this is the that memory stick uses `FAT32` or `exFAT` as its file system, as this is the
most compatible with the most operating systems. But if any of your files most compatible with the most operating systems. But if any of your files
have special characters like `:` or `?` in their names, this won't work (see have special characters like `:` or `?` in their names, this won't work (see
<https://learn.microsoft.com/en-gb/windows/win32/fileio/exfat-specification> <https://learn.microsoft.com/en-gb/windows/win32/fileio/exfat-specification>,
for more details). section 7.7.3 for more details).
Alternatively, say you have a file that needs to be on an older computer Alternatively, say you have a file that needs to be on an older computer
that will not render some characters (like multi-character emoji) that will not render some characters (like multi-character emoji)

137
main.go
View file

@ -37,19 +37,14 @@ import (
*/ */
type runeset = [][2]rune type runeset = [][2]rune
/**
* A runemap represents a list of rune swaps that should be preformed before
* dealing with any invalid runes. The first number in each entry is the
* Code Point of the rune to be replaced, the second the Code Point of its
* replacement.
*/
type runemap = [][2]rune
// Unrelated to the standard library's "context". // Unrelated to the standard library's "context".
type context = struct { type context = struct {
command string FileSpecs []string
hasDryRun bool Directories []string
fileSpecs []string DryRun bool
Strategy string
DoHelp bool
DoVersion bool
} }
@ -74,11 +69,6 @@ var FAT_RUNESET = runeset{
{ 0x7d, MAX_CODE_POINT}, { 0x7d, MAX_CODE_POINT},
} }
var DEFAULT_RUNEMAP = runemap{
{ 0x20, 0x5f }, // space -> underscore (a.k.a low line)
}
/// MAIN FUNCTIONS /// MAIN FUNCTIONS
func isFatValid(r rune) bool { func isFatValid(r rune) bool {
for _, runeRange := range FAT_RUNESET { for _, runeRange := range FAT_RUNESET {
@ -89,11 +79,8 @@ func isFatValid(r rune) bool {
return false return false
} }
func restrictRuneset(s, strategy string, userSubs runemap) string { func restrictRuneset(s, strategy string) string {
result := s result := s
for _, sub := range userSubs {
result = strings.ReplaceAll(result, string(sub[0]), string(sub[1]))
}
toValidSubs := make(map[rune]string) toValidSubs := make(map[rune]string)
if strategy == "remove" { if strategy == "remove" {
for _, r := range result { for _, r := range result {
@ -126,20 +113,35 @@ func kaput(err error) {
} }
} }
// TODO: Collect all invalid args into one error instead of failing fast.
func parseCLIArgs(args []string) (context, error) { func parseCLIArgs(args []string) (context, error) {
var result context // Set defaults
result := context{
FileSpecs: []string{},
Directories: []string{},
DryRun: false,
Strategy: "fat",
DoHelp: false,
DoVersion: false,
}
index := 1 index := 1
isFlag := func(arg string) bool { isFlag := func(arg string) bool {
return arg[0] == '-' return arg[0] == '-'
} }
if !isFlag(args[index]) {
result.command = args[index]
index++
}
for index < len(args) { for index < len(args) {
switch arg := args[index]; arg { switch arg := args[index]; arg {
case "-d", "--directory":
result.Directories = append(result.Directories, arg)
index++
case "-n", "--dry-run": case "-n", "--dry-run":
result.hasDryRun = true result.DryRun = true
case "-s", "--strategy":
result.Strategy = args[index]
index++
case "-h", "--help":
result.DoHelp = true
case "-v", "--version":
result.DoVersion = true
default: default:
if isFlag(arg) { if isFlag(arg) {
return result, errors.New(fmt.Sprintf( return result, errors.New(fmt.Sprintf(
@ -147,7 +149,7 @@ func parseCLIArgs(args []string) (context, error) {
arg, arg,
)) ))
} else { } else {
result.fileSpecs = append(result.fileSpecs, arg) result.FileSpecs = append(result.FileSpecs, arg)
} }
} }
index++ index++
@ -155,48 +157,55 @@ func parseCLIArgs(args []string) (context, error) {
return result, nil return result, nil
} }
func cleanCommand(ctx context) { // TODO: Finish writing options short help. Mention man page where relevant.
for _, file := range ctx.fileSpecs { func printHelp() {
if _, err := os.Stat(file); err != nil { fmt.Println(`Owl - a hunter of bad characters in filenames
warn("File <<%s>> does not exist!", file)
continue Usage:
} owl [options] FILES
oldName := fpath.Base(file)
dirName := fpath.Dir(file) Rename FILES such that all characters that invalid in FAT file systems
if ctx.hasDryRun { (?,\,*,etc.) are removed.
fmt.Printf(
"%s -> <<%s>>\n", Options:
file, -s,--strategy
restrictRuneset(oldName, "represent", DEFAULT_RUNEMAP), -h,--help
) -v,--version
} else { -d,--directory DIRECTORY
os.Rename(file,fpath.Join( `);
dirName,
restrictRuneset(oldName, "represent", DEFAULT_RUNEMAP),
))
}
}
} }
func helpCommand() {
fmt.Println(`TBD`)
}
func versionCommand() {
fmt.Printf("Owl file renaming tool, version %s\n", OwlVersion)
}
func main() { func main() {
ctx, err := parseCLIArgs(os.Args) ctx, err := parseCLIArgs(os.Args)
kaput(err) kaput(err)
switch ctx.command { if ctx.DoHelp {
case "clean": printHelp()
cleanCommand(ctx) } else if ctx.DoVersion {
case "help": fmt.Printf(
helpCommand() "Owl - a hunter of bad characters in file names\nversion %s\n",
case "version": OwlVersion,
versionCommand() )
default: } else {
helpCommand() for _, file := range ctx.FileSpecs {
if _, err := os.Stat(file); err != nil {
warn("File <<%s>> does not exist!", file)
continue
}
oldName := fpath.Base(file)
dirName := fpath.Dir(file)
if ctx.DryRun {
fmt.Printf(
"%s -> <<%s>>\n",
file,
restrictRuneset(oldName, "represent"),
)
} else {
os.Rename(file,fpath.Join(
dirName,
restrictRuneset(oldName, "represent"),
))
}
}
} }
} }