Compare commits

...

77 commits

Author SHA1 Message Date
Iwasaki Yudai
a080c85cbc Release v2.0.0-alpha.3 2017-12-13 17:44:18 +09:00
Iwasaki Yudai
8df0bf44a8
Merge pull request #179 from badoo/hterm_deactivate_fix_pull
Stop hterm spamming with errors to console after deactivate()
2017-11-24 17:55:50 -08:00
Korenevskiy Denis
b4728f6aa4 set deactivated terminal handlers to empty functions instead of null
this allows to avoid console spamming with 'not a function' messages
after the connection to server was closed.
2017-11-24 18:16:56 +03:00
Iwasaki Yudai
9ac120a557 Support local webpack 2017-10-03 15:34:51 +09:00
Iwasaki Yudai
0bb62e0381 Mention minimum go compiler version
Also npm is required.
2017-09-28 13:42:18 +09:00
Iwasaki Yudai
513b3a5c4d Do not compile asset by default
Run make all instead when you recreate server/asset.go.
2017-09-28 13:37:34 +09:00
Iwasaki Yudai
79e132824a Update README.md 2017-09-01 15:34:21 +09:00
Iwasaki Yudai
2c50c43290 Release v2.0.0-alpha.2 2017-08-27 15:56:11 +09:00
Iwasaki Yudai
6ab3093956 Pickup random port when port option is 0
With upgrading go to go1.9 to use http.Server.ServeTLS()
2017-08-26 17:23:04 +09:00
Iwasaki Yudai
b2c2db0764 Move responsibility to decode output encoding to terminal implementation 2017-08-26 16:55:07 +09:00
Iwasaki Yudai
807bcc25a4 Refine API of webtty package 2017-08-24 14:40:28 +09:00
Iwasaki Yudai
d1ec7125cf Run webpack when ts files are updated 2017-08-24 13:22:27 +09:00
Iwasaki Yudai
a6ae1210da Move decoder into setup 2017-08-24 13:22:16 +09:00
Iwasaki Yudai
973cf362fb Remove libapps submodule 2017-08-23 15:46:34 +09:00
Iwasaki Yudai
ba1aa690ed Use libapps utf8 decoder 2017-08-23 15:32:12 +09:00
Iwasaki Yudai
248f51b290 Release v2.0.0-alpha.1 2017-08-23 12:18:14 +09:00
Iwasaki Yudai
4d682aa01d Add "apple symbols' to xterm font list
Since Chrome on Mac OS doesn't show characters like U+1696,
we need to add a font that has those characters as a fallback target.
2017-08-23 11:11:04 +09:00
Iwasaki Yudai
d0f6481cab Fix typo in CONTRIBUTING.md 2017-08-23 11:04:41 +09:00
Iwasaki Yudai
7355d67a64 Add license-loader
Since xterm and hterm do not have proper comments for their license,
add license-loader to keep their license information in the minimized
bundle file.
2017-08-23 10:58:18 +09:00
Iwasaki Yudai
46a8b006f0 Fix typing of hterm 2017-08-23 10:56:34 +09:00
Iwasaki Yudai
48c91151ad Enable gzip compression 2017-08-22 17:31:27 +09:00
Iwasaki Yudai
024ab8f28e Minify bundled js code 2017-08-22 16:58:15 +09:00
Iwasaki Yudai
2b4eb55d28 Add typing for libapps 2017-08-22 16:16:28 +09:00
Iwasaki Yudai
a8bb23f570 Rename bundle.js to gotty-bundle.js 2017-08-22 16:03:19 +09:00
Iwasaki Yudai
27b6436196 Rename TermXterm and TermHterm to Xterm and Hterm 2017-08-22 15:59:24 +09:00
Iwasaki Yudai
70aaf33082 Bundle hterm 2017-08-22 15:58:50 +09:00
Iwasaki Yudai
8f95182392 Show command in log 2017-08-22 12:11:11 +09:00
Iwasaki Yudai
c1ccfdd859 Add npm to makefile 2017-08-22 12:10:52 +09:00
Iwasaki Yudai
7aafac9448 Merge pull request #151 from yudai/xterm_integartion
Add xterm itegration
2017-08-21 00:39:37 -07:00
Iwasaki Yudai
8803721f3d Add xterm itegration
* Move to TypeScript from legacy JavaScript
* Add support of xterm.js
* Hterm is still available for backward compatibility
2017-08-21 16:38:28 +09:00
Iwasaki Yudai
d6c98866b9 Output proper log when upgrade fails 2017-08-21 15:36:23 +09:00
Iwasaki Yudai
45f8f61103 Fix option handling of close signal 2017-08-20 13:39:06 +09:00
Iwasaki Yudai
56e9b89199 Log force closing 2017-08-20 13:38:42 +09:00
Iwasaki Yudai
91ee778665 Show commit ID on version 2017-08-17 14:04:17 +09:00
Iwasaki Yudai
4fd3ac376c Add browser to quetsions 2017-08-15 13:19:47 +09:00
Iwasaki Yudai
6765efbd61 Add new option to allow cross origin requests to WS endpoint 2017-08-13 15:09:22 +09:00
Iwasaki Yudai
84ec13ca19 Do not show ws log 2017-08-13 14:00:51 +09:00
Iwasaki Yudai
af41111458 Show alternative URLs when address is 0.0.0.0 2017-08-13 13:53:48 +09:00
Iwasaki Yudai
2a2a034788 Fix possible race condition on timeout 2017-08-13 13:40:00 +09:00
Iwasaki Yudai
9b8d2d5ed5 Reduce struct variables of server.Server 2017-08-12 17:56:46 +09:00
Iwasaki Yudai
21899e638b Add guidlines 2017-08-12 12:35:25 +09:00
Iwasaki Yudai
e81f4e9b7e Merge pull request #159 from yudai/develop
Merge refactoring
2017-08-11 18:43:36 -07:00
Iwasaki Yudai
a6133f34b7 Refactor 2017-08-11 15:31:11 +09:00
Iwasaki Yudai
b5c56d57f2 Remove Gitter link
I'm not using it.
2017-08-11 14:32:32 +09:00
zlji
54403dd678 exit on resizeTerminal error 2017-08-09 12:38:31 +09:00
zlji
496ef86339 refactor: decouple gotty app with terminal backends 2017-08-09 12:38:31 +09:00
zlji
d71e2fcfa8 generate falgs based on struct options instead of defining them externally 2017-08-09 12:38:31 +09:00
Iwasaki Yudai
c4ed7bc415 Merge pull request #158 from yudai/update_license
Remove license information of third party libraries
2017-08-08 20:37:56 -07:00
Iwasaki Yudai
0f6909a165 Remove license information of third party libraries
Github detects a wrong license.
Licenses are still available in the vendor directory.
2017-08-09 12:36:50 +09:00
Iwasaki Yudai
91be466d29 Release with commit ID
So that release branch can push assets with a proper commit ID.
2017-08-08 17:29:40 +09:00
Iwasaki Yudai
b181c6baf5 Merge pull request #157 from yudai/inmemory_storage
Use in memory storage not to clear local storage
2017-08-08 00:53:57 -07:00
Iwasaki Yudai
412ffeb06d Use in memory storage not to clear local storage
Closes #118.
2017-08-08 16:53:24 +09:00
Iwasaki Yudai
1b7f41ca6f Merge pull request #156 from yudai/max_conn
Fix max connection enforcement
2017-08-08 00:46:08 -07:00
Iwasaki Yudai
dbcdc904b9 Fix max connection enforcement
Closes #136.
2017-08-08 16:44:26 +09:00
Iwasaki Yudai
f23f38a492 Merge pull request #155 from yudai/env_vars
Update codegangsta/cli
2017-08-08 00:25:44 -07:00
Iwasaki Yudai
b1e5d95841 Update codegangsta/cli
To fix #122.
IsSet() doesn't return true even when env variables are set.
2017-08-08 16:21:59 +09:00
Iwasaki Yudai
b61b784187 Add comments for stable releases 2017-05-22 08:16:24 +09:00
Iwasaki Yudai
1aa392f29b Release 1.0.0 2017-05-21 14:39:33 +09:00
Iwasaki Yudai
5046d874bc Merge pull request #135 from stucchimax/master
Add --max-connection to the documentation
2017-05-02 00:09:57 -07:00
Iwasaki Yudai
e721482e57 Merge pull request #139 from tsl0922/add-ttyd
README: add ttyd to Alternatives
2017-03-28 21:49:34 -07:00
Iwasaki Yudai
63c9a78b06 Merge pull request #138 from xinsnake/fix-readme-comma
removed comma in sample config file
2017-03-28 21:48:59 -07:00
Shuanglei Tao
798d2ac1d9 README: add ttyd to Alternatives 2017-03-24 23:29:18 +08:00
Xinyun Zhou
7496404438 removed comma in sample config file 2017-03-24 22:25:06 +11:00
Iwasaki Yudai
ef91648d8f Merge pull request #137 from artdevjs/master
Update README.md
2017-03-20 21:10:32 -07:00
Artem Medvedev
456aa65d17
Update README.md 2017-03-19 05:21:42 +02:00
Massimiliano Stucchi
fd25f80257 Add --max-connection to the documentation 2017-03-07 11:14:51 +01:00
Iwasaki Yudai
92d8d62ae1 Bump to go1.7.3 2017-01-09 12:19:43 +09:00
Iwasaki Yudai
8c9433ff21 Add timeout option 2017-01-09 12:04:15 +09:00
Iwasaki Yudai
c91fef051b Merge pull request #121 from guywithnose/lockWindowSize
Add an option to disable client window resizes
2017-01-07 15:31:35 -08:00
Robert Bittle
8fd09cd9ec
Add an option to disable client window resizes
This goes great with tmux when you are sharing your terminal for
presentations and you don't want to give viewers the ability to resize
your terminal
2016-12-30 09:20:09 -05:00
Iwasaki Yudai
ddbaa983c0 Merge pull request #102 from skeltoac/auth-token-content-type
Set content-type on auth_token.js
2016-08-13 17:48:42 -07:00
Yifa Zhang
be07d420dd add option for max connection (#112)
add option for max connection
2016-08-13 00:29:21 -07:00
Iwasaki Yudai
54597c0ba6 Use SHA256SUM 2016-08-11 16:48:03 +09:00
Iwasaki Yudai
49f2051c93 Exclude darwin/arm from cross compile targets 2016-07-29 16:52:37 +09:00
Andy Skelton
ac49153071 Set content-type on auth_token.js 2016-06-24 19:47:14 +00:00
Iwasaki Yudai
510542b159 Release v0.0.13
* No functional changes
* Updated golang to 1.6.1
* Fixed `go get` broken binary
2016-04-13 16:18:21 +09:00
Iwasaki Yudai
a350994aa2 Change to go1.6.1 and vendoring 2016-04-13 16:17:57 +09:00
259 changed files with 12005 additions and 5799 deletions

27
.github/ISSUE_TEMPLATE.md vendored Normal file
View file

@ -0,0 +1,27 @@
# When file a bug report (see below for feature requests)
Please answer these quesions for a bug report. Thanks!
### What version of GoTTY are you using (`gotty --version`)?
### What operating system and browser are you using?
### What did you do?
If possible, please provide the command you ran.
### What did you expect to see?
### What did you see instead?
If possible, please provide the output of the command and your browser's console output.
# When file a new feature proposal
Please provide an actual usecase that requires your new feature.

1
.gitignore vendored
View file

@ -1,3 +1,4 @@
gotty
bindata
builds
js/node_modules/*

3
.gitmodules vendored
View file

@ -1,3 +0,0 @@
[submodule "libapps"]
path = libapps
url = https://chromium.googlesource.com/apps/libapps

8
.gotty
View file

@ -52,7 +52,13 @@
// [int] Interval time to try reconnection (seconds)
// To enable reconnection, set `true` to `enable_reconnect`
// reconnect_time = false
// reconnect_time = 10
// [int] Timeout seconds for waiting a client (0 to disable)
// timeout = 60
// [int] Maximum connection to gotty, 0(default) means no limit.
// max_connection = 0
// [bool] Accept only one client and exit gotty once the client exits
// once = false

44
CONTRIBUTING.md Normal file
View file

@ -0,0 +1,44 @@
# How to contribute
GoTTY is MIT licensed and accepts contributions via GitHub pull requests. We also accepts feature requests on GitHub issues.
## Reporting a bug
Reporting a bug is always welcome and one of the best ways to contribute. A good bug report helps the developers to improve the product much easier. We therefore would like to ask you to fill out the quesions on the issue template as much as possible. That helps us to figure out what's happening and discover the root cause.
## Requesting a new feature
When you find that GoTTY cannot fullfill your requirements because of lack of ability, you may want to open a new feature request. In that case, please file a new issue with your usecase and requirements.
## Opening a pull request
### Code Style
Please run `go fmt` on your Go code and make sure that your commits are organized for each logical change and your commit messages are in proper format (see below).
[Go's official code style guide](https://github.com/golang/go/wiki/CodeReviewComments) is also helpful.
### Format of the commit message
When you write a commit message, we recommend include following information to make review easier and keep the history cleaerer.
* What is the change
* The reason for the change
The following is an example:
```
Add something new to existing package
Since the existing function lacks that mechanism for some purpose,
this commit adds a new structure to provide it.
```
When your pull request is to add a new feature, we recommend add an actual usecase so that we can discuss the best way to achive your requirement. Opening a proposal issue in advance is another good way to start discussion of new features.
## Contact
If you have a trivial question about GoTTY for a bug or new feature, you can contact @i_yudai on Twitter (unfortunately, I cannot provide support on GoTTY though).

25
Godeps/Godeps.json generated
View file

@ -1,16 +1,16 @@
{
"ImportPath": "github.com/yudai/gotty",
"GoVersion": "go1.5.1",
"GoVersion": "go1.9",
"GodepVersion": "v79",
"Deps": [
{
"ImportPath": "github.com/braintree/manners",
"Comment": "0.4.0-6-g9e2a271",
"Rev": "9e2a2714de21eb092ead2ef56d8c7a60d7928819"
"ImportPath": "github.com/NYTimes/gziphandler",
"Rev": "967539e5e271a2bc9b3dcb1285078a1b1df105ae"
},
{
"ImportPath": "github.com/codegangsta/cli",
"Comment": "1.2.0-139-g142e6cd",
"Rev": "142e6cd241a4dfbf7f07a018f1f8225180018da4"
"Comment": "v1.19.1",
"Rev": "0bdeddeeb0f650497d603c4ad7b20cfe685682f6"
},
{
"ImportPath": "github.com/elazarl/go-bindata-assetfs",
@ -33,13 +33,22 @@
"Comment": "release.r56-28-g5cf931e",
"Rev": "5cf931ef8f76dccd0910001d74a58a7fca84a83d"
},
{
"ImportPath": "github.com/pkg/errors",
"Comment": "v0.8.0-2-g248dadf",
"Rev": "248dadf4e9068a0b3e79f02ed0a610d935de5302"
},
{
"ImportPath": "github.com/yudai/hcl",
"Rev": "5fa2393b3552119bf33a69adb1402a1160cba23d"
},
{
"ImportPath": "github.com/yudai/umutex",
"Rev": "18216d265c6bc72c3bb0ad9c8103d47d530b7003"
"ImportPath": "github.com/yudai/hcl/hcl",
"Rev": "5fa2393b3552119bf33a69adb1402a1160cba23d"
},
{
"ImportPath": "github.com/yudai/hcl/json",
"Rev": "5fa2393b3552119bf33a69adb1402a1160cba23d"
}
]
}

2
Godeps/_workspace/.gitignore generated vendored
View file

@ -1,2 +0,0 @@
/pkg
/bin

View file

@ -1,19 +0,0 @@
Copyright (c) 2014 Braintree, a division of PayPal, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View file

@ -1,36 +0,0 @@
# Manners
A *polite* webserver for Go.
Manners allows you to shut your Go webserver down gracefully, without dropping any requests. It can act as a drop-in replacement for the standard library's http.ListenAndServe function:
```go
func main() {
handler := MyHTTPHandler()
manners.ListenAndServe(":7000", handler)
}
```
Then, when you want to shut the server down:
```go
manners.Close()
```
(Note that this does not block until all the requests are finished. Rather, the call to manners.ListenAndServe will stop blocking when all the requests are finished.)
Manners ensures that all requests are served by incrementing a WaitGroup when a request comes in and decrementing it when the request finishes.
If your request handler spawns Goroutines that are not guaranteed to finish with the request, you can ensure they are also completed with the `StartRoutine` and `FinishRoutine` functions on the server.
### Known Issues
Manners does not correctly shut down long-lived keepalive connections when issued a shutdown command. Clients on an idle keepalive connection may see a connection reset error rather than a close. See https://github.com/braintree/manners/issues/13 for details.
### Compatability
Manners 0.3.0 and above uses standard library functionality introduced in Go 1.3.
### Installation
`go get github.com/braintree/manners`

View file

@ -1,7 +0,0 @@
package manners
type waitGroup interface {
Add(int)
Done()
Wait()
}

View file

@ -1,228 +0,0 @@
/*
Package manners provides a wrapper for a standard net/http server that
ensures all active HTTP client have completed their current request
before the server shuts down.
It can be used a drop-in replacement for the standard http package,
or can wrap a pre-configured Server.
eg.
http.Handle("/hello", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello\n"))
})
log.Fatal(manners.ListenAndServe(":8080", nil))
or for a customized server:
s := manners.NewWithServer(&http.Server{
Addr: ":8080",
Handler: myHandler,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
})
log.Fatal(s.ListenAndServe())
The server will shut down cleanly when the Close() method is called:
go func() {
sigchan := make(chan os.Signal, 1)
signal.Notify(sigchan, os.Interrupt, os.Kill)
<-sigchan
log.Info("Shutting down...")
manners.Close()
}()
http.Handle("/hello", myHandler)
log.Fatal(manners.ListenAndServe(":8080", nil))
*/
package manners
import (
"crypto/tls"
"net"
"net/http"
"sync"
"sync/atomic"
)
// A GracefulServer maintains a WaitGroup that counts how many in-flight
// requests the server is handling. When it receives a shutdown signal,
// it stops accepting new requests but does not actually shut down until
// all in-flight requests terminate.
//
// GracefulServer embeds the underlying net/http.Server making its non-override
// methods and properties avaiable.
//
// It must be initialized by calling NewWithServer.
type GracefulServer struct {
*http.Server
shutdown chan bool
wg waitGroup
lcsmu sync.RWMutex
lastConnState map[net.Conn]http.ConnState
up chan net.Listener // Only used by test code.
}
// NewWithServer wraps an existing http.Server object and returns a
// GracefulServer that supports all of the original Server operations.
func NewWithServer(s *http.Server) *GracefulServer {
return &GracefulServer{
Server: s,
shutdown: make(chan bool),
wg: new(sync.WaitGroup),
lastConnState: make(map[net.Conn]http.ConnState),
}
}
// Close stops the server from accepting new requets and begins shutting down.
// It returns true if it's the first time Close is called.
func (s *GracefulServer) Close() bool {
return <-s.shutdown
}
// ListenAndServe provides a graceful equivalent of net/http.Serve.ListenAndServe.
func (s *GracefulServer) ListenAndServe() error {
addr := s.Addr
if addr == "" {
addr = ":http"
}
listener, err := net.Listen("tcp", addr)
if err != nil {
return err
}
return s.Serve(listener)
}
// ListenAndServeTLS provides a graceful equivalent of net/http.Serve.ListenAndServeTLS.
func (s *GracefulServer) ListenAndServeTLS(certFile, keyFile string) error {
// direct lift from net/http/server.go
addr := s.Addr
if addr == "" {
addr = ":https"
}
config := &tls.Config{}
if s.TLSConfig != nil {
*config = *s.TLSConfig
}
if config.NextProtos == nil {
config.NextProtos = []string{"http/1.1"}
}
var err error
config.Certificates = make([]tls.Certificate, 1)
config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
return err
}
ln, err := net.Listen("tcp", addr)
if err != nil {
return err
}
return s.Serve(tls.NewListener(ln, config))
}
// Serve provides a graceful equivalent net/http.Server.Serve.
func (s *GracefulServer) Serve(listener net.Listener) error {
var closing int32
go func() {
s.shutdown <- true
close(s.shutdown)
atomic.StoreInt32(&closing, 1)
s.Server.SetKeepAlivesEnabled(false)
listener.Close()
}()
originalConnState := s.Server.ConnState
// s.ConnState is invoked by the net/http.Server every time a connectiion
// changes state. It keeps track of each connection's state over time,
// enabling manners to handle persisted connections correctly.
s.ConnState = func(conn net.Conn, newState http.ConnState) {
s.lcsmu.RLock()
lastConnState := s.lastConnState[conn]
s.lcsmu.RUnlock()
switch newState {
// New connection -> StateNew
case http.StateNew:
s.StartRoutine()
// (StateNew, StateIdle) -> StateActive
case http.StateActive:
// The connection transitioned from idle back to active
if lastConnState == http.StateIdle {
s.StartRoutine()
}
// StateActive -> StateIdle
// Immediately close newly idle connections; if not they may make
// one more request before SetKeepAliveEnabled(false) takes effect.
case http.StateIdle:
if atomic.LoadInt32(&closing) == 1 {
conn.Close()
}
s.FinishRoutine()
// (StateNew, StateActive, StateIdle) -> (StateClosed, StateHiJacked)
// If the connection was idle we do not need to decrement the counter.
case http.StateClosed, http.StateHijacked:
if lastConnState != http.StateIdle {
s.FinishRoutine()
}
}
s.lcsmu.Lock()
if newState == http.StateClosed || newState == http.StateHijacked {
delete(s.lastConnState, conn)
} else {
s.lastConnState[conn] = newState
}
s.lcsmu.Unlock()
if originalConnState != nil {
originalConnState(conn, newState)
}
}
// A hook to allow the server to notify others when it is ready to receive
// requests; only used by tests.
if s.up != nil {
s.up <- listener
}
err := s.Server.Serve(listener)
// This block is reached when the server has received a shut down command
// or a real error happened.
if err == nil || atomic.LoadInt32(&closing) == 1 {
s.wg.Wait()
return nil
}
return err
}
// StartRoutine increments the server's WaitGroup. Use this if a web request
// starts more goroutines and these goroutines are not guaranteed to finish
// before the request.
func (s *GracefulServer) StartRoutine() {
s.wg.Add(1)
}
// FinishRoutine decrements the server's WaitGroup. Use this to complement
// StartRoutine().
func (s *GracefulServer) FinishRoutine() {
s.wg.Done()
}

View file

@ -1,35 +0,0 @@
package manners
import (
"net"
"net/http"
)
var defaultServer *GracefulServer
// ListenAndServe provides a graceful version of the function provided by the
// net/http package. Call Close() to stop the server.
func ListenAndServe(addr string, handler http.Handler) error {
defaultServer = NewWithServer(&http.Server{Addr: addr, Handler: handler})
return defaultServer.ListenAndServe()
}
// ListenAndServeTLS provides a graceful version of the function provided by the
// net/http package. Call Close() to stop the server.
func ListenAndServeTLS(addr string, certFile string, keyFile string, handler http.Handler) error {
defaultServer = NewWithServer(&http.Server{Addr: addr, Handler: handler})
return defaultServer.ListenAndServeTLS(certFile, keyFile)
}
// Serve provides a graceful version of the function provided by the net/http
// package. Call Close() to stop the server.
func Serve(l net.Listener, handler http.Handler) error {
defaultServer = NewWithServer(&http.Server{Handler: handler})
return defaultServer.Serve(l)
}
// Shuts down the default server used by ListenAndServe, ListenAndServeTLS and
// Serve. It returns true if it's the first time Close is called.
func Close() bool {
return defaultServer.Close()
}

View file

@ -1,29 +0,0 @@
package test_helpers
// A PEM-encoded TLS cert with SAN IPs "127.0.0.1" and "[::1]", expiring at the
// last second of 2049 (the end of ASN.1 time).
// generated from src/pkg/crypto/tls:
// go run generate_cert.go --rsa-bits 512 --host 127.0.0.1,::1,example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h
var (
Cert = []byte(`-----BEGIN CERTIFICATE-----
MIIBdzCCASOgAwIBAgIBADALBgkqhkiG9w0BAQUwEjEQMA4GA1UEChMHQWNtZSBD
bzAeFw03MDAxMDEwMDAwMDBaFw00OTEyMzEyMzU5NTlaMBIxEDAOBgNVBAoTB0Fj
bWUgQ28wWjALBgkqhkiG9w0BAQEDSwAwSAJBAN55NcYKZeInyTuhcCwFMhDHCmwa
IUSdtXdcbItRB/yfXGBhiex00IaLXQnSU+QZPRZWYqeTEbFSgihqi1PUDy8CAwEA
AaNoMGYwDgYDVR0PAQH/BAQDAgCkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1Ud
EwEB/wQFMAMBAf8wLgYDVR0RBCcwJYILZXhhbXBsZS5jb22HBH8AAAGHEAAAAAAA
AAAAAAAAAAAAAAEwCwYJKoZIhvcNAQEFA0EAAoQn/ytgqpiLcZu9XKbCJsJcvkgk
Se6AbGXgSlq+ZCEVo0qIwSgeBqmsJxUu7NCSOwVJLYNEBO2DtIxoYVk+MA==
-----END CERTIFICATE-----`)
Key = []byte(`-----BEGIN RSA PRIVATE KEY-----
MIIBPAIBAAJBAN55NcYKZeInyTuhcCwFMhDHCmwaIUSdtXdcbItRB/yfXGBhiex0
0IaLXQnSU+QZPRZWYqeTEbFSgihqi1PUDy8CAwEAAQJBAQdUx66rfh8sYsgfdcvV
NoafYpnEcB5s4m/vSVe6SU7dCK6eYec9f9wpT353ljhDUHq3EbmE4foNzJngh35d
AekCIQDhRQG5Li0Wj8TM4obOnnXUXf1jRv0UkzE9AHWLG5q3AwIhAPzSjpYUDjVW
MCUXgckTpKCuGwbJk7424Nb8bLzf3kllAiA5mUBgjfr/WtFSJdWcPQ4Zt9KTMNKD
EUO0ukpTwEIl6wIhAMbGqZK3zAAFdq8DD2jPx+UJXnh0rnOkZBzDtJ6/iN69AiEA
1Aq8MJgTaYsDQWyU/hDq5YkDJc9e9DSCvUIzqxQWMQE=
-----END RSA PRIVATE KEY-----`)
)

View file

@ -1,13 +0,0 @@
package test_helpers
import "net"
type Conn struct {
net.Conn
CloseCalled bool
}
func (c *Conn) Close() error {
c.CloseCalled = true
return nil
}

View file

@ -1,34 +0,0 @@
package test_helpers
import (
"net"
"errors"
)
type Listener struct {
AcceptRelease chan bool
CloseCalled chan bool
}
func NewListener() *Listener {
return &Listener{
make(chan bool, 1),
make(chan bool, 1),
}
}
func (l *Listener) Addr() net.Addr {
addr, _ := net.ResolveTCPAddr("tcp", "127.0.0.1:8080")
return addr
}
func (l *Listener) Close() error {
l.CloseCalled <- true
l.AcceptRelease <- true
return nil
}
func (l *Listener) Accept() (net.Conn, error) {
<-l.AcceptRelease
return nil, errors.New("connection closed")
}

View file

@ -1,27 +0,0 @@
package test_helpers
import (
"io/ioutil"
"os"
)
type TempFile struct {
*os.File
}
func NewTempFile(content []byte) (*TempFile, error) {
f, err := ioutil.TempFile("", "graceful-test")
if err != nil {
return nil, err
}
f.Write(content)
return &TempFile{f}, nil
}
func (tf *TempFile) Unlink() {
if tf.File != nil {
os.Remove(tf.Name())
tf.File = nil
}
}

View file

@ -1,33 +0,0 @@
package test_helpers
import "sync"
type WaitGroup struct {
sync.Mutex
Count int
WaitCalled chan int
}
func NewWaitGroup() *WaitGroup {
return &WaitGroup{
WaitCalled: make(chan int, 1),
}
}
func (wg *WaitGroup) Add(delta int) {
wg.Lock()
wg.Count++
wg.Unlock()
}
func (wg *WaitGroup) Done() {
wg.Lock()
wg.Count--
wg.Unlock()
}
func (wg *WaitGroup) Wait() {
wg.Lock()
wg.WaitCalled <- wg.Count
wg.Unlock()
}

View file

@ -1,13 +0,0 @@
language: go
sudo: false
go:
- 1.0.3
- 1.1.2
- 1.2.2
- 1.3.3
- 1.4.2
script:
- go vet ./...
- go test -v ./...

View file

