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:
|
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)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// +build !windows,!darwin
|
// +build !windows,!darwin,!js
|
||||||
|
|
||||||
package zenity
|
package zenity
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// +build !windows,!darwin
|
// +build !windows,!darwin,!js
|
||||||
|
|
||||||
package zenity
|
package zenity
|
||||||
|
|
||||||
|
|
88
file.go
88
file.go
|
@ -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)
|
||||||
|
|
|
@ -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
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
|
package zenity
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// +build !windows,!darwin
|
// +build !windows,!darwin,!js
|
||||||
|
|
||||||
package zenutil
|
package zenutil
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// +build !windows,!darwin
|
// +build !windows,!darwin,!js
|
||||||
|
|
||||||
package zenity
|
package zenity
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// +build !windows,!darwin
|
// +build !windows,!darwin,!js
|
||||||
|
|
||||||
package zenity
|
package zenity
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// +build !windows,!darwin
|
// +build !windows,!darwin,!js
|
||||||
|
|
||||||
package zenity
|
package zenity
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// +build !windows,!darwin
|
// +build !windows,!darwin,!js
|
||||||
|
|
||||||
package zenity
|
package zenity
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// +build !windows
|
// +build !windows,!js
|
||||||
|
|
||||||
package zenity
|
package zenity
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue