File filters (macOS).
This commit is contained in:
parent
457b020545
commit
6a81d17a04
15 changed files with 133 additions and 35 deletions
|
@ -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)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// +build !windows,!darwin
|
||||
// +build !windows,!darwin,!js
|
||||
|
||||
package zenity
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// +build !windows,!darwin
|
||||
// +build !windows,!darwin,!js
|
||||
|
||||
package zenity
|
||||
|
||||
|
|
88
file.go
88
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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
31
file_filter_test.go
Normal file
31
file_filter_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
// +build !windows,!darwin
|
||||
// +build !windows,!darwin,!js
|
||||
|
||||
package zenity
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// +build !windows,!darwin
|
||||
// +build !windows,!darwin,!js
|
||||
|
||||
// Package zenutil is internal. DO NOT USE.
|
||||
package zenutil
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// +build !windows,!darwin
|
||||
// +build !windows,!darwin,!js
|
||||
|
||||
package zenutil
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// +build !windows,!darwin
|
||||
// +build !windows,!darwin,!js
|
||||
|
||||
package zenity
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// +build !windows,!darwin
|
||||
// +build !windows,!darwin,!js
|
||||
|
||||
package zenity
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// +build !windows,!darwin
|
||||
// +build !windows,!darwin,!js
|
||||
|
||||
package zenity
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// +build !windows,!darwin
|
||||
// +build !windows,!darwin,!js
|
||||
|
||||
package zenity
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// +build !windows
|
||||
// +build !windows,!js
|
||||
|
||||
package zenity
|
||||
|
||||
|
|
|
@ -51,7 +51,7 @@ type options struct {
|
|||
confirmCreate bool
|
||||
showHidden bool
|
||||
filename string
|
||||
fileFilters []FileFilter
|
||||
fileFilters FileFilters
|
||||
|
||||
// Color selection options
|
||||
color color.Color
|
||||
|
|
Loading…
Reference in a new issue