@ -1,21 +0,0 @@
Copyright (C) 2013 Jeremy Saenz
All Rights Reserved.
MIT LICENSE
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -1,308 +0,0 @@
[![Build Status](https://travis-ci.org/codegangsta/cli.png?branch=master)](https://travis-ci.org/codegangsta/cli)
# cli.go
cli.go is simple, fast, and fun package for building command line apps in Go. The goal is to enable developers to write fast and distributable command line applications in an expressive way.
You can view the API docs here:
http://godoc.org/github.com/codegangsta/cli
## Overview
Command line apps are usually so tiny that there is absolutely no reason why your code should *not* be self-documenting. Things like generating help text and parsing command flags/options should not hinder productivity when writing a command line app.
**This is where cli.go comes into play.** cli.go makes command line programming fun, organized, and expressive!
## Installation
Make sure you have a working Go environment (go 1.1+ is *required*). [See the install instructions](http://golang.org/doc/install.html).
To install `cli.go`, simply run:
```
$ go get github.com/codegangsta/cli
```
Make sure your `PATH` includes to the `$GOPATH/bin` directory so your commands can be easily used:
```
export PATH=$PATH:$GOPATH/bin
```
## Getting Started
One of the philosophies behind cli.go is that an API should be playful and full of discovery. So a cli.go app can be as little as one line of code in `main()`.
``` go
package main
import (
"os"
"github.com/codegangsta/cli"
)
func main() {
cli.NewApp().Run(os.Args)
}
```
This app will run and show help text, but is not very useful. Let's give an action to execute and some help documentation:
``` go
package main
import (
"os"
"github.com/codegangsta/cli"
)
func main() {
app := cli.NewApp()
app.Name = "boom"
app.Usage = "make an explosive entrance"
app.Action = func(c *cli.Context) {
println("boom! I say!")
}
app.Run(os.Args)
}
```
Running this already gives you a ton of functionality, plus support for things like subcommands and flags, which are covered below.
## Example
Being a programmer can be a lonely job. Thankfully by the power of automation that is not the case! Let's create a greeter app to fend off our demons of loneliness!
Start by creating a directory named `greet`, and within it, add a file, `greet.go` with the following code in it:
``` go
package main
import (
"os"
"github.com/codegangsta/cli"
)
func main() {
app := cli.NewApp()
app.Name = "greet"
app.Usage = "fight the loneliness!"
app.Action = func(c *cli.Context) {
println("Hello friend!")
}
app.Run(os.Args)
}
```
Install our command to the `$GOPATH/bin` directory:
```
$ go install
```
Finally run our new command:
```
$ greet
Hello friend!
```
cli.go also generates some bitchass help text:
```
$ greet help
NAME:
greet - fight the loneliness!
USAGE:
greet [global options] command [command options] [arguments...]
VERSION:
0.0.0
COMMANDS:
help, h Shows a list of commands or help for one command
GLOBAL OPTIONS
--version Shows version information
```
### Arguments
You can lookup arguments by calling the `Args` function on `cli.Context`.
``` go
...
app.Action = func(c *cli.Context) {
println("Hello", c.Args()[0])
}
...
```
### Flags
Setting and querying flags is simple.
``` go
...
app.Flags = []cli.Flag {
cli.StringFlag{
Name: "lang",
Value: "english",
Usage: "language for the greeting",
},
}
app.Action = func(c *cli.Context) {
name := "someone"
if len(c.Args()) > 0 {
name = c.Args()[0]
}
if c.String("lang") == "spanish" {
println("Hola", name)
} else {
println("Hello", name)
}
}
...
```
See full list of flags at http://godoc.org/github.com/codegangsta/cli
#### Alternate Names
You can set alternate (or short) names for flags by providing a comma-delimited list for the `Name`. e.g.
``` go
app.Flags = []cli.Flag {
cli.StringFlag{
Name: "lang, l",
Value: "english",
Usage: "language for the greeting",
},
}
```
That flag can then be set with `--lang spanish` or `-l spanish`. Note that giving two different forms of the same flag in the same command invocation is an error.
#### Values from the Environment
You can also have the default value set from the environment via `EnvVar`. e.g.
``` go
app.Flags = []cli.Flag {
cli.StringFlag{
Name: "lang, l",
Value: "english",
Usage: "language for the greeting",
EnvVar: "APP_LANG",
},
}
```
The `EnvVar` may also be given as a comma-delimited "cascade", where the first environment variable that resolves is used as the default.
``` go
app.Flags = []cli.Flag {
cli.StringFlag{
Name: "lang, l",
Value: "english",
Usage: "language for the greeting",
EnvVar: "LEGACY_COMPAT_LANG,APP_LANG,LANG",
},
}
```
### Subcommands
Subcommands can be defined for a more git-like command line app.
```go
...
app.Commands = []cli.Command{
{
Name: "add",
Aliases: []string{"a"},
Usage: "add a task to the list",
Action: func(c *cli.Context) {
println("added task: ", c.Args().First())
},
},
{
Name: "complete",
Aliases: []string{"c"},
Usage: "complete a task on the list",
Action: func(c *cli.Context) {
println("completed task: ", c.Args().First())
},
},
{
Name: "template",
Aliases: []string{"r"},
Usage: "options for task templates",
Subcommands: []cli.Command{
{
Name: "add",
Usage: "add a new template",
Action: func(c *cli.Context) {
println("new task template: ", c.Args().First())
},
},
{
Name: "remove",
Usage: "remove an existing template",
Action: func(c *cli.Context) {
println("removed task template: ", c.Args().First())
},
},
},
},
}
...
```
### Bash Completion
You can enable completion commands by setting the `EnableBashCompletion`
flag on the `App` object. By default, this setting will only auto-complete to
show an app's subcommands, but you can write your own completion methods for
the App or its subcommands.
```go
...
var tasks = []string{"cook", "clean", "laundry", "eat", "sleep", "code"}
app := cli.NewApp()
app.EnableBashCompletion = true
app.Commands = []cli.Command{
{
Name: "complete",
Aliases: []string{"c"},
Usage: "complete a task on the list",
Action: func(c *cli.Context) {
println("completed task: ", c.Args().First())
},
BashComplete: func(c *cli.Context) {
// This will complete if no args are passed
if len(c.Args()) > 0 {
return
}
for _, t := range tasks {
fmt.Println(t)
}
},
}
}
...
```
#### To Enable
Source the `autocomplete/bash_autocomplete` file in your `.bashrc` file while
setting the `PROG` variable to the name of your program:
`PROG=myprogram source /.../cli/autocomplete/bash_autocomplete`
#### To Distribute
Copy and modify `autocomplete/bash_autocomplete` to use your program name
rather than `$PROG` and have the user copy the file into
`/etc/bash_completion.d/` (or automatically install it there if you are
distributing a package). Alternatively you can just document that users should
source the generic `autocomplete/bash_autocomplete` with `$PROG` set to your
program name in their bash configuration.
## Contribution Guidelines
Feel free to put up a pull request to fix a bug or maybe add a feature. I will give it a code review and make sure that it does not break backwards compatibility. If I or any other collaborators agree that it is in line with the vision of the project, we will work with you to get the code into a mergeable state and merge it into the master branch.
If you have contributed something significant to the project, I will most likely add you as a collaborator. As a collaborator you are given the ability to merge others pull requests. It is very important that new code does not break existing code, so be careful about what code you do choose to merge. If you have any questions feel free to link @codegangsta to the issue in question and we can review it together.
If you feel like you have contributed to the project but have not yet been added as a collaborator, I probably forgot to add you. Hit @codegangsta up over email and we will get it figured out.

View file

@ -1,308 +0,0 @@
package cli
import (
"fmt"
"io"
"io/ioutil"
"os"
"time"
)
// App is the main structure of a cli application. It is recomended that
// an app be created with the cli.NewApp() function
type App struct {
// The name of the program. Defaults to os.Args[0]
Name string
// Description of the program.
Usage string
// Version of the program
Version string
// List of commands to execute
Commands []Command
// List of flags to parse
Flags []Flag
// Boolean to enable bash completion commands
EnableBashCompletion bool
// Boolean to hide built-in help command
HideHelp bool
// Boolean to hide built-in version flag
HideVersion bool
// An action to execute when the bash-completion flag is set
BashComplete func(context *Context)
// An action to execute before any subcommands are run, but after the context is ready
// If a non-nil error is returned, no subcommands are run
Before func(context *Context) error
// An action to execute after any subcommands are run, but after the subcommand has finished
// It is run even if Action() panics
After func(context *Context) error
// The action to execute when no subcommands are specified
Action func(context *Context)
// Execute this function if the proper command cannot be found
CommandNotFound func(context *Context, command string)
// Compilation date
Compiled time.Time
// List of all authors who contributed
Authors []Author
// Copyright of the binary if any
Copyright string
// Name of Author (Note: Use App.Authors, this is deprecated)
Author string
// Email of Author (Note: Use App.Authors, this is deprecated)
Email string
// Writer writer to write output to
Writer io.Writer
}
// Tries to find out when this binary was compiled.
// Returns the current time if it fails to find it.
func compileTime() time.Time {
info, err := os.Stat(os.Args[0])
if err != nil {
return time.Now()
}
return info.ModTime()
}
// Creates a new cli Application with some reasonable defaults for Name, Usage, Version and Action.
func NewApp() *App {
return &App{
Name: os.Args[0],
Usage: "A new cli application",
Version: "0.0.0",
BashComplete: DefaultAppComplete,
Action: helpCommand.Action,
Compiled: compileTime(),
Writer: os.Stdout,
}
}
// Entry point to the cli app. Parses the arguments slice and routes to the proper flag/args combination
func (a *App) Run(arguments []string) (err error) {
if a.Author != "" || a.Email != "" {
a.Authors = append(a.Authors, Author{Name: a.Author, Email: a.Email})
}
// append help to commands
if a.Command(helpCommand.Name) == nil && !a.HideHelp {
a.Commands = append(a.Commands, helpCommand)
if (HelpFlag != BoolFlag{}) {
a.appendFlag(HelpFlag)
}
}
//append version/help flags
if a.EnableBashCompletion {
a.appendFlag(BashCompletionFlag)
}
if !a.HideVersion {
a.appendFlag(VersionFlag)
}
// parse flags
set := flagSet(a.Name, a.Flags)
set.SetOutput(ioutil.Discard)
err = set.Parse(arguments[1:])
nerr := normalizeFlags(a.Flags, set)
if nerr != nil {
fmt.Fprintln(a.Writer, nerr)
context := NewContext(a, set, nil)
ShowAppHelp(context)
return nerr
}
context := NewContext(a, set, nil)
if err != nil {
fmt.Fprintln(a.Writer, "Incorrect Usage.")
fmt.Fprintln(a.Writer)
ShowAppHelp(context)
return err
}
if checkCompletions(context) {
return nil
}
if checkHelp(context) {
return nil
}
if checkVersion(context) {
return nil
}
if a.After != nil {
defer func() {
afterErr := a.After(context)
if afterErr != nil {
if err != nil {
err = NewMultiError(err, afterErr)
} else {
err = afterErr
}
}
}()
}
if a.Before != nil {
err := a.Before(context)
if err != nil {
return err
}
}
args := context.Args()
if args.Present() {
name := args.First()
c := a.Command(name)
if c != nil {
return c.Run(context)
}
}
// Run default Action
a.Action(context)
return nil
}
// Another entry point to the cli app, takes care of passing arguments and error handling
func (a *App) RunAndExitOnError() {
if err := a.Run(os.Args); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
// Invokes the subcommand given the context, parses ctx.Args() to generate command-specific flags
func (a *App) RunAsSubcommand(ctx *Context) (err error) {
// append help to commands
if len(a.Commands) > 0 {
if a.Command(helpCommand.Name) == nil && !a.HideHelp {
a.Commands = append(a.Commands, helpCommand)
if (HelpFlag != BoolFlag{}) {
a.appendFlag(HelpFlag)
}
}
}
// append flags
if a.EnableBashCompletion {
a.appendFlag(BashCompletionFlag)
}
// parse flags
set := flagSet(a.Name, a.Flags)
set.SetOutput(ioutil.Discard)
err = set.Parse(ctx.Args().Tail())
nerr := normalizeFlags(a.Flags, set)
context := NewContext(a, set, ctx)
if nerr != nil {
fmt.Fprintln(a.Writer, nerr)
fmt.Fprintln(a.Writer)
if len(a.Commands) > 0 {
ShowSubcommandHelp(context)
} else {
ShowCommandHelp(ctx, context.Args().First())
}
return nerr
}
if err != nil {
fmt.Fprintln(a.Writer, "Incorrect Usage.")
fmt.Fprintln(a.Writer)
ShowSubcommandHelp(context)
return err
}
if checkCompletions(context) {
return nil
}
if len(a.Commands) > 0 {
if checkSubcommandHelp(context) {
return nil
}
} else {
if checkCommandHelp(ctx, context.Args().First()) {
return nil
}
}
if a.After != nil {
defer func() {
afterErr := a.After(context)
if afterErr != nil {
if err != nil {
err = NewMultiError(err, afterErr)
} else {
err = afterErr
}
}
}()
}
if a.Before != nil {
err := a.Before(context)
if err != nil {
return err
}
}
args := context.Args()
if args.Present() {
name := args.First()
c := a.Command(name)
if c != nil {
return c.Run(context)
}
}
// Run default Action
a.Action(context)
return nil
}
// Returns the named command on App. Returns nil if the command does not exist
func (a *App) Command(name string) *Command {
for _, c := range a.Commands {
if c.HasName(name) {
return &c
}
}
return nil
}
func (a *App) hasFlag(flag Flag) bool {
for _, f := range a.Flags {
if flag == f {
return true
}
}
return false
}
func (a *App) appendFlag(flag Flag) {
if !a.hasFlag(flag) {
a.Flags = append(a.Flags, flag)
}
}
// Author represents someone who has contributed to a cli project.
type Author struct {
Name string // The Authors name
Email string // The Authors email
}
// String makes Author comply to the Stringer interface, to allow an easy print in the templating process
func (a Author) String() string {
e := ""
if a.Email != "" {
e = "<" + a.Email + "> "
}
return fmt.Sprintf("%v %v", a.Name, e)
}

View file

@ -1,13 +0,0 @@
#! /bin/bash
_cli_bash_autocomplete() {
local cur prev opts base
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion )
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
}
complete -F _cli_bash_autocomplete $PROG

View file

@ -1,5 +0,0 @@
autoload -U compinit && compinit
autoload -U bashcompinit && bashcompinit
script_dir=$(dirname $0)
source ${script_dir}/bash_autocomplete

View file

@ -1,200 +0,0 @@
package cli
import (
"fmt"
"io/ioutil"
"strings"
)
// Command is a subcommand for a cli.App.
type Command struct {
// The name of the command
Name string
// short name of the command. Typically one character (deprecated, use `Aliases`)
ShortName string
// A list of aliases for the command
Aliases []string
// A short description of the usage of this command
Usage string
// A longer explanation of how the command works
Description string
// The function to call when checking for bash command completions
BashComplete func(context *Context)
// An action to execute before any sub-subcommands are run, but after the context is ready
// If a non-nil error is returned, no sub-subcommands are run
Before func(context *Context) error
// An action to execute after any subcommands are run, but after the subcommand has finished
// It is run even if Action() panics
After func(context *Context) error
// The function to call when this command is invoked
Action func(context *Context)
// List of child commands
Subcommands []Command
// List of flags to parse
Flags []Flag
// Treat all flags as normal arguments if true
SkipFlagParsing bool
// Boolean to hide built-in help command
HideHelp bool
commandNamePath []string
}
// Returns the full name of the command.
// For subcommands this ensures that parent commands are part of the command path
func (c Command) FullName() string {
if c.commandNamePath == nil {
return c.Name
}
return strings.Join(c.commandNamePath, " ")
}
// Invokes the command given the context, parses ctx.Args() to generate command-specific flags
func (c Command) Run(ctx *Context) error {
if len(c.Subcommands) > 0 || c.Before != nil || c.After != nil {
return c.startApp(ctx)
}
if !c.HideHelp && (HelpFlag != BoolFlag{}) {
// append help to flags
c.Flags = append(
c.Flags,
HelpFlag,
)
}
if ctx.App.EnableBashCompletion {
c.Flags = append(c.Flags, BashCompletionFlag)
}
set := flagSet(c.Name, c.Flags)
set.SetOutput(ioutil.Discard)
firstFlagIndex := -1
terminatorIndex := -1
for index, arg := range ctx.Args() {
if arg == "--" {
terminatorIndex = index
break
} else if strings.HasPrefix(arg, "-") && firstFlagIndex == -1 {
firstFlagIndex = index
}
}
var err error
if firstFlagIndex > -1 && !c.SkipFlagParsing {
args := ctx.Args()
regularArgs := make([]string, len(args[1:firstFlagIndex]))
copy(regularArgs, args[1:firstFlagIndex])
var flagArgs []string
if terminatorIndex > -1 {
flagArgs = args[firstFlagIndex:terminatorIndex]
regularArgs = append(regularArgs, args[terminatorIndex:]...)
} else {
flagArgs = args[firstFlagIndex:]
}
err = set.Parse(append(flagArgs, regularArgs...))
} else {
err = set.Parse(ctx.Args().Tail())
}
if err != nil {
fmt.Fprintln(ctx.App.Writer, "Incorrect Usage.")
fmt.Fprintln(ctx.App.Writer)
ShowCommandHelp(ctx, c.Name)
return err
}
nerr := normalizeFlags(c.Flags, set)
if nerr != nil {
fmt.Fprintln(ctx.App.Writer, nerr)
fmt.Fprintln(ctx.App.Writer)
ShowCommandHelp(ctx, c.Name)
return nerr
}
context := NewContext(ctx.App, set, ctx)
if checkCommandCompletions(context, c.Name) {
return nil
}
if checkCommandHelp(context, c.Name) {
return nil
}
context.Command = c
c.Action(context)
return nil
}
func (c Command) Names() []string {
names := []string{c.Name}
if c.ShortName != "" {
names = append(names, c.ShortName)
}
return append(names, c.Aliases...)
}
// Returns true if Command.Name or Command.ShortName matches given name
func (c Command) HasName(name string) bool {
for _, n := range c.Names() {
if n == name {
return true
}
}
return false
}
func (c Command) startApp(ctx *Context) error {
app := NewApp()
// set the name and usage
app.Name = fmt.Sprintf("%s %s", ctx.App.Name, c.Name)
if c.Description != "" {
app.Usage = c.Description
} else {
app.Usage = c.Usage
}
// set CommandNotFound
app.CommandNotFound = ctx.App.CommandNotFound
// set the flags and commands
app.Commands = c.Subcommands
app.Flags = c.Flags
app.HideHelp = c.HideHelp
app.Version = ctx.App.Version
app.HideVersion = ctx.App.HideVersion
app.Compiled = ctx.App.Compiled
app.Author = ctx.App.Author
app.Email = ctx.App.Email
app.Writer = ctx.App.Writer
// bash completion
app.EnableBashCompletion = ctx.App.EnableBashCompletion
if c.BashComplete != nil {
app.BashComplete = c.BashComplete
}
// set the actions
app.Before = c.Before
app.After = c.After
if c.Action != nil {
app.Action = c.Action
} else {
app.Action = helpSubcommand.Action
}
var newCmds []Command
for _, cc := range app.Commands {
cc.commandNamePath = []string{c.Name, cc.Name}
newCmds = append(newCmds, cc)
}
app.Commands = newCmds
return app.RunAsSubcommand(ctx)
}

View file

@ -1,388 +0,0 @@
package cli
import (
"errors"
"flag"
"strconv"
"strings"
"time"
)
// Context is a type that is passed through to
// each Handler action in a cli application. Context
// can be used to retrieve context-specific Args and
// parsed command-line options.
type Context struct {
App *App
Command Command
flagSet *flag.FlagSet
setFlags map[string]bool
globalSetFlags map[string]bool
parentContext *Context
}
// Creates a new context. For use in when invoking an App or Command action.
func NewContext(app *App, set *flag.FlagSet, parentCtx *Context) *Context {
return &Context{App: app, flagSet: set, parentContext: parentCtx}
}
// Looks up the value of a local int flag, returns 0 if no int flag exists
func (c *Context) Int(name string) int {
return lookupInt(name, c.flagSet)
}
// Looks up the value of a local time.Duration flag, returns 0 if no time.Duration flag exists
func (c *Context) Duration(name string) time.Duration {
return lookupDuration(name, c.flagSet)
}
// Looks up the value of a local float64 flag, returns 0 if no float64 flag exists
func (c *Context) Float64(name string) float64 {
return lookupFloat64(name, c.flagSet)
}
// Looks up the value of a local bool flag, returns false if no bool flag exists
func (c *Context) Bool(name string) bool {
return lookupBool(name, c.flagSet)
}
// Looks up the value of a local boolT flag, returns false if no bool flag exists
func (c *Context) BoolT(name string) bool {
return lookupBoolT(name, c.flagSet)
}
// Looks up the value of a local string flag, returns "" if no string flag exists
func (c *Context) String(name string) string {
return lookupString(name, c.flagSet)
}
// Looks up the value of a local string slice flag, returns nil if no string slice flag exists
func (c *Context) StringSlice(name string) []string {
return lookupStringSlice(name, c.flagSet)
}
// Looks up the value of a local int slice flag, returns nil if no int slice flag exists
func (c *Context) IntSlice(name string) []int {
return lookupIntSlice(name, c.flagSet)
}
// Looks up the value of a local generic flag, returns nil if no generic flag exists
func (c *Context) Generic(name string) interface{} {
return lookupGeneric(name, c.flagSet)
}
// Looks up the value of a global int flag, returns 0 if no int flag exists
func (c *Context) GlobalInt(name string) int {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupInt(name, fs)
}
return 0
}
// Looks up the value of a global time.Duration flag, returns 0 if no time.Duration flag exists
func (c *Context) GlobalDuration(name string) time.Duration {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupDuration(name, fs)
}
return 0
}
// Looks up the value of a global bool flag, returns false if no bool flag exists
func (c *Context) GlobalBool(name string) bool {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupBool(name, fs)
}
return false
}
// Looks up the value of a global string flag, returns "" if no string flag exists
func (c *Context) GlobalString(name string) string {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupString(name, fs)
}
return ""
}
// Looks up the value of a global string slice flag, returns nil if no string slice flag exists
func (c *Context) GlobalStringSlice(name string) []string {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupStringSlice(name, fs)
}
return nil
}
// Looks up the value of a global int slice flag, returns nil if no int slice flag exists
func (c *Context) GlobalIntSlice(name string) []int {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupIntSlice(name, fs)
}
return nil
}
// Looks up the value of a global generic flag, returns nil if no generic flag exists
func (c *Context) GlobalGeneric(name string) interface{} {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupGeneric(name, fs)
}
return nil
}
// Returns the number of flags set
func (c *Context) NumFlags() int {
return c.flagSet.NFlag()
}
// Determines if the flag was actually set
func (c *Context) IsSet(name string) bool {
if c.setFlags == nil {
c.setFlags = make(map[string]bool)
c.flagSet.Visit(func(f *flag.Flag) {
c.setFlags[f.Name] = true
})
}
return c.setFlags[name] == true
}
// Determines if the global flag was actually set
func (c *Context) GlobalIsSet(name string) bool {
if c.globalSetFlags == nil {
c.globalSetFlags = make(map[string]bool)
ctx := c
if ctx.parentContext != nil {
ctx = ctx.parentContext
}
for ; ctx != nil && c.globalSetFlags[name] == false; ctx = ctx.parentContext {
ctx.flagSet.Visit(func(f *flag.Flag) {
c.globalSetFlags[f.Name] = true
})
}
}
return c.globalSetFlags[name]
}
// Returns a slice of flag names used in this context.
func (c *Context) FlagNames() (names []string) {
for _, flag := range c.Command.Flags {
name := strings.Split(flag.getName(), ",")[0]
if name == "help" {
continue
}
names = append(names, name)
}
return
}
// Returns a slice of global flag names used by the app.
func (c *Context) GlobalFlagNames() (names []string) {
for _, flag := range c.App.Flags {
name := strings.Split(flag.getName(), ",")[0]
if name == "help" || name == "version" {
continue
}
names = append(names, name)
}
return
}
// Returns the parent context, if any
func (c *Context) Parent() *Context {
return c.parentContext
}
type Args []string
// Returns the command line arguments associated with the context.
func (c *Context) Args() Args {
args := Args(c.flagSet.Args())
return args
}
// Returns the nth argument, or else a blank string
func (a Args) Get(n int) string {
if len(a) > n {
return a[n]
}
return ""
}
// Returns the first argument, or else a blank string
func (a Args) First() string {
return a.Get(0)
}
// Return the rest of the arguments (not the first one)
// or else an empty string slice
func (a Args) Tail() []string {
if len(a) >= 2 {
return []string(a)[1:]
}
return []string{}
}
// Checks if there are any arguments present
func (a Args) Present() bool {
return len(a) != 0
}
// Swaps arguments at the given indexes
func (a Args) Swap(from, to int) error {
if from >= len(a) || to >= len(a) {
return errors.New("index out of range")
}
a[from], a[to] = a[to], a[from]
return nil
}
func lookupGlobalFlagSet(name string, ctx *Context) *flag.FlagSet {
if ctx.parentContext != nil {
ctx = ctx.parentContext
}
for ; ctx != nil; ctx = ctx.parentContext {
if f := ctx.flagSet.Lookup(name); f != nil {
return ctx.flagSet
}
}
return nil
}
func lookupInt(name string, set *flag.FlagSet) int {
f := set.Lookup(name)
if f != nil {
val, err := strconv.Atoi(f.Value.String())
if err != nil {
return 0
}
return val
}
return 0
}
func lookupDuration(name string, set *flag.FlagSet) time.Duration {
f := set.Lookup(name)
if f != nil {
val, err := time.ParseDuration(f.Value.String())
if err == nil {
return val
}
}
return 0
}
func lookupFloat64(name string, set *flag.FlagSet) float64 {
f := set.Lookup(name)
if f != nil {
val, err := strconv.ParseFloat(f.Value.String(), 64)
if err != nil {
return 0
}
return val
}
return 0
}
func lookupString(name string, set *flag.FlagSet) string {
f := set.Lookup(name)
if f != nil {
return f.Value.String()
}
return ""
}
func lookupStringSlice(name string, set *flag.FlagSet) []string {
f := set.Lookup(name)
if f != nil {
return (f.Value.(*StringSlice)).Value()
}
return nil
}
func lookupIntSlice(name string, set *flag.FlagSet) []int {
f := set.Lookup(name)
if f != nil {
return (f.Value.(*IntSlice)).Value()
}
return nil
}
func lookupGeneric(name string, set *flag.FlagSet) interface{} {
f := set.Lookup(name)
if f != nil {
return f.Value
}
return nil
}
func lookupBool(name string, set *flag.FlagSet) bool {
f := set.Lookup(name)
if f != nil {
val, err := strconv.ParseBool(f.Value.String())
if err != nil {
return false
}
return val
}
return false
}
func lookupBoolT(name string, set *flag.FlagSet) bool {
f := set.Lookup(name)
if f != nil {
val, err := strconv.ParseBool(f.Value.String())
if err != nil {
return true
}
return val
}
return false
}
func copyFlag(name string, ff *flag.Flag, set *flag.FlagSet) {
switch ff.Value.(type) {
case *StringSlice:
default:
set.Set(name, ff.Value.String())
}
}
func normalizeFlags(flags []Flag, set *flag.FlagSet) error {
visited := make(map[string]bool)
set.Visit(func(f *flag.Flag) {
visited[f.Name] = true
})
for _, f := range flags {
parts := strings.Split(f.getName(), ",")
if len(parts) == 1 {
continue
}
var ff *flag.Flag
for _, name := range parts {
name = strings.Trim(name, " ")
if visited[name] {
if ff != nil {
return errors.New("Cannot use two forms of the same flag: " + name + " " + ff.Name)
}
ff = set.Lookup(name)
}
}
if ff == nil {
continue
}
for _, name := range parts {
name = strings.Trim(name, " ")
if !visited[name] {
copyFlag(name, ff, set)
}
}
}
return nil
}

