File filters (macOS).

This commit is contained in:
Nuno Cruces 2021-04-12 03:59:08 +01:00
parent 457b020545
commit 6a81d17a04
15 changed files with 133 additions and 35 deletions

View file

@ -11,7 +11,7 @@ as well as a *“port”* of the `zenity` command to both Windows and macOS base
Implemented dialogs: Implemented dialogs:
* [message](https://github.com/ncruces/zenity/wiki/Message-dialog) (error, info, question, warning) * [message](https://github.com/ncruces/zenity/wiki/Message-dialog) (error, info, question, warning)
* [text entry](https://github.com/ncruces/zenity/wiki/Text-Entry-dialog) * [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) * [password](https://github.com/ncruces/zenity/wiki/Password-dialog)
* [file selection](https://github.com/ncruces/zenity/wiki/File-Selection-dialog) * [file selection](https://github.com/ncruces/zenity/wiki/File-Selection-dialog)
* [color selection](https://github.com/ncruces/zenity/wiki/Color-Selection-dialog) * [color selection](https://github.com/ncruces/zenity/wiki/Color-Selection-dialog)

View file

@ -1,4 +1,4 @@
// +build !windows,!darwin // +build !windows,!darwin,!js
package zenity package zenity

View file

@ -1,4 +1,4 @@
// +build !windows,!darwin // +build !windows,!darwin,!js
package zenity package zenity

88
file.go
View file

@ -3,6 +3,7 @@ package zenity
import ( import (
"os" "os"
"path/filepath" "path/filepath"
"strings"
) )
// SelectFile displays the file selection dialog. // SelectFile displays the file selection dialog.
@ -84,6 +85,93 @@ func (f FileFilters) apply(o *options) {
o.fileFilters = append(o.fileFilters, f...) 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) { func splitDirAndName(path string) (dir, name string) {
if path != "" { if path != "" {
path = filepath.Clean(path) path = filepath.Clean(path)

View file

@ -1,8 +1,6 @@
package zenity package zenity
import ( import (
"strings"
"github.com/ncruces/zenity/internal/zenutil" "github.com/ncruces/zenity/internal/zenutil"
) )
@ -16,7 +14,7 @@ func selectFile(opts options) (string, error) {
data.Operation = "chooseFolder" data.Operation = "chooseFolder"
} else { } else {
data.Operation = "chooseFile" data.Operation = "chooseFile"
data.Options.Type = initFilters(opts.fileFilters) data.Options.Type = opts.fileFilters.darwin()
} }
out, err := zenutil.Run(opts.ctx, "file", data) out, err := zenutil.Run(opts.ctx, "file", data)
@ -36,7 +34,7 @@ func selectFileMutiple(opts options) ([]string, error) {
data.Operation = "chooseFolder" data.Operation = "chooseFolder"
} else { } else {
data.Operation = "chooseFile" data.Operation = "chooseFile"
data.Options.Type = initFilters(opts.fileFilters) data.Options.Type = opts.fileFilters.darwin()
} }
out, err := zenutil.Run(opts.ctx, "file", data) out, err := zenutil.Run(opts.ctx, "file", data)
@ -59,22 +57,3 @@ func selectFileSave(opts options) (string, error) {
str, _, err := strResult(opts, out, err) str, _, err := strResult(opts, out, err)
return str, 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
}

31
file_filter_test.go Normal file
View file

@ -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)
}
}
}

View file

@ -1,4 +1,4 @@
// +build !windows,!darwin // +build !windows,!darwin,!js
package zenity package zenity

View file

@ -1,4 +1,4 @@
// +build !windows,!darwin // +build !windows,!darwin,!js
// Package zenutil is internal. DO NOT USE. // Package zenutil is internal. DO NOT USE.
package zenutil package zenutil

View file

@ -1,4 +1,4 @@
// +build !windows,!darwin // +build !windows,!darwin,!js
package zenutil package zenutil

View file

@ -1,4 +1,4 @@
// +build !windows,!darwin // +build !windows,!darwin,!js
package zenity package zenity

View file

@ -1,4 +1,4 @@
// +build !windows,!darwin // +build !windows,!darwin,!js
package zenity package zenity

View file

@ -1,4 +1,4 @@
// +build !windows,!darwin // +build !windows,!darwin,!js
package zenity package zenity

View file

@ -1,4 +1,4 @@
// +build !windows,!darwin // +build !windows,!darwin,!js
package zenity package zenity

View file

@ -1,4 +1,4 @@
// +build !windows // +build !windows,!js
package zenity package zenity

View file

@ -51,7 +51,7 @@ type options struct {
confirmCreate bool confirmCreate bool
showHidden bool showHidden bool
filename string filename string
fileFilters []FileFilter fileFilters FileFilters
// Color selection options // Color selection options
color color.Color color color.Color