Selaa lähdekoodia

Merge pull request #13 from mattn/cygwin-msys2

Add IsCygwinTerminal to check cygwin/msys2 terminal
mattn 8 vuotta sitten
vanhempi
commit
281032e84a
7 muutettua tiedostoa jossa 159 lisäystä ja 5 poistoa
  1. 8 0
      .travis.yml
  2. 12 2
      README.md
  3. 4 1
      _example/example.go
  4. 14 0
      isatty_non_windows_test.go
  5. 9 0
      isatty_not_windows.go
  6. 77 2
      isatty_windows.go
  7. 35 0
      isatty_windows_test.go

+ 8 - 0
.travis.yml

@@ -0,0 +1,8 @@
+language: go
+go:
+  - tip
+before_install:
+  - go get github.com/mattn/goveralls
+  - go get golang.org/x/tools/cmd/cover
+script:
+    - $HOME/gopath/bin/goveralls -repotoken 3gHdORO5k5ziZcWMBxnd9LrMZaJs8m9x5

+ 12 - 2
README.md

@@ -1,5 +1,7 @@
 # go-isatty
 
+[![Build Status](https://travis-ci.org/mattn/go-isatty.svg?branch=master)](https://travis-ci.org/mattn/go-isatty) [![Coverage Status](https://coveralls.io/repos/github/mattn/go-isatty/badge.svg?branch=master)](https://coveralls.io/github/mattn/go-isatty?branch=master)
+
 isatty for golang
 
 ## Usage
@@ -16,6 +18,8 @@ import (
 func main() {
 	if isatty.IsTerminal(os.Stdout.Fd()) {
 		fmt.Println("Is Terminal")
+	} else if isatty.IsCygwinTerminal(os.Stdout.Fd()) {
+		fmt.Println("Is Cygwin/MSYS2 Terminal")
 	} else {
 		fmt.Println("Is Not Terminal")
 	}
@@ -28,10 +32,16 @@ func main() {
 $ go get github.com/mattn/go-isatty
 ```
 
-# License
+## License
 
 MIT
 
-# Author
+## Author
 
 Yasuhiro Matsumoto (a.k.a mattn)
+
+## Thanks
+
+* k-takata: base idea for IsCygwinTerminal
+
+    https://github.com/k-takata/go-iscygpty

+ 4 - 1
_example/example.go

@@ -2,13 +2,16 @@ package main
 
 import (
 	"fmt"
-	"github.com/mattn/go-isatty"
 	"os"
+
+	"github.com/mattn/go-isatty"
 )
 
 func main() {
 	if isatty.IsTerminal(os.Stdout.Fd()) {
 		fmt.Println("Is Terminal")
+	} else if isatty.IsCygwinTerminal(os.Stdout.Fd()) {
+		fmt.Println("Is Cygwin/MSYS2 Terminal")
 	} else {
 		fmt.Println("Is Not Terminal")
 	}

+ 14 - 0
isatty_non_windows_test.go

@@ -0,0 +1,14 @@
+// +build !windows
+
+package isatty
+
+import (
+	"os"
+	"testing"
+)
+
+func TestCygwinPipeName(t *testing.T) {
+	if IsCygwinTerminal(os.Stdout.Fd()) {
+		t.Fatal("should be false always")
+	}
+}

+ 9 - 0
isatty_not_windows.go

@@ -0,0 +1,9 @@
+// +build !windows appengine
+
+package isatty
+
+// IsCygwinTerminal() return true if the file descriptor is a cygwin or msys2
+// terminal. This is also always false on this environment.
+func IsCygwinTerminal(fd uintptr) bool {
+	return false
+}

+ 77 - 2
isatty_windows.go

@@ -4,12 +4,30 @@
 package isatty
 
 import (
+	"strings"
 	"syscall"
+	"unicode/utf16"
 	"unsafe"
 )
 
-var kernel32 = syscall.NewLazyDLL("kernel32.dll")
-var procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
+const (
+	fileNameInfo uintptr = 2
+	fileTypePipe         = 3
+)
+
+var (
+	kernel32                         = syscall.NewLazyDLL("kernel32.dll")
+	procGetConsoleMode               = kernel32.NewProc("GetConsoleMode")
+	procGetFileInformationByHandleEx = kernel32.NewProc("GetFileInformationByHandleEx")
+	procGetFileType                  = kernel32.NewProc("GetFileType")
+)
+
+func init() {
+	// Check if GetFileInformationByHandleEx is available.
+	if procGetFileInformationByHandleEx.Find() != nil {
+		procGetFileInformationByHandleEx = nil
+	}
+}
 
 // IsTerminal return true if the file descriptor is terminal.
 func IsTerminal(fd uintptr) bool {
@@ -17,3 +35,60 @@ func IsTerminal(fd uintptr) bool {
 	r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, fd, uintptr(unsafe.Pointer(&st)), 0)
 	return r != 0 && e == 0
 }
+
+// Check pipe name is used for cygwin/msys2 pty.
+// Cygwin/MSYS2 PTY has a name like:
+//   \{cygwin,msys}-XXXXXXXXXXXXXXXX-ptyN-{from,to}-master
+func isCygwinPipeName(name string) bool {
+	token := strings.Split(name, "-")
+	if len(token) < 5 {
+		return false
+	}
+
+	if token[0] != `\msys` && token[0] != `\cygwin` {
+		return false
+	}
+
+	if token[1] == "" {
+		return false
+	}
+
+	if !strings.HasPrefix(token[2], "pty") {
+		return false
+	}
+
+	if token[3] != `from` && token[3] != `to` {
+		return false
+	}
+
+	if token[4] != "master" {
+		return false
+	}
+
+	return true
+}
+
+// IsCygwinTerminal() return true if the file descriptor is a cygwin or msys2
+// terminal.
+func IsCygwinTerminal(fd uintptr) bool {
+	if procGetFileInformationByHandleEx == nil {
+		return false
+	}
+
+	// Cygwin/msys's pty is a pipe.
+	ft, _, e := syscall.Syscall(procGetFileType.Addr(), 1, fd, 0, 0)
+	if ft != fileTypePipe || e != 0 {
+		return false
+	}
+
+	var buf [2 + syscall.MAX_PATH]uint16
+	r, _, e := syscall.Syscall6(procGetFileInformationByHandleEx.Addr(),
+		4, fd, fileNameInfo, uintptr(unsafe.Pointer(&buf)),
+		uintptr(len(buf)*2), 0, 0)
+	if r == 0 || e != 0 {
+		return false
+	}
+
+	l := *(*uint32)(unsafe.Pointer(&buf))
+	return isCygwinPipeName(string(utf16.Decode(buf[2 : 2+l/2])))
+}

+ 35 - 0
isatty_windows_test.go

@@ -0,0 +1,35 @@
+// +build windows
+
+package isatty
+
+import (
+	"testing"
+)
+
+func TestCygwinPipeName(t *testing.T) {
+	tests := []struct {
+		name   string
+		result bool
+	}{
+		{``, false},
+		{`\msys-`, false},
+		{`\cygwin-----`, false},
+		{`\msys-x-PTY5-pty1-from-master`, false},
+		{`\cygwin-x-PTY5-from-master`, false},
+		{`\cygwin-x-pty2-from-toaster`, false},
+		{`\cygwin--pty2-from-master`, false},
+		{`\\cygwin-x-pty2-from-master`, false},
+		{`\cygwin-x-pty2-from-master-`, true}, // for the feature
+		{`\cygwin-e022582115c10879-pty4-from-master`, true},
+		{`\msys-e022582115c10879-pty4-to-master`, true},
+		{`\cygwin-e022582115c10879-pty4-to-master`, true},
+	}
+
+	for _, test := range tests {
+		want := test.result
+		got := isCygwinPipeName(test.name)
+		if want != got {
+			t.Fatalf("isatty(%q): got %v, want %v:", test.name, got, want)
+		}
+	}
+}