View file

@ -1,497 +0,0 @@
package cli
import (
"flag"
"fmt"
"os"
"strconv"
"strings"
"time"
)
// This flag enables bash-completion for all commands and subcommands
var BashCompletionFlag = BoolFlag{
Name: "generate-bash-completion",
}
// This flag prints the version for the application
var VersionFlag = BoolFlag{
Name: "version, v",
Usage: "print the version",
}
// This flag prints the help for all commands and subcommands
// Set to the zero value (BoolFlag{}) to disable flag -- keeps subcommand
// unless HideHelp is set to true)
var HelpFlag = BoolFlag{
Name: "help, h",
Usage: "show help",
}
// Flag is a common interface related to parsing flags in cli.
// For more advanced flag parsing techniques, it is recomended that
// this interface be implemented.
type Flag interface {
fmt.Stringer
// Apply Flag settings to the given flag set
Apply(*flag.FlagSet)
getName() string
}
func flagSet(name string, flags []Flag) *flag.FlagSet {
set := flag.NewFlagSet(name, flag.ContinueOnError)
for _, f := range flags {
f.Apply(set)
}
return set
}
func eachName(longName string, fn func(string)) {
parts := strings.Split(longName, ",")
for _, name := range parts {
name = strings.Trim(name, " ")
fn(name)
}
}
// Generic is a generic parseable type identified by a specific flag
type Generic interface {
Set(value string) error
String() string
}
// GenericFlag is the flag type for types implementing Generic
type GenericFlag struct {
Name string
Value Generic
Usage string
EnvVar string
}
// String returns the string representation of the generic flag to display the
// help text to the user (uses the String() method of the generic flag to show
// the value)
func (f GenericFlag) String() string {
return withEnvHint(f.EnvVar, fmt.Sprintf("%s%s \"%v\"\t%v", prefixFor(f.Name), f.Name, f.Value, f.Usage))
}
// Apply takes the flagset and calls Set on the generic flag with the value
// provided by the user for parsing by the flag
func (f GenericFlag) Apply(set *flag.FlagSet) {
val := f.Value
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" {
val.Set(envVal)
break
}
}
}
eachName(f.Name, func(name string) {
set.Var(f.Value, name, f.Usage)
})
}
func (f GenericFlag) getName() string {
return f.Name
}
// StringSlice is an opaque type for []string to satisfy flag.Value
type StringSlice []string
// Set appends the string value to the list of values
func (f *StringSlice) Set(value string) error {
*f = append(*f, value)
return nil
}
// String returns a readable representation of this value (for usage defaults)
func (f *StringSlice) String() string {
return fmt.Sprintf("%s", *f)
}
// Value returns the slice of strings set by this flag
func (f *StringSlice) Value() []string {
return *f
}
// StringSlice is a string flag that can be specified multiple times on the
// command-line
type StringSliceFlag struct {
Name string
Value *StringSlice
Usage string
EnvVar string
}
// String returns the usage
func (f StringSliceFlag) String() string {
firstName := strings.Trim(strings.Split(f.Name, ",")[0], " ")
pref := prefixFor(firstName)
return withEnvHint(f.EnvVar, fmt.Sprintf("%s [%v]\t%v", prefixedNames(f.Name), pref+firstName+" option "+pref+firstName+" option", f.Usage))
}
// Apply populates the flag given the flag set and environment
func (f StringSliceFlag) Apply(set *flag.FlagSet) {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" {
newVal := &StringSlice{}
for _, s := range strings.Split(envVal, ",") {
s = strings.TrimSpace(s)
newVal.Set(s)
}
f.Value = newVal
break
}
}
}
eachName(f.Name, func(name string) {
if f.Value == nil {
f.Value = &StringSlice{}
}
set.Var(f.Value, name, f.Usage)
})
}
func (f StringSliceFlag) getName() string {
return f.Name
}
// StringSlice is an opaque type for []int to satisfy flag.Value
type IntSlice []int
// Set parses the value into an integer and appends it to the list of values
func (f *IntSlice) Set(value string) error {
tmp, err := strconv.Atoi(value)
if err != nil {
return err
} else {
*f = append(*f, tmp)
}
return nil
}
// String returns a readable representation of this value (for usage defaults)
func (f *IntSlice) String() string {
return fmt.Sprintf("%d", *f)
}
// Value returns the slice of ints set by this flag
func (f *IntSlice) Value() []int {
return *f
}
// IntSliceFlag is an int flag that can be specified multiple times on the
// command-line
type IntSliceFlag struct {
Name string
Value *IntSlice
Usage string
EnvVar string
}
// String returns the usage
func (f IntSliceFlag) String() string {
firstName := strings.Trim(strings.Split(f.Name, ",")[0], " ")
pref := prefixFor(firstName)
return withEnvHint(f.EnvVar, fmt.Sprintf("%s [%v]\t%v", prefixedNames(f.Name), pref+firstName+" option "+pref+firstName+" option", f.Usage))
}
// Apply populates the flag given the flag set and environment
func (f IntSliceFlag) Apply(set *flag.FlagSet) {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" {
newVal := &IntSlice{}
for _, s := range strings.Split(envVal, ",") {
s = strings.TrimSpace(s)
err := newVal.Set(s)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
}
}
f.Value = newVal
break
}
}
}
eachName(f.Name, func(name string) {
if f.Value == nil {
f.Value = &IntSlice{}
}
set.Var(f.Value, name, f.Usage)
})
}
func (f IntSliceFlag) getName() string {
return f.Name
}
// BoolFlag is a switch that defaults to false
type BoolFlag struct {
Name string
Usage string
EnvVar string
}
// String returns a readable representation of this value (for usage defaults)
func (f BoolFlag) String() string {
return withEnvHint(f.EnvVar, fmt.Sprintf("%s\t%v", prefixedNames(f.Name), f.Usage))
}
// Apply populates the flag given the flag set and environment
func (f BoolFlag) Apply(set *flag.FlagSet) {
val := false
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" {
envValBool, err := strconv.ParseBool(envVal)
if err == nil {
val = envValBool
}
break
}
}
}
eachName(f.Name, func(name string) {
set.Bool(name, val, f.Usage)
})
}
func (f BoolFlag) getName() string {
return f.Name
}
// BoolTFlag this represents a boolean flag that is true by default, but can
// still be set to false by --some-flag=false
type BoolTFlag struct {
Name string
Usage string
EnvVar string
}
// String returns a readable representation of this value (for usage defaults)
func (f BoolTFlag) String() string {
return withEnvHint(f.EnvVar, fmt.Sprintf("%s\t%v", prefixedNames(f.Name), f.Usage))
}
// Apply populates the flag given the flag set and environment
func (f BoolTFlag) Apply(set *flag.FlagSet) {
val := true
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" {
envValBool, err := strconv.ParseBool(envVal)
if err == nil {
val = envValBool
break
}
}
}
}
eachName(f.Name, func(name string) {
set.Bool(name, val, f.Usage)
})
}
func (f BoolTFlag) getName() string {
return f.Name
}
// StringFlag represents a flag that takes as string value
type StringFlag struct {
Name string
Value string
Usage string
EnvVar string
}
// String returns the usage
func (f StringFlag) String() string {
var fmtString string
fmtString = "%s %v\t%v"
if len(f.Value) > 0 {
fmtString = "%s \"%v\"\t%v"
} else {
fmtString = "%s %v\t%v"
}
return withEnvHint(f.EnvVar, fmt.Sprintf(fmtString, prefixedNames(f.Name), f.Value, f.Usage))
}
// Apply populates the flag given the flag set and environment
func (f StringFlag) Apply(set *flag.FlagSet) {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" {
f.Value = envVal
break
}
}
}
eachName(f.Name, func(name string) {
set.String(name, f.Value, f.Usage)
})
}
func (f StringFlag) getName() string {
return f.Name
}
// IntFlag is a flag that takes an integer
// Errors if the value provided cannot be parsed
type IntFlag struct {
Name string
Value int
Usage string
EnvVar string
}
// String returns the usage
func (f IntFlag) String() string {
return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name), f.Value, f.Usage))
}
// Apply populates the flag given the flag set and environment
func (f IntFlag) Apply(set *flag.FlagSet) {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" {
envValInt, err := strconv.ParseInt(envVal, 0, 64)
if err == nil {
f.Value = int(envValInt)
break
}
}
}
}
eachName(f.Name, func(name string) {
set.Int(name, f.Value, f.Usage)
})
}
func (f IntFlag) getName() string {
return f.Name
}
// DurationFlag is a flag that takes a duration specified in Go's duration
// format: https://golang.org/pkg/time/#ParseDuration
type DurationFlag struct {
Name string
Value time.Duration
Usage string
EnvVar string
}
// String returns a readable representation of this value (for usage defaults)
func (f DurationFlag) String() string {
return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name), f.Value, f.Usage))
}
// Apply populates the flag given the flag set and environment
func (f DurationFlag) Apply(set *flag.FlagSet) {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" {
envValDuration, err := time.ParseDuration(envVal)
if err == nil {
f.Value = envValDuration
break
}
}
}
}
eachName(f.Name, func(name string) {
set.Duration(name, f.Value, f.Usage)
})
}
func (f DurationFlag) getName() string {
return f.Name
}
// Float64Flag is a flag that takes an float value
// Errors if the value provided cannot be parsed
type Float64Flag struct {
Name string
Value float64
Usage string
EnvVar string
}
// String returns the usage
func (f Float64Flag) String() string {
return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name), f.Value, f.Usage))
}
// Apply populates the flag given the flag set and environment
func (f Float64Flag) Apply(set *flag.FlagSet) {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" {
envValFloat, err := strconv.ParseFloat(envVal, 10)
if err == nil {
f.Value = float64(envValFloat)
}
}
}
}
eachName(f.Name, func(name string) {
set.Float64(name, f.Value, f.Usage)
})
}
func (f Float64Flag) getName() string {
return f.Name
}
func prefixFor(name string) (prefix string) {
if len(name) == 1 {
prefix = "-"
} else {
prefix = "--"
}
return
}
func prefixedNames(fullName string) (prefixed string) {
parts := strings.Split(fullName, ",")
for i, name := range parts {
name = strings.Trim(name, " ")
prefixed += prefixFor(name) + name
if i < len(parts)-1 {
prefixed += ", "
}
}
return
}
func withEnvHint(envVar, str string) string {
envText := ""
if envVar != "" {
envText = fmt.Sprintf(" [$%s]", strings.Join(strings.Split(envVar, ","), ", $"))
}
return str + envText
}

View file

@ -1,238 +0,0 @@
package cli
import (
"fmt"
"io"
"strings"
"text/tabwriter"
"text/template"
)
// The text template for the Default help topic.
// cli.go uses text/template to render templates. You can
// render custom help text by setting this variable.
var AppHelpTemplate = `NAME:
{{.Name}} - {{.Usage}}
USAGE:
{{.Name}} {{if .Flags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} [arguments...]
{{if .Version}}
VERSION:
{{.Version}}
{{end}}{{if len .Authors}}
AUTHOR(S):
{{range .Authors}}{{ . }}{{end}}
{{end}}{{if .Commands}}
COMMANDS:
{{range .Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}}
{{end}}{{end}}{{if .Flags}}
GLOBAL OPTIONS:
{{range .Flags}}{{.}}
{{end}}{{end}}{{if .Copyright }}
COPYRIGHT:
{{.Copyright}}
{{end}}
`
// The text template for the command help topic.
// cli.go uses text/template to render templates. You can
// render custom help text by setting this variable.
var CommandHelpTemplate = `NAME:
{{.FullName}} - {{.Usage}}
USAGE:
command {{.FullName}}{{if .Flags}} [command options]{{end}} [arguments...]{{if .Description}}
DESCRIPTION:
{{.Description}}{{end}}{{if .Flags}}
OPTIONS:
{{range .Flags}}{{.}}
{{end}}{{ end }}
`
// The text template for the subcommand help topic.
// cli.go uses text/template to render templates. You can
// render custom help text by setting this variable.
var SubcommandHelpTemplate = `NAME:
{{.Name}} - {{.Usage}}
USAGE:
{{.Name}} command{{if .Flags}} [command options]{{end}} [arguments...]
COMMANDS:
{{range .Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}}
{{end}}{{if .Flags}}
OPTIONS:
{{range .Flags}}{{.}}
{{end}}{{end}}
`
var helpCommand = Command{
Name: "help",
Aliases: []string{"h"},
Usage: "Shows a list of commands or help for one command",
Action: func(c *Context) {
args := c.Args()
if args.Present() {
ShowCommandHelp(c, args.First())
} else {
ShowAppHelp(c)
}
},
}
var helpSubcommand = Command{
Name: "help",
Aliases: []string{"h"},
Usage: "Shows a list of commands or help for one command",
Action: func(c *Context) {
args := c.Args()
if args.Present() {
ShowCommandHelp(c, args.First())
} else {
ShowSubcommandHelp(c)
}
},
}
// Prints help for the App or Command
type helpPrinter func(w io.Writer, templ string, data interface{})
var HelpPrinter helpPrinter = printHelp
// Prints version for the App
var VersionPrinter = printVersion
func ShowAppHelp(c *Context) {
HelpPrinter(c.App.Writer, AppHelpTemplate, c.App)
}
// Prints the list of subcommands as the default app completion method
func DefaultAppComplete(c *Context) {
for _, command := range c.App.Commands {
for _, name := range command.Names() {
fmt.Fprintln(c.App.Writer, name)
}
}
}
// Prints help for the given command
func ShowCommandHelp(ctx *Context, command string) {
// show the subcommand help for a command with subcommands
if command == "" {
HelpPrinter(ctx.App.Writer, SubcommandHelpTemplate, ctx.App)
return
}
for _, c := range ctx.App.Commands {
if c.HasName(command) {
HelpPrinter(ctx.App.Writer, CommandHelpTemplate, c)
return
}
}
if ctx.App.CommandNotFound != nil {
ctx.App.CommandNotFound(ctx, command)
} else {
fmt.Fprintf(ctx.App.Writer, "No help topic for '%v'\n", command)
}
}
// Prints help for the given subcommand
func ShowSubcommandHelp(c *Context) {
ShowCommandHelp(c, c.Command.Name)
}
// Prints the version number of the App
func ShowVersion(c *Context) {
VersionPrinter(c)
}
func printVersion(c *Context) {
fmt.Fprintf(c.App.Writer, "%v version %v\n", c.App.Name, c.App.Version)
}
// Prints the lists of commands within a given context
func ShowCompletions(c *Context) {
a := c.App
if a != nil && a.BashComplete != nil {
a.BashComplete(c)
}
}
// Prints the custom completions for a given command
func ShowCommandCompletions(ctx *Context, command string) {
c := ctx.App.Command(command)
if c != nil && c.BashComplete != nil {
c.BashComplete(ctx)
}
}
func printHelp(out io.Writer, templ string, data interface{}) {
funcMap := template.FuncMap{
"join": strings.Join,
}
w := tabwriter.NewWriter(out, 0, 8, 1, '\t', 0)
t := template.Must(template.New("help").Funcs(funcMap).Parse(templ))
err := t.Execute(w, data)
if err != nil {
panic(err)
}
w.Flush()
}
func checkVersion(c *Context) bool {
if c.GlobalBool("version") || c.GlobalBool("v") || c.Bool("version") || c.Bool("v") {
ShowVersion(c)
return true
}
return false
}
func checkHelp(c *Context) bool {
if c.GlobalBool("h") || c.GlobalBool("help") || c.Bool("h") || c.Bool("help") {
ShowAppHelp(c)
return true
}
return false
}
func checkCommandHelp(c *Context, name string) bool {
if c.Bool("h") || c.Bool("help") {
ShowCommandHelp(c, name)
return true
}
return false
}
func checkSubcommandHelp(c *Context) bool {
if c.GlobalBool("h") || c.GlobalBool("help") {
ShowSubcommandHelp(c)
return true
}
return false
}
func checkCompletions(c *Context) bool {
if (c.GlobalBool(BashCompletionFlag.Name) || c.Bool(BashCompletionFlag.Name)) && c.App.EnableBashCompletion {
ShowCompletions(c)
return true
}
return false
}
func checkCommandCompletions(c *Context, name string) bool {
if c.Bool(BashCompletionFlag.Name) && c.App.EnableBashCompletion {
ShowCommandCompletions(c, name)
return true
}
return false
}

