From 6a81d17a0435925e42af87a883f9c8393ca1ac49 Mon Sep 17 00:00:00 2001 From: Nuno Cruces Date: Mon, 12 Apr 2021 03:59:08 +0100 Subject: [PATCH] File filters (macOS). --- README.md | 2 +- color_unix.go | 2 +- entry_unix.go | 2 +- file.go | 88 ++++++++++++++++++++++++++++++++++++ file_darwin.go | 25 +--------- file_filter_test.go | 31 +++++++++++++ file_unix.go | 2 +- internal/zenutil/env_unix.go | 2 +- internal/zenutil/run_unix.go | 2 +- list_unix.go | 2 +- msg_unix.go | 2 +- notify_unix.go | 2 +- pwd_unix.go | 2 +- util_unix.go | 2 +- zenity.go | 2 +- 15 files changed, 133 insertions(+), 35 deletions(-) create mode 100644 file_filter_test.go diff --git a/README.md b/README.md index 514d516..6f7fd93 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ as well as a *“port”* of the `zenity` command to both Windows and macOS base Implemented dialogs: * [message](https://github.com/ncruces/zenity/wiki/Message-dialog) (error, info, question, warning) * [text entry](https://github.com/ncruces/zenity/wiki/Text-Entry-dialog) -* [list](https://github.com/ncruces/zenity/wiki/List-dialog) +* [list](https://github.com/ncruces/zenity/wiki/List-dialog) (simple) * [password](https://github.com/ncruces/zenity/wiki/Password-dialog) * [file selection](https://github.com/ncruces/zenity/wiki/File-Selection-dialog) * [color selection](https://github.com/ncruces/zenity/wiki/Color-Selection-dialog) diff --git a/color_unix.go b/color_unix.go index 464e0d1..b8efc1f 100644 --- a/color_unix.go +++ b/color_unix.go @@ -1,4 +1,4 @@ -// +build !windows,!darwin +// +build !windows,!darwin,!js package zenity diff --git a/entry_unix.go b/entry_unix.go index a688033..05b2a70 100644 --- a/entry_unix.go +++ b/entry_unix.go @@ -1,4 +1,4 @@ -// +build !windows,!darwin +// +build !windows,!darwin,!js package zenity diff --git a/file.go b/file.go index a1fcce1..386a18d 100644 --- a/file.go +++ b/file.go @@ -3,6 +3,7 @@ package zenity import ( "os" "path/filepath" + "strings" ) // SelectFile displays the file selection dialog. @@ -84,6 +85,93 @@ func (f FileFilters) apply(o *options) { o.fileFilters = append(o.fileFilters, f...) } +// macOS filters by case insensitive literal extension. +// Extract all literal extensions from all patterns. +// If those contain wildcards, or classes with more than one character, accept anything. +func (f FileFilters) darwin() []string { + var res []string + for _, filter := range f { + for _, pattern := range filter.Patterns { + ext := pattern[strings.LastIndexByte(pattern, '.')+1:] + + var escape bool + var buf strings.Builder + for _, r := range removeClasses(ext) { + switch { + case escape: + escape = false + case r == '\\': + escape = true + continue + case r == '*' || r == '?': + return nil + } + buf.WriteRune(r) + } + res = append(res, buf.String()) + } + } + return res +} + +func removeClasses(pattern string) string { + var res strings.Builder + for { + i, j := findClass(pattern) + if i < 0 { + res.WriteString(pattern) + return res.String() + } + res.WriteString(pattern[:i]) + + var char string + var escape, many bool + for _, r := range pattern[i+1 : j-1] { + if escape { + escape = false + } else if r == '\\' { + escape = true + continue + } + if char == "" { + char = string(r) + } else if !strings.EqualFold(char, string(r)) { + many = true + break + } + } + if many { + res.WriteByte('?') + } else { + res.WriteByte('\\') + res.WriteString(char) + } + pattern = pattern[j:] + } +} + +func findClass(pattern string) (start, end int) { + start = -1 + escape := false + for i, b := range []byte(pattern) { + switch { + case escape: + escape = false + case b == '\\': + escape = true + case start < 0: + if b == '[' { + start = i + } + case 0 <= start && start < i-1: + if b == ']' { + return start, i + 1 + } + } + } + return -1, -1 +} + func splitDirAndName(path string) (dir, name string) { if path != "" { path = filepath.Clean(path) diff --git a/file_darwin.go b/file_darwin.go index 438b83a..9023358 100644 --- a/file_darwin.go +++ b/file_darwin.go @@ -1,8 +1,6 @@ package zenity import ( - "strings" - "github.com/ncruces/zenity/internal/zenutil" ) @@ -16,7 +14,7 @@ func selectFile(opts options) (string, error) { data.Operation = "chooseFolder" } else { data.Operation = "chooseFile" - data.Options.Type = initFilters(opts.fileFilters) + data.Options.Type = opts.fileFilters.darwin() } out, err := zenutil.Run(opts.ctx, "file", data) @@ -36,7 +34,7 @@ func selectFileMutiple(opts options) ([]string, error) { data.Operation = "chooseFolder" } else { data.Operation = "chooseFile" - data.Options.Type = initFilters(opts.fileFilters) + data.Options.Type = opts.fileFilters.darwin() } out, err := zenutil.Run(opts.ctx, "file", data) @@ -59,22 +57,3 @@ func selectFileSave(opts options) (string, error) { str, _, err := strResult(opts, out, err) return str, err } - -func initFilters(filters []FileFilter) []string { - var filter []string - for _, f := range filters { - for _, p := range f.Patterns { - star := strings.LastIndexByte(p, '*') - if star >= 0 { - dot := strings.LastIndexByte(p, '.') - if star > dot { - return nil - } - filter = append(filter, p[dot+1:]) - } else { - filter = append(filter, p) - } - } - } - return filter -} diff --git a/file_filter_test.go b/file_filter_test.go new file mode 100644 index 0000000..55c525c --- /dev/null +++ b/file_filter_test.go @@ -0,0 +1,31 @@ +package zenity + +import ( + "reflect" + "testing" +) + +func TestFileFilters_darwin(t *testing.T) { + tests := []struct { + data FileFilters + want []string + }{ + {FileFilters{{"", []string{`*.png`}}}, []string{`png`}}, + {FileFilters{{"", []string{`*.pn?`}}}, nil}, + {FileFilters{{"", []string{`*.pn\?`}}}, []string{`pn?`}}, + {FileFilters{{"", []string{`*.[PpNnGg]`}}}, nil}, + {FileFilters{{"", []string{`*.[Pp][Nn][Gg]`}}}, []string{`PNG`}}, + {FileFilters{{"", []string{`*.[Pp][\Nn][G\g]`}}}, []string{`PNG`}}, + {FileFilters{{"", []string{`*.[PNG`}}}, []string{`[PNG`}}, + {FileFilters{{"", []string{`*.]PNG`}}}, []string{`]PNG`}}, + {FileFilters{{"", []string{`*.[[]PNG`}}}, []string{`[PNG`}}, + {FileFilters{{"", []string{`*.[]]PNG`}}}, []string{`]PNG`}}, + {FileFilters{{"", []string{`*.[\[]PNG`}}}, []string{`[PNG`}}, + {FileFilters{{"", []string{`*.[\]]PNG`}}}, []string{`]PNG`}}, + } + for _, tt := range tests { + if got := tt.data.darwin(); !reflect.DeepEqual(got, tt.want) { + t.Fatalf("FileFilters.darwin(%+v) = %v, want %v", tt.data, got, tt.want) + } + } +} diff --git a/file_unix.go b/file_unix.go index 347e205..a200562 100644 --- a/file_unix.go +++ b/file_unix.go @@ -1,4 +1,4 @@ -// +build !windows,!darwin +// +build !windows,!darwin,!js package zenity diff --git a/internal/zenutil/env_unix.go b/internal/zenutil/env_unix.go index 154e71e..9cd9e04 100644 --- a/internal/zenutil/env_unix.go +++ b/internal/zenutil/env_unix.go @@ -1,4 +1,4 @@ -// +build !windows,!darwin +// +build !windows,!darwin,!js // Package zenutil is internal. DO NOT USE. package zenutil diff --git a/internal/zenutil/run_unix.go b/internal/zenutil/run_unix.go index c378a6f..a648e94 100644 --- a/internal/zenutil/run_unix.go +++ b/internal/zenutil/run_unix.go @@ -1,4 +1,4 @@ -// +build !windows,!darwin +// +build !windows,!darwin,!js package zenutil diff --git a/list_unix.go b/list_unix.go index db6aad0..ce905a4 100644 --- a/list_unix.go +++ b/list_unix.go @@ -1,4 +1,4 @@ -// +build !windows,!darwin +// +build !windows,!darwin,!js package zenity diff --git a/msg_unix.go b/msg_unix.go index bcdf794..9ba81e1 100644 --- a/msg_unix.go +++ b/msg_unix.go @@ -1,4 +1,4 @@ -// +build !windows,!darwin +// +build !windows,!darwin,!js package zenity diff --git a/notify_unix.go b/notify_unix.go index 5c040d6..2f3a47d 100644 --- a/notify_unix.go +++ b/notify_unix.go @@ -1,4 +1,4 @@ -// +build !windows,!darwin +// +build !windows,!darwin,!js package zenity diff --git a/pwd_unix.go b/pwd_unix.go index dc19245..e97f3ed 100644 --- a/pwd_unix.go +++ b/pwd_unix.go @@ -1,4 +1,4 @@ -// +build !windows,!darwin +// +build !windows,!darwin,!js package zenity diff --git a/util_unix.go b/util_unix.go index 792fc6c..2348a28 100644 --- a/util_unix.go +++ b/util_unix.go @@ -1,4 +1,4 @@ -// +build !windows +// +build !windows,!js package zenity diff --git a/zenity.go b/zenity.go index 872c57a..0b872e9 100644 --- a/zenity.go +++ b/zenity.go @@ -51,7 +51,7 @@ type options struct { confirmCreate bool showHidden bool filename string - fileFilters []FileFilter + fileFilters FileFilters // Color selection options color color.Color