View file

@ -1,97 +0,0 @@
package main
import (
"bufio"
"bytes"
"flag"
"fmt"
"os"
"os/exec"
"strings"
)
const bindatafile = "bindata.go"
func isDebug(args []string) bool {
flagset := flag.NewFlagSet("", flag.ContinueOnError)
debug := flagset.Bool("debug", false, "")
debugArgs := make([]string, 0)
for _, arg := range args {
if strings.HasPrefix(arg, "-debug") {
debugArgs = append(debugArgs, arg)
}
}
flagset.Parse(debugArgs)
if debug == nil {
return false
}
return *debug
}
func main() {
if _, err := exec.LookPath("go-bindata"); err != nil {
fmt.Println("Cannot find go-bindata executable in path")
fmt.Println("Maybe you need: go get github.com/elazarl/go-bindata-assetfs/...")
os.Exit(1)
}
cmd := exec.Command("go-bindata", os.Args[1:]...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
os.Exit(1)
}
in, err := os.Open(bindatafile)
if err != nil {
fmt.Fprintln(os.Stderr, "Cannot read", bindatafile, err)
return
}
out, err := os.Create("bindata_assetfs.go")
if err != nil {
fmt.Fprintln(os.Stderr, "Cannot write 'bindata_assetfs.go'", err)
return
}
debug := isDebug(os.Args[1:])
r := bufio.NewReader(in)
done := false
for line, isPrefix, err := r.ReadLine(); err == nil; line, isPrefix, err = r.ReadLine() {
if !isPrefix {
line = append(line, '\n')
}
if _, err := out.Write(line); err != nil {
fmt.Fprintln(os.Stderr, "Cannot write to 'bindata_assetfs.go'", err)
return
}
if !done && !isPrefix && bytes.HasPrefix(line, []byte("import (")) {
if debug {
fmt.Fprintln(out, "\t\"net/http\"")
} else {
fmt.Fprintln(out, "\t\"github.com/elazarl/go-bindata-assetfs\"")
}
done = true
}
}
if debug {
fmt.Fprintln(out, `
func assetFS() http.FileSystem {
for k := range _bintree.Children {
return http.Dir(k)
}
panic("unreachable")
}`)
} else {
fmt.Fprintln(out, `
func assetFS() *assetfs.AssetFS {
for k := range _bintree.Children {
return &assetfs.AssetFS{Asset: Asset, AssetDir: AssetDir, Prefix: k}
}
panic("unreachable")
}`)
}
// Close files BEFORE remove calls (don't use defer).
in.Close()
out.Close()
if err := os.Remove(bindatafile); err != nil {
fmt.Fprintln(os.Stderr, "Cannot remove", bindatafile, err)
}
}

View file

@ -1,13 +0,0 @@
# Test Server
This package contains a server for the [Autobahn WebSockets Test Suite](http://autobahn.ws/testsuite).
To test the server, run
go run server.go
and start the client test driver
wstest -m fuzzingclient -s fuzzingclient.json
When the client completes, it writes a report to reports/clients/index.html.

View file

@ -1,14 +0,0 @@
{
"options": {"failByDrop": false},
"outdir": "./reports/clients",
"servers": [
{"agent": "ReadAllWriteMessage", "url": "ws://localhost:9000/m", "options": {"version": 18}},
{"agent": "ReadAllWrite", "url": "ws://localhost:9000/r", "options": {"version": 18}},
{"agent": "CopyFull", "url": "ws://localhost:9000/f", "options": {"version": 18}},
{"agent": "CopyWriterOnly", "url": "ws://localhost:9000/c", "options": {"version": 18}}
],
"cases": ["*"],
"exclude-cases": [],
"exclude-agent-cases": {}
}

View file

@ -1,246 +0,0 @@
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Command server is a test server for the Autobahn WebSockets Test Suite.
package main
import (
"errors"
"flag"
"github.com/gorilla/websocket"
"io"
"log"
"net/http"
"time"
"unicode/utf8"
)
var upgrader = websocket.Upgrader{
ReadBufferSize: 4096,
WriteBufferSize: 4096,
CheckOrigin: func(r *http.Request) bool {
return true
},
}
// echoCopy echoes messages from the client using io.Copy.
func echoCopy(w http.ResponseWriter, r *http.Request, writerOnly bool) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println("Upgrade:", err)
return
}
defer conn.Close()
for {
mt, r, err := conn.NextReader()
if err != nil {
if err != io.EOF {
log.Println("NextReader:", err)
}
return
}
if mt == websocket.TextMessage {
r = &validator{r: r}
}
w, err := conn.NextWriter(mt)
if err != nil {
log.Println("NextWriter:", err)
return
}
if mt == websocket.TextMessage {
r = &validator{r: r}
}
if writerOnly {
_, err = io.Copy(struct{ io.Writer }{w}, r)
} else {
_, err = io.Copy(w, r)
}
if err != nil {
if err == errInvalidUTF8 {
conn.WriteControl(websocket.CloseMessage,
websocket.FormatCloseMessage(websocket.CloseInvalidFramePayloadData, ""),
time.Time{})
}
log.Println("Copy:", err)
return
}
err = w.Close()
if err != nil {
log.Println("Close:", err)
return
}
}
}
func echoCopyWriterOnly(w http.ResponseWriter, r *http.Request) {
echoCopy(w, r, true)
}
func echoCopyFull(w http.ResponseWriter, r *http.Request) {
echoCopy(w, r, false)
}
// echoReadAll echoes messages from the client by reading the entire message
// with ioutil.ReadAll.
func echoReadAll(w http.ResponseWriter, r *http.Request, writeMessage bool) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println("Upgrade:", err)
return
}
defer conn.Close()
for {
mt, b, err := conn.ReadMessage()
if err != nil {
if err != io.EOF {
log.Println("NextReader:", err)
}
return
}
if mt == websocket.TextMessage {
if !utf8.Valid(b) {
conn.WriteControl(websocket.CloseMessage,
websocket.FormatCloseMessage(websocket.CloseInvalidFramePayloadData, ""),
time.Time{})
log.Println("ReadAll: invalid utf8")
}
}
if writeMessage {
err = conn.WriteMessage(mt, b)
if err != nil {
log.Println("WriteMessage:", err)
}
} else {
w, err := conn.NextWriter(mt)
if err != nil {
log.Println("NextWriter:", err)
return
}
if _, err := w.Write(b); err != nil {
log.Println("Writer:", err)
return
}
if err := w.Close(); err != nil {
log.Println("Close:", err)
return
}
}
}
}
func echoReadAllWriter(w http.ResponseWriter, r *http.Request) {
echoReadAll(w, r, false)
}
func echoReadAllWriteMessage(w http.ResponseWriter, r *http.Request) {
echoReadAll(w, r, true)
}
func serveHome(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.Error(w, "Not found.", 404)
return
}
if r.Method != "GET" {
http.Error(w, "Method not allowed", 405)
return
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
io.WriteString(w, "<html><body>Echo Server</body></html>")
}
var addr = flag.String("addr", ":9000", "http service address")
func main() {
flag.Parse()
http.HandleFunc("/", serveHome)
http.HandleFunc("/c", echoCopyWriterOnly)
http.HandleFunc("/f", echoCopyFull)
http.HandleFunc("/r", echoReadAllWriter)
http.HandleFunc("/m", echoReadAllWriteMessage)
err := http.ListenAndServe(*addr, nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
type validator struct {
state int
x rune
r io.Reader
}
var errInvalidUTF8 = errors.New("invalid utf8")
func (r *validator) Read(p []byte) (int, error) {
n, err := r.r.Read(p)
state := r.state
x := r.x
for _, b := range p[:n] {
state, x = decode(state, x, b)
if state == utf8Reject {
break
}
}
r.state = state
r.x = x
if state == utf8Reject || (err == io.EOF && state != utf8Accept) {
return n, errInvalidUTF8
}
return n, err
}
// UTF-8 decoder from http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
//
// Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
var utf8d = [...]byte{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..1f
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..3f
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..5f
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..7f
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 80..9f
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // a0..bf
8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // c0..df
0xa, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, // e0..ef
0xb, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, // f0..ff
0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, // s0..s0
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1..s2
1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s3..s4
1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s5..s6
1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // s7..s8
}
const (
utf8Accept = 0
utf8Reject = 1
)
func decode(state int, x rune, b byte) (int, rune) {
t := utf8d[b]
if state != utf8Accept {
x = rune(b&0x3f) | (x << 6)
} else {
x = rune((0xff >> t) & b)
}
state = int(utf8d[256+state*16+int(t)])
return state, x
}

View file

@ -1,20 +0,0 @@
# Chat Example
This application shows how to use use the
[websocket](https://github.com/gorilla/websocket) package and
[jQuery](http://jquery.com) to implement a simple web chat application.
## Running the example
The example requires a working Go development environment. The [Getting
Started](http://golang.org/doc/install) page describes how to install the
development environment.
Once you have Go up and running, you can download, build and run the example
using the following commands.
$ go get github.com/gorilla/websocket
$ cd `go list -f '{{.Dir}}' github.com/gorilla/websocket/examples/chat`
$ go run *.go
To use the chat example, open http://localhost:8080/ in your browser.

View file

@ -1,106 +0,0 @@
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"github.com/gorilla/websocket"
"log"
"net/http"
"time"
)
const (
// Time allowed to write a message to the peer.
writeWait = 10 * time.Second
// Time allowed to read the next pong message from the peer.
pongWait = 60 * time.Second
// Send pings to peer with this period. Must be less than pongWait.
pingPeriod = (pongWait * 9) / 10
// Maximum message size allowed from peer.
maxMessageSize = 512
)
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
}
// connection is an middleman between the websocket connection and the hub.
type connection struct {
// The websocket connection.
ws *websocket.Conn
// Buffered channel of outbound messages.
send chan []byte
}
// readPump pumps messages from the websocket connection to the hub.
func (c *connection) readPump() {
defer func() {
h.unregister <- c
c.ws.Close()
}()
c.ws.SetReadLimit(maxMessageSize)
c.ws.SetReadDeadline(time.Now().Add(pongWait))
c.ws.SetPongHandler(func(string) error { c.ws.SetReadDeadline(time.Now().Add(pongWait)); return nil })
for {
_, message, err := c.ws.ReadMessage()
if err != nil {
break
}
h.broadcast <- message
}
}
// write writes a message with the given message type and payload.
func (c *connection) write(mt int, payload []byte) error {
c.ws.SetWriteDeadline(time.Now().Add(writeWait))
return c.ws.WriteMessage(mt, payload)
}
// writePump pumps messages from the hub to the websocket connection.
func (c *connection) writePump() {
ticker := time.NewTicker(pingPeriod)
defer func() {
ticker.Stop()
c.ws.Close()
}()
for {
select {
case message, ok := <-c.send:
if !ok {
c.write(websocket.CloseMessage, []byte{})
return
}
if err := c.write(websocket.TextMessage, message); err != nil {
return
}
case <-ticker.C:
if err := c.write(websocket.PingMessage, []byte{}); err != nil {
return
}
}
}
}
// serverWs handles websocket requests from the peer.
func serveWs(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" {
http.Error(w, "Method not allowed", 405)
return
}
ws, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println(err)
return
}
c := &connection{send: make(chan []byte, 256), ws: ws}
h.register <- c
go c.writePump()
c.readPump()
}

View file

@ -1,92 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Chat Example</title>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<script type="text/javascript">
$(function() {
var conn;
var msg = $("#msg");
var log = $("#log");
function appendLog(msg) {
var d = log[0]
var doScroll = d.scrollTop == d.scrollHeight - d.clientHeight;
msg.appendTo(log)
if (doScroll) {
d.scrollTop = d.scrollHeight - d.clientHeight;
}
}
$("#form").submit(function() {
if (!conn) {
return false;
}
if (!msg.val()) {
return false;
}
conn.send(msg.val());
msg.val("");
return false
});
if (window["WebSocket"]) {
conn = new WebSocket("ws://{{$}}/ws");
conn.onclose = function(evt) {
appendLog($("<div><b>Connection closed.</b></div>"))
}
conn.onmessage = function(evt) {
appendLog($("<div/>").text(evt.data))
}
} else {
appendLog($("<div><b>Your browser does not support WebSockets.</b></div>"))
}
});
</script>
<style type="text/css">
html {
overflow: hidden;
}
body {
overflow: hidden;
padding: 0;
margin: 0;
width: 100%;
height: 100%;
background: gray;
}
#log {
background: white;
margin: 0;
padding: 0.5em 0.5em 0.5em 0.5em;
position: absolute;
top: 0.5em;
left: 0.5em;
right: 0.5em;
bottom: 3em;
overflow: auto;
}
#form {
padding: 0 0.5em 0 0.5em;
margin: 0;
position: absolute;
bottom: 1em;
left: 0px;
width: 100%;
overflow: hidden;
}
</style>
</head>
<body>
<div id="log"></div>
<form id="form">
<input type="submit" value="Send" />
<input type="text" id="msg" size="64"/>
</form>
</body>
</html>

View file

@ -1,51 +0,0 @@
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
// hub maintains the set of active connections and broadcasts messages to the
// connections.
type hub struct {
// Registered connections.
connections map[*connection]bool
// Inbound messages from the connections.
broadcast chan []byte
// Register requests from the connections.
register chan *connection
// Unregister requests from connections.
unregister chan *connection
}
var h = hub{
broadcast: make(chan []byte),
register: make(chan *connection),
unregister: make(chan *connection),
connections: make(map[*connection]bool),
}
func (h *hub) run() {
for {
select {
case c := <-h.register:
h.connections[c] = true
case c := <-h.unregister:
if _, ok := h.connections[c]; ok {
delete(h.connections, c)
close(c.send)
}
case m := <-h.broadcast:
for c := range h.connections {
select {
case c.send <- m:
default:
close(c.send)
delete(h.connections, c)
}
}
}
}
}

View file

@ -1,39 +0,0 @@
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"flag"
"log"
"net/http"
"text/template"
)
var addr = flag.String("addr", ":8080", "http service address")
var homeTempl = template.Must(template.ParseFiles("home.html"))
func serveHome(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.Error(w, "Not found", 404)
return
}
if r.Method != "GET" {
http.Error(w, "Method not allowed", 405)
return
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
homeTempl.Execute(w, r.Host)
}
func main() {
flag.Parse()
go h.run()
http.HandleFunc("/", serveHome)
http.HandleFunc("/ws", serveWs)
err := http.ListenAndServe(*addr, nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}

View file

@ -1,9 +0,0 @@
# File Watch example.
This example sends a file to the browser client for display whenever the file is modified.
$ go get github.com/gorilla/websocket
$ cd `go list -f '{{.Dir}}' github.com/gorilla/websocket/examples/filewatch`
$ go run main.go <name of file to watch>
# Open http://localhost:8080/ .
# Modify the file to see it update in the browser.

View file

@ -1,193 +0,0 @@
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"flag"
"io/ioutil"
"log"
"net/http"
"os"
"strconv"
"text/template"
"time"
"github.com/gorilla/websocket"
)
const (
// Time allowed to write the file to the client.
writeWait = 10 * time.Second
// Time allowed to read the next pong message from the client.
pongWait = 60 * time.Second
// Send pings to client with this period. Must be less than pongWait.
pingPeriod = (pongWait * 9) / 10
// Poll file for changes with this period.
filePeriod = 10 * time.Second
)
var (
addr = flag.String("addr", ":8080", "http service address")
homeTempl = template.Must(template.New("").Parse(homeHTML))
filename string
upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
}
)
func readFileIfModified(lastMod time.Time) ([]byte, time.Time, error) {
fi, err := os.Stat(filename)
if err != nil {
return nil, lastMod, err
}
if !fi.ModTime().After(lastMod) {
return nil, lastMod, nil
}
p, err := ioutil.ReadFile(filename)
if err != nil {
return nil, fi.ModTime(), err
}
return p, fi.ModTime(), nil
}
func reader(ws *websocket.Conn) {
defer ws.Close()
ws.SetReadLimit(512)
ws.SetReadDeadline(time.Now().Add(pongWait))
ws.SetPongHandler(func(string) error { ws.SetReadDeadline(time.Now().Add(pongWait)); return nil })
for {
_, _, err := ws.ReadMessage()
if err != nil {
break
}
}
}
func writer(ws *websocket.Conn, lastMod time.Time) {
lastError := ""
pingTicker := time.NewTicker(pingPeriod)
fileTicker := time.NewTicker(filePeriod)
defer func() {
pingTicker.Stop()
fileTicker.Stop()
ws.Close()
}()
for {
select {
case <-fileTicker.C:
var p []byte
var err error
p, lastMod, err = readFileIfModified(lastMod)
if err != nil {
if s := err.Error(); s != lastError {
lastError = s
p = []byte(lastError)
}
} else {
lastError = ""
}
if p != nil {
ws.SetWriteDeadline(time.Now().Add(writeWait))
if err := ws.WriteMessage(websocket.TextMessage, p); err != nil {
return
}
}
case <-pingTicker.C:
ws.SetWriteDeadline(time.Now().Add(writeWait))
if err := ws.WriteMessage(websocket.PingMessage, []byte{}); err != nil {
return
}
}
}
}
func serveWs(w http.ResponseWriter, r *http.Request) {
ws, err := upgrader.Upgrade(w, r, nil)
if err != nil {
if _, ok := err.(websocket.HandshakeError); !ok {
log.Println(err)
}
return
}
var lastMod time.Time
if n, err := strconv.ParseInt(r.FormValue("lastMod"), 16, 64); err != nil {
lastMod = time.Unix(0, n)
}
go writer(ws, lastMod)
reader(ws)
}
func serveHome(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.Error(w, "Not found", 404)
return
}
if r.Method != "GET" {
http.Error(w, "Method not allowed", 405)
return
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
p, lastMod, err := readFileIfModified(time.Time{})
if err != nil {
p = []byte(err.Error())
lastMod = time.Unix(0, 0)
}
var v = struct {
Host string
Data string
LastMod string
}{
r.Host,
string(p),
strconv.FormatInt(lastMod.UnixNano(), 16),
}
homeTempl.Execute(w, &v)
}
func main() {
flag.Parse()
if flag.NArg() != 1 {
log.Fatal("filename not specified")
}
filename = flag.Args()[0]
http.HandleFunc("/", serveHome)
http.HandleFunc("/ws", serveWs)
if err := http.ListenAndServe(*addr, nil); err != nil {
log.Fatal(err)
}
}
const homeHTML = `<!DOCTYPE html>
<html lang="en">
<head>
<title>WebSocket Example</title>
</head>
<body>
<pre id="fileData">{{.Data}}</pre>
<script type="text/javascript">
(function() {
var data = document.getElementById("fileData");
var conn = new WebSocket("ws://{{.Host}}/ws?lastMod={{.LastMod}}");
conn.onclose = function(evt) {
data.textContent = 'Connection closed';
}
conn.onmessage = function(evt) {
console.log('file updated');
data.textContent = evt.data;
}
})();
</script>
</body>
</html>
`

View file

@ -1,4 +0,0 @@
foo = [
"1",
"2", # comment
]

View file

@ -1,6 +0,0 @@
resource = [{
"foo": {
"bar": {},
"baz": [1, 2, "foo"],
}
}]

View file

@ -1,5 +0,0 @@
resource = [{
foo = [{
bar = {}
}]
}]

View file

@ -1,15 +0,0 @@
// Foo
/* Bar */
/*
/*
Baz
*/
# Another
# Multiple
# Lines
foo = "bar"

View file

@ -1 +0,0 @@
# Hello

View file

@ -1,42 +0,0 @@
// This comes from Terraform, as a test
variable "foo" {
default = "bar"
description = "bar"
}
provider "aws" {
access_key = "foo"
secret_key = "bar"
}
provider "do" {
api_key = "${var.foo}"
}
resource "aws_security_group" "firewall" {
count = 5
}
resource aws_instance "web" {
ami = "${var.foo}"
security_groups = [
"foo",
"${aws_security_group.firewall.foo}"
]
network_interface {
device_index = 0
description = "Main network interface"
}
}
resource "aws_instance" "db" {
security_groups = "${aws_security_group.firewall.*.id}"
VPC = "foo"
depends_on = ["aws_instance.web"]
}
output "web_ip" {
value = "${aws_instance.web.private_ip}"
}

View file

@ -1 +0,0 @@
foo.bar = "baz"

View file

@ -1 +0,0 @@
foo = [1, 2, "foo", true]

View file

@ -1 +0,0 @@
foo = [1, 2, "foo",]

View file

@ -1,2 +0,0 @@
foo = "bar"
key = 7

View file

@ -1,5 +0,0 @@
foo = null
bar = [1, null, 3]
baz {
foo = null
}

View file

@ -1,3 +0,0 @@
default = {
"eu-west-1": "ami-b1cf19c6",
}

View file

@ -1,5 +0,0 @@
// This is a test structure for the lexer
foo bar "baz" {
key = 7
foo = "bar"
}

View file

@ -1,5 +0,0 @@
foo {
value = 7
"value" = 8
"complex::value" = 9
}

View file

@ -1 +0,0 @@
resource "foo" "bar" {}

View file

@ -1,8 +0,0 @@
foo = "bar"
bar = 7
baz = [1,2,3]
foo = -12
bar = 3.14159
foo = true
bar = false
baz = null

View file

@ -1,4 +0,0 @@
{
"foo": [1, 2, "bar"],
"bar": "baz"
}

View file

@ -1,3 +0,0 @@
{
"foo": "bar"
}

View file

@ -1,5 +0,0 @@
{
"foo": {
"bar": [1,2]
}
}

View file

@ -1,10 +0,0 @@
{
"foo": "bar",
"bar": 7,
"baz": [1,2,3],
"foo": -12,
"bar": 3.14159,
"foo": true,
"bar": false,
"foo": null
}

View file

@ -1,2 +0,0 @@
foo = "bar"
bar = "${file("bing/bong.txt")}"

View file

@ -1,4 +0,0 @@
{
"foo": "bar",
"bar": "${file(\"bing/bong.txt\")}"
}

View file

@ -1 +0,0 @@
count = "3"

View file

@ -1,3 +0,0 @@
foo="bar"
bar="${file("bing/bong.txt")}"
foo-bar="baz"

View file

@ -1,15 +0,0 @@
key "" {
policy = "read"
}
key "foo/" {
policy = "write"
}
key "foo/bar/" {
policy = "read"
}
key "foo/bar/baz" {
policy = "deny"
}

View file

@ -1,19 +0,0 @@
{
"key": {
"": {
"policy": "read"
},
"foo/": {
"policy": "write"
},
"foo/bar/": {
"policy": "read"
},
"foo/bar/baz": {
"policy": "deny"
}
}
}

View file

@ -1,10 +0,0 @@
variable "foo" {
default = "bar"
description = "bar"
}
variable "amis" {
default = {
east = "foo"
}
}

View file

@ -1,14 +0,0 @@
{
"variable": {
"foo": {
"default": "bar",
"description": "bar"
},
"amis": {
"default": {
"east": "foo"
}
}
}
}

View file

@ -1 +0,0 @@
resource "foo" {}

View file

@ -1 +0,0 @@
foo = "bar\"baz\\n"

View file

@ -1,2 +0,0 @@
foo = "bar"
Key = 7

View file

@ -1 +0,0 @@
a = 1.02

View file

@ -1,3 +0,0 @@
{
"a": 1.02
}

View file

@ -1,3 +0,0 @@
{
"foo": "bar\nbaz"
}

View file

@ -1,4 +0,0 @@
foo = <<EOF
bar
baz
EOF

View file

@ -1,5 +0,0 @@
/*
foo = "bar/*"
*/
bar = "value"

View file

@ -1 +0,0 @@
foo = null

View file

@ -1,6 +0,0 @@
a = 1e-10
b = 1e+10
c = 1e10
d = 1.2e-10
e = 1.2e+10
f = 1.2e10

View file

@ -1,8 +0,0 @@
{
"a": 1e-10,
"b": 1e+10,
"c": 1e10,
"d": 1.2e-10,
"e": 1.2e+10,
"f": 1.2e10
}

View file

@ -1,5 +0,0 @@
// This is a test structure for the lexer
foo "baz" {
key = 7
foo = "bar"
}

View file

@ -1,8 +0,0 @@
{
"foo": [{
"baz": [{
"key": 7,
"foo": "bar"
}]
}]
}

View file

@ -1,9 +0,0 @@
// This is a test structure for the lexer
foo "baz" {
key = 7
foo = "bar"
}
foo {
key = 7
}

View file

@ -1,10 +0,0 @@
{
"foo": [{
"baz": {
"key": 7,
"foo": "bar"
}
}, {
"key": 7
}]
}

View file

@ -1,8 +0,0 @@
{
"foo": {
"baz": {
"key": 7,
"foo": "bar"
}
}
}

View file

@ -1,7 +0,0 @@
foo {
key = 7
}
foo {
foo = "bar"
}

View file

@ -1,6 +0,0 @@
foo {
key = 7
}
foo {
key = 12
}

View file

@ -1,7 +0,0 @@
{
"foo": [{
"key": 7
}, {
"key": 12
}]
}

View file

@ -1,16 +0,0 @@
{
"bar": {
"foo": {
"name": "terraform_example",
"ingress": [
{
"from_port": 22
},
{
"from_port": 80
}
]
}
}
}

View file

@ -1,7 +0,0 @@
foo "baz" {
key = 7
}
foo "bar" {
key = 12
}

View file

@ -1,11 +0,0 @@
{
"foo": {
"baz": {
"key": 7
},
"bar": {
"key": 12
}
}
}

View file

@ -1,5 +0,0 @@
name = "terraform-test-app"
config_vars {
FOO = "bar"
}

View file

@ -1,6 +0,0 @@
{
"name": "terraform-test-app",
"config_vars": {
"FOO": "bar"
}
}

View file

@ -1,53 +0,0 @@
# Unblocking Mutex
This simple package provides unblocking mutexes for those who don't want to write many `select` clauses or get confused by numerous channels.
## Usage Example
```go
package main
import (
"fmt"
"github.com/yudai/umutex"
)
func main() {
// Create mutex
mutex := umutex.New()
// First time, try should succeed
if mutex.TryLock() {
fmt.Println("SUCCESS")
} else {
fmt.Println("FAILURE")
}
// Second time, try should fail as it's locked
if mutex.TryLock() {
fmt.Println("SUCCESS")
} else {
fmt.Println("FAILURE")
}
// Unclock mutex
mutex.Unlock()
// Third time, try should succeed again
if mutex.TryLock() {
fmt.Println("SUCCESS")
} else {
fmt.Println("FAILURE")
}
}
```
The output is;
```sh
SUCCESS
FAILURE
SUCCESS
```
`ForceLock()` method is also availale for normal blocking lock.

View file

@ -1,38 +0,0 @@
// Package umutex provides unblocking mutex
package umutex
// UnblockingMutex represents an unblocking mutex.
type UnblockingMutex struct {
// Raw channel
C chan bool
}
// New returnes a new unblocking mutex instance.
func New() *UnblockingMutex {
return &UnblockingMutex{
C: make(chan bool, 1),
}
}
// TryLock tries to lock the mutex.
// When the mutex is free at the time, the function locks the mutex and return
// true. Otherwise false will be returned. In the both cases, this function
// doens't block and return the result immediately.
func (m UnblockingMutex) TryLock() (result bool) {
select {
case m.C <- true:
return true
default:
return false
}
}
// Unlock unclocks the mutex.
func (m UnblockingMutex) Unlock() {
<-m.C
}
// ForceLock surely locks the mutex, however, this function blocks when the mutex is locked at the time.
func (m UnblockingMutex) ForceLock() {
m.C <- false
}

621
LICENSE
View file

@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2015 Iwasaki Yudai
Copyright (c) 2015-2017 Iwasaki Yudai
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -19,622 +19,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
=============================================================================
This software is built with following third party open source software.
# golng/go
Copyright (c) 2009 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# libapps/hterm
Copyright (c) 2014, Google Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.
* Neither the name of Google Inc. nor the names of its contributors may be used
to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# odegangsta/cli
Copyright (C) 2013 Jeremy Saenz
All Rights Reserved.
MIT LICENSE
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
# jteeuwen/go-bindata
This work is subject to the CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
license. Its contents can be found at:
http://creativecommons.org/publicdomain/zero/1.0
# elazarl/go-bindata-assetfs
Copyright (c) 2014, Elazar Leibovich
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# gorilla/websocket
Copyright (c) 2013 The Gorilla WebSocket Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# kr/pty
Copyright (c) 2011 Keith Rarick
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute,
sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall
be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
# krishnasrinivas/wetty
The MIT License (MIT)
Copyright (c) 2014 Krishna Srinivas
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
# braintree/manners
Copyright (c) 2014 Braintree, a division of PayPal, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
# fatih/camelcase
The MIT License (MIT)
Copyright (c) 2015 Fatih Arslan
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
# fatih/structs
The MIT License (MIT)
Copyright (c) 2014 Fatih Arslan
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
# hashicorp/hcl
Mozilla Public License, version 2.0
1. Definitions
1.1. “Contributor”
means each individual or legal entity that creates, contributes to the
creation of, or owns Covered Software.
1.2. “Contributor Version”
means the combination of the Contributions of others (if any) used by a
Contributor and that particular Contributors Contribution.
1.3. “Contribution”
means Covered Software of a particular Contributor.
1.4. “Covered Software”
means Source Code Form to which the initial Contributor has attached the
notice in Exhibit A, the Executable Form of such Source Code Form, and
Modifications of such Source Code Form, in each case including portions
thereof.
1.5. “Incompatible With Secondary Licenses”
means
a. that the initial Contributor has attached the notice described in
Exhibit B to the Covered Software; or
b. that the Covered Software was made available under the terms of version
1.1 or earlier of the License, but not also under the terms of a
Secondary License.
1.6. “Executable Form”
means any form of the work other than Source Code Form.
1.7. “Larger Work”
means a work that combines Covered Software with other material, in a separate
file or files, that is not Covered Software.
1.8. “License”
means this document.
1.9. “Licensable”
means having the right to grant, to the maximum extent possible, whether at the
time of the initial grant or subsequently, any and all of the rights conveyed by
this License.
1.10. “Modifications”
means any of the following:
a. any file in Source Code Form that results from an addition to, deletion
from, or modification of the contents of Covered Software; or
b. any new file in Source Code Form that contains any Covered Software.
1.11. “Patent Claims” of a Contributor
means any patent claim(s), including without limitation, method, process,
and apparatus claims, in any patent Licensable by such Contributor that
would be infringed, but for the grant of the License, by the making,
using, selling, offering for sale, having made, import, or transfer of
either its Contributions or its Contributor Version.
1.12. “Secondary License”
means either the GNU General Public License, Version 2.0, the GNU Lesser
General Public License, Version 2.1, the GNU Affero General Public
License, Version 3.0, or any later versions of those licenses.
1.13. “Source Code Form”
means the form of the work preferred for making modifications.
1.14. “You” (or “Your”)
means an individual or a legal entity exercising rights under this
License. For legal entities, “You” includes any entity that controls, is
controlled by, or is under common control with You. For purposes of this
definition, “control” means (a) the power, direct or indirect, to cause
the direction or management of such entity, whether by contract or
otherwise, or (b) ownership of more than fifty percent (50%) of the
outstanding shares or beneficial ownership of such entity.
2. License Grants and Conditions
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
a. under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or as
part of a Larger Work; and
b. under Patent Claims of such Contributor to make, use, sell, offer for
sale, have made, import, and otherwise transfer either its Contributions
or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution become
effective for each Contribution on the date the Contributor first distributes
such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under this
License. No additional rights or licenses will be implied from the distribution
or licensing of Covered Software under this License. Notwithstanding Section
2.1(b) above, no patent license is granted by a Contributor:
a. for any code that a Contributor has removed from Covered Software; or
b. for infringements caused by: (i) Your and any other third partys
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
c. under Patent Claims infringed by Covered Software in the absence of its
Contributions.
This License does not grant any rights in the trademarks, service marks, or
logos of any Contributor (except as may be necessary to comply with the
notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this License
(see Section 10.2) or under the terms of a Secondary License (if permitted
under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its Contributions
are its original creation(s) or it has sufficient rights to grant the
rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under applicable
copyright doctrines of fair use, fair dealing, or other equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
Section 2.1.
3. Responsibilities
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under the
terms of this License. You must inform recipients that the Source Code Form
of the Covered Software is governed by the terms of this License, and how
they can obtain a copy of this License. You may not attempt to alter or
restrict the recipients rights in the Source Code Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
a. such Covered Software must also be made available in Source Code Form,
as described in Section 3.1, and You must inform recipients of the
Executable Form how they can obtain a copy of such Source Code Form by
reasonable means in a timely manner, at a charge no more than the cost
of distribution to the recipient; and
b. You may distribute such Executable Form under the terms of this License,
or sublicense it under different terms, provided that the license for
the Executable Form does not attempt to limit or alter the recipients
rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for the
Covered Software. If the Larger Work is a combination of Covered Software
with a work governed by one or more Secondary Licenses, and the Covered
Software is not Incompatible With Secondary Licenses, this License permits
You to additionally distribute such Covered Software under the terms of
such Secondary License(s), so that the recipient of the Larger Work may, at
their option, further distribute the Covered Software under the terms of
either this License or such Secondary License(s).
3.4. Notices
You may not remove or alter the substance of any license notices (including
copyright notices, patent notices, disclaimers of warranty, or limitations
of liability) contained within the Source Code Form of the Covered
Software, except that You may alter any license notices to the extent
required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on behalf
of any Contributor. You must make it absolutely clear that any such
warranty, support, indemnity, or liability obligation is offered by You
alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
If it is impossible for You to comply with any of the terms of this License
with respect to some or all of the Covered Software due to statute, judicial
order, or regulation then You must: (a) comply with the terms of this License
to the maximum extent possible; and (b) describe the limitations and the code
they affect. Such description must be placed in a text file included with all
distributions of the Covered Software under this License. Except to the
extent prohibited by statute or regulation, such description must be
sufficiently detailed for a recipient of ordinary skill to be able to
understand it.
5. Termination
5.1. The rights granted under this License will terminate automatically if You
fail to comply with any of its terms. However, if You become compliant,
then the rights granted under this License from a particular Contributor
are reinstated (a) provisionally, unless and until such Contributor
explicitly and finally terminates Your grants, and (b) on an ongoing basis,
if such Contributor fails to notify You of the non-compliance by some
reasonable means prior to 60 days after You have come back into compliance.
Moreover, Your grants from a particular Contributor are reinstated on an
ongoing basis if such Contributor notifies You of the non-compliance by
some reasonable means, this is the first time You have received notice of
non-compliance with this License from such Contributor, and You become
compliant prior to 30 days after Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions, counter-claims,
and cross-claims) alleging that a Contributor Version directly or
indirectly infringes any patent, then the rights granted to You by any and
all Contributors for the Covered Software under Section 2.1 of this License
shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
license agreements (excluding distributors and resellers) which have been
validly granted by You or Your distributors under this License prior to
termination shall survive termination.
6. Disclaimer of Warranty
Covered Software is provided under this License on an “as is” basis, without
warranty of any kind, either expressed, implied, or statutory, including,
without limitation, warranties that the Covered Software is free of defects,
merchantable, fit for a particular purpose or non-infringing. The entire
risk as to the quality and performance of the Covered Software is with You.
Should any Covered Software prove defective in any respect, You (not any
Contributor) assume the cost of any necessary servicing, repair, or
correction. This disclaimer of warranty constitutes an essential part of this
License. No use of any Covered Software is authorized under this License
except under this disclaimer.
7. Limitation of Liability
Under no circumstances and under no legal theory, whether tort (including
negligence), contract, or otherwise, shall any Contributor, or anyone who
distributes Covered Software as permitted above, be liable to You for any
direct, indirect, special, incidental, or consequential damages of any
character including, without limitation, damages for lost profits, loss of
goodwill, work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses, even if such party shall have been
informed of the possibility of such damages. This limitation of liability
shall not apply to liability for death or personal injury resulting from such
partys negligence to the extent applicable law prohibits such limitation.
Some jurisdictions do not allow the exclusion or limitation of incidental or
consequential damages, so this exclusion and limitation may not apply to You.
8. Litigation
Any litigation relating to this License may be brought only in the courts of
a jurisdiction where the defendant maintains its principal place of business
and such litigation shall be governed by laws of that jurisdiction, without
reference to its conflict-of-law provisions. Nothing in this Section shall
prevent a partys ability to bring cross-claims or counter-claims.
9. Miscellaneous
This License represents the complete agreement concerning the subject matter
hereof. If any provision of this License is held to be unenforceable, such
provision shall be reformed only to the extent necessary to make it
enforceable. Any law or regulation which provides that the language of a
contract shall be construed against the drafter shall not be used to construe
this License against a Contributor.
10. Versions of the License
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version of
the License under which You originally received the Covered Software, or
under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a modified
version of this License if you rename the license and remove any
references to the name of the license steward (except to note that such
modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular file, then
You may include the notice in a location (such as a LICENSE file in a relevant
directory) where a recipient would be likely to look for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - “Incompatible With Secondary Licenses” Notice
This Source Code Form is “Incompatible
With Secondary Licenses”, as defined by
the Mozilla Public License, v. 2.0.

View file

@ -1,13 +1,18 @@
OUTPUT_DIR = ./builds
GIT_COMMIT = `git rev-parse HEAD | cut -c1-7`
VERSION = 2.0.0-alpha.3
BUILD_OPTIONS = -ldflags "-X main.Version=$(VERSION) -X main.CommitID=$(GIT_COMMIT)"
gotty: app/resource.go main.go app/*.go
go build
gotty: main.go server/*.go webtty/*.go backend/*.go Makefile
godep go build ${BUILD_OPTIONS}
resource: app/resource.go
.PHONY: asset
asset: bindata/static/js/gotty-bundle.js bindata/static/index.html bindata/static/favicon.png bindata/static/css/index.css bindata/static/css/xterm.css bindata/static/css/xterm_customize.css
go-bindata -prefix bindata -pkg server -ignore=\\.gitkeep -o server/asset.go bindata/...
gofmt -w server/asset.go
app/resource.go: bindata/static/js/hterm.js bindata/static/js/gotty.js bindata/static/index.html bindata/static/favicon.png
go-bindata -prefix bindata -pkg app -ignore=\\.gitkeep -o app/resource.go bindata/...
gofmt -w app/resource.go
.PHONY: all
all: asset gotty
bindata:
mkdir bindata
@ -24,33 +29,52 @@ bindata/static/favicon.png: bindata/static resources/favicon.png
bindata/static/js: bindata/static
mkdir -p bindata/static/js
bindata/static/js/hterm.js: bindata/static/js libapps/hterm/js/*.js
cd libapps && \
LIBDOT_SEARCH_PATH=`pwd` ./libdot/bin/concat.sh -i ./hterm/concat/hterm_all.concat -o ../bindata/static/js/hterm.js
bindata/static/js/gotty.js: bindata/static/js resources/gotty.js
cp resources/gotty.js bindata/static/js/gotty.js
bindata/static/js/gotty-bundle.js: bindata/static/js js/dist/gotty-bundle.js
cp js/dist/gotty-bundle.js bindata/static/js/gotty-bundle.js
bindata/static/css: bindata/static
mkdir -p bindata/static/css
bindata/static/css/index.css: bindata/static/css resources/index.css
cp resources/index.css bindata/static/css/index.css
bindata/static/css/xterm_customize.css: bindata/static/css resources/xterm_customize.css
cp resources/xterm_customize.css bindata/static/css/xterm_customize.css
bindata/static/css/xterm.css: bindata/static/css js/node_modules/xterm/dist/xterm.css
cp js/node_modules/xterm/dist/xterm.css bindata/static/css/xterm.css
js/node_modules/xterm/dist/xterm.css:
cd js && \
npm install
js/dist/gotty-bundle.js: js/src/* js/node_modules/webpack
cd js && \
`npm bin`/webpack
js/node_modules/webpack:
cd js && \
npm install
tools:
go get github.com/tools/godep
go get github.com/mitchellh/gox
go get github.com/tcnksm/ghr
deps:
godep restore
go get github.com/jteeuwen/go-bindata/...
test:
if [ `go fmt ./... | wc -l` -gt 0 ]; then echo "go fmt error"; exit 1; fi
if [ `go fmt $(go list ./... | grep -v /vendor/) | wc -l` -gt 0 ]; then echo "go fmt error"; exit 1; fi
cross_compile:
GOARM=5 gox -os="darwin linux freebsd netbsd openbsd" -arch="386 amd64 arm" -output "${OUTPUT_DIR}/pkg/{{.OS}}_{{.Arch}}/{{.Dir}}"
GOARM=5 gox -os="darwin linux freebsd netbsd openbsd" -arch="386 amd64 arm" -osarch="!darwin/arm" -output "${OUTPUT_DIR}/pkg/{{.OS}}_{{.Arch}}/{{.Dir}}"
targz:
mkdir -p ${OUTPUT_DIR}/dist
cd ${OUTPUT_DIR}/pkg/; for osarch in *; do (cd $$osarch; tar zcvf ../../dist/gotty_$$osarch.tar.gz ./*); done;
cd ${OUTPUT_DIR}/pkg/; for osarch in *; do (cd $$osarch; tar zcvf ../../dist/gotty_${VERSION}_$$osarch.tar.gz ./*); done;
shasums:
cd ${OUTPUT_DIR}/dist; shasum * > ./SHASUMS
cd ${OUTPUT_DIR}/dist; sha256sum * > ./SHA256SUMS
release:
ghr --delete --prerelease -u yudai -r gotty pre-release ${OUTPUT_DIR}/dist
ghr -c ${GIT_COMMIT} --delete --prerelease -u yudai -r gotty pre-release ${OUTPUT_DIR}/dist

View file

@ -3,12 +3,10 @@
[![GitHub release](http://img.shields.io/github/release/yudai/gotty.svg?style=flat-square)][release]
[![Wercker](http://img.shields.io/wercker/ci/55d0eeff7331453f0801982c.svg?style=flat-square)][wercker]
[![MIT License](http://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)][license]
[![Join the chat at https://gitter.im/yudai/gotty](http://img.shields.io/badge/gitter-join%20chat%20%E2%86%92-brightgreen.svg?style=flat-square)][gitter]
[release]: https://github.com/yudai/gotty/releases
[wercker]: https://app.wercker.com/project/bykey/03b91f441bebeda34f80e09a9f14126f
[license]: https://github.com/yudai/gotty/blob/master/LICENSE
[gitter]: https://gitter.im/yudai/gotty?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
GoTTY is a simple command line tool that turns your CLI tools into web applications.
@ -16,22 +14,21 @@ GoTTY is a simple command line tool that turns your CLI tools into web applicati
# Installation
Download the latest binary file from the [Releases](https://github.com/yudai/gotty/releases) page.
Download the latest stable binary file from the [Releases](https://github.com/yudai/gotty/releases) page. Note that the release marked `Pre-release` is built for testing purpose, which can include unstable or breaking changes. Download a release marked [Latest release](https://github.com/yudai/gotty/releases/latest) for a stabale build.
(`darwin_amd64.tar.gz` is for Mac OS X users)
(Files named with `darwin_amd64` are for Mac OS X users)
## Homebrew Installation
You can install GoTTY with [Homebrew](http://brew.sh/) as well.
```sh
$ brew tap yudai/gotty
$ brew install gotty
$ brew install yudai/gotty/gotty
```
## `go get` Installation
## `go get` Installation (Development)
If you have a Go language environment, you can install GoTTY with the `go get` command.
If you have a Go language environment, you can install GoTTY with the `go get` command. However, this command builds a binary file from the latest master branch, which can include unstable or breaking changes. GoTTY requires go1.9 or later.
```sh
$ go get github.com/yudai/gotty
@ -50,25 +47,32 @@ By default, GoTTY starts a web server at port 8080. Open the URL on your web bro
## Options
```
--address, -a IP address to listen [$GOTTY_ADDRESS]
--port, -p "8080" Port number to listen [$GOTTY_PORT]
--permit-write, -w Permit clients to write to the TTY (BE CAREFUL) [$GOTTY_PERMIT_WRITE]
--credential, -c Credential for Basic Authentication (ex: user:pass, default disabled) [$GOTTY_CREDENTIAL]
--random-url, -r Add a random string to the URL [$GOTTY_RANDOM_URL]
--random-url-length "8" Random URL length [$GOTTY_RANDOM_URL_LENGTH]
--tls, -t Enable TLS/SSL [$GOTTY_TLS]
--tls-crt "~/.gotty.crt" TLS/SSL certificate file path [$GOTTY_TLS_CRT]
--tls-key "~/.gotty.key" TLS/SSL key file path [$GOTTY_TLS_KEY]
--tls-ca-crt "~/.gotty.ca.crt" TLS/SSL CA certificate file for client certifications [$GOTTY_TLS_CA_CRT]
--index Custom index.html file [$GOTTY_INDEX]
--title-format "GoTTY - {{ .Command }} ({{ .Hostname }})" Title format of browser window [$GOTTY_TITLE_FORMAT]
--reconnect Enable reconnection [$GOTTY_RECONNECT]
--reconnect-time "10" Time to reconnect [$GOTTY_RECONNECT_TIME]
--once Accept only one client and exit on disconnection [$GOTTY_ONCE]
--permit-arguments Permit clients to send command line arguments in URL (e.g. http://example.com:8080/?arg=AAA&arg=BBB) [$GOTTY_PERMIT_ARGUMENTS]
--close-signal "1" Signal sent to the command process when gotty close it (default: SIGHUP) [$GOTTY_CLOSE_SIGNAL]
--config "~/.gotty" Config file path [$GOTTY_CONFIG]
--version, -v print the version
--address value, -a value IP address to listen (default: "0.0.0.0") [$GOTTY_ADDRESS]
--port value, -p value Port number to liten (default: "8080") [$GOTTY_PORT]
--permit-write, -w Permit clients to write to the TTY (BE CAREFUL) [$GOTTY_PERMIT_WRITE]
--credential value, -c value Credential for Basic Authentication (ex: user:pass, default disabled) [$GOTTY_CREDENTIAL]
--random-url, -r Add a random string to the URL [$GOTTY_RANDOM_URL]
--random-url-length value Random URL length (default: 8) [$GOTTY_RANDOM_URL_LENGTH]
--tls, -t Enable TLS/SSL [$GOTTY_TLS]
--tls-crt value TLS/SSL certificate file path (default: "~/.gotty.crt") [$GOTTY_TLS_CRT]
--tls-key value TLS/SSL key file path (default: "~/.gotty.key") [$GOTTY_TLS_KEY]
--tls-ca-crt value TLS/SSL CA certificate file for client certifications (default: "~/.gotty.ca.crt") [$GOTTY_TLS_CA_CRT]
--index value Custom index.html file [$GOTTY_INDEX]
--title-format value Title format of browser window (default: "{{ .command }}@{{ .hostname }}") [$GOTTY_TITLE_FORMAT]
--reconnect Enable reconnection [$GOTTY_RECONNECT]
--reconnect-time value Time to reconnect (default: 10) [$GOTTY_RECONNECT_TIME]
--max-connection value Maximum connection to gotty (default: 0) [$GOTTY_MAX_CONNECTION]
--once Accept only one client and exit on disconnection [$GOTTY_ONCE]
--timeout value Timeout seconds for waiting a client(0 to disable) (default: 0) [$GOTTY_TIMEOUT]
--permit-arguments Permit clients to send command line arguments in URL (e.g. http://example.com:8080/?arg=AAA&arg=BBB) [$GOTTY_PERMIT_ARGUMENTS]
--width value Static width of the screen, 0(default) means dynamically resize (default: 0) [$GOTTY_WIDTH]
--height value Static height of the screen, 0(default) means dynamically resize (default: 0) [$GOTTY_HEIGHT]
--ws-origin value A regular expression that matches origin URLs to be accepted by WebSocket. No cross origin requests are acceptable by default [$GOTTY_WS_ORIGIN]
--term value Terminal name to use on the browser, one of xterm or hterm. (default: "xterm") [$GOTTY_TERM]
--close-signal value Signal sent to the command process when gotty close it (default: SIGHUP) (default: 1) [$GOTTY_CLOSE_SIGNAL]
--close-timeout value Time in seconds to force kill process after client is disconnected (default: -1) (default: -1) [$GOTTY_CLOSE_TIMEOUT]
--config value Config file path (default: "~/.gotty") [$GOTTY_CONFIG]
--version, -v print the version
```
### Config File
@ -85,7 +89,7 @@ enable_tls = true
// hterm preferences
// Smaller font and a little bit bluer background color
preferences {
font_size = 5,
font_size = 5
background_color = "rgb(16, 16, 32)"
}
```
@ -147,26 +151,22 @@ $ gotty -w docker run -it --rm busybox
## Development
You can build a binary using the following commands. Windows is not supported now.
You can build a binary using the following commands. Windows is not supported now. go1.9 is required.
```sh
# Install tools
go get github.com/jteeuwen/go-bindata/...
go get github.com/tools/godep
# Checkout hterm
git submodule sync && git submodule update --init --recursive
# Restore libraries in Godeps
godep restore
# Build
make
```
To build the frontend part (JS files and other static files), you need `npm`.
## Architecture
GoTTY uses [hterm](https://groups.google.com/a/chromium.org/forum/#!forum/chromium-hterm) to run a JavaScript based terminal on web browsers. GoTTY itself provides a websocket server that simply relays output from the TTY to clients and receives input from clients and forwards it to the TTY. This hterm + websocket idea is inspired by [Wetty](https://github.com/krishnasrinivas/wetty).
GoTTY uses [xterm.js](https://xtermjs.org/) and [hterm](https://groups.google.com/a/chromium.org/forum/#!forum/chromium-hterm) to run a JavaScript based terminal on web browsers. GoTTY itself provides a websocket server that simply relays output from the TTY to clients and receives input from clients and forwards it to the TTY. This hterm + websocket idea is inspired by [Wetty](https://github.com/krishnasrinivas/wetty).
## Alternatives
@ -178,6 +178,7 @@ GoTTY uses [hterm](https://groups.google.com/a/chromium.org/forum/#!forum/chromi
* [Secure Shell (Chrome App)](https://chrome.google.com/webstore/detail/secure-shell/pnhechapfaindjhompbnflcldabbghjo): If you are a chrome user and need a "real" SSH client on your web browser, perhaps the Secure Shell app is what you want
* [Wetty](https://github.com/krishnasrinivas/wetty): Node based web terminal (SSH/login)
* [ttyd](https://tsl0922.github.io/ttyd): C port of GoTTY with CJK and IME support
### Terminal Sharing

View file

@ -1,457 +0,0 @@
package app
import (
"crypto/rand"
"crypto/tls"
"crypto/x509"
"encoding/base64"
"encoding/json"
"errors"
"io/ioutil"
"log"
"math/big"
"net"
"net/http"
"net/url"
"os"
"os/exec"
"strconv"
"strings"
"sync"
"text/template"
"github.com/braintree/manners"
"github.com/elazarl/go-bindata-assetfs"
"github.com/gorilla/websocket"
"github.com/kr/pty"
"github.com/yudai/hcl"
"github.com/yudai/umutex"
)
type InitMessage struct {
Arguments string `json:"Arguments,omitempty"`
AuthToken string `json:"AuthToken,omitempty"`
}
type App struct {
command []string
options *Options
upgrader *websocket.Upgrader
server *manners.GracefulServer
titleTemplate *template.Template
onceMutex *umutex.UnblockingMutex
}
type Options struct {
Address string `hcl:"address"`
Port string `hcl:"port"`
PermitWrite bool `hcl:"permit_write"`
EnableBasicAuth bool `hcl:"enable_basic_auth"`
Credential string `hcl:"credential"`
EnableRandomUrl bool `hcl:"enable_random_url"`
RandomUrlLength int `hcl:"random_url_length"`
IndexFile string `hcl:"index_file"`
EnableTLS bool `hcl:"enable_tls"`
TLSCrtFile string `hcl:"tls_crt_file"`
TLSKeyFile string `hcl:"tls_key_file"`
EnableTLSClientAuth bool `hcl:"enable_tls_client_auth"`
TLSCACrtFile string `hcl:"tls_ca_crt_file"`
TitleFormat string `hcl:"title_format"`
EnableReconnect bool `hcl:"enable_reconnect"`
ReconnectTime int `hcl:"reconnect_time"`
Once bool `hcl:"once"`
PermitArguments bool `hcl:"permit_arguments"`
CloseSignal int `hcl:"close_signal"`
Preferences HtermPrefernces `hcl:"preferences"`
RawPreferences map[string]interface{} `hcl:"preferences"`
}
var Version = "0.0.12"
var DefaultOptions = Options{
Address: "",
Port: "8080",
PermitWrite: false,
EnableBasicAuth: false,
Credential: "",
EnableRandomUrl: false,
RandomUrlLength: 8,
IndexFile: "",
EnableTLS: false,
TLSCrtFile: "~/.gotty.crt",
TLSKeyFile: "~/.gotty.key",
EnableTLSClientAuth: false,
TLSCACrtFile: "~/.gotty.ca.crt",
TitleFormat: "GoTTY - {{ .Command }} ({{ .Hostname }})",
EnableReconnect: false,
ReconnectTime: 10,
Once: false,
CloseSignal: 1, // syscall.SIGHUP
Preferences: HtermPrefernces{},
}
func New(command []string, options *Options) (*App, error) {
titleTemplate, err := template.New("title").Parse(options.TitleFormat)
if err != nil {
return nil, errors.New("Title format string syntax error")
}
return &App{
command: command,
options: options,
upgrader: &websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
Subprotocols: []string{"gotty"},
},
titleTemplate: titleTemplate,
onceMutex: umutex.New(),
}, nil
}
func ApplyConfigFile(options *Options, filePath string) error {
filePath = ExpandHomeDir(filePath)
if _, err := os.Stat(filePath); os.IsNotExist(err) {
return err
}
fileString := []byte{}
log.Printf("Loading config file at: %s", filePath)
fileString, err := ioutil.ReadFile(filePath)
if err != nil {
return err
}
if err := hcl.Decode(options, string(fileString)); err != nil {
return err
}
return nil
}
func CheckConfig(options *Options) error {
if options.EnableTLSClientAuth && !options.EnableTLS {
return errors.New("TLS client authentication is enabled, but TLS is not enabled")
}
return nil
}
func (app *App) Run() error {
if app.options.PermitWrite {
log.Printf("Permitting clients to write input to the PTY.")
}
if app.options.Once {
log.Printf("Once option is provided, accepting only one client")
}
path := ""
if app.options.EnableRandomUrl {
path += "/" + generateRandomString(app.options.RandomUrlLength)
}
endpoint := net.JoinHostPort(app.options.Address, app.options.Port)
wsHandler := http.HandlerFunc(app.handleWS)
customIndexHandler := http.HandlerFunc(app.handleCustomIndex)
authTokenHandler := http.HandlerFunc(app.handleAuthToken)
staticHandler := http.FileServer(
&assetfs.AssetFS{Asset: Asset, AssetDir: AssetDir, Prefix: "static"},
)
var siteMux = http.NewServeMux()
if app.options.IndexFile != "" {
log.Printf("Using index file at " + app.options.IndexFile)
siteMux.Handle(path+"/", customIndexHandler)
} else {
siteMux.Handle(path+"/", http.StripPrefix(path+"/", staticHandler))
}
siteMux.Handle(path+"/auth_token.js", authTokenHandler)
siteMux.Handle(path+"/js/", http.StripPrefix(path+"/", staticHandler))
siteMux.Handle(path+"/favicon.png", http.StripPrefix(path+"/", staticHandler))
siteHandler := http.Handler(siteMux)
if app.options.EnableBasicAuth {
log.Printf("Using Basic Authentication")
siteHandler = wrapBasicAuth(siteHandler, app.options.Credential)
}
siteHandler = wrapHeaders(siteHandler)
wsMux := http.NewServeMux()
wsMux.Handle("/", siteHandler)
wsMux.Handle(path+"/ws", wsHandler)
siteHandler = (http.Handler(wsMux))
siteHandler = wrapLogger(siteHandler)
scheme := "http"
if app.options.EnableTLS {
scheme = "https"
}
log.Printf(
"Server is starting with command: %s",
strings.Join(app.command, " "),
)
if app.options.Address != "" {
log.Printf(
"URL: %s",
(&url.URL{Scheme: scheme, Host: endpoint, Path: path + "/"}).String(),
)
} else {
for _, address := range listAddresses() {
log.Printf(
"URL: %s",
(&url.URL{
Scheme: scheme,
Host: net.JoinHostPort(address, app.options.Port),
Path: path + "/",
}).String(),
)
}
}
server, err := app.makeServer(endpoint, &siteHandler)
if err != nil {
return errors.New("Failed to build server: " + err.Error())
}
app.server = manners.NewWithServer(
server,
)
if app.options.EnableTLS {
crtFile := ExpandHomeDir(app.options.TLSCrtFile)
keyFile := ExpandHomeDir(app.options.TLSKeyFile)
log.Printf("TLS crt file: " + crtFile)
log.Printf("TLS key file: " + keyFile)
err = app.server.ListenAndServeTLS(crtFile, keyFile)
} else {
err = app.server.ListenAndServe()
}
if err != nil {
return err
}
log.Printf("Exiting...")
return nil
}
func (app *App) makeServer(addr string, handler *http.Handler) (*http.Server, error) {
server := &http.Server{
Addr: addr,
Handler: *handler,
}
if app.options.EnableTLSClientAuth {
caFile := ExpandHomeDir(app.options.TLSCACrtFile)
log.Printf("CA file: " + caFile)
caCert, err := ioutil.ReadFile(caFile)
if err != nil {
return nil, errors.New("Could not open CA crt file " + caFile)
}
caCertPool := x509.NewCertPool()
if !caCertPool.AppendCertsFromPEM(caCert) {
return nil, errors.New("Could not parse CA crt file data in " + caFile)
}
tlsConfig := &tls.Config{
ClientCAs: caCertPool,
ClientAuth: tls.RequireAndVerifyClientCert,
}
server.TLSConfig = tlsConfig
}
return server, nil
}
func (app *App) handleWS(w http.ResponseWriter, r *http.Request) {
log.Printf("New client connected: %s", r.RemoteAddr)
if r.Method != "GET" {
http.Error(w, "Method not allowed", 405)
return
}
conn, err := app.upgrader.Upgrade(w, r, nil)
if err != nil {
log.Print("Failed to upgrade connection: " + err.Error())
return
}
_, stream, err := conn.ReadMessage()
if err != nil {
log.Print("Failed to authenticate websocket connection")
conn.Close()
return
}
var init InitMessage
err = json.Unmarshal(stream, &init)
if err != nil {
log.Printf("Failed to parse init message %v", err)
conn.Close()
return
}
if init.AuthToken != app.options.Credential {
log.Print("Failed to authenticate websocket connection")
conn.Close()
return
}
argv := app.command[1:]
if app.options.PermitArguments {
if init.Arguments == "" {
init.Arguments = "?"
}
query, err := url.Parse(init.Arguments)
if err != nil {
log.Print("Failed to parse arguments")
conn.Close()
return
}
params := query.Query()["arg"]
if len(params) != 0 {
argv = append(argv, params...)
}
}
app.server.StartRoutine()
if app.options.Once {
if app.onceMutex.TryLock() { // no unlock required, it will die soon
log.Printf("Last client accepted, closing the listener.")
app.server.Close()
} else {
log.Printf("Server is already closing.")
conn.Close()
return
}
}
cmd := exec.Command(app.command[0], argv...)
ptyIo, err := pty.Start(cmd)
if err != nil {
log.Print("Failed to execute command")
return
}
log.Printf("Command is running for client %s with PID %d (args=%q)", r.RemoteAddr, cmd.Process.Pid, strings.Join(argv, " "))
context := &clientContext{
app: app,
request: r,
connection: conn,
command: cmd,
pty: ptyIo,
writeMutex: &sync.Mutex{},
}
context.goHandleClient()
}
func (app *App) handleCustomIndex(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, ExpandHomeDir(app.options.IndexFile))
}
func (app *App) handleAuthToken(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("var gotty_auth_token = '" + app.options.Credential + "';"))
}
func (app *App) Exit() (firstCall bool) {
if app.server != nil {
firstCall = app.server.Close()
if firstCall {
log.Printf("Received Exit command, waiting for all clients to close sessions...")
}
return firstCall
}
return true
}
func wrapLogger(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
rw := &responseWrapper{w, 200}
handler.ServeHTTP(rw, r)
log.Printf("%s %d %s %s", r.RemoteAddr, rw.status, r.Method, r.URL.Path)
})
}
func wrapHeaders(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Server", "GoTTY/"+Version)
handler.ServeHTTP(w, r)
})
}
func wrapBasicAuth(handler http.Handler, credential string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := strings.SplitN(r.Header.Get("Authorization"), " ", 2)
if len(token) != 2 || strings.ToLower(token[0]) != "basic" {
w.Header().Set("WWW-Authenticate", `Basic realm="GoTTY"`)
http.Error(w, "Bad Request", http.StatusUnauthorized)
return
}
payload, err := base64.StdEncoding.DecodeString(token[1])
if err != nil {
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
if credential != string(payload) {
w.Header().Set("WWW-Authenticate", `Basic realm="GoTTY"`)
http.Error(w, "authorization failed", http.StatusUnauthorized)
return
}
log.Printf("Basic Authentication Succeeded: %s", r.RemoteAddr)
handler.ServeHTTP(w, r)
})
}
func generateRandomString(length int) string {
const base = 36
size := big.NewInt(base)
n := make([]byte, length)
for i, _ := range n {
c, _ := rand.Int(rand.Reader, size)
n[i] = strconv.FormatInt(c.Int64(), base)[0]
}
return string(n)
}
func listAddresses() (addresses []string) {
ifaces, _ := net.Interfaces()
addresses = make([]string, 0, len(ifaces))
for _, iface := range ifaces {
ifAddrs, _ := iface.Addrs()
for _, ifAddr := range ifAddrs {
switch v := ifAddr.(type) {
case *net.IPNet:
addresses = append(addresses, v.IP.String())
case *net.IPAddr:
addresses = append(addresses, v.IP.String())
}
}
}
return
}
func ExpandHomeDir(path string) string {
if path[0:2] == "~/" {
return os.Getenv("HOME") + path[1:]
} else {
return path
}
}

View file

@ -1,216 +0,0 @@
package app
import (
"bytes"
"encoding/base64"
"encoding/json"
"log"
"net/http"
"os"
"os/exec"
"strings"
"sync"
"syscall"
"unsafe"
"github.com/fatih/structs"
"github.com/gorilla/websocket"
)
type clientContext struct {
app *App
request *http.Request
connection *websocket.Conn
command *exec.Cmd
pty *os.File
writeMutex *sync.Mutex
}
const (
Input = '0'
Ping = '1'
ResizeTerminal = '2'
)
const (
Output = '0'
Pong = '1'
SetWindowTitle = '2'
SetPreferences = '3'
SetReconnect = '4'
)
type argResizeTerminal struct {
Columns float64
Rows float64
}
type ContextVars struct {
Command string
Pid int
Hostname string
RemoteAddr string
}
func (context *clientContext) goHandleClient() {
exit := make(chan bool, 2)
go func() {
defer func() { exit <- true }()
context.processSend()
}()
go func() {
defer func() { exit <- true }()
context.processReceive()
}()
go func() {
defer context.app.server.FinishRoutine()
<-exit
context.pty.Close()
// Even if the PTY has been closed,
// Read(0 in processSend() keeps blocking and the process doen't exit
context.command.Process.Signal(syscall.Signal(context.app.options.CloseSignal))
context.command.Wait()
context.connection.Close()
log.Printf("Connection closed: %s", context.request.RemoteAddr)
}()
}
func (context *clientContext) processSend() {
if err := context.sendInitialize(); err != nil {
log.Printf(err.Error())
return
}
buf := make([]byte, 1024)
for {
size, err := context.pty.Read(buf)
if err != nil {
log.Printf("Command exited for: %s", context.request.RemoteAddr)
return
}
safeMessage := base64.StdEncoding.EncodeToString([]byte(buf[:size]))
if err = context.write(append([]byte{Output}, []byte(safeMessage)...)); err != nil {
log.Printf(err.Error())
return
}
}
}
func (context *clientContext) write(data []byte) error {
context.writeMutex.Lock()
defer context.writeMutex.Unlock()
return context.connection.WriteMessage(websocket.TextMessage, data)
}
func (context *clientContext) sendInitialize() error {
hostname, _ := os.Hostname()
titleVars := ContextVars{
Command: strings.Join(context.app.command, " "),
Pid: context.command.Process.Pid,
Hostname: hostname,
RemoteAddr: context.request.RemoteAddr,
}
titleBuffer := new(bytes.Buffer)
if err := context.app.titleTemplate.Execute(titleBuffer, titleVars); err != nil {
return err
}
if err := context.write(append([]byte{SetWindowTitle}, titleBuffer.Bytes()...)); err != nil {
return err
}
prefStruct := structs.New(context.app.options.Preferences)
prefMap := prefStruct.Map()
htermPrefs := make(map[string]interface{})
for key, value := range prefMap {
rawKey := prefStruct.Field(key).Tag("hcl")
if _, ok := context.app.options.RawPreferences[rawKey]; ok {
htermPrefs[strings.Replace(rawKey, "_", "-", -1)] = value
}
}
prefs, err := json.Marshal(htermPrefs)
if err != nil {
return err
}
if err := context.write(append([]byte{SetPreferences}, prefs...)); err != nil {
return err
}
if context.app.options.EnableReconnect {
reconnect, _ := json.Marshal(context.app.options.ReconnectTime)
if err := context.write(append([]byte{SetReconnect}, reconnect...)); err != nil {
return err
}
}
return nil
}
func (context *clientContext) processReceive() {
for {
_, data, err := context.connection.ReadMessage()
if err != nil {
log.Print(err.Error())
return
}
if len(data) == 0 {
log.Print("An error has occured")
return
}
switch data[0] {
case Input:
if !context.app.options.PermitWrite {
break
}
_, err := context.pty.Write(data[1:])
if err != nil {
return
}
case Ping:
if err := context.write([]byte{Pong}); err != nil {
log.Print(err.Error())
return
}
case ResizeTerminal:
var args argResizeTerminal
err = json.Unmarshal(data[1:], &args)
if err != nil {
log.Print("Malformed remote command")
return
}
window := struct {
row uint16
col uint16
x uint16
y uint16
}{
uint16(args.Rows),
uint16(args.Columns),
0,
0,
}
syscall.Syscall(
syscall.SYS_IOCTL,
context.pty.Fd(),
syscall.TIOCSWINSZ,
uintptr(unsafe.Pointer(&window)),
)
default:
log.Print("Unknown message type")
return
}
}
}

View file

@ -1,58 +0,0 @@
package app
type HtermPrefernces struct {
AltGrMode *string `hcl:"alt_gr_mode"`
AltBackspaceIsMetaBackspace bool `hcl:"alt_backspace_is_meta_backspace"`
AltIsMeta bool `hcl:"alt_is_meta"`
AltSendsWhat string `hcl:"alt_sends_what"`
AudibleBellSound string `hcl:"audible_bell_sound"`
DesktopNotificationBell bool `hcl:"desktop_notification_bell"`
BackgroundColor string `hcl:"background_color"`
BackgroundImage string `hcl:"background_image"`
BackgroundSize string `hcl:"background_size"`
BackgroundPosition string `hcl:"background_position"`
BackspaceSendsBackspace bool `hcl:"backspace_sends_backspace"`
CharacterMapOverrides map[string]map[string]string `hcl:"character_map_overrides"`
CloseOnExit bool `hcl:"close_on_exit"`
CursorBlink bool `hcl:"cursor_blink"`
CursorBlinkCycle [2]int `hcl:"cursor_blink_cycle"`
CursorColor string `hcl:"cursor_color"`
ColorPaletteOverrides []*string `hcl:"color_palette_overrides"`
CopyOnSelect bool `hcl:"copy_on_select"`
UseDefaultWindowCopy bool `hcl:"use_default_window_copy"`
ClearSelectionAfterCopy bool `hcl:"clear_selection_after_copy"`
CtrlPlusMinusZeroZoom bool `hcl:"ctrl_plus_minus_zero_zoom"`
CtrlCCopy bool `hcl:"ctrl_c_copy"`
CtrlVPaste bool `hcl:"ctrl_v_paste"`
EastAsianAmbiguousAsTwoColumn bool `hcl:"east_asian_ambiguous_as_two_column"`
Enable8BitControl *bool `hcl:"enable_8_bit_control"`
EnableBold *bool `hcl:"enable_bold"`
EnableBoldAsBright bool `hcl:"enable_bold_as_bright"`
EnableClipboardNotice bool `hcl:"enable_clipboard_notice"`
EnableClipboardWrite bool `hcl:"enable_clipboard_write"`
EnableDec12 bool `hcl:"enable_dec12"`
Environment map[string]string `hcl:"environment"`
FontFamily string `hcl:"font_family"`
FontSize int `hcl:"font_size"`
FontSmoothing string `hcl:"font_smoothing"`
ForegroundColor string `hcl:"foreground_color"`
HomeKeysScroll bool `hcl:"home_keys_scroll"`
Keybindings map[string]string `hcl:"keybindings"`
MaxStringSequence int `hcl:"max_string_sequence"`
MediaKeysAreFkeys bool `hcl:"media_keys_are_fkeys"`
MetaSendsEscape bool `hcl:"meta_sends_escape"`
MousePasteButton *int `hcl:"mouse_paste_button"`
PageKeysScroll bool `hcl:"page_keys_scroll"`
PassAltNumber *bool `hcl:"pass_alt_number"`
PassCtrlNumber *bool `hcl:"pass_ctrl_number"`
PassMetaNumber *bool `hcl:"pass_meta_number"`
PassMetaV bool `hcl:"pass_meta_v"`
ReceiveEncoding string `hcl:"receive_encoding"`
ScrollOnKeystroke bool `hcl:"scroll_on_keystroke"`
ScrollOnOutput bool `hcl:"scroll_on_output"`
ScrollbarVisible bool `hcl:"scrollbar_visible"`
ScrollWheelMoveMultiplier int `hcl:"scroll_wheel_move_multiplier"`
SendEncoding string `hcl:"send_encoding"`
ShiftInsertPaste bool `hcl:"shift_insert_paste"`
UserCss string `hcl:"user_css"`
}

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show more